1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init_settings(cx: &mut App) {
314 EditorSettings::register(cx);
315}
316
317pub fn init(cx: &mut App) {
318 init_settings(cx);
319
320 cx.set_global(GlobalBlameRenderer(Arc::new(())));
321
322 workspace::register_project_item::<Editor>(cx);
323 workspace::FollowableViewRegistry::register::<Editor>(cx);
324 workspace::register_serializable_item::<Editor>(cx);
325
326 cx.observe_new(
327 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
328 workspace.register_action(Editor::new_file);
329 workspace.register_action(Editor::new_file_split);
330 workspace.register_action(Editor::new_file_vertical);
331 workspace.register_action(Editor::new_file_horizontal);
332 workspace.register_action(Editor::cancel_language_server_work);
333 workspace.register_action(Editor::toggle_focus);
334 },
335 )
336 .detach();
337
338 cx.on_action(move |_: &workspace::NewFile, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352 cx.on_action(move |_: &workspace::NewWindow, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 cx.activate(true);
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach();
365 }
366 });
367}
368
369pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
370 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
371}
372
373pub trait DiagnosticRenderer {
374 fn render_group(
375 &self,
376 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
377 buffer_id: BufferId,
378 snapshot: EditorSnapshot,
379 editor: WeakEntity<Editor>,
380 cx: &mut App,
381 ) -> Vec<BlockProperties<Anchor>>;
382
383 fn render_hover(
384 &self,
385 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
386 range: Range<Point>,
387 buffer_id: BufferId,
388 cx: &mut App,
389 ) -> Option<Entity<markdown::Markdown>>;
390
391 fn open_link(
392 &self,
393 editor: &mut Editor,
394 link: SharedString,
395 window: &mut Window,
396 cx: &mut Context<Editor>,
397 );
398}
399
400pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
401
402impl GlobalDiagnosticRenderer {
403 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
404 cx.try_global::<Self>().map(|g| g.0.clone())
405 }
406}
407
408impl gpui::Global for GlobalDiagnosticRenderer {}
409pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
410 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
411}
412
413pub struct SearchWithinRange;
414
415trait InvalidationRegion {
416 fn ranges(&self) -> &[Range<Anchor>];
417}
418
419#[derive(Clone, Debug, PartialEq)]
420pub enum SelectPhase {
421 Begin {
422 position: DisplayPoint,
423 add: bool,
424 click_count: usize,
425 },
426 BeginColumnar {
427 position: DisplayPoint,
428 reset: bool,
429 mode: ColumnarMode,
430 goal_column: u32,
431 },
432 Extend {
433 position: DisplayPoint,
434 click_count: usize,
435 },
436 Update {
437 position: DisplayPoint,
438 goal_column: u32,
439 scroll_delta: gpui::Point<f32>,
440 },
441 End,
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum ColumnarMode {
446 FromMouse,
447 FromSelection,
448}
449
450#[derive(Clone, Debug)]
451pub enum SelectMode {
452 Character,
453 Word(Range<Anchor>),
454 Line(Range<Anchor>),
455 All,
456}
457
458#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
459pub enum SizingBehavior {
460 /// The editor will layout itself using `size_full` and will include the vertical
461 /// scroll margin as requested by user settings.
462 #[default]
463 Default,
464 /// The editor will layout itself using `size_full`, but will not have any
465 /// vertical overscroll.
466 ExcludeOverscrollMargin,
467 /// The editor will request a vertical size according to its content and will be
468 /// layouted without a vertical scroll margin.
469 SizeByContent,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine,
475 AutoHeight {
476 min_lines: usize,
477 max_lines: Option<usize>,
478 },
479 Full {
480 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
481 scale_ui_elements_with_buffer_font_size: bool,
482 /// When set to `true`, the editor will render a background for the active line.
483 show_active_line_background: bool,
484 /// Determines the sizing behavior for this editor
485 sizing_behavior: SizingBehavior,
486 },
487 Minimap {
488 parent: WeakEntity<Editor>,
489 },
490}
491
492impl EditorMode {
493 pub fn full() -> Self {
494 Self::Full {
495 scale_ui_elements_with_buffer_font_size: true,
496 show_active_line_background: true,
497 sizing_behavior: SizingBehavior::Default,
498 }
499 }
500
501 #[inline]
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 #[inline]
507 pub fn is_single_line(&self) -> bool {
508 matches!(self, Self::SingleLine { .. })
509 }
510
511 #[inline]
512 fn is_minimap(&self) -> bool {
513 matches!(self, Self::Minimap { .. })
514 }
515}
516
517#[derive(Copy, Clone, Debug)]
518pub enum SoftWrap {
519 /// Prefer not to wrap at all.
520 ///
521 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
522 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
523 GitDiff,
524 /// Prefer a single line generally, unless an overly long line is encountered.
525 None,
526 /// Soft wrap lines that exceed the editor width.
527 EditorWidth,
528 /// Soft wrap lines at the preferred line length.
529 Column(u32),
530 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
531 Bounded(u32),
532}
533
534#[derive(Clone)]
535pub struct EditorStyle {
536 pub background: Hsla,
537 pub border: Hsla,
538 pub local_player: PlayerColor,
539 pub text: TextStyle,
540 pub scrollbar_width: Pixels,
541 pub syntax: Arc<SyntaxTheme>,
542 pub status: StatusColors,
543 pub inlay_hints_style: HighlightStyle,
544 pub edit_prediction_styles: EditPredictionStyles,
545 pub unnecessary_code_fade: f32,
546 pub show_underlines: bool,
547}
548
549impl Default for EditorStyle {
550 fn default() -> Self {
551 Self {
552 background: Hsla::default(),
553 border: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 edit_prediction_styles: EditPredictionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 let mut style = cx.theme().syntax().get("hint");
579
580 if style.color.is_none() {
581 style.color = Some(cx.theme().status().hint);
582 }
583
584 if !show_background {
585 style.background_color = None;
586 return style;
587 }
588
589 if style.background_color.is_none() {
590 style.background_color = Some(cx.theme().status().hint_background);
591 }
592
593 style
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
597 EditPredictionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum EditPrediction {
618 Edit {
619 edits: Vec<(Range<Anchor>, Arc<str>)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 /// Move to a specific location in the active editor
625 MoveWithin {
626 target: Anchor,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in a different editor (not the active one)
630 MoveOutside {
631 target: language::Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Option<Range<Anchor>>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Debug, Clone, Copy, PartialEq, Eq)]
816pub enum BufferSerialization {
817 All,
818 NonDirtyBuffers,
819}
820
821impl BufferSerialization {
822 fn new(restore_unsaved_buffers: bool) -> Self {
823 if restore_unsaved_buffers {
824 Self::All
825 } else {
826 Self::NonDirtyBuffers
827 }
828 }
829}
830
831#[derive(Clone, Debug)]
832struct RunnableTasks {
833 templates: Vec<(TaskSourceKind, TaskTemplate)>,
834 offset: multi_buffer::Anchor,
835 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
836 column: u32,
837 // Values of all named captures, including those starting with '_'
838 extra_variables: HashMap<String, String>,
839 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
840 context_range: Range<BufferOffset>,
841}
842
843impl RunnableTasks {
844 fn resolve<'a>(
845 &'a self,
846 cx: &'a task::TaskContext,
847 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
848 self.templates.iter().filter_map(|(kind, template)| {
849 template
850 .resolve_task(&kind.to_id_base(), cx)
851 .map(|task| (kind.clone(), task))
852 })
853 }
854}
855
856#[derive(Clone)]
857pub struct ResolvedTasks {
858 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
859 position: Anchor,
860}
861
862#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
863struct BufferOffset(usize);
864
865/// Addons allow storing per-editor state in other crates (e.g. Vim)
866pub trait Addon: 'static {
867 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
868
869 fn render_buffer_header_controls(
870 &self,
871 _: &ExcerptInfo,
872 _: &Window,
873 _: &App,
874 ) -> Option<AnyElement> {
875 None
876 }
877
878 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
879 None
880 }
881
882 fn to_any(&self) -> &dyn std::any::Any;
883
884 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
885 None
886 }
887}
888
889struct ChangeLocation {
890 current: Option<Vec<Anchor>>,
891 original: Vec<Anchor>,
892}
893impl ChangeLocation {
894 fn locations(&self) -> &[Anchor] {
895 self.current.as_ref().unwrap_or(&self.original)
896 }
897}
898
899/// A set of caret positions, registered when the editor was edited.
900pub struct ChangeList {
901 changes: Vec<ChangeLocation>,
902 /// Currently "selected" change.
903 position: Option<usize>,
904}
905
906impl ChangeList {
907 pub fn new() -> Self {
908 Self {
909 changes: Vec::new(),
910 position: None,
911 }
912 }
913
914 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
915 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
916 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
917 if self.changes.is_empty() {
918 return None;
919 }
920
921 let prev = self.position.unwrap_or(self.changes.len());
922 let next = if direction == Direction::Prev {
923 prev.saturating_sub(count)
924 } else {
925 (prev + count).min(self.changes.len() - 1)
926 };
927 self.position = Some(next);
928 self.changes.get(next).map(|change| change.locations())
929 }
930
931 /// Adds a new change to the list, resetting the change list position.
932 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
933 self.position.take();
934 if let Some(last) = self.changes.last_mut()
935 && group
936 {
937 last.current = Some(new_positions)
938 } else {
939 self.changes.push(ChangeLocation {
940 original: new_positions,
941 current: None,
942 });
943 }
944 }
945
946 pub fn last(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.locations())
948 }
949
950 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
951 self.changes.last().map(|change| change.original.as_slice())
952 }
953
954 pub fn invert_last_group(&mut self) {
955 if let Some(last) = self.changes.last_mut()
956 && let Some(current) = last.current.as_mut()
957 {
958 mem::swap(&mut last.original, current);
959 }
960 }
961}
962
963#[derive(Clone)]
964struct InlineBlamePopoverState {
965 scroll_handle: ScrollHandle,
966 commit_message: Option<ParsedCommitMessage>,
967 markdown: Entity<Markdown>,
968}
969
970struct InlineBlamePopover {
971 position: gpui::Point<Pixels>,
972 hide_task: Option<Task<()>>,
973 popover_bounds: Option<Bounds<Pixels>>,
974 popover_state: InlineBlamePopoverState,
975 keyboard_grace: bool,
976}
977
978enum SelectionDragState {
979 /// State when no drag related activity is detected.
980 None,
981 /// State when the mouse is down on a selection that is about to be dragged.
982 ReadyToDrag {
983 selection: Selection<Anchor>,
984 click_position: gpui::Point<Pixels>,
985 mouse_down_time: Instant,
986 },
987 /// State when the mouse is dragging the selection in the editor.
988 Dragging {
989 selection: Selection<Anchor>,
990 drop_cursor: Selection<Anchor>,
991 hide_drop_cursor: bool,
992 },
993}
994
995enum ColumnarSelectionState {
996 FromMouse {
997 selection_tail: Anchor,
998 display_point: Option<DisplayPoint>,
999 },
1000 FromSelection {
1001 selection_tail: Anchor,
1002 },
1003}
1004
1005/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1006/// a breakpoint on them.
1007#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1008struct PhantomBreakpointIndicator {
1009 display_row: DisplayRow,
1010 /// There's a small debounce between hovering over the line and showing the indicator.
1011 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1012 is_active: bool,
1013 collides_with_existing_breakpoint: bool,
1014}
1015
1016/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1017///
1018/// See the [module level documentation](self) for more information.
1019pub struct Editor {
1020 focus_handle: FocusHandle,
1021 last_focused_descendant: Option<WeakFocusHandle>,
1022 /// The text buffer being edited
1023 buffer: Entity<MultiBuffer>,
1024 /// Map of how text in the buffer should be displayed.
1025 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1026 pub display_map: Entity<DisplayMap>,
1027 placeholder_display_map: Option<Entity<DisplayMap>>,
1028 pub selections: SelectionsCollection,
1029 pub scroll_manager: ScrollManager,
1030 /// When inline assist editors are linked, they all render cursors because
1031 /// typing enters text into each of them, even the ones that aren't focused.
1032 pub(crate) show_cursor_when_unfocused: bool,
1033 columnar_selection_state: Option<ColumnarSelectionState>,
1034 add_selections_state: Option<AddSelectionsState>,
1035 select_next_state: Option<SelectNextState>,
1036 select_prev_state: Option<SelectNextState>,
1037 selection_history: SelectionHistory,
1038 defer_selection_effects: bool,
1039 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1040 autoclose_regions: Vec<AutocloseRegion>,
1041 snippet_stack: InvalidationStack<SnippetState>,
1042 select_syntax_node_history: SelectSyntaxNodeHistory,
1043 ime_transaction: Option<TransactionId>,
1044 pub diagnostics_max_severity: DiagnosticSeverity,
1045 active_diagnostics: ActiveDiagnostic,
1046 show_inline_diagnostics: bool,
1047 inline_diagnostics_update: Task<()>,
1048 inline_diagnostics_enabled: bool,
1049 diagnostics_enabled: bool,
1050 word_completions_enabled: bool,
1051 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1052 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1053 hard_wrap: Option<usize>,
1054 project: Option<Entity<Project>>,
1055 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1056 completion_provider: Option<Rc<dyn CompletionProvider>>,
1057 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1058 blink_manager: Entity<BlinkManager>,
1059 show_cursor_names: bool,
1060 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1061 pub show_local_selections: bool,
1062 mode: EditorMode,
1063 show_breadcrumbs: bool,
1064 show_gutter: bool,
1065 show_scrollbars: ScrollbarAxes,
1066 minimap_visibility: MinimapVisibility,
1067 offset_content: bool,
1068 disable_expand_excerpt_buttons: bool,
1069 show_line_numbers: Option<bool>,
1070 use_relative_line_numbers: Option<bool>,
1071 show_git_diff_gutter: Option<bool>,
1072 show_code_actions: Option<bool>,
1073 show_runnables: Option<bool>,
1074 show_breakpoints: Option<bool>,
1075 show_wrap_guides: Option<bool>,
1076 show_indent_guides: Option<bool>,
1077 highlight_order: usize,
1078 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1079 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1080 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1081 scrollbar_marker_state: ScrollbarMarkerState,
1082 active_indent_guides_state: ActiveIndentGuidesState,
1083 nav_history: Option<ItemNavHistory>,
1084 context_menu: RefCell<Option<CodeContextMenu>>,
1085 context_menu_options: Option<ContextMenuOptions>,
1086 mouse_context_menu: Option<MouseContextMenu>,
1087 completion_tasks: Vec<(CompletionId, Task<()>)>,
1088 inline_blame_popover: Option<InlineBlamePopover>,
1089 inline_blame_popover_show_task: Option<Task<()>>,
1090 signature_help_state: SignatureHelpState,
1091 auto_signature_help: Option<bool>,
1092 find_all_references_task_sources: Vec<Anchor>,
1093 next_completion_id: CompletionId,
1094 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1095 code_actions_task: Option<Task<Result<()>>>,
1096 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1097 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1098 document_highlights_task: Option<Task<()>>,
1099 linked_editing_range_task: Option<Task<Option<()>>>,
1100 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1101 pending_rename: Option<RenameState>,
1102 searchable: bool,
1103 cursor_shape: CursorShape,
1104 current_line_highlight: Option<CurrentLineHighlight>,
1105 autoindent_mode: Option<AutoindentMode>,
1106 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1107 input_enabled: bool,
1108 use_modal_editing: bool,
1109 read_only: bool,
1110 leader_id: Option<CollaboratorId>,
1111 remote_id: Option<ViewId>,
1112 pub hover_state: HoverState,
1113 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1114 gutter_hovered: bool,
1115 hovered_link_state: Option<HoveredLinkState>,
1116 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1117 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1118 active_edit_prediction: Option<EditPredictionState>,
1119 /// Used to prevent flickering as the user types while the menu is open
1120 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1121 edit_prediction_settings: EditPredictionSettings,
1122 edit_predictions_hidden_for_vim_mode: bool,
1123 show_edit_predictions_override: Option<bool>,
1124 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1125 edit_prediction_preview: EditPredictionPreview,
1126 edit_prediction_indent_conflict: bool,
1127 edit_prediction_requires_modifier_in_indent_conflict: bool,
1128 next_inlay_id: usize,
1129 next_color_inlay_id: usize,
1130 _subscriptions: Vec<Subscription>,
1131 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1132 gutter_dimensions: GutterDimensions,
1133 style: Option<EditorStyle>,
1134 text_style_refinement: Option<TextStyleRefinement>,
1135 next_editor_action_id: EditorActionId,
1136 editor_actions: Rc<
1137 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1138 >,
1139 use_autoclose: bool,
1140 use_auto_surround: bool,
1141 auto_replace_emoji_shortcode: bool,
1142 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1143 show_git_blame_gutter: bool,
1144 show_git_blame_inline: bool,
1145 show_git_blame_inline_delay_task: Option<Task<()>>,
1146 git_blame_inline_enabled: bool,
1147 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1148 buffer_serialization: Option<BufferSerialization>,
1149 show_selection_menu: Option<bool>,
1150 blame: Option<Entity<GitBlame>>,
1151 blame_subscription: Option<Subscription>,
1152 custom_context_menu: Option<
1153 Box<
1154 dyn 'static
1155 + Fn(
1156 &mut Self,
1157 DisplayPoint,
1158 &mut Window,
1159 &mut Context<Self>,
1160 ) -> Option<Entity<ui::ContextMenu>>,
1161 >,
1162 >,
1163 last_bounds: Option<Bounds<Pixels>>,
1164 last_position_map: Option<Rc<PositionMap>>,
1165 expect_bounds_change: Option<Bounds<Pixels>>,
1166 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1167 tasks_update_task: Option<Task<()>>,
1168 breakpoint_store: Option<Entity<BreakpointStore>>,
1169 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1170 hovered_diff_hunk_row: Option<DisplayRow>,
1171 pull_diagnostics_task: Task<()>,
1172 in_project_search: bool,
1173 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1174 breadcrumb_header: Option<String>,
1175 focused_block: Option<FocusedBlock>,
1176 next_scroll_position: NextScrollCursorCenterTopBottom,
1177 addons: HashMap<TypeId, Box<dyn Addon>>,
1178 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1179 load_diff_task: Option<Shared<Task<()>>>,
1180 /// Whether we are temporarily displaying a diff other than git's
1181 temporary_diff_override: bool,
1182 selection_mark_mode: bool,
1183 toggle_fold_multiple_buffers: Task<()>,
1184 _scroll_cursor_center_top_bottom_task: Task<()>,
1185 serialize_selections: Task<()>,
1186 serialize_folds: Task<()>,
1187 mouse_cursor_hidden: bool,
1188 minimap: Option<Entity<Self>>,
1189 hide_mouse_mode: HideMouseMode,
1190 pub change_list: ChangeList,
1191 inline_value_cache: InlineValueCache,
1192 selection_drag_state: SelectionDragState,
1193 colors: Option<LspColorData>,
1194 post_scroll_update: Task<()>,
1195 refresh_colors_task: Task<()>,
1196 inlay_hints: Option<LspInlayHintData>,
1197 folding_newlines: Task<()>,
1198 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1199}
1200
1201fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1202 if debounce_ms > 0 {
1203 Some(Duration::from_millis(debounce_ms))
1204 } else {
1205 None
1206 }
1207}
1208
1209#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1210enum NextScrollCursorCenterTopBottom {
1211 #[default]
1212 Center,
1213 Top,
1214 Bottom,
1215}
1216
1217impl NextScrollCursorCenterTopBottom {
1218 fn next(&self) -> Self {
1219 match self {
1220 Self::Center => Self::Top,
1221 Self::Top => Self::Bottom,
1222 Self::Bottom => Self::Center,
1223 }
1224 }
1225}
1226
1227#[derive(Clone)]
1228pub struct EditorSnapshot {
1229 pub mode: EditorMode,
1230 show_gutter: bool,
1231 show_line_numbers: Option<bool>,
1232 show_git_diff_gutter: Option<bool>,
1233 show_code_actions: Option<bool>,
1234 show_runnables: Option<bool>,
1235 show_breakpoints: Option<bool>,
1236 git_blame_gutter_max_author_length: Option<usize>,
1237 pub display_snapshot: DisplaySnapshot,
1238 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1239 is_focused: bool,
1240 scroll_anchor: ScrollAnchor,
1241 ongoing_scroll: OngoingScroll,
1242 current_line_highlight: CurrentLineHighlight,
1243 gutter_hovered: bool,
1244}
1245
1246#[derive(Default, Debug, Clone, Copy)]
1247pub struct GutterDimensions {
1248 pub left_padding: Pixels,
1249 pub right_padding: Pixels,
1250 pub width: Pixels,
1251 pub margin: Pixels,
1252 pub git_blame_entries_width: Option<Pixels>,
1253}
1254
1255impl GutterDimensions {
1256 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1257 Self {
1258 margin: Self::default_gutter_margin(font_id, font_size, cx),
1259 ..Default::default()
1260 }
1261 }
1262
1263 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1264 -cx.text_system().descent(font_id, font_size)
1265 }
1266 /// The full width of the space taken up by the gutter.
1267 pub fn full_width(&self) -> Pixels {
1268 self.margin + self.width
1269 }
1270
1271 /// The width of the space reserved for the fold indicators,
1272 /// use alongside 'justify_end' and `gutter_width` to
1273 /// right align content with the line numbers
1274 pub fn fold_area_width(&self) -> Pixels {
1275 self.margin + self.right_padding
1276 }
1277}
1278
1279struct CharacterDimensions {
1280 em_width: Pixels,
1281 em_advance: Pixels,
1282 line_height: Pixels,
1283}
1284
1285#[derive(Debug)]
1286pub struct RemoteSelection {
1287 pub replica_id: ReplicaId,
1288 pub selection: Selection<Anchor>,
1289 pub cursor_shape: CursorShape,
1290 pub collaborator_id: CollaboratorId,
1291 pub line_mode: bool,
1292 pub user_name: Option<SharedString>,
1293 pub color: PlayerColor,
1294}
1295
1296#[derive(Clone, Debug)]
1297struct SelectionHistoryEntry {
1298 selections: Arc<[Selection<Anchor>]>,
1299 select_next_state: Option<SelectNextState>,
1300 select_prev_state: Option<SelectNextState>,
1301 add_selections_state: Option<AddSelectionsState>,
1302}
1303
1304#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1305enum SelectionHistoryMode {
1306 Normal,
1307 Undoing,
1308 Redoing,
1309 Skipping,
1310}
1311
1312#[derive(Clone, PartialEq, Eq, Hash)]
1313struct HoveredCursor {
1314 replica_id: ReplicaId,
1315 selection_id: usize,
1316}
1317
1318impl Default for SelectionHistoryMode {
1319 fn default() -> Self {
1320 Self::Normal
1321 }
1322}
1323
1324#[derive(Debug)]
1325/// SelectionEffects controls the side-effects of updating the selection.
1326///
1327/// The default behaviour does "what you mostly want":
1328/// - it pushes to the nav history if the cursor moved by >10 lines
1329/// - it re-triggers completion requests
1330/// - it scrolls to fit
1331///
1332/// You might want to modify these behaviours. For example when doing a "jump"
1333/// like go to definition, we always want to add to nav history; but when scrolling
1334/// in vim mode we never do.
1335///
1336/// Similarly, you might want to disable scrolling if you don't want the viewport to
1337/// move.
1338#[derive(Clone)]
1339pub struct SelectionEffects {
1340 nav_history: Option<bool>,
1341 completions: bool,
1342 scroll: Option<Autoscroll>,
1343}
1344
1345impl Default for SelectionEffects {
1346 fn default() -> Self {
1347 Self {
1348 nav_history: None,
1349 completions: true,
1350 scroll: Some(Autoscroll::fit()),
1351 }
1352 }
1353}
1354impl SelectionEffects {
1355 pub fn scroll(scroll: Autoscroll) -> Self {
1356 Self {
1357 scroll: Some(scroll),
1358 ..Default::default()
1359 }
1360 }
1361
1362 pub fn no_scroll() -> Self {
1363 Self {
1364 scroll: None,
1365 ..Default::default()
1366 }
1367 }
1368
1369 pub fn completions(self, completions: bool) -> Self {
1370 Self {
1371 completions,
1372 ..self
1373 }
1374 }
1375
1376 pub fn nav_history(self, nav_history: bool) -> Self {
1377 Self {
1378 nav_history: Some(nav_history),
1379 ..self
1380 }
1381 }
1382}
1383
1384struct DeferredSelectionEffectsState {
1385 changed: bool,
1386 effects: SelectionEffects,
1387 old_cursor_position: Anchor,
1388 history_entry: SelectionHistoryEntry,
1389}
1390
1391#[derive(Default)]
1392struct SelectionHistory {
1393 #[allow(clippy::type_complexity)]
1394 selections_by_transaction:
1395 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1396 mode: SelectionHistoryMode,
1397 undo_stack: VecDeque<SelectionHistoryEntry>,
1398 redo_stack: VecDeque<SelectionHistoryEntry>,
1399}
1400
1401impl SelectionHistory {
1402 #[track_caller]
1403 fn insert_transaction(
1404 &mut self,
1405 transaction_id: TransactionId,
1406 selections: Arc<[Selection<Anchor>]>,
1407 ) {
1408 if selections.is_empty() {
1409 log::error!(
1410 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1411 std::panic::Location::caller()
1412 );
1413 return;
1414 }
1415 self.selections_by_transaction
1416 .insert(transaction_id, (selections, None));
1417 }
1418
1419 #[allow(clippy::type_complexity)]
1420 fn transaction(
1421 &self,
1422 transaction_id: TransactionId,
1423 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1424 self.selections_by_transaction.get(&transaction_id)
1425 }
1426
1427 #[allow(clippy::type_complexity)]
1428 fn transaction_mut(
1429 &mut self,
1430 transaction_id: TransactionId,
1431 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1432 self.selections_by_transaction.get_mut(&transaction_id)
1433 }
1434
1435 fn push(&mut self, entry: SelectionHistoryEntry) {
1436 if !entry.selections.is_empty() {
1437 match self.mode {
1438 SelectionHistoryMode::Normal => {
1439 self.push_undo(entry);
1440 self.redo_stack.clear();
1441 }
1442 SelectionHistoryMode::Undoing => self.push_redo(entry),
1443 SelectionHistoryMode::Redoing => self.push_undo(entry),
1444 SelectionHistoryMode::Skipping => {}
1445 }
1446 }
1447 }
1448
1449 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1450 if self
1451 .undo_stack
1452 .back()
1453 .is_none_or(|e| e.selections != entry.selections)
1454 {
1455 self.undo_stack.push_back(entry);
1456 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1457 self.undo_stack.pop_front();
1458 }
1459 }
1460 }
1461
1462 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1463 if self
1464 .redo_stack
1465 .back()
1466 .is_none_or(|e| e.selections != entry.selections)
1467 {
1468 self.redo_stack.push_back(entry);
1469 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1470 self.redo_stack.pop_front();
1471 }
1472 }
1473 }
1474}
1475
1476#[derive(Clone, Copy)]
1477pub struct RowHighlightOptions {
1478 pub autoscroll: bool,
1479 pub include_gutter: bool,
1480}
1481
1482impl Default for RowHighlightOptions {
1483 fn default() -> Self {
1484 Self {
1485 autoscroll: Default::default(),
1486 include_gutter: true,
1487 }
1488 }
1489}
1490
1491struct RowHighlight {
1492 index: usize,
1493 range: Range<Anchor>,
1494 color: Hsla,
1495 options: RowHighlightOptions,
1496 type_id: TypeId,
1497}
1498
1499#[derive(Clone, Debug)]
1500struct AddSelectionsState {
1501 groups: Vec<AddSelectionsGroup>,
1502}
1503
1504#[derive(Clone, Debug)]
1505struct AddSelectionsGroup {
1506 above: bool,
1507 stack: Vec<usize>,
1508}
1509
1510#[derive(Clone)]
1511struct SelectNextState {
1512 query: AhoCorasick,
1513 wordwise: bool,
1514 done: bool,
1515}
1516
1517impl std::fmt::Debug for SelectNextState {
1518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1519 f.debug_struct(std::any::type_name::<Self>())
1520 .field("wordwise", &self.wordwise)
1521 .field("done", &self.done)
1522 .finish()
1523 }
1524}
1525
1526#[derive(Debug)]
1527struct AutocloseRegion {
1528 selection_id: usize,
1529 range: Range<Anchor>,
1530 pair: BracketPair,
1531}
1532
1533#[derive(Debug)]
1534struct SnippetState {
1535 ranges: Vec<Vec<Range<Anchor>>>,
1536 active_index: usize,
1537 choices: Vec<Option<Vec<String>>>,
1538}
1539
1540#[doc(hidden)]
1541pub struct RenameState {
1542 pub range: Range<Anchor>,
1543 pub old_name: Arc<str>,
1544 pub editor: Entity<Editor>,
1545 block_id: CustomBlockId,
1546}
1547
1548struct InvalidationStack<T>(Vec<T>);
1549
1550struct RegisteredEditPredictionProvider {
1551 provider: Arc<dyn EditPredictionProviderHandle>,
1552 _subscription: Subscription,
1553}
1554
1555#[derive(Debug, PartialEq, Eq)]
1556pub struct ActiveDiagnosticGroup {
1557 pub active_range: Range<Anchor>,
1558 pub active_message: String,
1559 pub group_id: usize,
1560 pub blocks: HashSet<CustomBlockId>,
1561}
1562
1563#[derive(Debug, PartialEq, Eq)]
1564
1565pub(crate) enum ActiveDiagnostic {
1566 None,
1567 All,
1568 Group(ActiveDiagnosticGroup),
1569}
1570
1571#[derive(Serialize, Deserialize, Clone, Debug)]
1572pub struct ClipboardSelection {
1573 /// The number of bytes in this selection.
1574 pub len: usize,
1575 /// Whether this was a full-line selection.
1576 pub is_entire_line: bool,
1577 /// The indentation of the first line when this content was originally copied.
1578 pub first_line_indent: u32,
1579}
1580
1581// selections, scroll behavior, was newest selection reversed
1582type SelectSyntaxNodeHistoryState = (
1583 Box<[Selection<usize>]>,
1584 SelectSyntaxNodeScrollBehavior,
1585 bool,
1586);
1587
1588#[derive(Default)]
1589struct SelectSyntaxNodeHistory {
1590 stack: Vec<SelectSyntaxNodeHistoryState>,
1591 // disable temporarily to allow changing selections without losing the stack
1592 pub disable_clearing: bool,
1593}
1594
1595impl SelectSyntaxNodeHistory {
1596 pub fn try_clear(&mut self) {
1597 if !self.disable_clearing {
1598 self.stack.clear();
1599 }
1600 }
1601
1602 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1603 self.stack.push(selection);
1604 }
1605
1606 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1607 self.stack.pop()
1608 }
1609}
1610
1611enum SelectSyntaxNodeScrollBehavior {
1612 CursorTop,
1613 FitSelection,
1614 CursorBottom,
1615}
1616
1617#[derive(Debug)]
1618pub(crate) struct NavigationData {
1619 cursor_anchor: Anchor,
1620 cursor_position: Point,
1621 scroll_anchor: ScrollAnchor,
1622 scroll_top_row: u32,
1623}
1624
1625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1626pub enum GotoDefinitionKind {
1627 Symbol,
1628 Declaration,
1629 Type,
1630 Implementation,
1631}
1632
1633pub enum FormatTarget {
1634 Buffers(HashSet<Entity<Buffer>>),
1635 Ranges(Vec<Range<MultiBufferPoint>>),
1636}
1637
1638pub(crate) struct FocusedBlock {
1639 id: BlockId,
1640 focus_handle: WeakFocusHandle,
1641}
1642
1643#[derive(Clone)]
1644enum JumpData {
1645 MultiBufferRow {
1646 row: MultiBufferRow,
1647 line_offset_from_top: u32,
1648 },
1649 MultiBufferPoint {
1650 excerpt_id: ExcerptId,
1651 position: Point,
1652 anchor: text::Anchor,
1653 line_offset_from_top: u32,
1654 },
1655}
1656
1657pub enum MultibufferSelectionMode {
1658 First,
1659 All,
1660}
1661
1662#[derive(Clone, Copy, Debug, Default)]
1663pub struct RewrapOptions {
1664 pub override_language_settings: bool,
1665 pub preserve_existing_whitespace: bool,
1666}
1667
1668impl Editor {
1669 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1673 }
1674
1675 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(EditorMode::full(), buffer, None, window, cx)
1679 }
1680
1681 pub fn auto_height(
1682 min_lines: usize,
1683 max_lines: usize,
1684 window: &mut Window,
1685 cx: &mut Context<Self>,
1686 ) -> Self {
1687 let buffer = cx.new(|cx| Buffer::local("", cx));
1688 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1689 Self::new(
1690 EditorMode::AutoHeight {
1691 min_lines,
1692 max_lines: Some(max_lines),
1693 },
1694 buffer,
1695 None,
1696 window,
1697 cx,
1698 )
1699 }
1700
1701 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1702 /// The editor grows as tall as needed to fit its content.
1703 pub fn auto_height_unbounded(
1704 min_lines: usize,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 let buffer = cx.new(|cx| Buffer::local("", cx));
1709 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1710 Self::new(
1711 EditorMode::AutoHeight {
1712 min_lines,
1713 max_lines: None,
1714 },
1715 buffer,
1716 None,
1717 window,
1718 cx,
1719 )
1720 }
1721
1722 pub fn for_buffer(
1723 buffer: Entity<Buffer>,
1724 project: Option<Entity<Project>>,
1725 window: &mut Window,
1726 cx: &mut Context<Self>,
1727 ) -> Self {
1728 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1729 Self::new(EditorMode::full(), buffer, project, window, cx)
1730 }
1731
1732 pub fn for_multibuffer(
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Self::new(EditorMode::full(), buffer, project, window, cx)
1739 }
1740
1741 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1742 let mut clone = Self::new(
1743 self.mode.clone(),
1744 self.buffer.clone(),
1745 self.project.clone(),
1746 window,
1747 cx,
1748 );
1749 self.display_map.update(cx, |display_map, cx| {
1750 let snapshot = display_map.snapshot(cx);
1751 clone.display_map.update(cx, |display_map, cx| {
1752 display_map.set_state(&snapshot, cx);
1753 });
1754 });
1755 clone.folds_did_change(cx);
1756 clone.selections.clone_state(&self.selections);
1757 clone.scroll_manager.clone_state(&self.scroll_manager);
1758 clone.searchable = self.searchable;
1759 clone.read_only = self.read_only;
1760 clone
1761 }
1762
1763 pub fn new(
1764 mode: EditorMode,
1765 buffer: Entity<MultiBuffer>,
1766 project: Option<Entity<Project>>,
1767 window: &mut Window,
1768 cx: &mut Context<Self>,
1769 ) -> Self {
1770 Editor::new_internal(mode, buffer, project, None, window, cx)
1771 }
1772
1773 fn new_internal(
1774 mode: EditorMode,
1775 multi_buffer: Entity<MultiBuffer>,
1776 project: Option<Entity<Project>>,
1777 display_map: Option<Entity<DisplayMap>>,
1778 window: &mut Window,
1779 cx: &mut Context<Self>,
1780 ) -> Self {
1781 debug_assert!(
1782 display_map.is_none() || mode.is_minimap(),
1783 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1784 );
1785
1786 let full_mode = mode.is_full();
1787 let is_minimap = mode.is_minimap();
1788 let diagnostics_max_severity = if full_mode {
1789 EditorSettings::get_global(cx)
1790 .diagnostics_max_severity
1791 .unwrap_or(DiagnosticSeverity::Hint)
1792 } else {
1793 DiagnosticSeverity::Off
1794 };
1795 let style = window.text_style();
1796 let font_size = style.font_size.to_pixels(window.rem_size());
1797 let editor = cx.entity().downgrade();
1798 let fold_placeholder = FoldPlaceholder {
1799 constrain_width: false,
1800 render: Arc::new(move |fold_id, fold_range, cx| {
1801 let editor = editor.clone();
1802 div()
1803 .id(fold_id)
1804 .bg(cx.theme().colors().ghost_element_background)
1805 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1806 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1807 .rounded_xs()
1808 .size_full()
1809 .cursor_pointer()
1810 .child("⋯")
1811 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1812 .on_click(move |_, _window, cx| {
1813 editor
1814 .update(cx, |editor, cx| {
1815 editor.unfold_ranges(
1816 &[fold_range.start..fold_range.end],
1817 true,
1818 false,
1819 cx,
1820 );
1821 cx.stop_propagation();
1822 })
1823 .ok();
1824 })
1825 .into_any()
1826 }),
1827 merge_adjacent: true,
1828 ..FoldPlaceholder::default()
1829 };
1830 let display_map = display_map.unwrap_or_else(|| {
1831 cx.new(|cx| {
1832 DisplayMap::new(
1833 multi_buffer.clone(),
1834 style.font(),
1835 font_size,
1836 None,
1837 FILE_HEADER_HEIGHT,
1838 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1839 fold_placeholder,
1840 diagnostics_max_severity,
1841 cx,
1842 )
1843 })
1844 });
1845
1846 let selections = SelectionsCollection::new();
1847
1848 let blink_manager = cx.new(|cx| {
1849 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1850 if is_minimap {
1851 blink_manager.disable(cx);
1852 }
1853 blink_manager
1854 });
1855
1856 let soft_wrap_mode_override =
1857 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1858
1859 let mut project_subscriptions = Vec::new();
1860 if full_mode && let Some(project) = project.as_ref() {
1861 project_subscriptions.push(cx.subscribe_in(
1862 project,
1863 window,
1864 |editor, _, event, window, cx| match event {
1865 project::Event::RefreshCodeLens => {
1866 // we always query lens with actions, without storing them, always refreshing them
1867 }
1868 project::Event::RefreshInlayHints {
1869 server_id,
1870 request_id,
1871 } => {
1872 editor.refresh_inlay_hints(
1873 InlayHintRefreshReason::RefreshRequested {
1874 server_id: *server_id,
1875 request_id: *request_id,
1876 },
1877 cx,
1878 );
1879 }
1880 project::Event::LanguageServerRemoved(..) => {
1881 if editor.tasks_update_task.is_none() {
1882 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1883 }
1884 editor.registered_buffers.clear();
1885 editor.register_visible_buffers(cx);
1886 }
1887 project::Event::LanguageServerAdded(..) => {
1888 if editor.tasks_update_task.is_none() {
1889 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1890 }
1891 }
1892 project::Event::SnippetEdit(id, snippet_edits) => {
1893 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1894 let focus_handle = editor.focus_handle(cx);
1895 if focus_handle.is_focused(window) {
1896 let snapshot = buffer.read(cx).snapshot();
1897 for (range, snippet) in snippet_edits {
1898 let editor_range =
1899 language::range_from_lsp(*range).to_offset(&snapshot);
1900 editor
1901 .insert_snippet(
1902 &[editor_range],
1903 snippet.clone(),
1904 window,
1905 cx,
1906 )
1907 .ok();
1908 }
1909 }
1910 }
1911 }
1912 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1913 let buffer_id = *buffer_id;
1914 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1915 editor.register_buffer(buffer_id, cx);
1916 editor.update_lsp_data(Some(buffer_id), window, cx);
1917 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1918 refresh_linked_ranges(editor, window, cx);
1919 editor.refresh_code_actions(window, cx);
1920 editor.refresh_document_highlights(cx);
1921 }
1922 }
1923
1924 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1925 let Some(workspace) = editor.workspace() else {
1926 return;
1927 };
1928 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1929 else {
1930 return;
1931 };
1932
1933 if active_editor.entity_id() == cx.entity_id() {
1934 let entity_id = cx.entity_id();
1935 workspace.update(cx, |this, cx| {
1936 this.panes_mut()
1937 .iter_mut()
1938 .filter(|pane| pane.entity_id() != entity_id)
1939 .for_each(|p| {
1940 p.update(cx, |pane, _| {
1941 pane.nav_history_mut().rename_item(
1942 entity_id,
1943 project_path.clone(),
1944 abs_path.clone().into(),
1945 );
1946 })
1947 });
1948 });
1949 let edited_buffers_already_open = {
1950 let other_editors: Vec<Entity<Editor>> = workspace
1951 .read(cx)
1952 .panes()
1953 .iter()
1954 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1955 .filter(|editor| editor.entity_id() != cx.entity_id())
1956 .collect();
1957
1958 transaction.0.keys().all(|buffer| {
1959 other_editors.iter().any(|editor| {
1960 let multi_buffer = editor.read(cx).buffer();
1961 multi_buffer.read(cx).is_singleton()
1962 && multi_buffer.read(cx).as_singleton().map_or(
1963 false,
1964 |singleton| {
1965 singleton.entity_id() == buffer.entity_id()
1966 },
1967 )
1968 })
1969 })
1970 };
1971 if !edited_buffers_already_open {
1972 let workspace = workspace.downgrade();
1973 let transaction = transaction.clone();
1974 cx.defer_in(window, move |_, window, cx| {
1975 cx.spawn_in(window, async move |editor, cx| {
1976 Self::open_project_transaction(
1977 &editor,
1978 workspace,
1979 transaction,
1980 "Rename".to_string(),
1981 cx,
1982 )
1983 .await
1984 .ok()
1985 })
1986 .detach();
1987 });
1988 }
1989 }
1990 }
1991
1992 _ => {}
1993 },
1994 ));
1995 if let Some(task_inventory) = project
1996 .read(cx)
1997 .task_store()
1998 .read(cx)
1999 .task_inventory()
2000 .cloned()
2001 {
2002 project_subscriptions.push(cx.observe_in(
2003 &task_inventory,
2004 window,
2005 |editor, _, window, cx| {
2006 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2007 },
2008 ));
2009 };
2010
2011 project_subscriptions.push(cx.subscribe_in(
2012 &project.read(cx).breakpoint_store(),
2013 window,
2014 |editor, _, event, window, cx| match event {
2015 BreakpointStoreEvent::ClearDebugLines => {
2016 editor.clear_row_highlights::<ActiveDebugLine>();
2017 editor.refresh_inline_values(cx);
2018 }
2019 BreakpointStoreEvent::SetDebugLine => {
2020 if editor.go_to_active_debug_line(window, cx) {
2021 cx.stop_propagation();
2022 }
2023
2024 editor.refresh_inline_values(cx);
2025 }
2026 _ => {}
2027 },
2028 ));
2029 let git_store = project.read(cx).git_store().clone();
2030 let project = project.clone();
2031 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2032 if let GitStoreEvent::RepositoryAdded = event {
2033 this.load_diff_task = Some(
2034 update_uncommitted_diff_for_buffer(
2035 cx.entity(),
2036 &project,
2037 this.buffer.read(cx).all_buffers(),
2038 this.buffer.clone(),
2039 cx,
2040 )
2041 .shared(),
2042 );
2043 }
2044 }));
2045 }
2046
2047 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2048
2049 let inlay_hint_settings =
2050 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2051 let focus_handle = cx.focus_handle();
2052 if !is_minimap {
2053 cx.on_focus(&focus_handle, window, Self::handle_focus)
2054 .detach();
2055 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2056 .detach();
2057 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2058 .detach();
2059 cx.on_blur(&focus_handle, window, Self::handle_blur)
2060 .detach();
2061 cx.observe_pending_input(window, Self::observe_pending_input)
2062 .detach();
2063 }
2064
2065 let show_indent_guides =
2066 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2067 Some(false)
2068 } else {
2069 None
2070 };
2071
2072 let breakpoint_store = match (&mode, project.as_ref()) {
2073 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2074 _ => None,
2075 };
2076
2077 let mut code_action_providers = Vec::new();
2078 let mut load_uncommitted_diff = None;
2079 if let Some(project) = project.clone() {
2080 load_uncommitted_diff = Some(
2081 update_uncommitted_diff_for_buffer(
2082 cx.entity(),
2083 &project,
2084 multi_buffer.read(cx).all_buffers(),
2085 multi_buffer.clone(),
2086 cx,
2087 )
2088 .shared(),
2089 );
2090 code_action_providers.push(Rc::new(project) as Rc<_>);
2091 }
2092
2093 let mut editor = Self {
2094 focus_handle,
2095 show_cursor_when_unfocused: false,
2096 last_focused_descendant: None,
2097 buffer: multi_buffer.clone(),
2098 display_map: display_map.clone(),
2099 placeholder_display_map: None,
2100 selections,
2101 scroll_manager: ScrollManager::new(cx),
2102 columnar_selection_state: None,
2103 add_selections_state: None,
2104 select_next_state: None,
2105 select_prev_state: None,
2106 selection_history: SelectionHistory::default(),
2107 defer_selection_effects: false,
2108 deferred_selection_effects_state: None,
2109 autoclose_regions: Vec::new(),
2110 snippet_stack: InvalidationStack::default(),
2111 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2112 ime_transaction: None,
2113 active_diagnostics: ActiveDiagnostic::None,
2114 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2115 inline_diagnostics_update: Task::ready(()),
2116 inline_diagnostics: Vec::new(),
2117 soft_wrap_mode_override,
2118 diagnostics_max_severity,
2119 hard_wrap: None,
2120 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2121 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2122 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2123 project,
2124 blink_manager: blink_manager.clone(),
2125 show_local_selections: true,
2126 show_scrollbars: ScrollbarAxes {
2127 horizontal: full_mode,
2128 vertical: full_mode,
2129 },
2130 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2131 offset_content: !matches!(mode, EditorMode::SingleLine),
2132 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2133 show_gutter: full_mode,
2134 show_line_numbers: (!full_mode).then_some(false),
2135 use_relative_line_numbers: None,
2136 disable_expand_excerpt_buttons: !full_mode,
2137 show_git_diff_gutter: None,
2138 show_code_actions: None,
2139 show_runnables: None,
2140 show_breakpoints: None,
2141 show_wrap_guides: None,
2142 show_indent_guides,
2143 highlight_order: 0,
2144 highlighted_rows: HashMap::default(),
2145 background_highlights: HashMap::default(),
2146 gutter_highlights: HashMap::default(),
2147 scrollbar_marker_state: ScrollbarMarkerState::default(),
2148 active_indent_guides_state: ActiveIndentGuidesState::default(),
2149 nav_history: None,
2150 context_menu: RefCell::new(None),
2151 context_menu_options: None,
2152 mouse_context_menu: None,
2153 completion_tasks: Vec::new(),
2154 inline_blame_popover: None,
2155 inline_blame_popover_show_task: None,
2156 signature_help_state: SignatureHelpState::default(),
2157 auto_signature_help: None,
2158 find_all_references_task_sources: Vec::new(),
2159 next_completion_id: 0,
2160 next_inlay_id: 0,
2161 code_action_providers,
2162 available_code_actions: None,
2163 code_actions_task: None,
2164 quick_selection_highlight_task: None,
2165 debounced_selection_highlight_task: None,
2166 document_highlights_task: None,
2167 linked_editing_range_task: None,
2168 pending_rename: None,
2169 searchable: !is_minimap,
2170 cursor_shape: EditorSettings::get_global(cx)
2171 .cursor_shape
2172 .unwrap_or_default(),
2173 current_line_highlight: None,
2174 autoindent_mode: Some(AutoindentMode::EachLine),
2175
2176 workspace: None,
2177 input_enabled: !is_minimap,
2178 use_modal_editing: full_mode,
2179 read_only: is_minimap,
2180 use_autoclose: true,
2181 use_auto_surround: true,
2182 auto_replace_emoji_shortcode: false,
2183 jsx_tag_auto_close_enabled_in_any_buffer: false,
2184 leader_id: None,
2185 remote_id: None,
2186 hover_state: HoverState::default(),
2187 pending_mouse_down: None,
2188 hovered_link_state: None,
2189 edit_prediction_provider: None,
2190 active_edit_prediction: None,
2191 stale_edit_prediction_in_menu: None,
2192 edit_prediction_preview: EditPredictionPreview::Inactive {
2193 released_too_fast: false,
2194 },
2195 inline_diagnostics_enabled: full_mode,
2196 diagnostics_enabled: full_mode,
2197 word_completions_enabled: full_mode,
2198 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2199 gutter_hovered: false,
2200 pixel_position_of_newest_cursor: None,
2201 last_bounds: None,
2202 last_position_map: None,
2203 expect_bounds_change: None,
2204 gutter_dimensions: GutterDimensions::default(),
2205 style: None,
2206 show_cursor_names: false,
2207 hovered_cursors: HashMap::default(),
2208 next_editor_action_id: EditorActionId::default(),
2209 editor_actions: Rc::default(),
2210 edit_predictions_hidden_for_vim_mode: false,
2211 show_edit_predictions_override: None,
2212 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2213 edit_prediction_settings: EditPredictionSettings::Disabled,
2214 edit_prediction_indent_conflict: false,
2215 edit_prediction_requires_modifier_in_indent_conflict: true,
2216 custom_context_menu: None,
2217 show_git_blame_gutter: false,
2218 show_git_blame_inline: false,
2219 show_selection_menu: None,
2220 show_git_blame_inline_delay_task: None,
2221 git_blame_inline_enabled: full_mode
2222 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2223 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2224 buffer_serialization: is_minimap.not().then(|| {
2225 BufferSerialization::new(
2226 ProjectSettings::get_global(cx)
2227 .session
2228 .restore_unsaved_buffers,
2229 )
2230 }),
2231 blame: None,
2232 blame_subscription: None,
2233 tasks: BTreeMap::default(),
2234
2235 breakpoint_store,
2236 gutter_breakpoint_indicator: (None, None),
2237 hovered_diff_hunk_row: None,
2238 _subscriptions: (!is_minimap)
2239 .then(|| {
2240 vec![
2241 cx.observe(&multi_buffer, Self::on_buffer_changed),
2242 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2243 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2244 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2245 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2246 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2247 cx.observe_window_activation(window, |editor, window, cx| {
2248 let active = window.is_window_active();
2249 editor.blink_manager.update(cx, |blink_manager, cx| {
2250 if active {
2251 blink_manager.enable(cx);
2252 } else {
2253 blink_manager.disable(cx);
2254 }
2255 });
2256 if active {
2257 editor.show_mouse_cursor(cx);
2258 }
2259 }),
2260 ]
2261 })
2262 .unwrap_or_default(),
2263 tasks_update_task: None,
2264 pull_diagnostics_task: Task::ready(()),
2265 colors: None,
2266 refresh_colors_task: Task::ready(()),
2267 inlay_hints: None,
2268 next_color_inlay_id: 0,
2269 post_scroll_update: Task::ready(()),
2270 linked_edit_ranges: Default::default(),
2271 in_project_search: false,
2272 previous_search_ranges: None,
2273 breadcrumb_header: None,
2274 focused_block: None,
2275 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2276 addons: HashMap::default(),
2277 registered_buffers: HashMap::default(),
2278 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2279 selection_mark_mode: false,
2280 toggle_fold_multiple_buffers: Task::ready(()),
2281 serialize_selections: Task::ready(()),
2282 serialize_folds: Task::ready(()),
2283 text_style_refinement: None,
2284 load_diff_task: load_uncommitted_diff,
2285 temporary_diff_override: false,
2286 mouse_cursor_hidden: false,
2287 minimap: None,
2288 hide_mouse_mode: EditorSettings::get_global(cx)
2289 .hide_mouse
2290 .unwrap_or_default(),
2291 change_list: ChangeList::new(),
2292 mode,
2293 selection_drag_state: SelectionDragState::None,
2294 folding_newlines: Task::ready(()),
2295 lookup_key: None,
2296 };
2297
2298 if is_minimap {
2299 return editor;
2300 }
2301
2302 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2303 editor
2304 ._subscriptions
2305 .push(cx.observe(breakpoints, |_, _, cx| {
2306 cx.notify();
2307 }));
2308 }
2309 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2310 editor._subscriptions.extend(project_subscriptions);
2311
2312 editor._subscriptions.push(cx.subscribe_in(
2313 &cx.entity(),
2314 window,
2315 |editor, _, e: &EditorEvent, window, cx| match e {
2316 EditorEvent::ScrollPositionChanged { local, .. } => {
2317 if *local {
2318 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2319 editor.inline_blame_popover.take();
2320 let new_anchor = editor.scroll_manager.anchor();
2321 let snapshot = editor.snapshot(window, cx);
2322 editor.update_restoration_data(cx, move |data| {
2323 data.scroll_position = (
2324 new_anchor.top_row(snapshot.buffer_snapshot()),
2325 new_anchor.offset,
2326 );
2327 });
2328
2329 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2330 cx.background_executor()
2331 .timer(Duration::from_millis(50))
2332 .await;
2333 editor
2334 .update_in(cx, |editor, window, cx| {
2335 editor.register_visible_buffers(cx);
2336 editor.refresh_colors_for_visible_range(None, window, cx);
2337 editor.refresh_inlay_hints(
2338 InlayHintRefreshReason::NewLinesShown,
2339 cx,
2340 );
2341 })
2342 .ok();
2343 });
2344 }
2345 }
2346 EditorEvent::Edited { .. } => {
2347 if vim_flavor(cx).is_none() {
2348 let display_map = editor.display_snapshot(cx);
2349 let selections = editor.selections.all_adjusted_display(&display_map);
2350 let pop_state = editor
2351 .change_list
2352 .last()
2353 .map(|previous| {
2354 previous.len() == selections.len()
2355 && previous.iter().enumerate().all(|(ix, p)| {
2356 p.to_display_point(&display_map).row()
2357 == selections[ix].head().row()
2358 })
2359 })
2360 .unwrap_or(false);
2361 let new_positions = selections
2362 .into_iter()
2363 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2364 .collect();
2365 editor
2366 .change_list
2367 .push_to_change_list(pop_state, new_positions);
2368 }
2369 }
2370 _ => (),
2371 },
2372 ));
2373
2374 if let Some(dap_store) = editor
2375 .project
2376 .as_ref()
2377 .map(|project| project.read(cx).dap_store())
2378 {
2379 let weak_editor = cx.weak_entity();
2380
2381 editor
2382 ._subscriptions
2383 .push(
2384 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2385 let session_entity = cx.entity();
2386 weak_editor
2387 .update(cx, |editor, cx| {
2388 editor._subscriptions.push(
2389 cx.subscribe(&session_entity, Self::on_debug_session_event),
2390 );
2391 })
2392 .ok();
2393 }),
2394 );
2395
2396 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2397 editor
2398 ._subscriptions
2399 .push(cx.subscribe(&session, Self::on_debug_session_event));
2400 }
2401 }
2402
2403 // skip adding the initial selection to selection history
2404 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2405 editor.end_selection(window, cx);
2406 editor.selection_history.mode = SelectionHistoryMode::Normal;
2407
2408 editor.scroll_manager.show_scrollbars(window, cx);
2409 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2410
2411 if full_mode {
2412 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2413 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2414
2415 if editor.git_blame_inline_enabled {
2416 editor.start_git_blame_inline(false, window, cx);
2417 }
2418
2419 editor.go_to_active_debug_line(window, cx);
2420
2421 editor.minimap =
2422 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2423 editor.colors = Some(LspColorData::new(cx));
2424 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2425
2426 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2427 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2428 }
2429 editor.update_lsp_data(None, window, cx);
2430 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2431 }
2432
2433 editor
2434 }
2435
2436 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2437 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2438 }
2439
2440 pub fn deploy_mouse_context_menu(
2441 &mut self,
2442 position: gpui::Point<Pixels>,
2443 context_menu: Entity<ContextMenu>,
2444 window: &mut Window,
2445 cx: &mut Context<Self>,
2446 ) {
2447 self.mouse_context_menu = Some(MouseContextMenu::new(
2448 self,
2449 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2450 context_menu,
2451 window,
2452 cx,
2453 ));
2454 }
2455
2456 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2457 self.mouse_context_menu
2458 .as_ref()
2459 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2460 }
2461
2462 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2463 if self
2464 .selections
2465 .pending_anchor()
2466 .is_some_and(|pending_selection| {
2467 let snapshot = self.buffer().read(cx).snapshot(cx);
2468 pending_selection.range().includes(range, &snapshot)
2469 })
2470 {
2471 return true;
2472 }
2473
2474 self.selections
2475 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2476 .into_iter()
2477 .any(|selection| {
2478 // This is needed to cover a corner case, if we just check for an existing
2479 // selection in the fold range, having a cursor at the start of the fold
2480 // marks it as selected. Non-empty selections don't cause this.
2481 let length = selection.end - selection.start;
2482 length > 0
2483 })
2484 }
2485
2486 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2487 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2488 }
2489
2490 fn key_context_internal(
2491 &self,
2492 has_active_edit_prediction: bool,
2493 window: &mut Window,
2494 cx: &mut App,
2495 ) -> KeyContext {
2496 let mut key_context = KeyContext::new_with_defaults();
2497 key_context.add("Editor");
2498 let mode = match self.mode {
2499 EditorMode::SingleLine => "single_line",
2500 EditorMode::AutoHeight { .. } => "auto_height",
2501 EditorMode::Minimap { .. } => "minimap",
2502 EditorMode::Full { .. } => "full",
2503 };
2504
2505 if EditorSettings::jupyter_enabled(cx) {
2506 key_context.add("jupyter");
2507 }
2508
2509 key_context.set("mode", mode);
2510 if self.pending_rename.is_some() {
2511 key_context.add("renaming");
2512 }
2513
2514 if let Some(snippet_stack) = self.snippet_stack.last() {
2515 key_context.add("in_snippet");
2516
2517 if snippet_stack.active_index > 0 {
2518 key_context.add("has_previous_tabstop");
2519 }
2520
2521 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2522 key_context.add("has_next_tabstop");
2523 }
2524 }
2525
2526 match self.context_menu.borrow().as_ref() {
2527 Some(CodeContextMenu::Completions(menu)) => {
2528 if menu.visible() {
2529 key_context.add("menu");
2530 key_context.add("showing_completions");
2531 }
2532 }
2533 Some(CodeContextMenu::CodeActions(menu)) => {
2534 if menu.visible() {
2535 key_context.add("menu");
2536 key_context.add("showing_code_actions")
2537 }
2538 }
2539 None => {}
2540 }
2541
2542 if self.signature_help_state.has_multiple_signatures() {
2543 key_context.add("showing_signature_help");
2544 }
2545
2546 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2547 if !self.focus_handle(cx).contains_focused(window, cx)
2548 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2549 {
2550 for addon in self.addons.values() {
2551 addon.extend_key_context(&mut key_context, cx)
2552 }
2553 }
2554
2555 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2556 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2557 Some(
2558 file.full_path(cx)
2559 .extension()?
2560 .to_string_lossy()
2561 .into_owned(),
2562 )
2563 }) {
2564 key_context.set("extension", extension);
2565 }
2566 } else {
2567 key_context.add("multibuffer");
2568 }
2569
2570 if has_active_edit_prediction {
2571 if self.edit_prediction_in_conflict() {
2572 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2573 } else {
2574 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2575 key_context.add("copilot_suggestion");
2576 }
2577 }
2578
2579 if self.selection_mark_mode {
2580 key_context.add("selection_mode");
2581 }
2582
2583 let disjoint = self.selections.disjoint_anchors();
2584 let snapshot = self.snapshot(window, cx);
2585 let snapshot = snapshot.buffer_snapshot();
2586 if self.mode == EditorMode::SingleLine
2587 && let [selection] = disjoint
2588 && selection.start == selection.end
2589 && selection.end.to_offset(snapshot) == snapshot.len()
2590 {
2591 key_context.add("end_of_input");
2592 }
2593
2594 key_context
2595 }
2596
2597 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2598 self.last_bounds.as_ref()
2599 }
2600
2601 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2602 if self.mouse_cursor_hidden {
2603 self.mouse_cursor_hidden = false;
2604 cx.notify();
2605 }
2606 }
2607
2608 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2609 let hide_mouse_cursor = match origin {
2610 HideMouseCursorOrigin::TypingAction => {
2611 matches!(
2612 self.hide_mouse_mode,
2613 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2614 )
2615 }
2616 HideMouseCursorOrigin::MovementAction => {
2617 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2618 }
2619 };
2620 if self.mouse_cursor_hidden != hide_mouse_cursor {
2621 self.mouse_cursor_hidden = hide_mouse_cursor;
2622 cx.notify();
2623 }
2624 }
2625
2626 pub fn edit_prediction_in_conflict(&self) -> bool {
2627 if !self.show_edit_predictions_in_menu() {
2628 return false;
2629 }
2630
2631 let showing_completions = self
2632 .context_menu
2633 .borrow()
2634 .as_ref()
2635 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2636
2637 showing_completions
2638 || self.edit_prediction_requires_modifier()
2639 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2640 // bindings to insert tab characters.
2641 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2642 }
2643
2644 pub fn accept_edit_prediction_keybind(
2645 &self,
2646 accept_partial: bool,
2647 window: &mut Window,
2648 cx: &mut App,
2649 ) -> AcceptEditPredictionBinding {
2650 let key_context = self.key_context_internal(true, window, cx);
2651 let in_conflict = self.edit_prediction_in_conflict();
2652
2653 let bindings = if accept_partial {
2654 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2655 } else {
2656 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2657 };
2658
2659 // TODO: if the binding contains multiple keystrokes, display all of them, not
2660 // just the first one.
2661 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2662 !in_conflict
2663 || binding
2664 .keystrokes()
2665 .first()
2666 .is_some_and(|keystroke| keystroke.modifiers().modified())
2667 }))
2668 }
2669
2670 pub fn new_file(
2671 workspace: &mut Workspace,
2672 _: &workspace::NewFile,
2673 window: &mut Window,
2674 cx: &mut Context<Workspace>,
2675 ) {
2676 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2677 "Failed to create buffer",
2678 window,
2679 cx,
2680 |e, _, _| match e.error_code() {
2681 ErrorCode::RemoteUpgradeRequired => Some(format!(
2682 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2683 e.error_tag("required").unwrap_or("the latest version")
2684 )),
2685 _ => None,
2686 },
2687 );
2688 }
2689
2690 pub fn new_in_workspace(
2691 workspace: &mut Workspace,
2692 window: &mut Window,
2693 cx: &mut Context<Workspace>,
2694 ) -> Task<Result<Entity<Editor>>> {
2695 let project = workspace.project().clone();
2696 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2697
2698 cx.spawn_in(window, async move |workspace, cx| {
2699 let buffer = create.await?;
2700 workspace.update_in(cx, |workspace, window, cx| {
2701 let editor =
2702 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2703 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2704 editor
2705 })
2706 })
2707 }
2708
2709 fn new_file_vertical(
2710 workspace: &mut Workspace,
2711 _: &workspace::NewFileSplitVertical,
2712 window: &mut Window,
2713 cx: &mut Context<Workspace>,
2714 ) {
2715 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2716 }
2717
2718 fn new_file_horizontal(
2719 workspace: &mut Workspace,
2720 _: &workspace::NewFileSplitHorizontal,
2721 window: &mut Window,
2722 cx: &mut Context<Workspace>,
2723 ) {
2724 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2725 }
2726
2727 fn new_file_split(
2728 workspace: &mut Workspace,
2729 action: &workspace::NewFileSplit,
2730 window: &mut Window,
2731 cx: &mut Context<Workspace>,
2732 ) {
2733 Self::new_file_in_direction(workspace, action.0, window, cx)
2734 }
2735
2736 fn new_file_in_direction(
2737 workspace: &mut Workspace,
2738 direction: SplitDirection,
2739 window: &mut Window,
2740 cx: &mut Context<Workspace>,
2741 ) {
2742 let project = workspace.project().clone();
2743 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2744
2745 cx.spawn_in(window, async move |workspace, cx| {
2746 let buffer = create.await?;
2747 workspace.update_in(cx, move |workspace, window, cx| {
2748 workspace.split_item(
2749 direction,
2750 Box::new(
2751 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2752 ),
2753 window,
2754 cx,
2755 )
2756 })?;
2757 anyhow::Ok(())
2758 })
2759 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2760 match e.error_code() {
2761 ErrorCode::RemoteUpgradeRequired => Some(format!(
2762 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2763 e.error_tag("required").unwrap_or("the latest version")
2764 )),
2765 _ => None,
2766 }
2767 });
2768 }
2769
2770 pub fn leader_id(&self) -> Option<CollaboratorId> {
2771 self.leader_id
2772 }
2773
2774 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2775 &self.buffer
2776 }
2777
2778 pub fn project(&self) -> Option<&Entity<Project>> {
2779 self.project.as_ref()
2780 }
2781
2782 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2783 self.workspace.as_ref()?.0.upgrade()
2784 }
2785
2786 /// Returns the workspace serialization ID if this editor should be serialized.
2787 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2788 self.workspace
2789 .as_ref()
2790 .filter(|_| self.should_serialize_buffer())
2791 .and_then(|workspace| workspace.1)
2792 }
2793
2794 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2795 self.buffer().read(cx).title(cx)
2796 }
2797
2798 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2799 let git_blame_gutter_max_author_length = self
2800 .render_git_blame_gutter(cx)
2801 .then(|| {
2802 if let Some(blame) = self.blame.as_ref() {
2803 let max_author_length =
2804 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2805 Some(max_author_length)
2806 } else {
2807 None
2808 }
2809 })
2810 .flatten();
2811
2812 EditorSnapshot {
2813 mode: self.mode.clone(),
2814 show_gutter: self.show_gutter,
2815 show_line_numbers: self.show_line_numbers,
2816 show_git_diff_gutter: self.show_git_diff_gutter,
2817 show_code_actions: self.show_code_actions,
2818 show_runnables: self.show_runnables,
2819 show_breakpoints: self.show_breakpoints,
2820 git_blame_gutter_max_author_length,
2821 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2822 placeholder_display_snapshot: self
2823 .placeholder_display_map
2824 .as_ref()
2825 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2826 scroll_anchor: self.scroll_manager.anchor(),
2827 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2828 is_focused: self.focus_handle.is_focused(window),
2829 current_line_highlight: self
2830 .current_line_highlight
2831 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2832 gutter_hovered: self.gutter_hovered,
2833 }
2834 }
2835
2836 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2837 self.buffer.read(cx).language_at(point, cx)
2838 }
2839
2840 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2841 self.buffer.read(cx).read(cx).file_at(point).cloned()
2842 }
2843
2844 pub fn active_excerpt(
2845 &self,
2846 cx: &App,
2847 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2848 self.buffer
2849 .read(cx)
2850 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2851 }
2852
2853 pub fn mode(&self) -> &EditorMode {
2854 &self.mode
2855 }
2856
2857 pub fn set_mode(&mut self, mode: EditorMode) {
2858 self.mode = mode;
2859 }
2860
2861 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2862 self.collaboration_hub.as_deref()
2863 }
2864
2865 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2866 self.collaboration_hub = Some(hub);
2867 }
2868
2869 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2870 self.in_project_search = in_project_search;
2871 }
2872
2873 pub fn set_custom_context_menu(
2874 &mut self,
2875 f: impl 'static
2876 + Fn(
2877 &mut Self,
2878 DisplayPoint,
2879 &mut Window,
2880 &mut Context<Self>,
2881 ) -> Option<Entity<ui::ContextMenu>>,
2882 ) {
2883 self.custom_context_menu = Some(Box::new(f))
2884 }
2885
2886 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2887 self.completion_provider = provider;
2888 }
2889
2890 #[cfg(any(test, feature = "test-support"))]
2891 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2892 self.completion_provider.clone()
2893 }
2894
2895 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2896 self.semantics_provider.clone()
2897 }
2898
2899 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2900 self.semantics_provider = provider;
2901 }
2902
2903 pub fn set_edit_prediction_provider<T>(
2904 &mut self,
2905 provider: Option<Entity<T>>,
2906 window: &mut Window,
2907 cx: &mut Context<Self>,
2908 ) where
2909 T: EditPredictionProvider,
2910 {
2911 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2912 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2913 if this.focus_handle.is_focused(window) {
2914 this.update_visible_edit_prediction(window, cx);
2915 }
2916 }),
2917 provider: Arc::new(provider),
2918 });
2919 self.update_edit_prediction_settings(cx);
2920 self.refresh_edit_prediction(false, false, window, cx);
2921 }
2922
2923 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2924 self.placeholder_display_map
2925 .as_ref()
2926 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2927 }
2928
2929 pub fn set_placeholder_text(
2930 &mut self,
2931 placeholder_text: &str,
2932 window: &mut Window,
2933 cx: &mut Context<Self>,
2934 ) {
2935 let multibuffer = cx
2936 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2937
2938 let style = window.text_style();
2939
2940 self.placeholder_display_map = Some(cx.new(|cx| {
2941 DisplayMap::new(
2942 multibuffer,
2943 style.font(),
2944 style.font_size.to_pixels(window.rem_size()),
2945 None,
2946 FILE_HEADER_HEIGHT,
2947 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2948 Default::default(),
2949 DiagnosticSeverity::Off,
2950 cx,
2951 )
2952 }));
2953 cx.notify();
2954 }
2955
2956 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2957 self.cursor_shape = cursor_shape;
2958
2959 // Disrupt blink for immediate user feedback that the cursor shape has changed
2960 self.blink_manager.update(cx, BlinkManager::show_cursor);
2961
2962 cx.notify();
2963 }
2964
2965 pub fn set_current_line_highlight(
2966 &mut self,
2967 current_line_highlight: Option<CurrentLineHighlight>,
2968 ) {
2969 self.current_line_highlight = current_line_highlight;
2970 }
2971
2972 pub fn range_for_match<T: std::marker::Copy>(
2973 &self,
2974 range: &Range<T>,
2975 collapse: bool,
2976 ) -> Range<T> {
2977 if collapse {
2978 return range.start..range.start;
2979 }
2980 range.clone()
2981 }
2982
2983 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2984 if self.display_map.read(cx).clip_at_line_ends != clip {
2985 self.display_map
2986 .update(cx, |map, _| map.clip_at_line_ends = clip);
2987 }
2988 }
2989
2990 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2991 self.input_enabled = input_enabled;
2992 }
2993
2994 pub fn set_edit_predictions_hidden_for_vim_mode(
2995 &mut self,
2996 hidden: bool,
2997 window: &mut Window,
2998 cx: &mut Context<Self>,
2999 ) {
3000 if hidden != self.edit_predictions_hidden_for_vim_mode {
3001 self.edit_predictions_hidden_for_vim_mode = hidden;
3002 if hidden {
3003 self.update_visible_edit_prediction(window, cx);
3004 } else {
3005 self.refresh_edit_prediction(true, false, window, cx);
3006 }
3007 }
3008 }
3009
3010 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3011 self.menu_edit_predictions_policy = value;
3012 }
3013
3014 pub fn set_autoindent(&mut self, autoindent: bool) {
3015 if autoindent {
3016 self.autoindent_mode = Some(AutoindentMode::EachLine);
3017 } else {
3018 self.autoindent_mode = None;
3019 }
3020 }
3021
3022 pub fn read_only(&self, cx: &App) -> bool {
3023 self.read_only || self.buffer.read(cx).read_only()
3024 }
3025
3026 pub fn set_read_only(&mut self, read_only: bool) {
3027 self.read_only = read_only;
3028 }
3029
3030 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3031 self.use_autoclose = autoclose;
3032 }
3033
3034 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3035 self.use_auto_surround = auto_surround;
3036 }
3037
3038 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3039 self.auto_replace_emoji_shortcode = auto_replace;
3040 }
3041
3042 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3043 self.buffer_serialization = should_serialize.then(|| {
3044 BufferSerialization::new(
3045 ProjectSettings::get_global(cx)
3046 .session
3047 .restore_unsaved_buffers,
3048 )
3049 })
3050 }
3051
3052 fn should_serialize_buffer(&self) -> bool {
3053 self.buffer_serialization.is_some()
3054 }
3055
3056 pub fn toggle_edit_predictions(
3057 &mut self,
3058 _: &ToggleEditPrediction,
3059 window: &mut Window,
3060 cx: &mut Context<Self>,
3061 ) {
3062 if self.show_edit_predictions_override.is_some() {
3063 self.set_show_edit_predictions(None, window, cx);
3064 } else {
3065 let show_edit_predictions = !self.edit_predictions_enabled();
3066 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3067 }
3068 }
3069
3070 pub fn set_show_edit_predictions(
3071 &mut self,
3072 show_edit_predictions: Option<bool>,
3073 window: &mut Window,
3074 cx: &mut Context<Self>,
3075 ) {
3076 self.show_edit_predictions_override = show_edit_predictions;
3077 self.update_edit_prediction_settings(cx);
3078
3079 if let Some(false) = show_edit_predictions {
3080 self.discard_edit_prediction(false, cx);
3081 } else {
3082 self.refresh_edit_prediction(false, true, window, cx);
3083 }
3084 }
3085
3086 fn edit_predictions_disabled_in_scope(
3087 &self,
3088 buffer: &Entity<Buffer>,
3089 buffer_position: language::Anchor,
3090 cx: &App,
3091 ) -> bool {
3092 let snapshot = buffer.read(cx).snapshot();
3093 let settings = snapshot.settings_at(buffer_position, cx);
3094
3095 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3096 return false;
3097 };
3098
3099 scope.override_name().is_some_and(|scope_name| {
3100 settings
3101 .edit_predictions_disabled_in
3102 .iter()
3103 .any(|s| s == scope_name)
3104 })
3105 }
3106
3107 pub fn set_use_modal_editing(&mut self, to: bool) {
3108 self.use_modal_editing = to;
3109 }
3110
3111 pub fn use_modal_editing(&self) -> bool {
3112 self.use_modal_editing
3113 }
3114
3115 fn selections_did_change(
3116 &mut self,
3117 local: bool,
3118 old_cursor_position: &Anchor,
3119 effects: SelectionEffects,
3120 window: &mut Window,
3121 cx: &mut Context<Self>,
3122 ) {
3123 window.invalidate_character_coordinates();
3124
3125 // Copy selections to primary selection buffer
3126 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3127 if local {
3128 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3129 let buffer_handle = self.buffer.read(cx).read(cx);
3130
3131 let mut text = String::new();
3132 for (index, selection) in selections.iter().enumerate() {
3133 let text_for_selection = buffer_handle
3134 .text_for_range(selection.start..selection.end)
3135 .collect::<String>();
3136
3137 text.push_str(&text_for_selection);
3138 if index != selections.len() - 1 {
3139 text.push('\n');
3140 }
3141 }
3142
3143 if !text.is_empty() {
3144 cx.write_to_primary(ClipboardItem::new_string(text));
3145 }
3146 }
3147
3148 let selection_anchors = self.selections.disjoint_anchors_arc();
3149
3150 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3151 self.buffer.update(cx, |buffer, cx| {
3152 buffer.set_active_selections(
3153 &selection_anchors,
3154 self.selections.line_mode(),
3155 self.cursor_shape,
3156 cx,
3157 )
3158 });
3159 }
3160 let display_map = self
3161 .display_map
3162 .update(cx, |display_map, cx| display_map.snapshot(cx));
3163 let buffer = display_map.buffer_snapshot();
3164 if self.selections.count() == 1 {
3165 self.add_selections_state = None;
3166 }
3167 self.select_next_state = None;
3168 self.select_prev_state = None;
3169 self.select_syntax_node_history.try_clear();
3170 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3171 self.snippet_stack.invalidate(&selection_anchors, buffer);
3172 self.take_rename(false, window, cx);
3173
3174 let newest_selection = self.selections.newest_anchor();
3175 let new_cursor_position = newest_selection.head();
3176 let selection_start = newest_selection.start;
3177
3178 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3179 self.push_to_nav_history(
3180 *old_cursor_position,
3181 Some(new_cursor_position.to_point(buffer)),
3182 false,
3183 effects.nav_history == Some(true),
3184 cx,
3185 );
3186 }
3187
3188 if local {
3189 if let Some(buffer_id) = new_cursor_position.buffer_id {
3190 self.register_buffer(buffer_id, cx);
3191 }
3192
3193 let mut context_menu = self.context_menu.borrow_mut();
3194 let completion_menu = match context_menu.as_ref() {
3195 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3196 Some(CodeContextMenu::CodeActions(_)) => {
3197 *context_menu = None;
3198 None
3199 }
3200 None => None,
3201 };
3202 let completion_position = completion_menu.map(|menu| menu.initial_position);
3203 drop(context_menu);
3204
3205 if effects.completions
3206 && let Some(completion_position) = completion_position
3207 {
3208 let start_offset = selection_start.to_offset(buffer);
3209 let position_matches = start_offset == completion_position.to_offset(buffer);
3210 let continue_showing = if position_matches {
3211 if self.snippet_stack.is_empty() {
3212 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3213 == Some(CharKind::Word)
3214 } else {
3215 // Snippet choices can be shown even when the cursor is in whitespace.
3216 // Dismissing the menu with actions like backspace is handled by
3217 // invalidation regions.
3218 true
3219 }
3220 } else {
3221 false
3222 };
3223
3224 if continue_showing {
3225 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3226 } else {
3227 self.hide_context_menu(window, cx);
3228 }
3229 }
3230
3231 hide_hover(self, cx);
3232
3233 if old_cursor_position.to_display_point(&display_map).row()
3234 != new_cursor_position.to_display_point(&display_map).row()
3235 {
3236 self.available_code_actions.take();
3237 }
3238 self.refresh_code_actions(window, cx);
3239 self.refresh_document_highlights(cx);
3240 refresh_linked_ranges(self, window, cx);
3241
3242 self.refresh_selected_text_highlights(false, window, cx);
3243 self.refresh_matching_bracket_highlights(window, cx);
3244 self.update_visible_edit_prediction(window, cx);
3245 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3246 self.inline_blame_popover.take();
3247 if self.git_blame_inline_enabled {
3248 self.start_inline_blame_timer(window, cx);
3249 }
3250 }
3251
3252 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3253 cx.emit(EditorEvent::SelectionsChanged { local });
3254
3255 let selections = &self.selections.disjoint_anchors_arc();
3256 if selections.len() == 1 {
3257 cx.emit(SearchEvent::ActiveMatchChanged)
3258 }
3259 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3260 let inmemory_selections = selections
3261 .iter()
3262 .map(|s| {
3263 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3264 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3265 })
3266 .collect();
3267 self.update_restoration_data(cx, |data| {
3268 data.selections = inmemory_selections;
3269 });
3270
3271 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3272 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3273 {
3274 let snapshot = self.buffer().read(cx).snapshot(cx);
3275 let selections = selections.clone();
3276 let background_executor = cx.background_executor().clone();
3277 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3278 self.serialize_selections = cx.background_spawn(async move {
3279 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3280 let db_selections = selections
3281 .iter()
3282 .map(|selection| {
3283 (
3284 selection.start.to_offset(&snapshot),
3285 selection.end.to_offset(&snapshot),
3286 )
3287 })
3288 .collect();
3289
3290 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3291 .await
3292 .with_context(|| {
3293 format!(
3294 "persisting editor selections for editor {editor_id}, \
3295 workspace {workspace_id:?}"
3296 )
3297 })
3298 .log_err();
3299 });
3300 }
3301 }
3302
3303 cx.notify();
3304 }
3305
3306 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3307 use text::ToOffset as _;
3308 use text::ToPoint as _;
3309
3310 if self.mode.is_minimap()
3311 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3312 {
3313 return;
3314 }
3315
3316 if !self.buffer().read(cx).is_singleton() {
3317 return;
3318 }
3319
3320 let display_snapshot = self
3321 .display_map
3322 .update(cx, |display_map, cx| display_map.snapshot(cx));
3323 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3324 return;
3325 };
3326 let inmemory_folds = display_snapshot
3327 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3328 .map(|fold| {
3329 fold.range.start.text_anchor.to_point(&snapshot)
3330 ..fold.range.end.text_anchor.to_point(&snapshot)
3331 })
3332 .collect();
3333 self.update_restoration_data(cx, |data| {
3334 data.folds = inmemory_folds;
3335 });
3336
3337 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3338 return;
3339 };
3340 let background_executor = cx.background_executor().clone();
3341 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3342 let db_folds = display_snapshot
3343 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3344 .map(|fold| {
3345 (
3346 fold.range.start.text_anchor.to_offset(&snapshot),
3347 fold.range.end.text_anchor.to_offset(&snapshot),
3348 )
3349 })
3350 .collect();
3351 self.serialize_folds = cx.background_spawn(async move {
3352 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3353 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3354 .await
3355 .with_context(|| {
3356 format!(
3357 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3358 )
3359 })
3360 .log_err();
3361 });
3362 }
3363
3364 pub fn sync_selections(
3365 &mut self,
3366 other: Entity<Editor>,
3367 cx: &mut Context<Self>,
3368 ) -> gpui::Subscription {
3369 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3370 if !other_selections.is_empty() {
3371 self.selections
3372 .change_with(&self.display_snapshot(cx), |selections| {
3373 selections.select_anchors(other_selections);
3374 });
3375 }
3376
3377 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3378 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3379 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3380 if other_selections.is_empty() {
3381 return;
3382 }
3383 let snapshot = this.display_snapshot(cx);
3384 this.selections.change_with(&snapshot, |selections| {
3385 selections.select_anchors(other_selections);
3386 });
3387 }
3388 });
3389
3390 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3391 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3392 let these_selections = this.selections.disjoint_anchors().to_vec();
3393 if these_selections.is_empty() {
3394 return;
3395 }
3396 other.update(cx, |other_editor, cx| {
3397 let snapshot = other_editor.display_snapshot(cx);
3398 other_editor
3399 .selections
3400 .change_with(&snapshot, |selections| {
3401 selections.select_anchors(these_selections);
3402 })
3403 });
3404 }
3405 });
3406
3407 Subscription::join(other_subscription, this_subscription)
3408 }
3409
3410 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3411 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3412 /// effects of selection change occur at the end of the transaction.
3413 pub fn change_selections<R>(
3414 &mut self,
3415 effects: SelectionEffects,
3416 window: &mut Window,
3417 cx: &mut Context<Self>,
3418 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3419 ) -> R {
3420 let snapshot = self.display_snapshot(cx);
3421 if let Some(state) = &mut self.deferred_selection_effects_state {
3422 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3423 state.effects.completions = effects.completions;
3424 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3425 let (changed, result) = self.selections.change_with(&snapshot, change);
3426 state.changed |= changed;
3427 return result;
3428 }
3429 let mut state = DeferredSelectionEffectsState {
3430 changed: false,
3431 effects,
3432 old_cursor_position: self.selections.newest_anchor().head(),
3433 history_entry: SelectionHistoryEntry {
3434 selections: self.selections.disjoint_anchors_arc(),
3435 select_next_state: self.select_next_state.clone(),
3436 select_prev_state: self.select_prev_state.clone(),
3437 add_selections_state: self.add_selections_state.clone(),
3438 },
3439 };
3440 let (changed, result) = self.selections.change_with(&snapshot, change);
3441 state.changed = state.changed || changed;
3442 if self.defer_selection_effects {
3443 self.deferred_selection_effects_state = Some(state);
3444 } else {
3445 self.apply_selection_effects(state, window, cx);
3446 }
3447 result
3448 }
3449
3450 /// Defers the effects of selection change, so that the effects of multiple calls to
3451 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3452 /// to selection history and the state of popovers based on selection position aren't
3453 /// erroneously updated.
3454 pub fn with_selection_effects_deferred<R>(
3455 &mut self,
3456 window: &mut Window,
3457 cx: &mut Context<Self>,
3458 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3459 ) -> R {
3460 let already_deferred = self.defer_selection_effects;
3461 self.defer_selection_effects = true;
3462 let result = update(self, window, cx);
3463 if !already_deferred {
3464 self.defer_selection_effects = false;
3465 if let Some(state) = self.deferred_selection_effects_state.take() {
3466 self.apply_selection_effects(state, window, cx);
3467 }
3468 }
3469 result
3470 }
3471
3472 fn apply_selection_effects(
3473 &mut self,
3474 state: DeferredSelectionEffectsState,
3475 window: &mut Window,
3476 cx: &mut Context<Self>,
3477 ) {
3478 if state.changed {
3479 self.selection_history.push(state.history_entry);
3480
3481 if let Some(autoscroll) = state.effects.scroll {
3482 self.request_autoscroll(autoscroll, cx);
3483 }
3484
3485 let old_cursor_position = &state.old_cursor_position;
3486
3487 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3488
3489 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3490 self.show_signature_help(&ShowSignatureHelp, window, cx);
3491 }
3492 }
3493 }
3494
3495 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3496 where
3497 I: IntoIterator<Item = (Range<S>, T)>,
3498 S: ToOffset,
3499 T: Into<Arc<str>>,
3500 {
3501 if self.read_only(cx) {
3502 return;
3503 }
3504
3505 self.buffer
3506 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3507 }
3508
3509 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3510 where
3511 I: IntoIterator<Item = (Range<S>, T)>,
3512 S: ToOffset,
3513 T: Into<Arc<str>>,
3514 {
3515 if self.read_only(cx) {
3516 return;
3517 }
3518
3519 self.buffer.update(cx, |buffer, cx| {
3520 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3521 });
3522 }
3523
3524 pub fn edit_with_block_indent<I, S, T>(
3525 &mut self,
3526 edits: I,
3527 original_indent_columns: Vec<Option<u32>>,
3528 cx: &mut Context<Self>,
3529 ) where
3530 I: IntoIterator<Item = (Range<S>, T)>,
3531 S: ToOffset,
3532 T: Into<Arc<str>>,
3533 {
3534 if self.read_only(cx) {
3535 return;
3536 }
3537
3538 self.buffer.update(cx, |buffer, cx| {
3539 buffer.edit(
3540 edits,
3541 Some(AutoindentMode::Block {
3542 original_indent_columns,
3543 }),
3544 cx,
3545 )
3546 });
3547 }
3548
3549 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3550 self.hide_context_menu(window, cx);
3551
3552 match phase {
3553 SelectPhase::Begin {
3554 position,
3555 add,
3556 click_count,
3557 } => self.begin_selection(position, add, click_count, window, cx),
3558 SelectPhase::BeginColumnar {
3559 position,
3560 goal_column,
3561 reset,
3562 mode,
3563 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3564 SelectPhase::Extend {
3565 position,
3566 click_count,
3567 } => self.extend_selection(position, click_count, window, cx),
3568 SelectPhase::Update {
3569 position,
3570 goal_column,
3571 scroll_delta,
3572 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3573 SelectPhase::End => self.end_selection(window, cx),
3574 }
3575 }
3576
3577 fn extend_selection(
3578 &mut self,
3579 position: DisplayPoint,
3580 click_count: usize,
3581 window: &mut Window,
3582 cx: &mut Context<Self>,
3583 ) {
3584 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3585 let tail = self.selections.newest::<usize>(&display_map).tail();
3586 let click_count = click_count.max(match self.selections.select_mode() {
3587 SelectMode::Character => 1,
3588 SelectMode::Word(_) => 2,
3589 SelectMode::Line(_) => 3,
3590 SelectMode::All => 4,
3591 });
3592 self.begin_selection(position, false, click_count, window, cx);
3593
3594 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3595
3596 let current_selection = match self.selections.select_mode() {
3597 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3598 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3599 };
3600
3601 let mut pending_selection = self
3602 .selections
3603 .pending_anchor()
3604 .cloned()
3605 .expect("extend_selection not called with pending selection");
3606
3607 if pending_selection
3608 .start
3609 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3610 == Ordering::Greater
3611 {
3612 pending_selection.start = current_selection.start;
3613 }
3614 if pending_selection
3615 .end
3616 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3617 == Ordering::Less
3618 {
3619 pending_selection.end = current_selection.end;
3620 pending_selection.reversed = true;
3621 }
3622
3623 let mut pending_mode = self.selections.pending_mode().unwrap();
3624 match &mut pending_mode {
3625 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3626 _ => {}
3627 }
3628
3629 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3630 SelectionEffects::scroll(Autoscroll::fit())
3631 } else {
3632 SelectionEffects::no_scroll()
3633 };
3634
3635 self.change_selections(effects, window, cx, |s| {
3636 s.set_pending(pending_selection.clone(), pending_mode);
3637 s.set_is_extending(true);
3638 });
3639 }
3640
3641 fn begin_selection(
3642 &mut self,
3643 position: DisplayPoint,
3644 add: bool,
3645 click_count: usize,
3646 window: &mut Window,
3647 cx: &mut Context<Self>,
3648 ) {
3649 if !self.focus_handle.is_focused(window) {
3650 self.last_focused_descendant = None;
3651 window.focus(&self.focus_handle);
3652 }
3653
3654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3655 let buffer = display_map.buffer_snapshot();
3656 let position = display_map.clip_point(position, Bias::Left);
3657
3658 let start;
3659 let end;
3660 let mode;
3661 let mut auto_scroll;
3662 match click_count {
3663 1 => {
3664 start = buffer.anchor_before(position.to_point(&display_map));
3665 end = start;
3666 mode = SelectMode::Character;
3667 auto_scroll = true;
3668 }
3669 2 => {
3670 let position = display_map
3671 .clip_point(position, Bias::Left)
3672 .to_offset(&display_map, Bias::Left);
3673 let (range, _) = buffer.surrounding_word(position, None);
3674 start = buffer.anchor_before(range.start);
3675 end = buffer.anchor_before(range.end);
3676 mode = SelectMode::Word(start..end);
3677 auto_scroll = true;
3678 }
3679 3 => {
3680 let position = display_map
3681 .clip_point(position, Bias::Left)
3682 .to_point(&display_map);
3683 let line_start = display_map.prev_line_boundary(position).0;
3684 let next_line_start = buffer.clip_point(
3685 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3686 Bias::Left,
3687 );
3688 start = buffer.anchor_before(line_start);
3689 end = buffer.anchor_before(next_line_start);
3690 mode = SelectMode::Line(start..end);
3691 auto_scroll = true;
3692 }
3693 _ => {
3694 start = buffer.anchor_before(0);
3695 end = buffer.anchor_before(buffer.len());
3696 mode = SelectMode::All;
3697 auto_scroll = false;
3698 }
3699 }
3700 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3701
3702 let point_to_delete: Option<usize> = {
3703 let selected_points: Vec<Selection<Point>> =
3704 self.selections.disjoint_in_range(start..end, &display_map);
3705
3706 if !add || click_count > 1 {
3707 None
3708 } else if !selected_points.is_empty() {
3709 Some(selected_points[0].id)
3710 } else {
3711 let clicked_point_already_selected =
3712 self.selections.disjoint_anchors().iter().find(|selection| {
3713 selection.start.to_point(buffer) == start.to_point(buffer)
3714 || selection.end.to_point(buffer) == end.to_point(buffer)
3715 });
3716
3717 clicked_point_already_selected.map(|selection| selection.id)
3718 }
3719 };
3720
3721 let selections_count = self.selections.count();
3722 let effects = if auto_scroll {
3723 SelectionEffects::default()
3724 } else {
3725 SelectionEffects::no_scroll()
3726 };
3727
3728 self.change_selections(effects, window, cx, |s| {
3729 if let Some(point_to_delete) = point_to_delete {
3730 s.delete(point_to_delete);
3731
3732 if selections_count == 1 {
3733 s.set_pending_anchor_range(start..end, mode);
3734 }
3735 } else {
3736 if !add {
3737 s.clear_disjoint();
3738 }
3739
3740 s.set_pending_anchor_range(start..end, mode);
3741 }
3742 });
3743 }
3744
3745 fn begin_columnar_selection(
3746 &mut self,
3747 position: DisplayPoint,
3748 goal_column: u32,
3749 reset: bool,
3750 mode: ColumnarMode,
3751 window: &mut Window,
3752 cx: &mut Context<Self>,
3753 ) {
3754 if !self.focus_handle.is_focused(window) {
3755 self.last_focused_descendant = None;
3756 window.focus(&self.focus_handle);
3757 }
3758
3759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3760
3761 if reset {
3762 let pointer_position = display_map
3763 .buffer_snapshot()
3764 .anchor_before(position.to_point(&display_map));
3765
3766 self.change_selections(
3767 SelectionEffects::scroll(Autoscroll::newest()),
3768 window,
3769 cx,
3770 |s| {
3771 s.clear_disjoint();
3772 s.set_pending_anchor_range(
3773 pointer_position..pointer_position,
3774 SelectMode::Character,
3775 );
3776 },
3777 );
3778 };
3779
3780 let tail = self.selections.newest::<Point>(&display_map).tail();
3781 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3782 self.columnar_selection_state = match mode {
3783 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3784 selection_tail: selection_anchor,
3785 display_point: if reset {
3786 if position.column() != goal_column {
3787 Some(DisplayPoint::new(position.row(), goal_column))
3788 } else {
3789 None
3790 }
3791 } else {
3792 None
3793 },
3794 }),
3795 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3796 selection_tail: selection_anchor,
3797 }),
3798 };
3799
3800 if !reset {
3801 self.select_columns(position, goal_column, &display_map, window, cx);
3802 }
3803 }
3804
3805 fn update_selection(
3806 &mut self,
3807 position: DisplayPoint,
3808 goal_column: u32,
3809 scroll_delta: gpui::Point<f32>,
3810 window: &mut Window,
3811 cx: &mut Context<Self>,
3812 ) {
3813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3814
3815 if self.columnar_selection_state.is_some() {
3816 self.select_columns(position, goal_column, &display_map, window, cx);
3817 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3818 let buffer = display_map.buffer_snapshot();
3819 let head;
3820 let tail;
3821 let mode = self.selections.pending_mode().unwrap();
3822 match &mode {
3823 SelectMode::Character => {
3824 head = position.to_point(&display_map);
3825 tail = pending.tail().to_point(buffer);
3826 }
3827 SelectMode::Word(original_range) => {
3828 let offset = display_map
3829 .clip_point(position, Bias::Left)
3830 .to_offset(&display_map, Bias::Left);
3831 let original_range = original_range.to_offset(buffer);
3832
3833 let head_offset = if buffer.is_inside_word(offset, None)
3834 || original_range.contains(&offset)
3835 {
3836 let (word_range, _) = buffer.surrounding_word(offset, None);
3837 if word_range.start < original_range.start {
3838 word_range.start
3839 } else {
3840 word_range.end
3841 }
3842 } else {
3843 offset
3844 };
3845
3846 head = head_offset.to_point(buffer);
3847 if head_offset <= original_range.start {
3848 tail = original_range.end.to_point(buffer);
3849 } else {
3850 tail = original_range.start.to_point(buffer);
3851 }
3852 }
3853 SelectMode::Line(original_range) => {
3854 let original_range = original_range.to_point(display_map.buffer_snapshot());
3855
3856 let position = display_map
3857 .clip_point(position, Bias::Left)
3858 .to_point(&display_map);
3859 let line_start = display_map.prev_line_boundary(position).0;
3860 let next_line_start = buffer.clip_point(
3861 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3862 Bias::Left,
3863 );
3864
3865 if line_start < original_range.start {
3866 head = line_start
3867 } else {
3868 head = next_line_start
3869 }
3870
3871 if head <= original_range.start {
3872 tail = original_range.end;
3873 } else {
3874 tail = original_range.start;
3875 }
3876 }
3877 SelectMode::All => {
3878 return;
3879 }
3880 };
3881
3882 if head < tail {
3883 pending.start = buffer.anchor_before(head);
3884 pending.end = buffer.anchor_before(tail);
3885 pending.reversed = true;
3886 } else {
3887 pending.start = buffer.anchor_before(tail);
3888 pending.end = buffer.anchor_before(head);
3889 pending.reversed = false;
3890 }
3891
3892 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3893 s.set_pending(pending.clone(), mode);
3894 });
3895 } else {
3896 log::error!("update_selection dispatched with no pending selection");
3897 return;
3898 }
3899
3900 self.apply_scroll_delta(scroll_delta, window, cx);
3901 cx.notify();
3902 }
3903
3904 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3905 self.columnar_selection_state.take();
3906 if let Some(pending_mode) = self.selections.pending_mode() {
3907 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3908 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3909 s.select(selections);
3910 s.clear_pending();
3911 if s.is_extending() {
3912 s.set_is_extending(false);
3913 } else {
3914 s.set_select_mode(pending_mode);
3915 }
3916 });
3917 }
3918 }
3919
3920 fn select_columns(
3921 &mut self,
3922 head: DisplayPoint,
3923 goal_column: u32,
3924 display_map: &DisplaySnapshot,
3925 window: &mut Window,
3926 cx: &mut Context<Self>,
3927 ) {
3928 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3929 return;
3930 };
3931
3932 let tail = match columnar_state {
3933 ColumnarSelectionState::FromMouse {
3934 selection_tail,
3935 display_point,
3936 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3937 ColumnarSelectionState::FromSelection { selection_tail } => {
3938 selection_tail.to_display_point(display_map)
3939 }
3940 };
3941
3942 let start_row = cmp::min(tail.row(), head.row());
3943 let end_row = cmp::max(tail.row(), head.row());
3944 let start_column = cmp::min(tail.column(), goal_column);
3945 let end_column = cmp::max(tail.column(), goal_column);
3946 let reversed = start_column < tail.column();
3947
3948 let selection_ranges = (start_row.0..=end_row.0)
3949 .map(DisplayRow)
3950 .filter_map(|row| {
3951 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3952 || start_column <= display_map.line_len(row))
3953 && !display_map.is_block_line(row)
3954 {
3955 let start = display_map
3956 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3957 .to_point(display_map);
3958 let end = display_map
3959 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3960 .to_point(display_map);
3961 if reversed {
3962 Some(end..start)
3963 } else {
3964 Some(start..end)
3965 }
3966 } else {
3967 None
3968 }
3969 })
3970 .collect::<Vec<_>>();
3971 if selection_ranges.is_empty() {
3972 return;
3973 }
3974
3975 let ranges = match columnar_state {
3976 ColumnarSelectionState::FromMouse { .. } => {
3977 let mut non_empty_ranges = selection_ranges
3978 .iter()
3979 .filter(|selection_range| selection_range.start != selection_range.end)
3980 .peekable();
3981 if non_empty_ranges.peek().is_some() {
3982 non_empty_ranges.cloned().collect()
3983 } else {
3984 selection_ranges
3985 }
3986 }
3987 _ => selection_ranges,
3988 };
3989
3990 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3991 s.select_ranges(ranges);
3992 });
3993 cx.notify();
3994 }
3995
3996 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3997 self.selections
3998 .all_adjusted(snapshot)
3999 .iter()
4000 .any(|selection| !selection.is_empty())
4001 }
4002
4003 pub fn has_pending_nonempty_selection(&self) -> bool {
4004 let pending_nonempty_selection = match self.selections.pending_anchor() {
4005 Some(Selection { start, end, .. }) => start != end,
4006 None => false,
4007 };
4008
4009 pending_nonempty_selection
4010 || (self.columnar_selection_state.is_some()
4011 && self.selections.disjoint_anchors().len() > 1)
4012 }
4013
4014 pub fn has_pending_selection(&self) -> bool {
4015 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4016 }
4017
4018 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4019 self.selection_mark_mode = false;
4020 self.selection_drag_state = SelectionDragState::None;
4021
4022 if self.clear_expanded_diff_hunks(cx) {
4023 cx.notify();
4024 return;
4025 }
4026 if self.dismiss_menus_and_popups(true, window, cx) {
4027 return;
4028 }
4029
4030 if self.mode.is_full()
4031 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4032 {
4033 return;
4034 }
4035
4036 cx.propagate();
4037 }
4038
4039 pub fn dismiss_menus_and_popups(
4040 &mut self,
4041 is_user_requested: bool,
4042 window: &mut Window,
4043 cx: &mut Context<Self>,
4044 ) -> bool {
4045 if self.take_rename(false, window, cx).is_some() {
4046 return true;
4047 }
4048
4049 if self.hide_blame_popover(true, cx) {
4050 return true;
4051 }
4052
4053 if hide_hover(self, cx) {
4054 return true;
4055 }
4056
4057 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4058 return true;
4059 }
4060
4061 if self.hide_context_menu(window, cx).is_some() {
4062 return true;
4063 }
4064
4065 if self.mouse_context_menu.take().is_some() {
4066 return true;
4067 }
4068
4069 if is_user_requested && self.discard_edit_prediction(true, cx) {
4070 return true;
4071 }
4072
4073 if self.snippet_stack.pop().is_some() {
4074 return true;
4075 }
4076
4077 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4078 self.dismiss_diagnostics(cx);
4079 return true;
4080 }
4081
4082 false
4083 }
4084
4085 fn linked_editing_ranges_for(
4086 &self,
4087 selection: Range<text::Anchor>,
4088 cx: &App,
4089 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4090 if self.linked_edit_ranges.is_empty() {
4091 return None;
4092 }
4093 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4094 selection.end.buffer_id.and_then(|end_buffer_id| {
4095 if selection.start.buffer_id != Some(end_buffer_id) {
4096 return None;
4097 }
4098 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4099 let snapshot = buffer.read(cx).snapshot();
4100 self.linked_edit_ranges
4101 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4102 .map(|ranges| (ranges, snapshot, buffer))
4103 })?;
4104 use text::ToOffset as TO;
4105 // find offset from the start of current range to current cursor position
4106 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4107
4108 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4109 let start_difference = start_offset - start_byte_offset;
4110 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4111 let end_difference = end_offset - start_byte_offset;
4112 // Current range has associated linked ranges.
4113 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4114 for range in linked_ranges.iter() {
4115 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4116 let end_offset = start_offset + end_difference;
4117 let start_offset = start_offset + start_difference;
4118 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4119 continue;
4120 }
4121 if self.selections.disjoint_anchor_ranges().any(|s| {
4122 if s.start.buffer_id != selection.start.buffer_id
4123 || s.end.buffer_id != selection.end.buffer_id
4124 {
4125 return false;
4126 }
4127 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4128 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4129 }) {
4130 continue;
4131 }
4132 let start = buffer_snapshot.anchor_after(start_offset);
4133 let end = buffer_snapshot.anchor_after(end_offset);
4134 linked_edits
4135 .entry(buffer.clone())
4136 .or_default()
4137 .push(start..end);
4138 }
4139 Some(linked_edits)
4140 }
4141
4142 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4143 let text: Arc<str> = text.into();
4144
4145 if self.read_only(cx) {
4146 return;
4147 }
4148
4149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4150
4151 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4152 let mut bracket_inserted = false;
4153 let mut edits = Vec::new();
4154 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4155 let mut new_selections = Vec::with_capacity(selections.len());
4156 let mut new_autoclose_regions = Vec::new();
4157 let snapshot = self.buffer.read(cx).read(cx);
4158 let mut clear_linked_edit_ranges = false;
4159
4160 for (selection, autoclose_region) in
4161 self.selections_with_autoclose_regions(selections, &snapshot)
4162 {
4163 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4164 // Determine if the inserted text matches the opening or closing
4165 // bracket of any of this language's bracket pairs.
4166 let mut bracket_pair = None;
4167 let mut is_bracket_pair_start = false;
4168 let mut is_bracket_pair_end = false;
4169 if !text.is_empty() {
4170 let mut bracket_pair_matching_end = None;
4171 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4172 // and they are removing the character that triggered IME popup.
4173 for (pair, enabled) in scope.brackets() {
4174 if !pair.close && !pair.surround {
4175 continue;
4176 }
4177
4178 if enabled && pair.start.ends_with(text.as_ref()) {
4179 let prefix_len = pair.start.len() - text.len();
4180 let preceding_text_matches_prefix = prefix_len == 0
4181 || (selection.start.column >= (prefix_len as u32)
4182 && snapshot.contains_str_at(
4183 Point::new(
4184 selection.start.row,
4185 selection.start.column - (prefix_len as u32),
4186 ),
4187 &pair.start[..prefix_len],
4188 ));
4189 if preceding_text_matches_prefix {
4190 bracket_pair = Some(pair.clone());
4191 is_bracket_pair_start = true;
4192 break;
4193 }
4194 }
4195 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4196 {
4197 // take first bracket pair matching end, but don't break in case a later bracket
4198 // pair matches start
4199 bracket_pair_matching_end = Some(pair.clone());
4200 }
4201 }
4202 if let Some(end) = bracket_pair_matching_end
4203 && bracket_pair.is_none()
4204 {
4205 bracket_pair = Some(end);
4206 is_bracket_pair_end = true;
4207 }
4208 }
4209
4210 if let Some(bracket_pair) = bracket_pair {
4211 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4212 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4213 let auto_surround =
4214 self.use_auto_surround && snapshot_settings.use_auto_surround;
4215 if selection.is_empty() {
4216 if is_bracket_pair_start {
4217 // If the inserted text is a suffix of an opening bracket and the
4218 // selection is preceded by the rest of the opening bracket, then
4219 // insert the closing bracket.
4220 let following_text_allows_autoclose = snapshot
4221 .chars_at(selection.start)
4222 .next()
4223 .is_none_or(|c| scope.should_autoclose_before(c));
4224
4225 let preceding_text_allows_autoclose = selection.start.column == 0
4226 || snapshot
4227 .reversed_chars_at(selection.start)
4228 .next()
4229 .is_none_or(|c| {
4230 bracket_pair.start != bracket_pair.end
4231 || !snapshot
4232 .char_classifier_at(selection.start)
4233 .is_word(c)
4234 });
4235
4236 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4237 && bracket_pair.start.len() == 1
4238 {
4239 let target = bracket_pair.start.chars().next().unwrap();
4240 let current_line_count = snapshot
4241 .reversed_chars_at(selection.start)
4242 .take_while(|&c| c != '\n')
4243 .filter(|&c| c == target)
4244 .count();
4245 current_line_count % 2 == 1
4246 } else {
4247 false
4248 };
4249
4250 if autoclose
4251 && bracket_pair.close
4252 && following_text_allows_autoclose
4253 && preceding_text_allows_autoclose
4254 && !is_closing_quote
4255 {
4256 let anchor = snapshot.anchor_before(selection.end);
4257 new_selections.push((selection.map(|_| anchor), text.len()));
4258 new_autoclose_regions.push((
4259 anchor,
4260 text.len(),
4261 selection.id,
4262 bracket_pair.clone(),
4263 ));
4264 edits.push((
4265 selection.range(),
4266 format!("{}{}", text, bracket_pair.end).into(),
4267 ));
4268 bracket_inserted = true;
4269 continue;
4270 }
4271 }
4272
4273 if let Some(region) = autoclose_region {
4274 // If the selection is followed by an auto-inserted closing bracket,
4275 // then don't insert that closing bracket again; just move the selection
4276 // past the closing bracket.
4277 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4278 && text.as_ref() == region.pair.end.as_str()
4279 && snapshot.contains_str_at(region.range.end, text.as_ref());
4280 if should_skip {
4281 let anchor = snapshot.anchor_after(selection.end);
4282 new_selections
4283 .push((selection.map(|_| anchor), region.pair.end.len()));
4284 continue;
4285 }
4286 }
4287
4288 let always_treat_brackets_as_autoclosed = snapshot
4289 .language_settings_at(selection.start, cx)
4290 .always_treat_brackets_as_autoclosed;
4291 if always_treat_brackets_as_autoclosed
4292 && is_bracket_pair_end
4293 && snapshot.contains_str_at(selection.end, text.as_ref())
4294 {
4295 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4296 // and the inserted text is a closing bracket and the selection is followed
4297 // by the closing bracket then move the selection past the closing bracket.
4298 let anchor = snapshot.anchor_after(selection.end);
4299 new_selections.push((selection.map(|_| anchor), text.len()));
4300 continue;
4301 }
4302 }
4303 // If an opening bracket is 1 character long and is typed while
4304 // text is selected, then surround that text with the bracket pair.
4305 else if auto_surround
4306 && bracket_pair.surround
4307 && is_bracket_pair_start
4308 && bracket_pair.start.chars().count() == 1
4309 {
4310 edits.push((selection.start..selection.start, text.clone()));
4311 edits.push((
4312 selection.end..selection.end,
4313 bracket_pair.end.as_str().into(),
4314 ));
4315 bracket_inserted = true;
4316 new_selections.push((
4317 Selection {
4318 id: selection.id,
4319 start: snapshot.anchor_after(selection.start),
4320 end: snapshot.anchor_before(selection.end),
4321 reversed: selection.reversed,
4322 goal: selection.goal,
4323 },
4324 0,
4325 ));
4326 continue;
4327 }
4328 }
4329 }
4330
4331 if self.auto_replace_emoji_shortcode
4332 && selection.is_empty()
4333 && text.as_ref().ends_with(':')
4334 && let Some(possible_emoji_short_code) =
4335 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4336 && !possible_emoji_short_code.is_empty()
4337 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4338 {
4339 let emoji_shortcode_start = Point::new(
4340 selection.start.row,
4341 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4342 );
4343
4344 // Remove shortcode from buffer
4345 edits.push((
4346 emoji_shortcode_start..selection.start,
4347 "".to_string().into(),
4348 ));
4349 new_selections.push((
4350 Selection {
4351 id: selection.id,
4352 start: snapshot.anchor_after(emoji_shortcode_start),
4353 end: snapshot.anchor_before(selection.start),
4354 reversed: selection.reversed,
4355 goal: selection.goal,
4356 },
4357 0,
4358 ));
4359
4360 // Insert emoji
4361 let selection_start_anchor = snapshot.anchor_after(selection.start);
4362 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4363 edits.push((selection.start..selection.end, emoji.to_string().into()));
4364
4365 continue;
4366 }
4367
4368 // If not handling any auto-close operation, then just replace the selected
4369 // text with the given input and move the selection to the end of the
4370 // newly inserted text.
4371 let anchor = snapshot.anchor_after(selection.end);
4372 if !self.linked_edit_ranges.is_empty() {
4373 let start_anchor = snapshot.anchor_before(selection.start);
4374
4375 let is_word_char = text.chars().next().is_none_or(|char| {
4376 let classifier = snapshot
4377 .char_classifier_at(start_anchor.to_offset(&snapshot))
4378 .scope_context(Some(CharScopeContext::LinkedEdit));
4379 classifier.is_word(char)
4380 });
4381
4382 if is_word_char {
4383 if let Some(ranges) = self
4384 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4385 {
4386 for (buffer, edits) in ranges {
4387 linked_edits
4388 .entry(buffer.clone())
4389 .or_default()
4390 .extend(edits.into_iter().map(|range| (range, text.clone())));
4391 }
4392 }
4393 } else {
4394 clear_linked_edit_ranges = true;
4395 }
4396 }
4397
4398 new_selections.push((selection.map(|_| anchor), 0));
4399 edits.push((selection.start..selection.end, text.clone()));
4400 }
4401
4402 drop(snapshot);
4403
4404 self.transact(window, cx, |this, window, cx| {
4405 if clear_linked_edit_ranges {
4406 this.linked_edit_ranges.clear();
4407 }
4408 let initial_buffer_versions =
4409 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4410
4411 this.buffer.update(cx, |buffer, cx| {
4412 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4413 });
4414 for (buffer, edits) in linked_edits {
4415 buffer.update(cx, |buffer, cx| {
4416 let snapshot = buffer.snapshot();
4417 let edits = edits
4418 .into_iter()
4419 .map(|(range, text)| {
4420 use text::ToPoint as TP;
4421 let end_point = TP::to_point(&range.end, &snapshot);
4422 let start_point = TP::to_point(&range.start, &snapshot);
4423 (start_point..end_point, text)
4424 })
4425 .sorted_by_key(|(range, _)| range.start);
4426 buffer.edit(edits, None, cx);
4427 })
4428 }
4429 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4430 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4431 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4432 let new_selections =
4433 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4434 .zip(new_selection_deltas)
4435 .map(|(selection, delta)| Selection {
4436 id: selection.id,
4437 start: selection.start + delta,
4438 end: selection.end + delta,
4439 reversed: selection.reversed,
4440 goal: SelectionGoal::None,
4441 })
4442 .collect::<Vec<_>>();
4443
4444 let mut i = 0;
4445 for (position, delta, selection_id, pair) in new_autoclose_regions {
4446 let position = position.to_offset(map.buffer_snapshot()) + delta;
4447 let start = map.buffer_snapshot().anchor_before(position);
4448 let end = map.buffer_snapshot().anchor_after(position);
4449 while let Some(existing_state) = this.autoclose_regions.get(i) {
4450 match existing_state
4451 .range
4452 .start
4453 .cmp(&start, map.buffer_snapshot())
4454 {
4455 Ordering::Less => i += 1,
4456 Ordering::Greater => break,
4457 Ordering::Equal => {
4458 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4459 Ordering::Less => i += 1,
4460 Ordering::Equal => break,
4461 Ordering::Greater => break,
4462 }
4463 }
4464 }
4465 }
4466 this.autoclose_regions.insert(
4467 i,
4468 AutocloseRegion {
4469 selection_id,
4470 range: start..end,
4471 pair,
4472 },
4473 );
4474 }
4475
4476 let had_active_edit_prediction = this.has_active_edit_prediction();
4477 this.change_selections(
4478 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4479 window,
4480 cx,
4481 |s| s.select(new_selections),
4482 );
4483
4484 if !bracket_inserted
4485 && let Some(on_type_format_task) =
4486 this.trigger_on_type_formatting(text.to_string(), window, cx)
4487 {
4488 on_type_format_task.detach_and_log_err(cx);
4489 }
4490
4491 let editor_settings = EditorSettings::get_global(cx);
4492 if bracket_inserted
4493 && (editor_settings.auto_signature_help
4494 || editor_settings.show_signature_help_after_edits)
4495 {
4496 this.show_signature_help(&ShowSignatureHelp, window, cx);
4497 }
4498
4499 let trigger_in_words =
4500 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4501 if this.hard_wrap.is_some() {
4502 let latest: Range<Point> = this.selections.newest(&map).range();
4503 if latest.is_empty()
4504 && this
4505 .buffer()
4506 .read(cx)
4507 .snapshot(cx)
4508 .line_len(MultiBufferRow(latest.start.row))
4509 == latest.start.column
4510 {
4511 this.rewrap_impl(
4512 RewrapOptions {
4513 override_language_settings: true,
4514 preserve_existing_whitespace: true,
4515 },
4516 cx,
4517 )
4518 }
4519 }
4520 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4521 refresh_linked_ranges(this, window, cx);
4522 this.refresh_edit_prediction(true, false, window, cx);
4523 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4524 });
4525 }
4526
4527 fn find_possible_emoji_shortcode_at_position(
4528 snapshot: &MultiBufferSnapshot,
4529 position: Point,
4530 ) -> Option<String> {
4531 let mut chars = Vec::new();
4532 let mut found_colon = false;
4533 for char in snapshot.reversed_chars_at(position).take(100) {
4534 // Found a possible emoji shortcode in the middle of the buffer
4535 if found_colon {
4536 if char.is_whitespace() {
4537 chars.reverse();
4538 return Some(chars.iter().collect());
4539 }
4540 // If the previous character is not a whitespace, we are in the middle of a word
4541 // and we only want to complete the shortcode if the word is made up of other emojis
4542 let mut containing_word = String::new();
4543 for ch in snapshot
4544 .reversed_chars_at(position)
4545 .skip(chars.len() + 1)
4546 .take(100)
4547 {
4548 if ch.is_whitespace() {
4549 break;
4550 }
4551 containing_word.push(ch);
4552 }
4553 let containing_word = containing_word.chars().rev().collect::<String>();
4554 if util::word_consists_of_emojis(containing_word.as_str()) {
4555 chars.reverse();
4556 return Some(chars.iter().collect());
4557 }
4558 }
4559
4560 if char.is_whitespace() || !char.is_ascii() {
4561 return None;
4562 }
4563 if char == ':' {
4564 found_colon = true;
4565 } else {
4566 chars.push(char);
4567 }
4568 }
4569 // Found a possible emoji shortcode at the beginning of the buffer
4570 chars.reverse();
4571 Some(chars.iter().collect())
4572 }
4573
4574 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4576 self.transact(window, cx, |this, window, cx| {
4577 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4578 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4579 let multi_buffer = this.buffer.read(cx);
4580 let buffer = multi_buffer.snapshot(cx);
4581 selections
4582 .iter()
4583 .map(|selection| {
4584 let start_point = selection.start.to_point(&buffer);
4585 let mut existing_indent =
4586 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4587 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4588 let start = selection.start;
4589 let end = selection.end;
4590 let selection_is_empty = start == end;
4591 let language_scope = buffer.language_scope_at(start);
4592 let (
4593 comment_delimiter,
4594 doc_delimiter,
4595 insert_extra_newline,
4596 indent_on_newline,
4597 indent_on_extra_newline,
4598 ) = if let Some(language) = &language_scope {
4599 let mut insert_extra_newline =
4600 insert_extra_newline_brackets(&buffer, start..end, language)
4601 || insert_extra_newline_tree_sitter(&buffer, start..end);
4602
4603 // Comment extension on newline is allowed only for cursor selections
4604 let comment_delimiter = maybe!({
4605 if !selection_is_empty {
4606 return None;
4607 }
4608
4609 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4610 return None;
4611 }
4612
4613 let delimiters = language.line_comment_prefixes();
4614 let max_len_of_delimiter =
4615 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4616 let (snapshot, range) =
4617 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4618
4619 let num_of_whitespaces = snapshot
4620 .chars_for_range(range.clone())
4621 .take_while(|c| c.is_whitespace())
4622 .count();
4623 let comment_candidate = snapshot
4624 .chars_for_range(range.clone())
4625 .skip(num_of_whitespaces)
4626 .take(max_len_of_delimiter)
4627 .collect::<String>();
4628 let (delimiter, trimmed_len) = delimiters
4629 .iter()
4630 .filter_map(|delimiter| {
4631 let prefix = delimiter.trim_end();
4632 if comment_candidate.starts_with(prefix) {
4633 Some((delimiter, prefix.len()))
4634 } else {
4635 None
4636 }
4637 })
4638 .max_by_key(|(_, len)| *len)?;
4639
4640 if let Some(BlockCommentConfig {
4641 start: block_start, ..
4642 }) = language.block_comment()
4643 {
4644 let block_start_trimmed = block_start.trim_end();
4645 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4646 let line_content = snapshot
4647 .chars_for_range(range)
4648 .skip(num_of_whitespaces)
4649 .take(block_start_trimmed.len())
4650 .collect::<String>();
4651
4652 if line_content.starts_with(block_start_trimmed) {
4653 return None;
4654 }
4655 }
4656 }
4657
4658 let cursor_is_placed_after_comment_marker =
4659 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4660 if cursor_is_placed_after_comment_marker {
4661 Some(delimiter.clone())
4662 } else {
4663 None
4664 }
4665 });
4666
4667 let mut indent_on_newline = IndentSize::spaces(0);
4668 let mut indent_on_extra_newline = IndentSize::spaces(0);
4669
4670 let doc_delimiter = maybe!({
4671 if !selection_is_empty {
4672 return None;
4673 }
4674
4675 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4676 return None;
4677 }
4678
4679 let BlockCommentConfig {
4680 start: start_tag,
4681 end: end_tag,
4682 prefix: delimiter,
4683 tab_size: len,
4684 } = language.documentation_comment()?;
4685 let is_within_block_comment = buffer
4686 .language_scope_at(start_point)
4687 .is_some_and(|scope| scope.override_name() == Some("comment"));
4688 if !is_within_block_comment {
4689 return None;
4690 }
4691
4692 let (snapshot, range) =
4693 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4694
4695 let num_of_whitespaces = snapshot
4696 .chars_for_range(range.clone())
4697 .take_while(|c| c.is_whitespace())
4698 .count();
4699
4700 // 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.
4701 let column = start_point.column;
4702 let cursor_is_after_start_tag = {
4703 let start_tag_len = start_tag.len();
4704 let start_tag_line = snapshot
4705 .chars_for_range(range.clone())
4706 .skip(num_of_whitespaces)
4707 .take(start_tag_len)
4708 .collect::<String>();
4709 if start_tag_line.starts_with(start_tag.as_ref()) {
4710 num_of_whitespaces + start_tag_len <= column as usize
4711 } else {
4712 false
4713 }
4714 };
4715
4716 let cursor_is_after_delimiter = {
4717 let delimiter_trim = delimiter.trim_end();
4718 let delimiter_line = snapshot
4719 .chars_for_range(range.clone())
4720 .skip(num_of_whitespaces)
4721 .take(delimiter_trim.len())
4722 .collect::<String>();
4723 if delimiter_line.starts_with(delimiter_trim) {
4724 num_of_whitespaces + delimiter_trim.len() <= column as usize
4725 } else {
4726 false
4727 }
4728 };
4729
4730 let cursor_is_before_end_tag_if_exists = {
4731 let mut char_position = 0u32;
4732 let mut end_tag_offset = None;
4733
4734 'outer: for chunk in snapshot.text_for_range(range) {
4735 if let Some(byte_pos) = chunk.find(&**end_tag) {
4736 let chars_before_match =
4737 chunk[..byte_pos].chars().count() as u32;
4738 end_tag_offset =
4739 Some(char_position + chars_before_match);
4740 break 'outer;
4741 }
4742 char_position += chunk.chars().count() as u32;
4743 }
4744
4745 if let Some(end_tag_offset) = end_tag_offset {
4746 let cursor_is_before_end_tag = column <= end_tag_offset;
4747 if cursor_is_after_start_tag {
4748 if cursor_is_before_end_tag {
4749 insert_extra_newline = true;
4750 }
4751 let cursor_is_at_start_of_end_tag =
4752 column == end_tag_offset;
4753 if cursor_is_at_start_of_end_tag {
4754 indent_on_extra_newline.len = *len;
4755 }
4756 }
4757 cursor_is_before_end_tag
4758 } else {
4759 true
4760 }
4761 };
4762
4763 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4764 && cursor_is_before_end_tag_if_exists
4765 {
4766 if cursor_is_after_start_tag {
4767 indent_on_newline.len = *len;
4768 }
4769 Some(delimiter.clone())
4770 } else {
4771 None
4772 }
4773 });
4774
4775 (
4776 comment_delimiter,
4777 doc_delimiter,
4778 insert_extra_newline,
4779 indent_on_newline,
4780 indent_on_extra_newline,
4781 )
4782 } else {
4783 (
4784 None,
4785 None,
4786 false,
4787 IndentSize::default(),
4788 IndentSize::default(),
4789 )
4790 };
4791
4792 let prevent_auto_indent = doc_delimiter.is_some();
4793 let delimiter = comment_delimiter.or(doc_delimiter);
4794
4795 let capacity_for_delimiter =
4796 delimiter.as_deref().map(str::len).unwrap_or_default();
4797 let mut new_text = String::with_capacity(
4798 1 + capacity_for_delimiter
4799 + existing_indent.len as usize
4800 + indent_on_newline.len as usize
4801 + indent_on_extra_newline.len as usize,
4802 );
4803 new_text.push('\n');
4804 new_text.extend(existing_indent.chars());
4805 new_text.extend(indent_on_newline.chars());
4806
4807 if let Some(delimiter) = &delimiter {
4808 new_text.push_str(delimiter);
4809 }
4810
4811 if insert_extra_newline {
4812 new_text.push('\n');
4813 new_text.extend(existing_indent.chars());
4814 new_text.extend(indent_on_extra_newline.chars());
4815 }
4816
4817 let anchor = buffer.anchor_after(end);
4818 let new_selection = selection.map(|_| anchor);
4819 (
4820 ((start..end, new_text), prevent_auto_indent),
4821 (insert_extra_newline, new_selection),
4822 )
4823 })
4824 .unzip()
4825 };
4826
4827 let mut auto_indent_edits = Vec::new();
4828 let mut edits = Vec::new();
4829 for (edit, prevent_auto_indent) in edits_with_flags {
4830 if prevent_auto_indent {
4831 edits.push(edit);
4832 } else {
4833 auto_indent_edits.push(edit);
4834 }
4835 }
4836 if !edits.is_empty() {
4837 this.edit(edits, cx);
4838 }
4839 if !auto_indent_edits.is_empty() {
4840 this.edit_with_autoindent(auto_indent_edits, cx);
4841 }
4842
4843 let buffer = this.buffer.read(cx).snapshot(cx);
4844 let new_selections = selection_info
4845 .into_iter()
4846 .map(|(extra_newline_inserted, new_selection)| {
4847 let mut cursor = new_selection.end.to_point(&buffer);
4848 if extra_newline_inserted {
4849 cursor.row -= 1;
4850 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4851 }
4852 new_selection.map(|_| cursor)
4853 })
4854 .collect();
4855
4856 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4857 this.refresh_edit_prediction(true, false, window, cx);
4858 });
4859 }
4860
4861 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4863
4864 let buffer = self.buffer.read(cx);
4865 let snapshot = buffer.snapshot(cx);
4866
4867 let mut edits = Vec::new();
4868 let mut rows = Vec::new();
4869
4870 for (rows_inserted, selection) in self
4871 .selections
4872 .all_adjusted(&self.display_snapshot(cx))
4873 .into_iter()
4874 .enumerate()
4875 {
4876 let cursor = selection.head();
4877 let row = cursor.row;
4878
4879 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4880
4881 let newline = "\n".to_string();
4882 edits.push((start_of_line..start_of_line, newline));
4883
4884 rows.push(row + rows_inserted as u32);
4885 }
4886
4887 self.transact(window, cx, |editor, window, cx| {
4888 editor.edit(edits, cx);
4889
4890 editor.change_selections(Default::default(), window, cx, |s| {
4891 let mut index = 0;
4892 s.move_cursors_with(|map, _, _| {
4893 let row = rows[index];
4894 index += 1;
4895
4896 let point = Point::new(row, 0);
4897 let boundary = map.next_line_boundary(point).1;
4898 let clipped = map.clip_point(boundary, Bias::Left);
4899
4900 (clipped, SelectionGoal::None)
4901 });
4902 });
4903
4904 let mut indent_edits = Vec::new();
4905 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4906 for row in rows {
4907 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4908 for (row, indent) in indents {
4909 if indent.len == 0 {
4910 continue;
4911 }
4912
4913 let text = match indent.kind {
4914 IndentKind::Space => " ".repeat(indent.len as usize),
4915 IndentKind::Tab => "\t".repeat(indent.len as usize),
4916 };
4917 let point = Point::new(row.0, 0);
4918 indent_edits.push((point..point, text));
4919 }
4920 }
4921 editor.edit(indent_edits, cx);
4922 });
4923 }
4924
4925 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4927
4928 let buffer = self.buffer.read(cx);
4929 let snapshot = buffer.snapshot(cx);
4930
4931 let mut edits = Vec::new();
4932 let mut rows = Vec::new();
4933 let mut rows_inserted = 0;
4934
4935 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4936 let cursor = selection.head();
4937 let row = cursor.row;
4938
4939 let point = Point::new(row + 1, 0);
4940 let start_of_line = snapshot.clip_point(point, Bias::Left);
4941
4942 let newline = "\n".to_string();
4943 edits.push((start_of_line..start_of_line, newline));
4944
4945 rows_inserted += 1;
4946 rows.push(row + rows_inserted);
4947 }
4948
4949 self.transact(window, cx, |editor, window, cx| {
4950 editor.edit(edits, cx);
4951
4952 editor.change_selections(Default::default(), window, cx, |s| {
4953 let mut index = 0;
4954 s.move_cursors_with(|map, _, _| {
4955 let row = rows[index];
4956 index += 1;
4957
4958 let point = Point::new(row, 0);
4959 let boundary = map.next_line_boundary(point).1;
4960 let clipped = map.clip_point(boundary, Bias::Left);
4961
4962 (clipped, SelectionGoal::None)
4963 });
4964 });
4965
4966 let mut indent_edits = Vec::new();
4967 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4968 for row in rows {
4969 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4970 for (row, indent) in indents {
4971 if indent.len == 0 {
4972 continue;
4973 }
4974
4975 let text = match indent.kind {
4976 IndentKind::Space => " ".repeat(indent.len as usize),
4977 IndentKind::Tab => "\t".repeat(indent.len as usize),
4978 };
4979 let point = Point::new(row.0, 0);
4980 indent_edits.push((point..point, text));
4981 }
4982 }
4983 editor.edit(indent_edits, cx);
4984 });
4985 }
4986
4987 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4988 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4989 original_indent_columns: Vec::new(),
4990 });
4991 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4992 }
4993
4994 fn insert_with_autoindent_mode(
4995 &mut self,
4996 text: &str,
4997 autoindent_mode: Option<AutoindentMode>,
4998 window: &mut Window,
4999 cx: &mut Context<Self>,
5000 ) {
5001 if self.read_only(cx) {
5002 return;
5003 }
5004
5005 let text: Arc<str> = text.into();
5006 self.transact(window, cx, |this, window, cx| {
5007 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5008 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5009 let anchors = {
5010 let snapshot = buffer.read(cx);
5011 old_selections
5012 .iter()
5013 .map(|s| {
5014 let anchor = snapshot.anchor_after(s.head());
5015 s.map(|_| anchor)
5016 })
5017 .collect::<Vec<_>>()
5018 };
5019 buffer.edit(
5020 old_selections
5021 .iter()
5022 .map(|s| (s.start..s.end, text.clone())),
5023 autoindent_mode,
5024 cx,
5025 );
5026 anchors
5027 });
5028
5029 this.change_selections(Default::default(), window, cx, |s| {
5030 s.select_anchors(selection_anchors);
5031 });
5032
5033 cx.notify();
5034 });
5035 }
5036
5037 fn trigger_completion_on_input(
5038 &mut self,
5039 text: &str,
5040 trigger_in_words: bool,
5041 window: &mut Window,
5042 cx: &mut Context<Self>,
5043 ) {
5044 let completions_source = self
5045 .context_menu
5046 .borrow()
5047 .as_ref()
5048 .and_then(|menu| match menu {
5049 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5050 CodeContextMenu::CodeActions(_) => None,
5051 });
5052
5053 match completions_source {
5054 Some(CompletionsMenuSource::Words { .. }) => {
5055 self.open_or_update_completions_menu(
5056 Some(CompletionsMenuSource::Words {
5057 ignore_threshold: false,
5058 }),
5059 None,
5060 window,
5061 cx,
5062 );
5063 }
5064 Some(CompletionsMenuSource::Normal)
5065 | Some(CompletionsMenuSource::SnippetChoices)
5066 | None
5067 if self.is_completion_trigger(
5068 text,
5069 trigger_in_words,
5070 completions_source.is_some(),
5071 cx,
5072 ) =>
5073 {
5074 self.show_completions(
5075 &ShowCompletions {
5076 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5077 },
5078 window,
5079 cx,
5080 )
5081 }
5082 _ => {
5083 self.hide_context_menu(window, cx);
5084 }
5085 }
5086 }
5087
5088 fn is_completion_trigger(
5089 &self,
5090 text: &str,
5091 trigger_in_words: bool,
5092 menu_is_open: bool,
5093 cx: &mut Context<Self>,
5094 ) -> bool {
5095 let position = self.selections.newest_anchor().head();
5096 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5097 return false;
5098 };
5099
5100 if let Some(completion_provider) = &self.completion_provider {
5101 completion_provider.is_completion_trigger(
5102 &buffer,
5103 position.text_anchor,
5104 text,
5105 trigger_in_words,
5106 menu_is_open,
5107 cx,
5108 )
5109 } else {
5110 false
5111 }
5112 }
5113
5114 /// If any empty selections is touching the start of its innermost containing autoclose
5115 /// region, expand it to select the brackets.
5116 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5117 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5118 let buffer = self.buffer.read(cx).read(cx);
5119 let new_selections = self
5120 .selections_with_autoclose_regions(selections, &buffer)
5121 .map(|(mut selection, region)| {
5122 if !selection.is_empty() {
5123 return selection;
5124 }
5125
5126 if let Some(region) = region {
5127 let mut range = region.range.to_offset(&buffer);
5128 if selection.start == range.start && range.start >= region.pair.start.len() {
5129 range.start -= region.pair.start.len();
5130 if buffer.contains_str_at(range.start, ®ion.pair.start)
5131 && buffer.contains_str_at(range.end, ®ion.pair.end)
5132 {
5133 range.end += region.pair.end.len();
5134 selection.start = range.start;
5135 selection.end = range.end;
5136
5137 return selection;
5138 }
5139 }
5140 }
5141
5142 let always_treat_brackets_as_autoclosed = buffer
5143 .language_settings_at(selection.start, cx)
5144 .always_treat_brackets_as_autoclosed;
5145
5146 if !always_treat_brackets_as_autoclosed {
5147 return selection;
5148 }
5149
5150 if let Some(scope) = buffer.language_scope_at(selection.start) {
5151 for (pair, enabled) in scope.brackets() {
5152 if !enabled || !pair.close {
5153 continue;
5154 }
5155
5156 if buffer.contains_str_at(selection.start, &pair.end) {
5157 let pair_start_len = pair.start.len();
5158 if buffer.contains_str_at(
5159 selection.start.saturating_sub(pair_start_len),
5160 &pair.start,
5161 ) {
5162 selection.start -= pair_start_len;
5163 selection.end += pair.end.len();
5164
5165 return selection;
5166 }
5167 }
5168 }
5169 }
5170
5171 selection
5172 })
5173 .collect();
5174
5175 drop(buffer);
5176 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5177 selections.select(new_selections)
5178 });
5179 }
5180
5181 /// Iterate the given selections, and for each one, find the smallest surrounding
5182 /// autoclose region. This uses the ordering of the selections and the autoclose
5183 /// regions to avoid repeated comparisons.
5184 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5185 &'a self,
5186 selections: impl IntoIterator<Item = Selection<D>>,
5187 buffer: &'a MultiBufferSnapshot,
5188 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5189 let mut i = 0;
5190 let mut regions = self.autoclose_regions.as_slice();
5191 selections.into_iter().map(move |selection| {
5192 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5193
5194 let mut enclosing = None;
5195 while let Some(pair_state) = regions.get(i) {
5196 if pair_state.range.end.to_offset(buffer) < range.start {
5197 regions = ®ions[i + 1..];
5198 i = 0;
5199 } else if pair_state.range.start.to_offset(buffer) > range.end {
5200 break;
5201 } else {
5202 if pair_state.selection_id == selection.id {
5203 enclosing = Some(pair_state);
5204 }
5205 i += 1;
5206 }
5207 }
5208
5209 (selection, enclosing)
5210 })
5211 }
5212
5213 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5214 fn invalidate_autoclose_regions(
5215 &mut self,
5216 mut selections: &[Selection<Anchor>],
5217 buffer: &MultiBufferSnapshot,
5218 ) {
5219 self.autoclose_regions.retain(|state| {
5220 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5221 return false;
5222 }
5223
5224 let mut i = 0;
5225 while let Some(selection) = selections.get(i) {
5226 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5227 selections = &selections[1..];
5228 continue;
5229 }
5230 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5231 break;
5232 }
5233 if selection.id == state.selection_id {
5234 return true;
5235 } else {
5236 i += 1;
5237 }
5238 }
5239 false
5240 });
5241 }
5242
5243 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5244 let offset = position.to_offset(buffer);
5245 let (word_range, kind) =
5246 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5247 if offset > word_range.start && kind == Some(CharKind::Word) {
5248 Some(
5249 buffer
5250 .text_for_range(word_range.start..offset)
5251 .collect::<String>(),
5252 )
5253 } else {
5254 None
5255 }
5256 }
5257
5258 pub fn visible_excerpts(
5259 &self,
5260 cx: &mut Context<Editor>,
5261 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5262 let Some(project) = self.project() else {
5263 return HashMap::default();
5264 };
5265 let project = project.read(cx);
5266 let multi_buffer = self.buffer().read(cx);
5267 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5268 let multi_buffer_visible_start = self
5269 .scroll_manager
5270 .anchor()
5271 .anchor
5272 .to_point(&multi_buffer_snapshot);
5273 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5274 multi_buffer_visible_start
5275 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5276 Bias::Left,
5277 );
5278 multi_buffer_snapshot
5279 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5280 .into_iter()
5281 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5282 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5283 let buffer_file = project::File::from_dyn(buffer.file())?;
5284 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5285 let worktree_entry = buffer_worktree
5286 .read(cx)
5287 .entry_for_id(buffer_file.project_entry_id()?)?;
5288 if worktree_entry.is_ignored {
5289 None
5290 } else {
5291 Some((
5292 excerpt_id,
5293 (
5294 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5295 buffer.version().clone(),
5296 excerpt_visible_range,
5297 ),
5298 ))
5299 }
5300 })
5301 .collect()
5302 }
5303
5304 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5305 TextLayoutDetails {
5306 text_system: window.text_system().clone(),
5307 editor_style: self.style.clone().unwrap(),
5308 rem_size: window.rem_size(),
5309 scroll_anchor: self.scroll_manager.anchor(),
5310 visible_rows: self.visible_line_count(),
5311 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5312 }
5313 }
5314
5315 fn trigger_on_type_formatting(
5316 &self,
5317 input: String,
5318 window: &mut Window,
5319 cx: &mut Context<Self>,
5320 ) -> Option<Task<Result<()>>> {
5321 if input.len() != 1 {
5322 return None;
5323 }
5324
5325 let project = self.project()?;
5326 let position = self.selections.newest_anchor().head();
5327 let (buffer, buffer_position) = self
5328 .buffer
5329 .read(cx)
5330 .text_anchor_for_position(position, cx)?;
5331
5332 let settings = language_settings::language_settings(
5333 buffer
5334 .read(cx)
5335 .language_at(buffer_position)
5336 .map(|l| l.name()),
5337 buffer.read(cx).file(),
5338 cx,
5339 );
5340 if !settings.use_on_type_format {
5341 return None;
5342 }
5343
5344 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5345 // hence we do LSP request & edit on host side only — add formats to host's history.
5346 let push_to_lsp_host_history = true;
5347 // If this is not the host, append its history with new edits.
5348 let push_to_client_history = project.read(cx).is_via_collab();
5349
5350 let on_type_formatting = project.update(cx, |project, cx| {
5351 project.on_type_format(
5352 buffer.clone(),
5353 buffer_position,
5354 input,
5355 push_to_lsp_host_history,
5356 cx,
5357 )
5358 });
5359 Some(cx.spawn_in(window, async move |editor, cx| {
5360 if let Some(transaction) = on_type_formatting.await? {
5361 if push_to_client_history {
5362 buffer
5363 .update(cx, |buffer, _| {
5364 buffer.push_transaction(transaction, Instant::now());
5365 buffer.finalize_last_transaction();
5366 })
5367 .ok();
5368 }
5369 editor.update(cx, |editor, cx| {
5370 editor.refresh_document_highlights(cx);
5371 })?;
5372 }
5373 Ok(())
5374 }))
5375 }
5376
5377 pub fn show_word_completions(
5378 &mut self,
5379 _: &ShowWordCompletions,
5380 window: &mut Window,
5381 cx: &mut Context<Self>,
5382 ) {
5383 self.open_or_update_completions_menu(
5384 Some(CompletionsMenuSource::Words {
5385 ignore_threshold: true,
5386 }),
5387 None,
5388 window,
5389 cx,
5390 );
5391 }
5392
5393 pub fn show_completions(
5394 &mut self,
5395 options: &ShowCompletions,
5396 window: &mut Window,
5397 cx: &mut Context<Self>,
5398 ) {
5399 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5400 }
5401
5402 fn open_or_update_completions_menu(
5403 &mut self,
5404 requested_source: Option<CompletionsMenuSource>,
5405 trigger: Option<&str>,
5406 window: &mut Window,
5407 cx: &mut Context<Self>,
5408 ) {
5409 if self.pending_rename.is_some() {
5410 return;
5411 }
5412
5413 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5414
5415 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5416 // inserted and selected. To handle that case, the start of the selection is used so that
5417 // the menu starts with all choices.
5418 let position = self
5419 .selections
5420 .newest_anchor()
5421 .start
5422 .bias_right(&multibuffer_snapshot);
5423 if position.diff_base_anchor.is_some() {
5424 return;
5425 }
5426 let buffer_position = multibuffer_snapshot.anchor_before(position);
5427 let Some(buffer) = buffer_position
5428 .buffer_id
5429 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5430 else {
5431 return;
5432 };
5433 let buffer_snapshot = buffer.read(cx).snapshot();
5434
5435 let query: Option<Arc<String>> =
5436 Self::completion_query(&multibuffer_snapshot, buffer_position)
5437 .map(|query| query.into());
5438
5439 drop(multibuffer_snapshot);
5440
5441 // Hide the current completions menu when query is empty. Without this, cached
5442 // completions from before the trigger char may be reused (#32774).
5443 if query.is_none() {
5444 let menu_is_open = matches!(
5445 self.context_menu.borrow().as_ref(),
5446 Some(CodeContextMenu::Completions(_))
5447 );
5448 if menu_is_open {
5449 self.hide_context_menu(window, cx);
5450 }
5451 }
5452
5453 let mut ignore_word_threshold = false;
5454 let provider = match requested_source {
5455 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5456 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5457 ignore_word_threshold = ignore_threshold;
5458 None
5459 }
5460 Some(CompletionsMenuSource::SnippetChoices) => {
5461 log::error!("bug: SnippetChoices requested_source is not handled");
5462 None
5463 }
5464 };
5465
5466 let sort_completions = provider
5467 .as_ref()
5468 .is_some_and(|provider| provider.sort_completions());
5469
5470 let filter_completions = provider
5471 .as_ref()
5472 .is_none_or(|provider| provider.filter_completions());
5473
5474 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5475 if filter_completions {
5476 menu.filter(query.clone(), provider.clone(), window, cx);
5477 }
5478 // When `is_incomplete` is false, no need to re-query completions when the current query
5479 // is a suffix of the initial query.
5480 if !menu.is_incomplete {
5481 // If the new query is a suffix of the old query (typing more characters) and
5482 // the previous result was complete, the existing completions can be filtered.
5483 //
5484 // Note that this is always true for snippet completions.
5485 let query_matches = match (&menu.initial_query, &query) {
5486 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5487 (None, _) => true,
5488 _ => false,
5489 };
5490 if query_matches {
5491 let position_matches = if menu.initial_position == position {
5492 true
5493 } else {
5494 let snapshot = self.buffer.read(cx).read(cx);
5495 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5496 };
5497 if position_matches {
5498 return;
5499 }
5500 }
5501 }
5502 };
5503
5504 let trigger_kind = match trigger {
5505 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5506 CompletionTriggerKind::TRIGGER_CHARACTER
5507 }
5508 _ => CompletionTriggerKind::INVOKED,
5509 };
5510 let completion_context = CompletionContext {
5511 trigger_character: trigger.and_then(|trigger| {
5512 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5513 Some(String::from(trigger))
5514 } else {
5515 None
5516 }
5517 }),
5518 trigger_kind,
5519 };
5520
5521 let Anchor {
5522 excerpt_id: buffer_excerpt_id,
5523 text_anchor: buffer_position,
5524 ..
5525 } = buffer_position;
5526
5527 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5528 buffer_snapshot.surrounding_word(buffer_position, None)
5529 {
5530 let word_to_exclude = buffer_snapshot
5531 .text_for_range(word_range.clone())
5532 .collect::<String>();
5533 (
5534 buffer_snapshot.anchor_before(word_range.start)
5535 ..buffer_snapshot.anchor_after(buffer_position),
5536 Some(word_to_exclude),
5537 )
5538 } else {
5539 (buffer_position..buffer_position, None)
5540 };
5541
5542 let language = buffer_snapshot
5543 .language_at(buffer_position)
5544 .map(|language| language.name());
5545
5546 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5547 .completions
5548 .clone();
5549
5550 let show_completion_documentation = buffer_snapshot
5551 .settings_at(buffer_position, cx)
5552 .show_completion_documentation;
5553
5554 // The document can be large, so stay in reasonable bounds when searching for words,
5555 // otherwise completion pop-up might be slow to appear.
5556 const WORD_LOOKUP_ROWS: u32 = 5_000;
5557 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5558 let min_word_search = buffer_snapshot.clip_point(
5559 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5560 Bias::Left,
5561 );
5562 let max_word_search = buffer_snapshot.clip_point(
5563 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5564 Bias::Right,
5565 );
5566 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5567 ..buffer_snapshot.point_to_offset(max_word_search);
5568
5569 let skip_digits = query
5570 .as_ref()
5571 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5572
5573 let omit_word_completions = !self.word_completions_enabled
5574 || (!ignore_word_threshold
5575 && match &query {
5576 Some(query) => query.chars().count() < completion_settings.words_min_length,
5577 None => completion_settings.words_min_length != 0,
5578 });
5579
5580 let (mut words, provider_responses) = match &provider {
5581 Some(provider) => {
5582 let provider_responses = provider.completions(
5583 buffer_excerpt_id,
5584 &buffer,
5585 buffer_position,
5586 completion_context,
5587 window,
5588 cx,
5589 );
5590
5591 let words = match (omit_word_completions, completion_settings.words) {
5592 (true, _) | (_, WordsCompletionMode::Disabled) => {
5593 Task::ready(BTreeMap::default())
5594 }
5595 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5596 .background_spawn(async move {
5597 buffer_snapshot.words_in_range(WordsQuery {
5598 fuzzy_contents: None,
5599 range: word_search_range,
5600 skip_digits,
5601 })
5602 }),
5603 };
5604
5605 (words, provider_responses)
5606 }
5607 None => {
5608 let words = if omit_word_completions {
5609 Task::ready(BTreeMap::default())
5610 } else {
5611 cx.background_spawn(async move {
5612 buffer_snapshot.words_in_range(WordsQuery {
5613 fuzzy_contents: None,
5614 range: word_search_range,
5615 skip_digits,
5616 })
5617 })
5618 };
5619 (words, Task::ready(Ok(Vec::new())))
5620 }
5621 };
5622
5623 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5624
5625 let id = post_inc(&mut self.next_completion_id);
5626 let task = cx.spawn_in(window, async move |editor, cx| {
5627 let Ok(()) = editor.update(cx, |this, _| {
5628 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5629 }) else {
5630 return;
5631 };
5632
5633 // TODO: Ideally completions from different sources would be selectively re-queried, so
5634 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5635 let mut completions = Vec::new();
5636 let mut is_incomplete = false;
5637 let mut display_options: Option<CompletionDisplayOptions> = None;
5638 if let Some(provider_responses) = provider_responses.await.log_err()
5639 && !provider_responses.is_empty()
5640 {
5641 for response in provider_responses {
5642 completions.extend(response.completions);
5643 is_incomplete = is_incomplete || response.is_incomplete;
5644 match display_options.as_mut() {
5645 None => {
5646 display_options = Some(response.display_options);
5647 }
5648 Some(options) => options.merge(&response.display_options),
5649 }
5650 }
5651 if completion_settings.words == WordsCompletionMode::Fallback {
5652 words = Task::ready(BTreeMap::default());
5653 }
5654 }
5655 let display_options = display_options.unwrap_or_default();
5656
5657 let mut words = words.await;
5658 if let Some(word_to_exclude) = &word_to_exclude {
5659 words.remove(word_to_exclude);
5660 }
5661 for lsp_completion in &completions {
5662 words.remove(&lsp_completion.new_text);
5663 }
5664 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5665 replace_range: word_replace_range.clone(),
5666 new_text: word.clone(),
5667 label: CodeLabel::plain(word, None),
5668 icon_path: None,
5669 documentation: None,
5670 source: CompletionSource::BufferWord {
5671 word_range,
5672 resolved: false,
5673 },
5674 insert_text_mode: Some(InsertTextMode::AS_IS),
5675 confirm: None,
5676 }));
5677
5678 let menu = if completions.is_empty() {
5679 None
5680 } else {
5681 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5682 let languages = editor
5683 .workspace
5684 .as_ref()
5685 .and_then(|(workspace, _)| workspace.upgrade())
5686 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5687 let menu = CompletionsMenu::new(
5688 id,
5689 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5690 sort_completions,
5691 show_completion_documentation,
5692 position,
5693 query.clone(),
5694 is_incomplete,
5695 buffer.clone(),
5696 completions.into(),
5697 display_options,
5698 snippet_sort_order,
5699 languages,
5700 language,
5701 cx,
5702 );
5703
5704 let query = if filter_completions { query } else { None };
5705 let matches_task = if let Some(query) = query {
5706 menu.do_async_filtering(query, cx)
5707 } else {
5708 Task::ready(menu.unfiltered_matches())
5709 };
5710 (menu, matches_task)
5711 }) else {
5712 return;
5713 };
5714
5715 let matches = matches_task.await;
5716
5717 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5718 // Newer menu already set, so exit.
5719 if let Some(CodeContextMenu::Completions(prev_menu)) =
5720 editor.context_menu.borrow().as_ref()
5721 && prev_menu.id > id
5722 {
5723 return;
5724 };
5725
5726 // Only valid to take prev_menu because it the new menu is immediately set
5727 // below, or the menu is hidden.
5728 if let Some(CodeContextMenu::Completions(prev_menu)) =
5729 editor.context_menu.borrow_mut().take()
5730 {
5731 let position_matches =
5732 if prev_menu.initial_position == menu.initial_position {
5733 true
5734 } else {
5735 let snapshot = editor.buffer.read(cx).read(cx);
5736 prev_menu.initial_position.to_offset(&snapshot)
5737 == menu.initial_position.to_offset(&snapshot)
5738 };
5739 if position_matches {
5740 // Preserve markdown cache before `set_filter_results` because it will
5741 // try to populate the documentation cache.
5742 menu.preserve_markdown_cache(prev_menu);
5743 }
5744 };
5745
5746 menu.set_filter_results(matches, provider, window, cx);
5747 }) else {
5748 return;
5749 };
5750
5751 menu.visible().then_some(menu)
5752 };
5753
5754 editor
5755 .update_in(cx, |editor, window, cx| {
5756 if editor.focus_handle.is_focused(window)
5757 && let Some(menu) = menu
5758 {
5759 *editor.context_menu.borrow_mut() =
5760 Some(CodeContextMenu::Completions(menu));
5761
5762 crate::hover_popover::hide_hover(editor, cx);
5763 if editor.show_edit_predictions_in_menu() {
5764 editor.update_visible_edit_prediction(window, cx);
5765 } else {
5766 editor.discard_edit_prediction(false, cx);
5767 }
5768
5769 cx.notify();
5770 return;
5771 }
5772
5773 if editor.completion_tasks.len() <= 1 {
5774 // If there are no more completion tasks and the last menu was empty, we should hide it.
5775 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5776 // If it was already hidden and we don't show edit predictions in the menu,
5777 // we should also show the edit prediction when available.
5778 if was_hidden && editor.show_edit_predictions_in_menu() {
5779 editor.update_visible_edit_prediction(window, cx);
5780 }
5781 }
5782 })
5783 .ok();
5784 });
5785
5786 self.completion_tasks.push((id, task));
5787 }
5788
5789 #[cfg(feature = "test-support")]
5790 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5791 let menu = self.context_menu.borrow();
5792 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5793 let completions = menu.completions.borrow();
5794 Some(completions.to_vec())
5795 } else {
5796 None
5797 }
5798 }
5799
5800 pub fn with_completions_menu_matching_id<R>(
5801 &self,
5802 id: CompletionId,
5803 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5804 ) -> R {
5805 let mut context_menu = self.context_menu.borrow_mut();
5806 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5807 return f(None);
5808 };
5809 if completions_menu.id != id {
5810 return f(None);
5811 }
5812 f(Some(completions_menu))
5813 }
5814
5815 pub fn confirm_completion(
5816 &mut self,
5817 action: &ConfirmCompletion,
5818 window: &mut Window,
5819 cx: &mut Context<Self>,
5820 ) -> Option<Task<Result<()>>> {
5821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5822 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5823 }
5824
5825 pub fn confirm_completion_insert(
5826 &mut self,
5827 _: &ConfirmCompletionInsert,
5828 window: &mut Window,
5829 cx: &mut Context<Self>,
5830 ) -> Option<Task<Result<()>>> {
5831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5832 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5833 }
5834
5835 pub fn confirm_completion_replace(
5836 &mut self,
5837 _: &ConfirmCompletionReplace,
5838 window: &mut Window,
5839 cx: &mut Context<Self>,
5840 ) -> Option<Task<Result<()>>> {
5841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5842 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5843 }
5844
5845 pub fn compose_completion(
5846 &mut self,
5847 action: &ComposeCompletion,
5848 window: &mut Window,
5849 cx: &mut Context<Self>,
5850 ) -> Option<Task<Result<()>>> {
5851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5852 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5853 }
5854
5855 fn do_completion(
5856 &mut self,
5857 item_ix: Option<usize>,
5858 intent: CompletionIntent,
5859 window: &mut Window,
5860 cx: &mut Context<Editor>,
5861 ) -> Option<Task<Result<()>>> {
5862 use language::ToOffset as _;
5863
5864 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5865 else {
5866 return None;
5867 };
5868
5869 let candidate_id = {
5870 let entries = completions_menu.entries.borrow();
5871 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5872 if self.show_edit_predictions_in_menu() {
5873 self.discard_edit_prediction(true, cx);
5874 }
5875 mat.candidate_id
5876 };
5877
5878 let completion = completions_menu
5879 .completions
5880 .borrow()
5881 .get(candidate_id)?
5882 .clone();
5883 cx.stop_propagation();
5884
5885 let buffer_handle = completions_menu.buffer.clone();
5886
5887 let CompletionEdit {
5888 new_text,
5889 snippet,
5890 replace_range,
5891 } = process_completion_for_edit(
5892 &completion,
5893 intent,
5894 &buffer_handle,
5895 &completions_menu.initial_position.text_anchor,
5896 cx,
5897 );
5898
5899 let buffer = buffer_handle.read(cx);
5900 let snapshot = self.buffer.read(cx).snapshot(cx);
5901 let newest_anchor = self.selections.newest_anchor();
5902 let replace_range_multibuffer = {
5903 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5904 excerpt.map_range_from_buffer(replace_range.clone())
5905 };
5906 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5907 return None;
5908 }
5909
5910 let old_text = buffer
5911 .text_for_range(replace_range.clone())
5912 .collect::<String>();
5913 let lookbehind = newest_anchor
5914 .start
5915 .text_anchor
5916 .to_offset(buffer)
5917 .saturating_sub(replace_range.start);
5918 let lookahead = replace_range
5919 .end
5920 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5921 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5922 let suffix = &old_text[lookbehind.min(old_text.len())..];
5923
5924 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5925 let mut ranges = Vec::new();
5926 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5927
5928 for selection in &selections {
5929 let range = if selection.id == newest_anchor.id {
5930 replace_range_multibuffer.clone()
5931 } else {
5932 let mut range = selection.range();
5933
5934 // if prefix is present, don't duplicate it
5935 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5936 range.start = range.start.saturating_sub(lookbehind);
5937
5938 // if suffix is also present, mimic the newest cursor and replace it
5939 if selection.id != newest_anchor.id
5940 && snapshot.contains_str_at(range.end, suffix)
5941 {
5942 range.end += lookahead;
5943 }
5944 }
5945 range
5946 };
5947
5948 ranges.push(range.clone());
5949
5950 if !self.linked_edit_ranges.is_empty() {
5951 let start_anchor = snapshot.anchor_before(range.start);
5952 let end_anchor = snapshot.anchor_after(range.end);
5953 if let Some(ranges) = self
5954 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5955 {
5956 for (buffer, edits) in ranges {
5957 linked_edits
5958 .entry(buffer.clone())
5959 .or_default()
5960 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5961 }
5962 }
5963 }
5964 }
5965
5966 let common_prefix_len = old_text
5967 .chars()
5968 .zip(new_text.chars())
5969 .take_while(|(a, b)| a == b)
5970 .map(|(a, _)| a.len_utf8())
5971 .sum::<usize>();
5972
5973 cx.emit(EditorEvent::InputHandled {
5974 utf16_range_to_replace: None,
5975 text: new_text[common_prefix_len..].into(),
5976 });
5977
5978 self.transact(window, cx, |editor, window, cx| {
5979 if let Some(mut snippet) = snippet {
5980 snippet.text = new_text.to_string();
5981 editor
5982 .insert_snippet(&ranges, snippet, window, cx)
5983 .log_err();
5984 } else {
5985 editor.buffer.update(cx, |multi_buffer, cx| {
5986 let auto_indent = match completion.insert_text_mode {
5987 Some(InsertTextMode::AS_IS) => None,
5988 _ => editor.autoindent_mode.clone(),
5989 };
5990 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5991 multi_buffer.edit(edits, auto_indent, cx);
5992 });
5993 }
5994 for (buffer, edits) in linked_edits {
5995 buffer.update(cx, |buffer, cx| {
5996 let snapshot = buffer.snapshot();
5997 let edits = edits
5998 .into_iter()
5999 .map(|(range, text)| {
6000 use text::ToPoint as TP;
6001 let end_point = TP::to_point(&range.end, &snapshot);
6002 let start_point = TP::to_point(&range.start, &snapshot);
6003 (start_point..end_point, text)
6004 })
6005 .sorted_by_key(|(range, _)| range.start);
6006 buffer.edit(edits, None, cx);
6007 })
6008 }
6009
6010 editor.refresh_edit_prediction(true, false, window, cx);
6011 });
6012 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6013
6014 let show_new_completions_on_confirm = completion
6015 .confirm
6016 .as_ref()
6017 .is_some_and(|confirm| confirm(intent, window, cx));
6018 if show_new_completions_on_confirm {
6019 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6020 }
6021
6022 let provider = self.completion_provider.as_ref()?;
6023 drop(completion);
6024 let apply_edits = provider.apply_additional_edits_for_completion(
6025 buffer_handle,
6026 completions_menu.completions.clone(),
6027 candidate_id,
6028 true,
6029 cx,
6030 );
6031
6032 let editor_settings = EditorSettings::get_global(cx);
6033 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6034 // After the code completion is finished, users often want to know what signatures are needed.
6035 // so we should automatically call signature_help
6036 self.show_signature_help(&ShowSignatureHelp, window, cx);
6037 }
6038
6039 Some(cx.foreground_executor().spawn(async move {
6040 apply_edits.await?;
6041 Ok(())
6042 }))
6043 }
6044
6045 pub fn toggle_code_actions(
6046 &mut self,
6047 action: &ToggleCodeActions,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) {
6051 let quick_launch = action.quick_launch;
6052 let mut context_menu = self.context_menu.borrow_mut();
6053 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6054 if code_actions.deployed_from == action.deployed_from {
6055 // Toggle if we're selecting the same one
6056 *context_menu = None;
6057 cx.notify();
6058 return;
6059 } else {
6060 // Otherwise, clear it and start a new one
6061 *context_menu = None;
6062 cx.notify();
6063 }
6064 }
6065 drop(context_menu);
6066 let snapshot = self.snapshot(window, cx);
6067 let deployed_from = action.deployed_from.clone();
6068 let action = action.clone();
6069 self.completion_tasks.clear();
6070 self.discard_edit_prediction(false, cx);
6071
6072 let multibuffer_point = match &action.deployed_from {
6073 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6074 DisplayPoint::new(*row, 0).to_point(&snapshot)
6075 }
6076 _ => self
6077 .selections
6078 .newest::<Point>(&snapshot.display_snapshot)
6079 .head(),
6080 };
6081 let Some((buffer, buffer_row)) = snapshot
6082 .buffer_snapshot()
6083 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6084 .and_then(|(buffer_snapshot, range)| {
6085 self.buffer()
6086 .read(cx)
6087 .buffer(buffer_snapshot.remote_id())
6088 .map(|buffer| (buffer, range.start.row))
6089 })
6090 else {
6091 return;
6092 };
6093 let buffer_id = buffer.read(cx).remote_id();
6094 let tasks = self
6095 .tasks
6096 .get(&(buffer_id, buffer_row))
6097 .map(|t| Arc::new(t.to_owned()));
6098
6099 if !self.focus_handle.is_focused(window) {
6100 return;
6101 }
6102 let project = self.project.clone();
6103
6104 let code_actions_task = match deployed_from {
6105 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6106 _ => self.code_actions(buffer_row, window, cx),
6107 };
6108
6109 let runnable_task = match deployed_from {
6110 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6111 _ => {
6112 let mut task_context_task = Task::ready(None);
6113 if let Some(tasks) = &tasks
6114 && let Some(project) = project
6115 {
6116 task_context_task =
6117 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6118 }
6119
6120 cx.spawn_in(window, {
6121 let buffer = buffer.clone();
6122 async move |editor, cx| {
6123 let task_context = task_context_task.await;
6124
6125 let resolved_tasks =
6126 tasks
6127 .zip(task_context.clone())
6128 .map(|(tasks, task_context)| ResolvedTasks {
6129 templates: tasks.resolve(&task_context).collect(),
6130 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6131 multibuffer_point.row,
6132 tasks.column,
6133 )),
6134 });
6135 let debug_scenarios = editor
6136 .update(cx, |editor, cx| {
6137 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6138 })?
6139 .await;
6140 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6141 }
6142 })
6143 }
6144 };
6145
6146 cx.spawn_in(window, async move |editor, cx| {
6147 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6148 let code_actions = code_actions_task.await;
6149 let spawn_straight_away = quick_launch
6150 && resolved_tasks
6151 .as_ref()
6152 .is_some_and(|tasks| tasks.templates.len() == 1)
6153 && code_actions
6154 .as_ref()
6155 .is_none_or(|actions| actions.is_empty())
6156 && debug_scenarios.is_empty();
6157
6158 editor.update_in(cx, |editor, window, cx| {
6159 crate::hover_popover::hide_hover(editor, cx);
6160 let actions = CodeActionContents::new(
6161 resolved_tasks,
6162 code_actions,
6163 debug_scenarios,
6164 task_context.unwrap_or_default(),
6165 );
6166
6167 // Don't show the menu if there are no actions available
6168 if actions.is_empty() {
6169 cx.notify();
6170 return Task::ready(Ok(()));
6171 }
6172
6173 *editor.context_menu.borrow_mut() =
6174 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6175 buffer,
6176 actions,
6177 selected_item: Default::default(),
6178 scroll_handle: UniformListScrollHandle::default(),
6179 deployed_from,
6180 }));
6181 cx.notify();
6182 if spawn_straight_away
6183 && let Some(task) = editor.confirm_code_action(
6184 &ConfirmCodeAction { item_ix: Some(0) },
6185 window,
6186 cx,
6187 )
6188 {
6189 return task;
6190 }
6191
6192 Task::ready(Ok(()))
6193 })
6194 })
6195 .detach_and_log_err(cx);
6196 }
6197
6198 fn debug_scenarios(
6199 &mut self,
6200 resolved_tasks: &Option<ResolvedTasks>,
6201 buffer: &Entity<Buffer>,
6202 cx: &mut App,
6203 ) -> Task<Vec<task::DebugScenario>> {
6204 maybe!({
6205 let project = self.project()?;
6206 let dap_store = project.read(cx).dap_store();
6207 let mut scenarios = vec![];
6208 let resolved_tasks = resolved_tasks.as_ref()?;
6209 let buffer = buffer.read(cx);
6210 let language = buffer.language()?;
6211 let file = buffer.file();
6212 let debug_adapter = language_settings(language.name().into(), file, cx)
6213 .debuggers
6214 .first()
6215 .map(SharedString::from)
6216 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6217
6218 dap_store.update(cx, |dap_store, cx| {
6219 for (_, task) in &resolved_tasks.templates {
6220 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6221 task.original_task().clone(),
6222 debug_adapter.clone().into(),
6223 task.display_label().to_owned().into(),
6224 cx,
6225 );
6226 scenarios.push(maybe_scenario);
6227 }
6228 });
6229 Some(cx.background_spawn(async move {
6230 futures::future::join_all(scenarios)
6231 .await
6232 .into_iter()
6233 .flatten()
6234 .collect::<Vec<_>>()
6235 }))
6236 })
6237 .unwrap_or_else(|| Task::ready(vec![]))
6238 }
6239
6240 fn code_actions(
6241 &mut self,
6242 buffer_row: u32,
6243 window: &mut Window,
6244 cx: &mut Context<Self>,
6245 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6246 let mut task = self.code_actions_task.take();
6247 cx.spawn_in(window, async move |editor, cx| {
6248 while let Some(prev_task) = task {
6249 prev_task.await.log_err();
6250 task = editor
6251 .update(cx, |this, _| this.code_actions_task.take())
6252 .ok()?;
6253 }
6254
6255 editor
6256 .update(cx, |editor, cx| {
6257 editor
6258 .available_code_actions
6259 .clone()
6260 .and_then(|(location, code_actions)| {
6261 let snapshot = location.buffer.read(cx).snapshot();
6262 let point_range = location.range.to_point(&snapshot);
6263 let point_range = point_range.start.row..=point_range.end.row;
6264 if point_range.contains(&buffer_row) {
6265 Some(code_actions)
6266 } else {
6267 None
6268 }
6269 })
6270 })
6271 .ok()
6272 .flatten()
6273 })
6274 }
6275
6276 pub fn confirm_code_action(
6277 &mut self,
6278 action: &ConfirmCodeAction,
6279 window: &mut Window,
6280 cx: &mut Context<Self>,
6281 ) -> Option<Task<Result<()>>> {
6282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6283
6284 let actions_menu =
6285 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6286 menu
6287 } else {
6288 return None;
6289 };
6290
6291 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6292 let action = actions_menu.actions.get(action_ix)?;
6293 let title = action.label();
6294 let buffer = actions_menu.buffer;
6295 let workspace = self.workspace()?;
6296
6297 match action {
6298 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6299 workspace.update(cx, |workspace, cx| {
6300 workspace.schedule_resolved_task(
6301 task_source_kind,
6302 resolved_task,
6303 false,
6304 window,
6305 cx,
6306 );
6307
6308 Some(Task::ready(Ok(())))
6309 })
6310 }
6311 CodeActionsItem::CodeAction {
6312 excerpt_id,
6313 action,
6314 provider,
6315 } => {
6316 let apply_code_action =
6317 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6318 let workspace = workspace.downgrade();
6319 Some(cx.spawn_in(window, async move |editor, cx| {
6320 let project_transaction = apply_code_action.await?;
6321 Self::open_project_transaction(
6322 &editor,
6323 workspace,
6324 project_transaction,
6325 title,
6326 cx,
6327 )
6328 .await
6329 }))
6330 }
6331 CodeActionsItem::DebugScenario(scenario) => {
6332 let context = actions_menu.actions.context;
6333
6334 workspace.update(cx, |workspace, cx| {
6335 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6336 workspace.start_debug_session(
6337 scenario,
6338 context,
6339 Some(buffer),
6340 None,
6341 window,
6342 cx,
6343 );
6344 });
6345 Some(Task::ready(Ok(())))
6346 }
6347 }
6348 }
6349
6350 pub async fn open_project_transaction(
6351 editor: &WeakEntity<Editor>,
6352 workspace: WeakEntity<Workspace>,
6353 transaction: ProjectTransaction,
6354 title: String,
6355 cx: &mut AsyncWindowContext,
6356 ) -> Result<()> {
6357 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6358 cx.update(|_, cx| {
6359 entries.sort_unstable_by_key(|(buffer, _)| {
6360 buffer.read(cx).file().map(|f| f.path().clone())
6361 });
6362 })?;
6363 if entries.is_empty() {
6364 return Ok(());
6365 }
6366
6367 // If the project transaction's edits are all contained within this editor, then
6368 // avoid opening a new editor to display them.
6369
6370 if let [(buffer, transaction)] = &*entries {
6371 let excerpt = editor.update(cx, |editor, cx| {
6372 editor
6373 .buffer()
6374 .read(cx)
6375 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6376 })?;
6377 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6378 && excerpted_buffer == *buffer
6379 {
6380 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6381 let excerpt_range = excerpt_range.to_offset(buffer);
6382 buffer
6383 .edited_ranges_for_transaction::<usize>(transaction)
6384 .all(|range| {
6385 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6386 })
6387 })?;
6388
6389 if all_edits_within_excerpt {
6390 return Ok(());
6391 }
6392 }
6393 }
6394
6395 let mut ranges_to_highlight = Vec::new();
6396 let excerpt_buffer = cx.new(|cx| {
6397 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6398 for (buffer_handle, transaction) in &entries {
6399 let edited_ranges = buffer_handle
6400 .read(cx)
6401 .edited_ranges_for_transaction::<Point>(transaction)
6402 .collect::<Vec<_>>();
6403 let (ranges, _) = multibuffer.set_excerpts_for_path(
6404 PathKey::for_buffer(buffer_handle, cx),
6405 buffer_handle.clone(),
6406 edited_ranges,
6407 multibuffer_context_lines(cx),
6408 cx,
6409 );
6410
6411 ranges_to_highlight.extend(ranges);
6412 }
6413 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6414 multibuffer
6415 })?;
6416
6417 workspace.update_in(cx, |workspace, window, cx| {
6418 let project = workspace.project().clone();
6419 let editor =
6420 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6421 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6422 editor.update(cx, |editor, cx| {
6423 editor.highlight_background::<Self>(
6424 &ranges_to_highlight,
6425 |theme| theme.colors().editor_highlighted_line_background,
6426 cx,
6427 );
6428 });
6429 })?;
6430
6431 Ok(())
6432 }
6433
6434 pub fn clear_code_action_providers(&mut self) {
6435 self.code_action_providers.clear();
6436 self.available_code_actions.take();
6437 }
6438
6439 pub fn add_code_action_provider(
6440 &mut self,
6441 provider: Rc<dyn CodeActionProvider>,
6442 window: &mut Window,
6443 cx: &mut Context<Self>,
6444 ) {
6445 if self
6446 .code_action_providers
6447 .iter()
6448 .any(|existing_provider| existing_provider.id() == provider.id())
6449 {
6450 return;
6451 }
6452
6453 self.code_action_providers.push(provider);
6454 self.refresh_code_actions(window, cx);
6455 }
6456
6457 pub fn remove_code_action_provider(
6458 &mut self,
6459 id: Arc<str>,
6460 window: &mut Window,
6461 cx: &mut Context<Self>,
6462 ) {
6463 self.code_action_providers
6464 .retain(|provider| provider.id() != id);
6465 self.refresh_code_actions(window, cx);
6466 }
6467
6468 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6469 !self.code_action_providers.is_empty()
6470 && EditorSettings::get_global(cx).toolbar.code_actions
6471 }
6472
6473 pub fn has_available_code_actions(&self) -> bool {
6474 self.available_code_actions
6475 .as_ref()
6476 .is_some_and(|(_, actions)| !actions.is_empty())
6477 }
6478
6479 fn render_inline_code_actions(
6480 &self,
6481 icon_size: ui::IconSize,
6482 display_row: DisplayRow,
6483 is_active: bool,
6484 cx: &mut Context<Self>,
6485 ) -> AnyElement {
6486 let show_tooltip = !self.context_menu_visible();
6487 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6488 .icon_size(icon_size)
6489 .shape(ui::IconButtonShape::Square)
6490 .icon_color(ui::Color::Hidden)
6491 .toggle_state(is_active)
6492 .when(show_tooltip, |this| {
6493 this.tooltip({
6494 let focus_handle = self.focus_handle.clone();
6495 move |_window, cx| {
6496 Tooltip::for_action_in(
6497 "Toggle Code Actions",
6498 &ToggleCodeActions {
6499 deployed_from: None,
6500 quick_launch: false,
6501 },
6502 &focus_handle,
6503 cx,
6504 )
6505 }
6506 })
6507 })
6508 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6509 window.focus(&editor.focus_handle(cx));
6510 editor.toggle_code_actions(
6511 &crate::actions::ToggleCodeActions {
6512 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6513 display_row,
6514 )),
6515 quick_launch: false,
6516 },
6517 window,
6518 cx,
6519 );
6520 }))
6521 .into_any_element()
6522 }
6523
6524 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6525 &self.context_menu
6526 }
6527
6528 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6529 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6530 cx.background_executor()
6531 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6532 .await;
6533
6534 let (start_buffer, start, _, end, newest_selection) = this
6535 .update(cx, |this, cx| {
6536 let newest_selection = this.selections.newest_anchor().clone();
6537 if newest_selection.head().diff_base_anchor.is_some() {
6538 return None;
6539 }
6540 let display_snapshot = this.display_snapshot(cx);
6541 let newest_selection_adjusted =
6542 this.selections.newest_adjusted(&display_snapshot);
6543 let buffer = this.buffer.read(cx);
6544
6545 let (start_buffer, start) =
6546 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6547 let (end_buffer, end) =
6548 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6549
6550 Some((start_buffer, start, end_buffer, end, newest_selection))
6551 })?
6552 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6553 .context(
6554 "Expected selection to lie in a single buffer when refreshing code actions",
6555 )?;
6556 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6557 let providers = this.code_action_providers.clone();
6558 let tasks = this
6559 .code_action_providers
6560 .iter()
6561 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6562 .collect::<Vec<_>>();
6563 (providers, tasks)
6564 })?;
6565
6566 let mut actions = Vec::new();
6567 for (provider, provider_actions) in
6568 providers.into_iter().zip(future::join_all(tasks).await)
6569 {
6570 if let Some(provider_actions) = provider_actions.log_err() {
6571 actions.extend(provider_actions.into_iter().map(|action| {
6572 AvailableCodeAction {
6573 excerpt_id: newest_selection.start.excerpt_id,
6574 action,
6575 provider: provider.clone(),
6576 }
6577 }));
6578 }
6579 }
6580
6581 this.update(cx, |this, cx| {
6582 this.available_code_actions = if actions.is_empty() {
6583 None
6584 } else {
6585 Some((
6586 Location {
6587 buffer: start_buffer,
6588 range: start..end,
6589 },
6590 actions.into(),
6591 ))
6592 };
6593 cx.notify();
6594 })
6595 }));
6596 }
6597
6598 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6599 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6600 self.show_git_blame_inline = false;
6601
6602 self.show_git_blame_inline_delay_task =
6603 Some(cx.spawn_in(window, async move |this, cx| {
6604 cx.background_executor().timer(delay).await;
6605
6606 this.update(cx, |this, cx| {
6607 this.show_git_blame_inline = true;
6608 cx.notify();
6609 })
6610 .log_err();
6611 }));
6612 }
6613 }
6614
6615 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6616 let snapshot = self.snapshot(window, cx);
6617 let cursor = self
6618 .selections
6619 .newest::<Point>(&snapshot.display_snapshot)
6620 .head();
6621 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6622 else {
6623 return;
6624 };
6625
6626 let Some(blame) = self.blame.as_ref() else {
6627 return;
6628 };
6629
6630 let row_info = RowInfo {
6631 buffer_id: Some(buffer.remote_id()),
6632 buffer_row: Some(point.row),
6633 ..Default::default()
6634 };
6635 let Some((buffer, blame_entry)) = blame
6636 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6637 .flatten()
6638 else {
6639 return;
6640 };
6641
6642 let anchor = self.selections.newest_anchor().head();
6643 let position = self.to_pixel_point(anchor, &snapshot, window);
6644 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6645 self.show_blame_popover(
6646 buffer,
6647 &blame_entry,
6648 position + last_bounds.origin,
6649 true,
6650 cx,
6651 );
6652 };
6653 }
6654
6655 fn show_blame_popover(
6656 &mut self,
6657 buffer: BufferId,
6658 blame_entry: &BlameEntry,
6659 position: gpui::Point<Pixels>,
6660 ignore_timeout: bool,
6661 cx: &mut Context<Self>,
6662 ) {
6663 if let Some(state) = &mut self.inline_blame_popover {
6664 state.hide_task.take();
6665 } else {
6666 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6667 let blame_entry = blame_entry.clone();
6668 let show_task = cx.spawn(async move |editor, cx| {
6669 if !ignore_timeout {
6670 cx.background_executor()
6671 .timer(std::time::Duration::from_millis(blame_popover_delay))
6672 .await;
6673 }
6674 editor
6675 .update(cx, |editor, cx| {
6676 editor.inline_blame_popover_show_task.take();
6677 let Some(blame) = editor.blame.as_ref() else {
6678 return;
6679 };
6680 let blame = blame.read(cx);
6681 let details = blame.details_for_entry(buffer, &blame_entry);
6682 let markdown = cx.new(|cx| {
6683 Markdown::new(
6684 details
6685 .as_ref()
6686 .map(|message| message.message.clone())
6687 .unwrap_or_default(),
6688 None,
6689 None,
6690 cx,
6691 )
6692 });
6693 editor.inline_blame_popover = Some(InlineBlamePopover {
6694 position,
6695 hide_task: None,
6696 popover_bounds: None,
6697 popover_state: InlineBlamePopoverState {
6698 scroll_handle: ScrollHandle::new(),
6699 commit_message: details,
6700 markdown,
6701 },
6702 keyboard_grace: ignore_timeout,
6703 });
6704 cx.notify();
6705 })
6706 .ok();
6707 });
6708 self.inline_blame_popover_show_task = Some(show_task);
6709 }
6710 }
6711
6712 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6713 self.inline_blame_popover_show_task.take();
6714 if let Some(state) = &mut self.inline_blame_popover {
6715 let hide_task = cx.spawn(async move |editor, cx| {
6716 if !ignore_timeout {
6717 cx.background_executor()
6718 .timer(std::time::Duration::from_millis(100))
6719 .await;
6720 }
6721 editor
6722 .update(cx, |editor, cx| {
6723 editor.inline_blame_popover.take();
6724 cx.notify();
6725 })
6726 .ok();
6727 });
6728 state.hide_task = Some(hide_task);
6729 true
6730 } else {
6731 false
6732 }
6733 }
6734
6735 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6736 if self.pending_rename.is_some() {
6737 return None;
6738 }
6739
6740 let provider = self.semantics_provider.clone()?;
6741 let buffer = self.buffer.read(cx);
6742 let newest_selection = self.selections.newest_anchor().clone();
6743 let cursor_position = newest_selection.head();
6744 let (cursor_buffer, cursor_buffer_position) =
6745 buffer.text_anchor_for_position(cursor_position, cx)?;
6746 let (tail_buffer, tail_buffer_position) =
6747 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6748 if cursor_buffer != tail_buffer {
6749 return None;
6750 }
6751
6752 let snapshot = cursor_buffer.read(cx).snapshot();
6753 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6754 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6755 if start_word_range != end_word_range {
6756 self.document_highlights_task.take();
6757 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6758 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6759 return None;
6760 }
6761
6762 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6763 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6764 cx.background_executor()
6765 .timer(Duration::from_millis(debounce))
6766 .await;
6767
6768 let highlights = if let Some(highlights) = cx
6769 .update(|cx| {
6770 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6771 })
6772 .ok()
6773 .flatten()
6774 {
6775 highlights.await.log_err()
6776 } else {
6777 None
6778 };
6779
6780 if let Some(highlights) = highlights {
6781 this.update(cx, |this, cx| {
6782 if this.pending_rename.is_some() {
6783 return;
6784 }
6785
6786 let buffer = this.buffer.read(cx);
6787 if buffer
6788 .text_anchor_for_position(cursor_position, cx)
6789 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6790 {
6791 return;
6792 }
6793
6794 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6795 let mut write_ranges = Vec::new();
6796 let mut read_ranges = Vec::new();
6797 for highlight in highlights {
6798 let buffer_id = cursor_buffer.read(cx).remote_id();
6799 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6800 {
6801 let start = highlight
6802 .range
6803 .start
6804 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6805 let end = highlight
6806 .range
6807 .end
6808 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6809 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6810 continue;
6811 }
6812
6813 let range =
6814 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6815 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6816 write_ranges.push(range);
6817 } else {
6818 read_ranges.push(range);
6819 }
6820 }
6821 }
6822
6823 this.highlight_background::<DocumentHighlightRead>(
6824 &read_ranges,
6825 |theme| theme.colors().editor_document_highlight_read_background,
6826 cx,
6827 );
6828 this.highlight_background::<DocumentHighlightWrite>(
6829 &write_ranges,
6830 |theme| theme.colors().editor_document_highlight_write_background,
6831 cx,
6832 );
6833 cx.notify();
6834 })
6835 .log_err();
6836 }
6837 }));
6838 None
6839 }
6840
6841 fn prepare_highlight_query_from_selection(
6842 &mut self,
6843 window: &Window,
6844 cx: &mut Context<Editor>,
6845 ) -> Option<(String, Range<Anchor>)> {
6846 if matches!(self.mode, EditorMode::SingleLine) {
6847 return None;
6848 }
6849 if !EditorSettings::get_global(cx).selection_highlight {
6850 return None;
6851 }
6852 if self.selections.count() != 1 || self.selections.line_mode() {
6853 return None;
6854 }
6855 let snapshot = self.snapshot(window, cx);
6856 let selection = self.selections.newest::<Point>(&snapshot);
6857 // If the selection spans multiple rows OR it is empty
6858 if selection.start.row != selection.end.row
6859 || selection.start.column == selection.end.column
6860 {
6861 return None;
6862 }
6863 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6864 let query = snapshot
6865 .buffer_snapshot()
6866 .text_for_range(selection_anchor_range.clone())
6867 .collect::<String>();
6868 if query.trim().is_empty() {
6869 return None;
6870 }
6871 Some((query, selection_anchor_range))
6872 }
6873
6874 fn update_selection_occurrence_highlights(
6875 &mut self,
6876 query_text: String,
6877 query_range: Range<Anchor>,
6878 multi_buffer_range_to_query: Range<Point>,
6879 use_debounce: bool,
6880 window: &mut Window,
6881 cx: &mut Context<Editor>,
6882 ) -> Task<()> {
6883 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6884 cx.spawn_in(window, async move |editor, cx| {
6885 if use_debounce {
6886 cx.background_executor()
6887 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6888 .await;
6889 }
6890 let match_task = cx.background_spawn(async move {
6891 let buffer_ranges = multi_buffer_snapshot
6892 .range_to_buffer_ranges(multi_buffer_range_to_query)
6893 .into_iter()
6894 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6895 let mut match_ranges = Vec::new();
6896 let Ok(regex) = project::search::SearchQuery::text(
6897 query_text.clone(),
6898 false,
6899 false,
6900 false,
6901 Default::default(),
6902 Default::default(),
6903 false,
6904 None,
6905 ) else {
6906 return Vec::default();
6907 };
6908 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6909 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6910 match_ranges.extend(
6911 regex
6912 .search(buffer_snapshot, Some(search_range.clone()))
6913 .await
6914 .into_iter()
6915 .filter_map(|match_range| {
6916 let match_start = buffer_snapshot
6917 .anchor_after(search_range.start + match_range.start);
6918 let match_end = buffer_snapshot
6919 .anchor_before(search_range.start + match_range.end);
6920 let match_anchor_range = Anchor::range_in_buffer(
6921 excerpt_id,
6922 buffer_snapshot.remote_id(),
6923 match_start..match_end,
6924 );
6925 (match_anchor_range != query_range).then_some(match_anchor_range)
6926 }),
6927 );
6928 }
6929 match_ranges
6930 });
6931 let match_ranges = match_task.await;
6932 editor
6933 .update_in(cx, |editor, _, cx| {
6934 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6935 if !match_ranges.is_empty() {
6936 editor.highlight_background::<SelectedTextHighlight>(
6937 &match_ranges,
6938 |theme| theme.colors().editor_document_highlight_bracket_background,
6939 cx,
6940 )
6941 }
6942 })
6943 .log_err();
6944 })
6945 }
6946
6947 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6948 struct NewlineFold;
6949 let type_id = std::any::TypeId::of::<NewlineFold>();
6950 if !self.mode.is_single_line() {
6951 return;
6952 }
6953 let snapshot = self.snapshot(window, cx);
6954 if snapshot.buffer_snapshot().max_point().row == 0 {
6955 return;
6956 }
6957 let task = cx.background_spawn(async move {
6958 let new_newlines = snapshot
6959 .buffer_chars_at(0)
6960 .filter_map(|(c, i)| {
6961 if c == '\n' {
6962 Some(
6963 snapshot.buffer_snapshot().anchor_after(i)
6964 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6965 )
6966 } else {
6967 None
6968 }
6969 })
6970 .collect::<Vec<_>>();
6971 let existing_newlines = snapshot
6972 .folds_in_range(0..snapshot.buffer_snapshot().len())
6973 .filter_map(|fold| {
6974 if fold.placeholder.type_tag == Some(type_id) {
6975 Some(fold.range.start..fold.range.end)
6976 } else {
6977 None
6978 }
6979 })
6980 .collect::<Vec<_>>();
6981
6982 (new_newlines, existing_newlines)
6983 });
6984 self.folding_newlines = cx.spawn(async move |this, cx| {
6985 let (new_newlines, existing_newlines) = task.await;
6986 if new_newlines == existing_newlines {
6987 return;
6988 }
6989 let placeholder = FoldPlaceholder {
6990 render: Arc::new(move |_, _, cx| {
6991 div()
6992 .bg(cx.theme().status().hint_background)
6993 .border_b_1()
6994 .size_full()
6995 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6996 .border_color(cx.theme().status().hint)
6997 .child("\\n")
6998 .into_any()
6999 }),
7000 constrain_width: false,
7001 merge_adjacent: false,
7002 type_tag: Some(type_id),
7003 };
7004 let creases = new_newlines
7005 .into_iter()
7006 .map(|range| Crease::simple(range, placeholder.clone()))
7007 .collect();
7008 this.update(cx, |this, cx| {
7009 this.display_map.update(cx, |display_map, cx| {
7010 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7011 display_map.fold(creases, cx);
7012 });
7013 })
7014 .ok();
7015 });
7016 }
7017
7018 fn refresh_selected_text_highlights(
7019 &mut self,
7020 on_buffer_edit: bool,
7021 window: &mut Window,
7022 cx: &mut Context<Editor>,
7023 ) {
7024 let Some((query_text, query_range)) =
7025 self.prepare_highlight_query_from_selection(window, cx)
7026 else {
7027 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7028 self.quick_selection_highlight_task.take();
7029 self.debounced_selection_highlight_task.take();
7030 return;
7031 };
7032 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7033 if on_buffer_edit
7034 || self
7035 .quick_selection_highlight_task
7036 .as_ref()
7037 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7038 {
7039 let multi_buffer_visible_start = self
7040 .scroll_manager
7041 .anchor()
7042 .anchor
7043 .to_point(&multi_buffer_snapshot);
7044 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7045 multi_buffer_visible_start
7046 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7047 Bias::Left,
7048 );
7049 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7050 self.quick_selection_highlight_task = Some((
7051 query_range.clone(),
7052 self.update_selection_occurrence_highlights(
7053 query_text.clone(),
7054 query_range.clone(),
7055 multi_buffer_visible_range,
7056 false,
7057 window,
7058 cx,
7059 ),
7060 ));
7061 }
7062 if on_buffer_edit
7063 || self
7064 .debounced_selection_highlight_task
7065 .as_ref()
7066 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7067 {
7068 let multi_buffer_start = multi_buffer_snapshot
7069 .anchor_before(0)
7070 .to_point(&multi_buffer_snapshot);
7071 let multi_buffer_end = multi_buffer_snapshot
7072 .anchor_after(multi_buffer_snapshot.len())
7073 .to_point(&multi_buffer_snapshot);
7074 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7075 self.debounced_selection_highlight_task = Some((
7076 query_range.clone(),
7077 self.update_selection_occurrence_highlights(
7078 query_text,
7079 query_range,
7080 multi_buffer_full_range,
7081 true,
7082 window,
7083 cx,
7084 ),
7085 ));
7086 }
7087 }
7088
7089 pub fn refresh_edit_prediction(
7090 &mut self,
7091 debounce: bool,
7092 user_requested: bool,
7093 window: &mut Window,
7094 cx: &mut Context<Self>,
7095 ) -> Option<()> {
7096 if DisableAiSettings::get_global(cx).disable_ai {
7097 return None;
7098 }
7099
7100 let provider = self.edit_prediction_provider()?;
7101 let cursor = self.selections.newest_anchor().head();
7102 let (buffer, cursor_buffer_position) =
7103 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7104
7105 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7106 self.discard_edit_prediction(false, cx);
7107 return None;
7108 }
7109
7110 self.update_visible_edit_prediction(window, cx);
7111
7112 if !user_requested
7113 && (!self.should_show_edit_predictions()
7114 || !self.is_focused(window)
7115 || buffer.read(cx).is_empty())
7116 {
7117 self.discard_edit_prediction(false, cx);
7118 return None;
7119 }
7120
7121 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7122 Some(())
7123 }
7124
7125 fn show_edit_predictions_in_menu(&self) -> bool {
7126 match self.edit_prediction_settings {
7127 EditPredictionSettings::Disabled => false,
7128 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7129 }
7130 }
7131
7132 pub fn edit_predictions_enabled(&self) -> bool {
7133 match self.edit_prediction_settings {
7134 EditPredictionSettings::Disabled => false,
7135 EditPredictionSettings::Enabled { .. } => true,
7136 }
7137 }
7138
7139 fn edit_prediction_requires_modifier(&self) -> bool {
7140 match self.edit_prediction_settings {
7141 EditPredictionSettings::Disabled => false,
7142 EditPredictionSettings::Enabled {
7143 preview_requires_modifier,
7144 ..
7145 } => preview_requires_modifier,
7146 }
7147 }
7148
7149 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7150 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7151 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7152 self.discard_edit_prediction(false, cx);
7153 } else {
7154 let selection = self.selections.newest_anchor();
7155 let cursor = selection.head();
7156
7157 if let Some((buffer, cursor_buffer_position)) =
7158 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7159 {
7160 self.edit_prediction_settings =
7161 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7162 }
7163 }
7164 }
7165
7166 fn edit_prediction_settings_at_position(
7167 &self,
7168 buffer: &Entity<Buffer>,
7169 buffer_position: language::Anchor,
7170 cx: &App,
7171 ) -> EditPredictionSettings {
7172 if !self.mode.is_full()
7173 || !self.show_edit_predictions_override.unwrap_or(true)
7174 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7175 {
7176 return EditPredictionSettings::Disabled;
7177 }
7178
7179 let buffer = buffer.read(cx);
7180
7181 let file = buffer.file();
7182
7183 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7184 return EditPredictionSettings::Disabled;
7185 };
7186
7187 let by_provider = matches!(
7188 self.menu_edit_predictions_policy,
7189 MenuEditPredictionsPolicy::ByProvider
7190 );
7191
7192 let show_in_menu = by_provider
7193 && self
7194 .edit_prediction_provider
7195 .as_ref()
7196 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7197
7198 let preview_requires_modifier =
7199 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7200
7201 EditPredictionSettings::Enabled {
7202 show_in_menu,
7203 preview_requires_modifier,
7204 }
7205 }
7206
7207 fn should_show_edit_predictions(&self) -> bool {
7208 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7209 }
7210
7211 pub fn edit_prediction_preview_is_active(&self) -> bool {
7212 matches!(
7213 self.edit_prediction_preview,
7214 EditPredictionPreview::Active { .. }
7215 )
7216 }
7217
7218 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7219 let cursor = self.selections.newest_anchor().head();
7220 if let Some((buffer, cursor_position)) =
7221 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7222 {
7223 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7224 } else {
7225 false
7226 }
7227 }
7228
7229 pub fn supports_minimap(&self, cx: &App) -> bool {
7230 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7231 }
7232
7233 fn edit_predictions_enabled_in_buffer(
7234 &self,
7235 buffer: &Entity<Buffer>,
7236 buffer_position: language::Anchor,
7237 cx: &App,
7238 ) -> bool {
7239 maybe!({
7240 if self.read_only(cx) {
7241 return Some(false);
7242 }
7243 let provider = self.edit_prediction_provider()?;
7244 if !provider.is_enabled(buffer, buffer_position, cx) {
7245 return Some(false);
7246 }
7247 let buffer = buffer.read(cx);
7248 let Some(file) = buffer.file() else {
7249 return Some(true);
7250 };
7251 let settings = all_language_settings(Some(file), cx);
7252 Some(settings.edit_predictions_enabled_for_file(file, cx))
7253 })
7254 .unwrap_or(false)
7255 }
7256
7257 fn cycle_edit_prediction(
7258 &mut self,
7259 direction: Direction,
7260 window: &mut Window,
7261 cx: &mut Context<Self>,
7262 ) -> Option<()> {
7263 let provider = self.edit_prediction_provider()?;
7264 let cursor = self.selections.newest_anchor().head();
7265 let (buffer, cursor_buffer_position) =
7266 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7267 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7268 return None;
7269 }
7270
7271 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7272 self.update_visible_edit_prediction(window, cx);
7273
7274 Some(())
7275 }
7276
7277 pub fn show_edit_prediction(
7278 &mut self,
7279 _: &ShowEditPrediction,
7280 window: &mut Window,
7281 cx: &mut Context<Self>,
7282 ) {
7283 if !self.has_active_edit_prediction() {
7284 self.refresh_edit_prediction(false, true, window, cx);
7285 return;
7286 }
7287
7288 self.update_visible_edit_prediction(window, cx);
7289 }
7290
7291 pub fn display_cursor_names(
7292 &mut self,
7293 _: &DisplayCursorNames,
7294 window: &mut Window,
7295 cx: &mut Context<Self>,
7296 ) {
7297 self.show_cursor_names(window, cx);
7298 }
7299
7300 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7301 self.show_cursor_names = true;
7302 cx.notify();
7303 cx.spawn_in(window, async move |this, cx| {
7304 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7305 this.update(cx, |this, cx| {
7306 this.show_cursor_names = false;
7307 cx.notify()
7308 })
7309 .ok()
7310 })
7311 .detach();
7312 }
7313
7314 pub fn next_edit_prediction(
7315 &mut self,
7316 _: &NextEditPrediction,
7317 window: &mut Window,
7318 cx: &mut Context<Self>,
7319 ) {
7320 if self.has_active_edit_prediction() {
7321 self.cycle_edit_prediction(Direction::Next, window, cx);
7322 } else {
7323 let is_copilot_disabled = self
7324 .refresh_edit_prediction(false, true, window, cx)
7325 .is_none();
7326 if is_copilot_disabled {
7327 cx.propagate();
7328 }
7329 }
7330 }
7331
7332 pub fn previous_edit_prediction(
7333 &mut self,
7334 _: &PreviousEditPrediction,
7335 window: &mut Window,
7336 cx: &mut Context<Self>,
7337 ) {
7338 if self.has_active_edit_prediction() {
7339 self.cycle_edit_prediction(Direction::Prev, window, cx);
7340 } else {
7341 let is_copilot_disabled = self
7342 .refresh_edit_prediction(false, true, window, cx)
7343 .is_none();
7344 if is_copilot_disabled {
7345 cx.propagate();
7346 }
7347 }
7348 }
7349
7350 pub fn accept_edit_prediction(
7351 &mut self,
7352 _: &AcceptEditPrediction,
7353 window: &mut Window,
7354 cx: &mut Context<Self>,
7355 ) {
7356 if self.show_edit_predictions_in_menu() {
7357 self.hide_context_menu(window, cx);
7358 }
7359
7360 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7361 return;
7362 };
7363
7364 match &active_edit_prediction.completion {
7365 EditPrediction::MoveWithin { target, .. } => {
7366 let target = *target;
7367
7368 if let Some(position_map) = &self.last_position_map {
7369 if position_map
7370 .visible_row_range
7371 .contains(&target.to_display_point(&position_map.snapshot).row())
7372 || !self.edit_prediction_requires_modifier()
7373 {
7374 self.unfold_ranges(&[target..target], true, false, cx);
7375 // Note that this is also done in vim's handler of the Tab action.
7376 self.change_selections(
7377 SelectionEffects::scroll(Autoscroll::newest()),
7378 window,
7379 cx,
7380 |selections| {
7381 selections.select_anchor_ranges([target..target]);
7382 },
7383 );
7384 self.clear_row_highlights::<EditPredictionPreview>();
7385
7386 self.edit_prediction_preview
7387 .set_previous_scroll_position(None);
7388 } else {
7389 self.edit_prediction_preview
7390 .set_previous_scroll_position(Some(
7391 position_map.snapshot.scroll_anchor,
7392 ));
7393
7394 self.highlight_rows::<EditPredictionPreview>(
7395 target..target,
7396 cx.theme().colors().editor_highlighted_line_background,
7397 RowHighlightOptions {
7398 autoscroll: true,
7399 ..Default::default()
7400 },
7401 cx,
7402 );
7403 self.request_autoscroll(Autoscroll::fit(), cx);
7404 }
7405 }
7406 }
7407 EditPrediction::MoveOutside { snapshot, target } => {
7408 if let Some(workspace) = self.workspace() {
7409 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7410 .detach_and_log_err(cx);
7411 }
7412 }
7413 EditPrediction::Edit { edits, .. } => {
7414 self.report_edit_prediction_event(
7415 active_edit_prediction.completion_id.clone(),
7416 true,
7417 cx,
7418 );
7419
7420 if let Some(provider) = self.edit_prediction_provider() {
7421 provider.accept(cx);
7422 }
7423
7424 // Store the transaction ID and selections before applying the edit
7425 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7426
7427 let snapshot = self.buffer.read(cx).snapshot(cx);
7428 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7429
7430 self.buffer.update(cx, |buffer, cx| {
7431 buffer.edit(edits.iter().cloned(), None, cx)
7432 });
7433
7434 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7435 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7436 });
7437
7438 let selections = self.selections.disjoint_anchors_arc();
7439 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7440 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7441 if has_new_transaction {
7442 self.selection_history
7443 .insert_transaction(transaction_id_now, selections);
7444 }
7445 }
7446
7447 self.update_visible_edit_prediction(window, cx);
7448 if self.active_edit_prediction.is_none() {
7449 self.refresh_edit_prediction(true, true, window, cx);
7450 }
7451
7452 cx.notify();
7453 }
7454 }
7455
7456 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7457 }
7458
7459 pub fn accept_partial_edit_prediction(
7460 &mut self,
7461 _: &AcceptPartialEditPrediction,
7462 window: &mut Window,
7463 cx: &mut Context<Self>,
7464 ) {
7465 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7466 return;
7467 };
7468 if self.selections.count() != 1 {
7469 return;
7470 }
7471
7472 match &active_edit_prediction.completion {
7473 EditPrediction::MoveWithin { target, .. } => {
7474 let target = *target;
7475 self.change_selections(
7476 SelectionEffects::scroll(Autoscroll::newest()),
7477 window,
7478 cx,
7479 |selections| {
7480 selections.select_anchor_ranges([target..target]);
7481 },
7482 );
7483 }
7484 EditPrediction::MoveOutside { snapshot, target } => {
7485 if let Some(workspace) = self.workspace() {
7486 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7487 .detach_and_log_err(cx);
7488 }
7489 }
7490 EditPrediction::Edit { edits, .. } => {
7491 self.report_edit_prediction_event(
7492 active_edit_prediction.completion_id.clone(),
7493 true,
7494 cx,
7495 );
7496
7497 // Find an insertion that starts at the cursor position.
7498 let snapshot = self.buffer.read(cx).snapshot(cx);
7499 let cursor_offset = self
7500 .selections
7501 .newest::<usize>(&self.display_snapshot(cx))
7502 .head();
7503 let insertion = edits.iter().find_map(|(range, text)| {
7504 let range = range.to_offset(&snapshot);
7505 if range.is_empty() && range.start == cursor_offset {
7506 Some(text)
7507 } else {
7508 None
7509 }
7510 });
7511
7512 if let Some(text) = insertion {
7513 let mut partial_completion = text
7514 .chars()
7515 .by_ref()
7516 .take_while(|c| c.is_alphabetic())
7517 .collect::<String>();
7518 if partial_completion.is_empty() {
7519 partial_completion = text
7520 .chars()
7521 .by_ref()
7522 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7523 .collect::<String>();
7524 }
7525
7526 cx.emit(EditorEvent::InputHandled {
7527 utf16_range_to_replace: None,
7528 text: partial_completion.clone().into(),
7529 });
7530
7531 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7532
7533 self.refresh_edit_prediction(true, true, window, cx);
7534 cx.notify();
7535 } else {
7536 self.accept_edit_prediction(&Default::default(), window, cx);
7537 }
7538 }
7539 }
7540 }
7541
7542 fn discard_edit_prediction(
7543 &mut self,
7544 should_report_edit_prediction_event: bool,
7545 cx: &mut Context<Self>,
7546 ) -> bool {
7547 if should_report_edit_prediction_event {
7548 let completion_id = self
7549 .active_edit_prediction
7550 .as_ref()
7551 .and_then(|active_completion| active_completion.completion_id.clone());
7552
7553 self.report_edit_prediction_event(completion_id, false, cx);
7554 }
7555
7556 if let Some(provider) = self.edit_prediction_provider() {
7557 provider.discard(cx);
7558 }
7559
7560 self.take_active_edit_prediction(cx)
7561 }
7562
7563 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7564 let Some(provider) = self.edit_prediction_provider() else {
7565 return;
7566 };
7567
7568 let Some((_, buffer, _)) = self
7569 .buffer
7570 .read(cx)
7571 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7572 else {
7573 return;
7574 };
7575
7576 let extension = buffer
7577 .read(cx)
7578 .file()
7579 .and_then(|file| Some(file.path().extension()?.to_string()));
7580
7581 let event_type = match accepted {
7582 true => "Edit Prediction Accepted",
7583 false => "Edit Prediction Discarded",
7584 };
7585 telemetry::event!(
7586 event_type,
7587 provider = provider.name(),
7588 prediction_id = id,
7589 suggestion_accepted = accepted,
7590 file_extension = extension,
7591 );
7592 }
7593
7594 fn open_editor_at_anchor(
7595 snapshot: &language::BufferSnapshot,
7596 target: language::Anchor,
7597 workspace: &Entity<Workspace>,
7598 window: &mut Window,
7599 cx: &mut App,
7600 ) -> Task<Result<()>> {
7601 workspace.update(cx, |workspace, cx| {
7602 let path = snapshot.file().map(|file| file.full_path(cx));
7603 let Some(path) =
7604 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7605 else {
7606 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7607 };
7608 let target = text::ToPoint::to_point(&target, snapshot);
7609 let item = workspace.open_path(path, None, true, window, cx);
7610 window.spawn(cx, async move |cx| {
7611 let Some(editor) = item.await?.downcast::<Editor>() else {
7612 return Ok(());
7613 };
7614 editor
7615 .update_in(cx, |editor, window, cx| {
7616 editor.go_to_singleton_buffer_point(target, window, cx);
7617 })
7618 .ok();
7619 anyhow::Ok(())
7620 })
7621 })
7622 }
7623
7624 pub fn has_active_edit_prediction(&self) -> bool {
7625 self.active_edit_prediction.is_some()
7626 }
7627
7628 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7629 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7630 return false;
7631 };
7632
7633 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7634 self.clear_highlights::<EditPredictionHighlight>(cx);
7635 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7636 true
7637 }
7638
7639 /// Returns true when we're displaying the edit prediction popover below the cursor
7640 /// like we are not previewing and the LSP autocomplete menu is visible
7641 /// or we are in `when_holding_modifier` mode.
7642 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7643 if self.edit_prediction_preview_is_active()
7644 || !self.show_edit_predictions_in_menu()
7645 || !self.edit_predictions_enabled()
7646 {
7647 return false;
7648 }
7649
7650 if self.has_visible_completions_menu() {
7651 return true;
7652 }
7653
7654 has_completion && self.edit_prediction_requires_modifier()
7655 }
7656
7657 fn handle_modifiers_changed(
7658 &mut self,
7659 modifiers: Modifiers,
7660 position_map: &PositionMap,
7661 window: &mut Window,
7662 cx: &mut Context<Self>,
7663 ) {
7664 // Ensure that the edit prediction preview is updated, even when not
7665 // enabled, if there's an active edit prediction preview.
7666 if self.show_edit_predictions_in_menu()
7667 || matches!(
7668 self.edit_prediction_preview,
7669 EditPredictionPreview::Active { .. }
7670 )
7671 {
7672 self.update_edit_prediction_preview(&modifiers, window, cx);
7673 }
7674
7675 self.update_selection_mode(&modifiers, position_map, window, cx);
7676
7677 let mouse_position = window.mouse_position();
7678 if !position_map.text_hitbox.is_hovered(window) {
7679 return;
7680 }
7681
7682 self.update_hovered_link(
7683 position_map.point_for_position(mouse_position),
7684 &position_map.snapshot,
7685 modifiers,
7686 window,
7687 cx,
7688 )
7689 }
7690
7691 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7692 match EditorSettings::get_global(cx).multi_cursor_modifier {
7693 MultiCursorModifier::Alt => modifiers.secondary(),
7694 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7695 }
7696 }
7697
7698 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7699 match EditorSettings::get_global(cx).multi_cursor_modifier {
7700 MultiCursorModifier::Alt => modifiers.alt,
7701 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7702 }
7703 }
7704
7705 fn columnar_selection_mode(
7706 modifiers: &Modifiers,
7707 cx: &mut Context<Self>,
7708 ) -> Option<ColumnarMode> {
7709 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7710 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7711 Some(ColumnarMode::FromMouse)
7712 } else if Self::is_alt_pressed(modifiers, cx) {
7713 Some(ColumnarMode::FromSelection)
7714 } else {
7715 None
7716 }
7717 } else {
7718 None
7719 }
7720 }
7721
7722 fn update_selection_mode(
7723 &mut self,
7724 modifiers: &Modifiers,
7725 position_map: &PositionMap,
7726 window: &mut Window,
7727 cx: &mut Context<Self>,
7728 ) {
7729 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7730 return;
7731 };
7732 if self.selections.pending_anchor().is_none() {
7733 return;
7734 }
7735
7736 let mouse_position = window.mouse_position();
7737 let point_for_position = position_map.point_for_position(mouse_position);
7738 let position = point_for_position.previous_valid;
7739
7740 self.select(
7741 SelectPhase::BeginColumnar {
7742 position,
7743 reset: false,
7744 mode,
7745 goal_column: point_for_position.exact_unclipped.column(),
7746 },
7747 window,
7748 cx,
7749 );
7750 }
7751
7752 fn update_edit_prediction_preview(
7753 &mut self,
7754 modifiers: &Modifiers,
7755 window: &mut Window,
7756 cx: &mut Context<Self>,
7757 ) {
7758 let mut modifiers_held = false;
7759 if let Some(accept_keystroke) = self
7760 .accept_edit_prediction_keybind(false, window, cx)
7761 .keystroke()
7762 {
7763 modifiers_held = modifiers_held
7764 || (accept_keystroke.modifiers() == modifiers
7765 && accept_keystroke.modifiers().modified());
7766 };
7767 if let Some(accept_partial_keystroke) = self
7768 .accept_edit_prediction_keybind(true, window, cx)
7769 .keystroke()
7770 {
7771 modifiers_held = modifiers_held
7772 || (accept_partial_keystroke.modifiers() == modifiers
7773 && accept_partial_keystroke.modifiers().modified());
7774 }
7775
7776 if modifiers_held {
7777 if matches!(
7778 self.edit_prediction_preview,
7779 EditPredictionPreview::Inactive { .. }
7780 ) {
7781 self.edit_prediction_preview = EditPredictionPreview::Active {
7782 previous_scroll_position: None,
7783 since: Instant::now(),
7784 };
7785
7786 self.update_visible_edit_prediction(window, cx);
7787 cx.notify();
7788 }
7789 } else if let EditPredictionPreview::Active {
7790 previous_scroll_position,
7791 since,
7792 } = self.edit_prediction_preview
7793 {
7794 if let (Some(previous_scroll_position), Some(position_map)) =
7795 (previous_scroll_position, self.last_position_map.as_ref())
7796 {
7797 self.set_scroll_position(
7798 previous_scroll_position
7799 .scroll_position(&position_map.snapshot.display_snapshot),
7800 window,
7801 cx,
7802 );
7803 }
7804
7805 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7806 released_too_fast: since.elapsed() < Duration::from_millis(200),
7807 };
7808 self.clear_row_highlights::<EditPredictionPreview>();
7809 self.update_visible_edit_prediction(window, cx);
7810 cx.notify();
7811 }
7812 }
7813
7814 fn update_visible_edit_prediction(
7815 &mut self,
7816 _window: &mut Window,
7817 cx: &mut Context<Self>,
7818 ) -> Option<()> {
7819 if DisableAiSettings::get_global(cx).disable_ai {
7820 return None;
7821 }
7822
7823 if self.ime_transaction.is_some() {
7824 self.discard_edit_prediction(false, cx);
7825 return None;
7826 }
7827
7828 let selection = self.selections.newest_anchor();
7829 let cursor = selection.head();
7830 let multibuffer = self.buffer.read(cx).snapshot(cx);
7831 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7832 let excerpt_id = cursor.excerpt_id;
7833
7834 let show_in_menu = self.show_edit_predictions_in_menu();
7835 let completions_menu_has_precedence = !show_in_menu
7836 && (self.context_menu.borrow().is_some()
7837 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7838
7839 if completions_menu_has_precedence
7840 || !offset_selection.is_empty()
7841 || self
7842 .active_edit_prediction
7843 .as_ref()
7844 .is_some_and(|completion| {
7845 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7846 return false;
7847 };
7848 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7849 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7850 !invalidation_range.contains(&offset_selection.head())
7851 })
7852 {
7853 self.discard_edit_prediction(false, cx);
7854 return None;
7855 }
7856
7857 self.take_active_edit_prediction(cx);
7858 let Some(provider) = self.edit_prediction_provider() else {
7859 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7860 return None;
7861 };
7862
7863 let (buffer, cursor_buffer_position) =
7864 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7865
7866 self.edit_prediction_settings =
7867 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7868
7869 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7870
7871 if self.edit_prediction_indent_conflict {
7872 let cursor_point = cursor.to_point(&multibuffer);
7873
7874 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7875
7876 if let Some((_, indent)) = indents.iter().next()
7877 && indent.len == cursor_point.column
7878 {
7879 self.edit_prediction_indent_conflict = false;
7880 }
7881 }
7882
7883 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7884
7885 let (completion_id, edits, edit_preview) = match edit_prediction {
7886 edit_prediction::EditPrediction::Local {
7887 id,
7888 edits,
7889 edit_preview,
7890 } => (id, edits, edit_preview),
7891 edit_prediction::EditPrediction::Jump {
7892 id,
7893 snapshot,
7894 target,
7895 } => {
7896 self.stale_edit_prediction_in_menu = None;
7897 self.active_edit_prediction = Some(EditPredictionState {
7898 inlay_ids: vec![],
7899 completion: EditPrediction::MoveOutside { snapshot, target },
7900 completion_id: id,
7901 invalidation_range: None,
7902 });
7903 cx.notify();
7904 return Some(());
7905 }
7906 };
7907
7908 let edits = edits
7909 .into_iter()
7910 .flat_map(|(range, new_text)| {
7911 Some((
7912 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7913 new_text,
7914 ))
7915 })
7916 .collect::<Vec<_>>();
7917 if edits.is_empty() {
7918 return None;
7919 }
7920
7921 let first_edit_start = edits.first().unwrap().0.start;
7922 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7923 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7924
7925 let last_edit_end = edits.last().unwrap().0.end;
7926 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7927 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7928
7929 let cursor_row = cursor.to_point(&multibuffer).row;
7930
7931 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7932
7933 let mut inlay_ids = Vec::new();
7934 let invalidation_row_range;
7935 let move_invalidation_row_range = if cursor_row < edit_start_row {
7936 Some(cursor_row..edit_end_row)
7937 } else if cursor_row > edit_end_row {
7938 Some(edit_start_row..cursor_row)
7939 } else {
7940 None
7941 };
7942 let supports_jump = self
7943 .edit_prediction_provider
7944 .as_ref()
7945 .map(|provider| provider.provider.supports_jump_to_edit())
7946 .unwrap_or(true);
7947
7948 let is_move = supports_jump
7949 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7950 let completion = if is_move {
7951 invalidation_row_range =
7952 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7953 let target = first_edit_start;
7954 EditPrediction::MoveWithin { target, snapshot }
7955 } else {
7956 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7957 && !self.edit_predictions_hidden_for_vim_mode;
7958
7959 if show_completions_in_buffer {
7960 if edits
7961 .iter()
7962 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7963 {
7964 let mut inlays = Vec::new();
7965 for (range, new_text) in &edits {
7966 let inlay = Inlay::edit_prediction(
7967 post_inc(&mut self.next_inlay_id),
7968 range.start,
7969 new_text.as_ref(),
7970 );
7971 inlay_ids.push(inlay.id);
7972 inlays.push(inlay);
7973 }
7974
7975 self.splice_inlays(&[], inlays, cx);
7976 } else {
7977 let background_color = cx.theme().status().deleted_background;
7978 self.highlight_text::<EditPredictionHighlight>(
7979 edits.iter().map(|(range, _)| range.clone()).collect(),
7980 HighlightStyle {
7981 background_color: Some(background_color),
7982 ..Default::default()
7983 },
7984 cx,
7985 );
7986 }
7987 }
7988
7989 invalidation_row_range = edit_start_row..edit_end_row;
7990
7991 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7992 if provider.show_tab_accept_marker() {
7993 EditDisplayMode::TabAccept
7994 } else {
7995 EditDisplayMode::Inline
7996 }
7997 } else {
7998 EditDisplayMode::DiffPopover
7999 };
8000
8001 EditPrediction::Edit {
8002 edits,
8003 edit_preview,
8004 display_mode,
8005 snapshot,
8006 }
8007 };
8008
8009 let invalidation_range = multibuffer
8010 .anchor_before(Point::new(invalidation_row_range.start, 0))
8011 ..multibuffer.anchor_after(Point::new(
8012 invalidation_row_range.end,
8013 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8014 ));
8015
8016 self.stale_edit_prediction_in_menu = None;
8017 self.active_edit_prediction = Some(EditPredictionState {
8018 inlay_ids,
8019 completion,
8020 completion_id,
8021 invalidation_range: Some(invalidation_range),
8022 });
8023
8024 cx.notify();
8025
8026 Some(())
8027 }
8028
8029 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8030 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8031 }
8032
8033 fn clear_tasks(&mut self) {
8034 self.tasks.clear()
8035 }
8036
8037 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8038 if self.tasks.insert(key, value).is_some() {
8039 // This case should hopefully be rare, but just in case...
8040 log::error!(
8041 "multiple different run targets found on a single line, only the last target will be rendered"
8042 )
8043 }
8044 }
8045
8046 /// Get all display points of breakpoints that will be rendered within editor
8047 ///
8048 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8049 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8050 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8051 fn active_breakpoints(
8052 &self,
8053 range: Range<DisplayRow>,
8054 window: &mut Window,
8055 cx: &mut Context<Self>,
8056 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8057 let mut breakpoint_display_points = HashMap::default();
8058
8059 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8060 return breakpoint_display_points;
8061 };
8062
8063 let snapshot = self.snapshot(window, cx);
8064
8065 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8066 let Some(project) = self.project() else {
8067 return breakpoint_display_points;
8068 };
8069
8070 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8071 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8072
8073 for (buffer_snapshot, range, excerpt_id) in
8074 multi_buffer_snapshot.range_to_buffer_ranges(range)
8075 {
8076 let Some(buffer) = project
8077 .read(cx)
8078 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8079 else {
8080 continue;
8081 };
8082 let breakpoints = breakpoint_store.read(cx).breakpoints(
8083 &buffer,
8084 Some(
8085 buffer_snapshot.anchor_before(range.start)
8086 ..buffer_snapshot.anchor_after(range.end),
8087 ),
8088 buffer_snapshot,
8089 cx,
8090 );
8091 for (breakpoint, state) in breakpoints {
8092 let multi_buffer_anchor =
8093 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8094 let position = multi_buffer_anchor
8095 .to_point(&multi_buffer_snapshot)
8096 .to_display_point(&snapshot);
8097
8098 breakpoint_display_points.insert(
8099 position.row(),
8100 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8101 );
8102 }
8103 }
8104
8105 breakpoint_display_points
8106 }
8107
8108 fn breakpoint_context_menu(
8109 &self,
8110 anchor: Anchor,
8111 window: &mut Window,
8112 cx: &mut Context<Self>,
8113 ) -> Entity<ui::ContextMenu> {
8114 let weak_editor = cx.weak_entity();
8115 let focus_handle = self.focus_handle(cx);
8116
8117 let row = self
8118 .buffer
8119 .read(cx)
8120 .snapshot(cx)
8121 .summary_for_anchor::<Point>(&anchor)
8122 .row;
8123
8124 let breakpoint = self
8125 .breakpoint_at_row(row, window, cx)
8126 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8127
8128 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8129 "Edit Log Breakpoint"
8130 } else {
8131 "Set Log Breakpoint"
8132 };
8133
8134 let condition_breakpoint_msg = if breakpoint
8135 .as_ref()
8136 .is_some_and(|bp| bp.1.condition.is_some())
8137 {
8138 "Edit Condition Breakpoint"
8139 } else {
8140 "Set Condition Breakpoint"
8141 };
8142
8143 let hit_condition_breakpoint_msg = if breakpoint
8144 .as_ref()
8145 .is_some_and(|bp| bp.1.hit_condition.is_some())
8146 {
8147 "Edit Hit Condition Breakpoint"
8148 } else {
8149 "Set Hit Condition Breakpoint"
8150 };
8151
8152 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8153 "Unset Breakpoint"
8154 } else {
8155 "Set Breakpoint"
8156 };
8157
8158 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8159
8160 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8161 BreakpointState::Enabled => Some("Disable"),
8162 BreakpointState::Disabled => Some("Enable"),
8163 });
8164
8165 let (anchor, breakpoint) =
8166 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8167
8168 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8169 menu.on_blur_subscription(Subscription::new(|| {}))
8170 .context(focus_handle)
8171 .when(run_to_cursor, |this| {
8172 let weak_editor = weak_editor.clone();
8173 this.entry("Run to cursor", None, move |window, cx| {
8174 weak_editor
8175 .update(cx, |editor, cx| {
8176 editor.change_selections(
8177 SelectionEffects::no_scroll(),
8178 window,
8179 cx,
8180 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8181 );
8182 })
8183 .ok();
8184
8185 window.dispatch_action(Box::new(RunToCursor), cx);
8186 })
8187 .separator()
8188 })
8189 .when_some(toggle_state_msg, |this, msg| {
8190 this.entry(msg, None, {
8191 let weak_editor = weak_editor.clone();
8192 let breakpoint = breakpoint.clone();
8193 move |_window, cx| {
8194 weak_editor
8195 .update(cx, |this, cx| {
8196 this.edit_breakpoint_at_anchor(
8197 anchor,
8198 breakpoint.as_ref().clone(),
8199 BreakpointEditAction::InvertState,
8200 cx,
8201 );
8202 })
8203 .log_err();
8204 }
8205 })
8206 })
8207 .entry(set_breakpoint_msg, None, {
8208 let weak_editor = weak_editor.clone();
8209 let breakpoint = breakpoint.clone();
8210 move |_window, cx| {
8211 weak_editor
8212 .update(cx, |this, cx| {
8213 this.edit_breakpoint_at_anchor(
8214 anchor,
8215 breakpoint.as_ref().clone(),
8216 BreakpointEditAction::Toggle,
8217 cx,
8218 );
8219 })
8220 .log_err();
8221 }
8222 })
8223 .entry(log_breakpoint_msg, None, {
8224 let breakpoint = breakpoint.clone();
8225 let weak_editor = weak_editor.clone();
8226 move |window, cx| {
8227 weak_editor
8228 .update(cx, |this, cx| {
8229 this.add_edit_breakpoint_block(
8230 anchor,
8231 breakpoint.as_ref(),
8232 BreakpointPromptEditAction::Log,
8233 window,
8234 cx,
8235 );
8236 })
8237 .log_err();
8238 }
8239 })
8240 .entry(condition_breakpoint_msg, None, {
8241 let breakpoint = breakpoint.clone();
8242 let weak_editor = weak_editor.clone();
8243 move |window, cx| {
8244 weak_editor
8245 .update(cx, |this, cx| {
8246 this.add_edit_breakpoint_block(
8247 anchor,
8248 breakpoint.as_ref(),
8249 BreakpointPromptEditAction::Condition,
8250 window,
8251 cx,
8252 );
8253 })
8254 .log_err();
8255 }
8256 })
8257 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8258 weak_editor
8259 .update(cx, |this, cx| {
8260 this.add_edit_breakpoint_block(
8261 anchor,
8262 breakpoint.as_ref(),
8263 BreakpointPromptEditAction::HitCondition,
8264 window,
8265 cx,
8266 );
8267 })
8268 .log_err();
8269 })
8270 })
8271 }
8272
8273 fn render_breakpoint(
8274 &self,
8275 position: Anchor,
8276 row: DisplayRow,
8277 breakpoint: &Breakpoint,
8278 state: Option<BreakpointSessionState>,
8279 cx: &mut Context<Self>,
8280 ) -> IconButton {
8281 let is_rejected = state.is_some_and(|s| !s.verified);
8282 // Is it a breakpoint that shows up when hovering over gutter?
8283 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8284 (false, false),
8285 |PhantomBreakpointIndicator {
8286 is_active,
8287 display_row,
8288 collides_with_existing_breakpoint,
8289 }| {
8290 (
8291 is_active && display_row == row,
8292 collides_with_existing_breakpoint,
8293 )
8294 },
8295 );
8296
8297 let (color, icon) = {
8298 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8299 (false, false) => ui::IconName::DebugBreakpoint,
8300 (true, false) => ui::IconName::DebugLogBreakpoint,
8301 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8302 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8303 };
8304
8305 let color = if is_phantom {
8306 Color::Hint
8307 } else if is_rejected {
8308 Color::Disabled
8309 } else {
8310 Color::Debugger
8311 };
8312
8313 (color, icon)
8314 };
8315
8316 let breakpoint = Arc::from(breakpoint.clone());
8317
8318 let alt_as_text = gpui::Keystroke {
8319 modifiers: Modifiers::secondary_key(),
8320 ..Default::default()
8321 };
8322 let primary_action_text = if breakpoint.is_disabled() {
8323 "Enable breakpoint"
8324 } else if is_phantom && !collides_with_existing {
8325 "Set breakpoint"
8326 } else {
8327 "Unset breakpoint"
8328 };
8329 let focus_handle = self.focus_handle.clone();
8330
8331 let meta = if is_rejected {
8332 SharedString::from("No executable code is associated with this line.")
8333 } else if collides_with_existing && !breakpoint.is_disabled() {
8334 SharedString::from(format!(
8335 "{alt_as_text}-click to disable,\nright-click for more options."
8336 ))
8337 } else {
8338 SharedString::from("Right-click for more options.")
8339 };
8340 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8341 .icon_size(IconSize::XSmall)
8342 .size(ui::ButtonSize::None)
8343 .when(is_rejected, |this| {
8344 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8345 })
8346 .icon_color(color)
8347 .style(ButtonStyle::Transparent)
8348 .on_click(cx.listener({
8349 move |editor, event: &ClickEvent, window, cx| {
8350 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8351 BreakpointEditAction::InvertState
8352 } else {
8353 BreakpointEditAction::Toggle
8354 };
8355
8356 window.focus(&editor.focus_handle(cx));
8357 editor.edit_breakpoint_at_anchor(
8358 position,
8359 breakpoint.as_ref().clone(),
8360 edit_action,
8361 cx,
8362 );
8363 }
8364 }))
8365 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8366 editor.set_breakpoint_context_menu(
8367 row,
8368 Some(position),
8369 event.position(),
8370 window,
8371 cx,
8372 );
8373 }))
8374 .tooltip(move |_window, cx| {
8375 Tooltip::with_meta_in(
8376 primary_action_text,
8377 Some(&ToggleBreakpoint),
8378 meta.clone(),
8379 &focus_handle,
8380 cx,
8381 )
8382 })
8383 }
8384
8385 fn build_tasks_context(
8386 project: &Entity<Project>,
8387 buffer: &Entity<Buffer>,
8388 buffer_row: u32,
8389 tasks: &Arc<RunnableTasks>,
8390 cx: &mut Context<Self>,
8391 ) -> Task<Option<task::TaskContext>> {
8392 let position = Point::new(buffer_row, tasks.column);
8393 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8394 let location = Location {
8395 buffer: buffer.clone(),
8396 range: range_start..range_start,
8397 };
8398 // Fill in the environmental variables from the tree-sitter captures
8399 let mut captured_task_variables = TaskVariables::default();
8400 for (capture_name, value) in tasks.extra_variables.clone() {
8401 captured_task_variables.insert(
8402 task::VariableName::Custom(capture_name.into()),
8403 value.clone(),
8404 );
8405 }
8406 project.update(cx, |project, cx| {
8407 project.task_store().update(cx, |task_store, cx| {
8408 task_store.task_context_for_location(captured_task_variables, location, cx)
8409 })
8410 })
8411 }
8412
8413 pub fn spawn_nearest_task(
8414 &mut self,
8415 action: &SpawnNearestTask,
8416 window: &mut Window,
8417 cx: &mut Context<Self>,
8418 ) {
8419 let Some((workspace, _)) = self.workspace.clone() else {
8420 return;
8421 };
8422 let Some(project) = self.project.clone() else {
8423 return;
8424 };
8425
8426 // Try to find a closest, enclosing node using tree-sitter that has a task
8427 let Some((buffer, buffer_row, tasks)) = self
8428 .find_enclosing_node_task(cx)
8429 // Or find the task that's closest in row-distance.
8430 .or_else(|| self.find_closest_task(cx))
8431 else {
8432 return;
8433 };
8434
8435 let reveal_strategy = action.reveal;
8436 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8437 cx.spawn_in(window, async move |_, cx| {
8438 let context = task_context.await?;
8439 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8440
8441 let resolved = &mut resolved_task.resolved;
8442 resolved.reveal = reveal_strategy;
8443
8444 workspace
8445 .update_in(cx, |workspace, window, cx| {
8446 workspace.schedule_resolved_task(
8447 task_source_kind,
8448 resolved_task,
8449 false,
8450 window,
8451 cx,
8452 );
8453 })
8454 .ok()
8455 })
8456 .detach();
8457 }
8458
8459 fn find_closest_task(
8460 &mut self,
8461 cx: &mut Context<Self>,
8462 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8463 let cursor_row = self
8464 .selections
8465 .newest_adjusted(&self.display_snapshot(cx))
8466 .head()
8467 .row;
8468
8469 let ((buffer_id, row), tasks) = self
8470 .tasks
8471 .iter()
8472 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8473
8474 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8475 let tasks = Arc::new(tasks.to_owned());
8476 Some((buffer, *row, tasks))
8477 }
8478
8479 fn find_enclosing_node_task(
8480 &mut self,
8481 cx: &mut Context<Self>,
8482 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8483 let snapshot = self.buffer.read(cx).snapshot(cx);
8484 let offset = self
8485 .selections
8486 .newest::<usize>(&self.display_snapshot(cx))
8487 .head();
8488 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8489 let buffer_id = excerpt.buffer().remote_id();
8490
8491 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8492 let mut cursor = layer.node().walk();
8493
8494 while cursor.goto_first_child_for_byte(offset).is_some() {
8495 if cursor.node().end_byte() == offset {
8496 cursor.goto_next_sibling();
8497 }
8498 }
8499
8500 // Ascend to the smallest ancestor that contains the range and has a task.
8501 loop {
8502 let node = cursor.node();
8503 let node_range = node.byte_range();
8504 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8505
8506 // Check if this node contains our offset
8507 if node_range.start <= offset && node_range.end >= offset {
8508 // If it contains offset, check for task
8509 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8510 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8511 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8512 }
8513 }
8514
8515 if !cursor.goto_parent() {
8516 break;
8517 }
8518 }
8519 None
8520 }
8521
8522 fn render_run_indicator(
8523 &self,
8524 _style: &EditorStyle,
8525 is_active: bool,
8526 row: DisplayRow,
8527 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8528 cx: &mut Context<Self>,
8529 ) -> IconButton {
8530 let color = Color::Muted;
8531 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8532
8533 IconButton::new(
8534 ("run_indicator", row.0 as usize),
8535 ui::IconName::PlayOutlined,
8536 )
8537 .shape(ui::IconButtonShape::Square)
8538 .icon_size(IconSize::XSmall)
8539 .icon_color(color)
8540 .toggle_state(is_active)
8541 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8542 let quick_launch = match e {
8543 ClickEvent::Keyboard(_) => true,
8544 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8545 };
8546
8547 window.focus(&editor.focus_handle(cx));
8548 editor.toggle_code_actions(
8549 &ToggleCodeActions {
8550 deployed_from: Some(CodeActionSource::RunMenu(row)),
8551 quick_launch,
8552 },
8553 window,
8554 cx,
8555 );
8556 }))
8557 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8558 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8559 }))
8560 }
8561
8562 pub fn context_menu_visible(&self) -> bool {
8563 !self.edit_prediction_preview_is_active()
8564 && self
8565 .context_menu
8566 .borrow()
8567 .as_ref()
8568 .is_some_and(|menu| menu.visible())
8569 }
8570
8571 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8572 self.context_menu
8573 .borrow()
8574 .as_ref()
8575 .map(|menu| menu.origin())
8576 }
8577
8578 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8579 self.context_menu_options = Some(options);
8580 }
8581
8582 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8583 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8584
8585 fn render_edit_prediction_popover(
8586 &mut self,
8587 text_bounds: &Bounds<Pixels>,
8588 content_origin: gpui::Point<Pixels>,
8589 right_margin: Pixels,
8590 editor_snapshot: &EditorSnapshot,
8591 visible_row_range: Range<DisplayRow>,
8592 scroll_top: ScrollOffset,
8593 scroll_bottom: ScrollOffset,
8594 line_layouts: &[LineWithInvisibles],
8595 line_height: Pixels,
8596 scroll_position: gpui::Point<ScrollOffset>,
8597 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8598 newest_selection_head: Option<DisplayPoint>,
8599 editor_width: Pixels,
8600 style: &EditorStyle,
8601 window: &mut Window,
8602 cx: &mut App,
8603 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8604 if self.mode().is_minimap() {
8605 return None;
8606 }
8607 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8608
8609 if self.edit_prediction_visible_in_cursor_popover(true) {
8610 return None;
8611 }
8612
8613 match &active_edit_prediction.completion {
8614 EditPrediction::MoveWithin { target, .. } => {
8615 let target_display_point = target.to_display_point(editor_snapshot);
8616
8617 if self.edit_prediction_requires_modifier() {
8618 if !self.edit_prediction_preview_is_active() {
8619 return None;
8620 }
8621
8622 self.render_edit_prediction_modifier_jump_popover(
8623 text_bounds,
8624 content_origin,
8625 visible_row_range,
8626 line_layouts,
8627 line_height,
8628 scroll_pixel_position,
8629 newest_selection_head,
8630 target_display_point,
8631 window,
8632 cx,
8633 )
8634 } else {
8635 self.render_edit_prediction_eager_jump_popover(
8636 text_bounds,
8637 content_origin,
8638 editor_snapshot,
8639 visible_row_range,
8640 scroll_top,
8641 scroll_bottom,
8642 line_height,
8643 scroll_pixel_position,
8644 target_display_point,
8645 editor_width,
8646 window,
8647 cx,
8648 )
8649 }
8650 }
8651 EditPrediction::Edit {
8652 display_mode: EditDisplayMode::Inline,
8653 ..
8654 } => None,
8655 EditPrediction::Edit {
8656 display_mode: EditDisplayMode::TabAccept,
8657 edits,
8658 ..
8659 } => {
8660 let range = &edits.first()?.0;
8661 let target_display_point = range.end.to_display_point(editor_snapshot);
8662
8663 self.render_edit_prediction_end_of_line_popover(
8664 "Accept",
8665 editor_snapshot,
8666 visible_row_range,
8667 target_display_point,
8668 line_height,
8669 scroll_pixel_position,
8670 content_origin,
8671 editor_width,
8672 window,
8673 cx,
8674 )
8675 }
8676 EditPrediction::Edit {
8677 edits,
8678 edit_preview,
8679 display_mode: EditDisplayMode::DiffPopover,
8680 snapshot,
8681 } => self.render_edit_prediction_diff_popover(
8682 text_bounds,
8683 content_origin,
8684 right_margin,
8685 editor_snapshot,
8686 visible_row_range,
8687 line_layouts,
8688 line_height,
8689 scroll_position,
8690 scroll_pixel_position,
8691 newest_selection_head,
8692 editor_width,
8693 style,
8694 edits,
8695 edit_preview,
8696 snapshot,
8697 window,
8698 cx,
8699 ),
8700 EditPrediction::MoveOutside { snapshot, .. } => {
8701 let file_name = snapshot
8702 .file()
8703 .map(|file| file.file_name(cx))
8704 .unwrap_or("untitled");
8705 let mut element = self
8706 .render_edit_prediction_line_popover(
8707 format!("Jump to {file_name}"),
8708 Some(IconName::ZedPredict),
8709 window,
8710 cx,
8711 )
8712 .into_any();
8713
8714 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8715 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8716 let origin_y = text_bounds.size.height - size.height - px(30.);
8717 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8718 element.prepaint_at(origin, window, cx);
8719
8720 Some((element, origin))
8721 }
8722 }
8723 }
8724
8725 fn render_edit_prediction_modifier_jump_popover(
8726 &mut self,
8727 text_bounds: &Bounds<Pixels>,
8728 content_origin: gpui::Point<Pixels>,
8729 visible_row_range: Range<DisplayRow>,
8730 line_layouts: &[LineWithInvisibles],
8731 line_height: Pixels,
8732 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8733 newest_selection_head: Option<DisplayPoint>,
8734 target_display_point: DisplayPoint,
8735 window: &mut Window,
8736 cx: &mut App,
8737 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8738 let scrolled_content_origin =
8739 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8740
8741 const SCROLL_PADDING_Y: Pixels = px(12.);
8742
8743 if target_display_point.row() < visible_row_range.start {
8744 return self.render_edit_prediction_scroll_popover(
8745 |_| SCROLL_PADDING_Y,
8746 IconName::ArrowUp,
8747 visible_row_range,
8748 line_layouts,
8749 newest_selection_head,
8750 scrolled_content_origin,
8751 window,
8752 cx,
8753 );
8754 } else if target_display_point.row() >= visible_row_range.end {
8755 return self.render_edit_prediction_scroll_popover(
8756 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8757 IconName::ArrowDown,
8758 visible_row_range,
8759 line_layouts,
8760 newest_selection_head,
8761 scrolled_content_origin,
8762 window,
8763 cx,
8764 );
8765 }
8766
8767 const POLE_WIDTH: Pixels = px(2.);
8768
8769 let line_layout =
8770 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8771 let target_column = target_display_point.column() as usize;
8772
8773 let target_x = line_layout.x_for_index(target_column);
8774 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8775 - scroll_pixel_position.y;
8776
8777 let flag_on_right = target_x < text_bounds.size.width / 2.;
8778
8779 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8780 border_color.l += 0.001;
8781
8782 let mut element = v_flex()
8783 .items_end()
8784 .when(flag_on_right, |el| el.items_start())
8785 .child(if flag_on_right {
8786 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8787 .rounded_bl(px(0.))
8788 .rounded_tl(px(0.))
8789 .border_l_2()
8790 .border_color(border_color)
8791 } else {
8792 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8793 .rounded_br(px(0.))
8794 .rounded_tr(px(0.))
8795 .border_r_2()
8796 .border_color(border_color)
8797 })
8798 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8799 .into_any();
8800
8801 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8802
8803 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8804 - point(
8805 if flag_on_right {
8806 POLE_WIDTH
8807 } else {
8808 size.width - POLE_WIDTH
8809 },
8810 size.height - line_height,
8811 );
8812
8813 origin.x = origin.x.max(content_origin.x);
8814
8815 element.prepaint_at(origin, window, cx);
8816
8817 Some((element, origin))
8818 }
8819
8820 fn render_edit_prediction_scroll_popover(
8821 &mut self,
8822 to_y: impl Fn(Size<Pixels>) -> Pixels,
8823 scroll_icon: IconName,
8824 visible_row_range: Range<DisplayRow>,
8825 line_layouts: &[LineWithInvisibles],
8826 newest_selection_head: Option<DisplayPoint>,
8827 scrolled_content_origin: gpui::Point<Pixels>,
8828 window: &mut Window,
8829 cx: &mut App,
8830 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8831 let mut element = self
8832 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8833 .into_any();
8834
8835 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8836
8837 let cursor = newest_selection_head?;
8838 let cursor_row_layout =
8839 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8840 let cursor_column = cursor.column() as usize;
8841
8842 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8843
8844 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8845
8846 element.prepaint_at(origin, window, cx);
8847 Some((element, origin))
8848 }
8849
8850 fn render_edit_prediction_eager_jump_popover(
8851 &mut self,
8852 text_bounds: &Bounds<Pixels>,
8853 content_origin: gpui::Point<Pixels>,
8854 editor_snapshot: &EditorSnapshot,
8855 visible_row_range: Range<DisplayRow>,
8856 scroll_top: ScrollOffset,
8857 scroll_bottom: ScrollOffset,
8858 line_height: Pixels,
8859 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8860 target_display_point: DisplayPoint,
8861 editor_width: Pixels,
8862 window: &mut Window,
8863 cx: &mut App,
8864 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8865 if target_display_point.row().as_f64() < scroll_top {
8866 let mut element = self
8867 .render_edit_prediction_line_popover(
8868 "Jump to Edit",
8869 Some(IconName::ArrowUp),
8870 window,
8871 cx,
8872 )
8873 .into_any();
8874
8875 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8876 let offset = point(
8877 (text_bounds.size.width - size.width) / 2.,
8878 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8879 );
8880
8881 let origin = text_bounds.origin + offset;
8882 element.prepaint_at(origin, window, cx);
8883 Some((element, origin))
8884 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8885 let mut element = self
8886 .render_edit_prediction_line_popover(
8887 "Jump to Edit",
8888 Some(IconName::ArrowDown),
8889 window,
8890 cx,
8891 )
8892 .into_any();
8893
8894 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8895 let offset = point(
8896 (text_bounds.size.width - size.width) / 2.,
8897 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8898 );
8899
8900 let origin = text_bounds.origin + offset;
8901 element.prepaint_at(origin, window, cx);
8902 Some((element, origin))
8903 } else {
8904 self.render_edit_prediction_end_of_line_popover(
8905 "Jump to Edit",
8906 editor_snapshot,
8907 visible_row_range,
8908 target_display_point,
8909 line_height,
8910 scroll_pixel_position,
8911 content_origin,
8912 editor_width,
8913 window,
8914 cx,
8915 )
8916 }
8917 }
8918
8919 fn render_edit_prediction_end_of_line_popover(
8920 self: &mut Editor,
8921 label: &'static str,
8922 editor_snapshot: &EditorSnapshot,
8923 visible_row_range: Range<DisplayRow>,
8924 target_display_point: DisplayPoint,
8925 line_height: Pixels,
8926 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8927 content_origin: gpui::Point<Pixels>,
8928 editor_width: Pixels,
8929 window: &mut Window,
8930 cx: &mut App,
8931 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8932 let target_line_end = DisplayPoint::new(
8933 target_display_point.row(),
8934 editor_snapshot.line_len(target_display_point.row()),
8935 );
8936
8937 let mut element = self
8938 .render_edit_prediction_line_popover(label, None, window, cx)
8939 .into_any();
8940
8941 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8942
8943 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8944
8945 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8946 let mut origin = start_point
8947 + line_origin
8948 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8949 origin.x = origin.x.max(content_origin.x);
8950
8951 let max_x = content_origin.x + editor_width - size.width;
8952
8953 if origin.x > max_x {
8954 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8955
8956 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8957 origin.y += offset;
8958 IconName::ArrowUp
8959 } else {
8960 origin.y -= offset;
8961 IconName::ArrowDown
8962 };
8963
8964 element = self
8965 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8966 .into_any();
8967
8968 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8969
8970 origin.x = content_origin.x + editor_width - size.width - px(2.);
8971 }
8972
8973 element.prepaint_at(origin, window, cx);
8974 Some((element, origin))
8975 }
8976
8977 fn render_edit_prediction_diff_popover(
8978 self: &Editor,
8979 text_bounds: &Bounds<Pixels>,
8980 content_origin: gpui::Point<Pixels>,
8981 right_margin: Pixels,
8982 editor_snapshot: &EditorSnapshot,
8983 visible_row_range: Range<DisplayRow>,
8984 line_layouts: &[LineWithInvisibles],
8985 line_height: Pixels,
8986 scroll_position: gpui::Point<ScrollOffset>,
8987 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8988 newest_selection_head: Option<DisplayPoint>,
8989 editor_width: Pixels,
8990 style: &EditorStyle,
8991 edits: &Vec<(Range<Anchor>, Arc<str>)>,
8992 edit_preview: &Option<language::EditPreview>,
8993 snapshot: &language::BufferSnapshot,
8994 window: &mut Window,
8995 cx: &mut App,
8996 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8997 let edit_start = edits
8998 .first()
8999 .unwrap()
9000 .0
9001 .start
9002 .to_display_point(editor_snapshot);
9003 let edit_end = edits
9004 .last()
9005 .unwrap()
9006 .0
9007 .end
9008 .to_display_point(editor_snapshot);
9009
9010 let is_visible = visible_row_range.contains(&edit_start.row())
9011 || visible_row_range.contains(&edit_end.row());
9012 if !is_visible {
9013 return None;
9014 }
9015
9016 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9017 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9018 } else {
9019 // Fallback for providers without edit_preview
9020 crate::edit_prediction_fallback_text(edits, cx)
9021 };
9022
9023 let styled_text = highlighted_edits.to_styled_text(&style.text);
9024 let line_count = highlighted_edits.text.lines().count();
9025
9026 const BORDER_WIDTH: Pixels = px(1.);
9027
9028 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9029 let has_keybind = keybind.is_some();
9030
9031 let mut element = h_flex()
9032 .items_start()
9033 .child(
9034 h_flex()
9035 .bg(cx.theme().colors().editor_background)
9036 .border(BORDER_WIDTH)
9037 .shadow_xs()
9038 .border_color(cx.theme().colors().border)
9039 .rounded_l_lg()
9040 .when(line_count > 1, |el| el.rounded_br_lg())
9041 .pr_1()
9042 .child(styled_text),
9043 )
9044 .child(
9045 h_flex()
9046 .h(line_height + BORDER_WIDTH * 2.)
9047 .px_1p5()
9048 .gap_1()
9049 // Workaround: For some reason, there's a gap if we don't do this
9050 .ml(-BORDER_WIDTH)
9051 .shadow(vec![gpui::BoxShadow {
9052 color: gpui::black().opacity(0.05),
9053 offset: point(px(1.), px(1.)),
9054 blur_radius: px(2.),
9055 spread_radius: px(0.),
9056 }])
9057 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9058 .border(BORDER_WIDTH)
9059 .border_color(cx.theme().colors().border)
9060 .rounded_r_lg()
9061 .id("edit_prediction_diff_popover_keybind")
9062 .when(!has_keybind, |el| {
9063 let status_colors = cx.theme().status();
9064
9065 el.bg(status_colors.error_background)
9066 .border_color(status_colors.error.opacity(0.6))
9067 .child(Icon::new(IconName::Info).color(Color::Error))
9068 .cursor_default()
9069 .hoverable_tooltip(move |_window, cx| {
9070 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9071 })
9072 })
9073 .children(keybind),
9074 )
9075 .into_any();
9076
9077 let longest_row =
9078 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9079 let longest_line_width = if visible_row_range.contains(&longest_row) {
9080 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9081 } else {
9082 layout_line(
9083 longest_row,
9084 editor_snapshot,
9085 style,
9086 editor_width,
9087 |_| false,
9088 window,
9089 cx,
9090 )
9091 .width
9092 };
9093
9094 let viewport_bounds =
9095 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9096 right: -right_margin,
9097 ..Default::default()
9098 });
9099
9100 let x_after_longest = Pixels::from(
9101 ScrollPixelOffset::from(
9102 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9103 ) - scroll_pixel_position.x,
9104 );
9105
9106 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9107
9108 // Fully visible if it can be displayed within the window (allow overlapping other
9109 // panes). However, this is only allowed if the popover starts within text_bounds.
9110 let can_position_to_the_right = x_after_longest < text_bounds.right()
9111 && x_after_longest + element_bounds.width < viewport_bounds.right();
9112
9113 let mut origin = if can_position_to_the_right {
9114 point(
9115 x_after_longest,
9116 text_bounds.origin.y
9117 + Pixels::from(
9118 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9119 - scroll_pixel_position.y,
9120 ),
9121 )
9122 } else {
9123 let cursor_row = newest_selection_head.map(|head| head.row());
9124 let above_edit = edit_start
9125 .row()
9126 .0
9127 .checked_sub(line_count as u32)
9128 .map(DisplayRow);
9129 let below_edit = Some(edit_end.row() + 1);
9130 let above_cursor =
9131 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9132 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9133
9134 // Place the edit popover adjacent to the edit if there is a location
9135 // available that is onscreen and does not obscure the cursor. Otherwise,
9136 // place it adjacent to the cursor.
9137 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9138 .into_iter()
9139 .flatten()
9140 .find(|&start_row| {
9141 let end_row = start_row + line_count as u32;
9142 visible_row_range.contains(&start_row)
9143 && visible_row_range.contains(&end_row)
9144 && cursor_row
9145 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9146 })?;
9147
9148 content_origin
9149 + point(
9150 Pixels::from(-scroll_pixel_position.x),
9151 Pixels::from(
9152 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9153 ),
9154 )
9155 };
9156
9157 origin.x -= BORDER_WIDTH;
9158
9159 window.defer_draw(element, origin, 1);
9160
9161 // Do not return an element, since it will already be drawn due to defer_draw.
9162 None
9163 }
9164
9165 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9166 px(30.)
9167 }
9168
9169 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9170 if self.read_only(cx) {
9171 cx.theme().players().read_only()
9172 } else {
9173 self.style.as_ref().unwrap().local_player
9174 }
9175 }
9176
9177 fn render_edit_prediction_accept_keybind(
9178 &self,
9179 window: &mut Window,
9180 cx: &mut App,
9181 ) -> Option<AnyElement> {
9182 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9183 let accept_keystroke = accept_binding.keystroke()?;
9184
9185 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9186
9187 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9188 Color::Accent
9189 } else {
9190 Color::Muted
9191 };
9192
9193 h_flex()
9194 .px_0p5()
9195 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9196 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9197 .text_size(TextSize::XSmall.rems(cx))
9198 .child(h_flex().children(ui::render_modifiers(
9199 accept_keystroke.modifiers(),
9200 PlatformStyle::platform(),
9201 Some(modifiers_color),
9202 Some(IconSize::XSmall.rems().into()),
9203 true,
9204 )))
9205 .when(is_platform_style_mac, |parent| {
9206 parent.child(accept_keystroke.key().to_string())
9207 })
9208 .when(!is_platform_style_mac, |parent| {
9209 parent.child(
9210 Key::new(
9211 util::capitalize(accept_keystroke.key()),
9212 Some(Color::Default),
9213 )
9214 .size(Some(IconSize::XSmall.rems().into())),
9215 )
9216 })
9217 .into_any()
9218 .into()
9219 }
9220
9221 fn render_edit_prediction_line_popover(
9222 &self,
9223 label: impl Into<SharedString>,
9224 icon: Option<IconName>,
9225 window: &mut Window,
9226 cx: &mut App,
9227 ) -> Stateful<Div> {
9228 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9229
9230 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9231 let has_keybind = keybind.is_some();
9232
9233 h_flex()
9234 .id("ep-line-popover")
9235 .py_0p5()
9236 .pl_1()
9237 .pr(padding_right)
9238 .gap_1()
9239 .rounded_md()
9240 .border_1()
9241 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9242 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9243 .shadow_xs()
9244 .when(!has_keybind, |el| {
9245 let status_colors = cx.theme().status();
9246
9247 el.bg(status_colors.error_background)
9248 .border_color(status_colors.error.opacity(0.6))
9249 .pl_2()
9250 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9251 .cursor_default()
9252 .hoverable_tooltip(move |_window, cx| {
9253 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9254 })
9255 })
9256 .children(keybind)
9257 .child(
9258 Label::new(label)
9259 .size(LabelSize::Small)
9260 .when(!has_keybind, |el| {
9261 el.color(cx.theme().status().error.into()).strikethrough()
9262 }),
9263 )
9264 .when(!has_keybind, |el| {
9265 el.child(
9266 h_flex().ml_1().child(
9267 Icon::new(IconName::Info)
9268 .size(IconSize::Small)
9269 .color(cx.theme().status().error.into()),
9270 ),
9271 )
9272 })
9273 .when_some(icon, |element, icon| {
9274 element.child(
9275 div()
9276 .mt(px(1.5))
9277 .child(Icon::new(icon).size(IconSize::Small)),
9278 )
9279 })
9280 }
9281
9282 fn edit_prediction_line_popover_bg_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.1))
9286 }
9287
9288 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9289 let accent_color = cx.theme().colors().text_accent;
9290 let editor_bg_color = cx.theme().colors().editor_background;
9291 editor_bg_color.blend(accent_color.opacity(0.6))
9292 }
9293 fn get_prediction_provider_icon_name(
9294 provider: &Option<RegisteredEditPredictionProvider>,
9295 ) -> IconName {
9296 match provider {
9297 Some(provider) => match provider.provider.name() {
9298 "copilot" => IconName::Copilot,
9299 "supermaven" => IconName::Supermaven,
9300 _ => IconName::ZedPredict,
9301 },
9302 None => IconName::ZedPredict,
9303 }
9304 }
9305
9306 fn render_edit_prediction_cursor_popover(
9307 &self,
9308 min_width: Pixels,
9309 max_width: Pixels,
9310 cursor_point: Point,
9311 style: &EditorStyle,
9312 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9313 _window: &Window,
9314 cx: &mut Context<Editor>,
9315 ) -> Option<AnyElement> {
9316 let provider = self.edit_prediction_provider.as_ref()?;
9317 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9318
9319 let is_refreshing = provider.provider.is_refreshing(cx);
9320
9321 fn pending_completion_container(icon: IconName) -> Div {
9322 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9323 }
9324
9325 let completion = match &self.active_edit_prediction {
9326 Some(prediction) => {
9327 if !self.has_visible_completions_menu() {
9328 const RADIUS: Pixels = px(6.);
9329 const BORDER_WIDTH: Pixels = px(1.);
9330
9331 return Some(
9332 h_flex()
9333 .elevation_2(cx)
9334 .border(BORDER_WIDTH)
9335 .border_color(cx.theme().colors().border)
9336 .when(accept_keystroke.is_none(), |el| {
9337 el.border_color(cx.theme().status().error)
9338 })
9339 .rounded(RADIUS)
9340 .rounded_tl(px(0.))
9341 .overflow_hidden()
9342 .child(div().px_1p5().child(match &prediction.completion {
9343 EditPrediction::MoveWithin { target, snapshot } => {
9344 use text::ToPoint as _;
9345 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9346 {
9347 Icon::new(IconName::ZedPredictDown)
9348 } else {
9349 Icon::new(IconName::ZedPredictUp)
9350 }
9351 }
9352 EditPrediction::MoveOutside { .. } => {
9353 // TODO [zeta2] custom icon for external jump?
9354 Icon::new(provider_icon)
9355 }
9356 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9357 }))
9358 .child(
9359 h_flex()
9360 .gap_1()
9361 .py_1()
9362 .px_2()
9363 .rounded_r(RADIUS - BORDER_WIDTH)
9364 .border_l_1()
9365 .border_color(cx.theme().colors().border)
9366 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9367 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9368 el.child(
9369 Label::new("Hold")
9370 .size(LabelSize::Small)
9371 .when(accept_keystroke.is_none(), |el| {
9372 el.strikethrough()
9373 })
9374 .line_height_style(LineHeightStyle::UiLabel),
9375 )
9376 })
9377 .id("edit_prediction_cursor_popover_keybind")
9378 .when(accept_keystroke.is_none(), |el| {
9379 let status_colors = cx.theme().status();
9380
9381 el.bg(status_colors.error_background)
9382 .border_color(status_colors.error.opacity(0.6))
9383 .child(Icon::new(IconName::Info).color(Color::Error))
9384 .cursor_default()
9385 .hoverable_tooltip(move |_window, cx| {
9386 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9387 .into()
9388 })
9389 })
9390 .when_some(
9391 accept_keystroke.as_ref(),
9392 |el, accept_keystroke| {
9393 el.child(h_flex().children(ui::render_modifiers(
9394 accept_keystroke.modifiers(),
9395 PlatformStyle::platform(),
9396 Some(Color::Default),
9397 Some(IconSize::XSmall.rems().into()),
9398 false,
9399 )))
9400 },
9401 ),
9402 )
9403 .into_any(),
9404 );
9405 }
9406
9407 self.render_edit_prediction_cursor_popover_preview(
9408 prediction,
9409 cursor_point,
9410 style,
9411 cx,
9412 )?
9413 }
9414
9415 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9416 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9417 stale_completion,
9418 cursor_point,
9419 style,
9420 cx,
9421 )?,
9422
9423 None => pending_completion_container(provider_icon)
9424 .child(Label::new("...").size(LabelSize::Small)),
9425 },
9426
9427 None => pending_completion_container(provider_icon)
9428 .child(Label::new("...").size(LabelSize::Small)),
9429 };
9430
9431 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9432 completion
9433 .with_animation(
9434 "loading-completion",
9435 Animation::new(Duration::from_secs(2))
9436 .repeat()
9437 .with_easing(pulsating_between(0.4, 0.8)),
9438 |label, delta| label.opacity(delta),
9439 )
9440 .into_any_element()
9441 } else {
9442 completion.into_any_element()
9443 };
9444
9445 let has_completion = self.active_edit_prediction.is_some();
9446
9447 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9448 Some(
9449 h_flex()
9450 .min_w(min_width)
9451 .max_w(max_width)
9452 .flex_1()
9453 .elevation_2(cx)
9454 .border_color(cx.theme().colors().border)
9455 .child(
9456 div()
9457 .flex_1()
9458 .py_1()
9459 .px_2()
9460 .overflow_hidden()
9461 .child(completion),
9462 )
9463 .when_some(accept_keystroke, |el, accept_keystroke| {
9464 if !accept_keystroke.modifiers().modified() {
9465 return el;
9466 }
9467
9468 el.child(
9469 h_flex()
9470 .h_full()
9471 .border_l_1()
9472 .rounded_r_lg()
9473 .border_color(cx.theme().colors().border)
9474 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9475 .gap_1()
9476 .py_1()
9477 .px_2()
9478 .child(
9479 h_flex()
9480 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9481 .when(is_platform_style_mac, |parent| parent.gap_1())
9482 .child(h_flex().children(ui::render_modifiers(
9483 accept_keystroke.modifiers(),
9484 PlatformStyle::platform(),
9485 Some(if !has_completion {
9486 Color::Muted
9487 } else {
9488 Color::Default
9489 }),
9490 None,
9491 false,
9492 ))),
9493 )
9494 .child(Label::new("Preview").into_any_element())
9495 .opacity(if has_completion { 1.0 } else { 0.4 }),
9496 )
9497 })
9498 .into_any(),
9499 )
9500 }
9501
9502 fn render_edit_prediction_cursor_popover_preview(
9503 &self,
9504 completion: &EditPredictionState,
9505 cursor_point: Point,
9506 style: &EditorStyle,
9507 cx: &mut Context<Editor>,
9508 ) -> Option<Div> {
9509 use text::ToPoint as _;
9510
9511 fn render_relative_row_jump(
9512 prefix: impl Into<String>,
9513 current_row: u32,
9514 target_row: u32,
9515 ) -> Div {
9516 let (row_diff, arrow) = if target_row < current_row {
9517 (current_row - target_row, IconName::ArrowUp)
9518 } else {
9519 (target_row - current_row, IconName::ArrowDown)
9520 };
9521
9522 h_flex()
9523 .child(
9524 Label::new(format!("{}{}", prefix.into(), row_diff))
9525 .color(Color::Muted)
9526 .size(LabelSize::Small),
9527 )
9528 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9529 }
9530
9531 let supports_jump = self
9532 .edit_prediction_provider
9533 .as_ref()
9534 .map(|provider| provider.provider.supports_jump_to_edit())
9535 .unwrap_or(true);
9536
9537 match &completion.completion {
9538 EditPrediction::MoveWithin {
9539 target, snapshot, ..
9540 } => {
9541 if !supports_jump {
9542 return None;
9543 }
9544
9545 Some(
9546 h_flex()
9547 .px_2()
9548 .gap_2()
9549 .flex_1()
9550 .child(
9551 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9552 Icon::new(IconName::ZedPredictDown)
9553 } else {
9554 Icon::new(IconName::ZedPredictUp)
9555 },
9556 )
9557 .child(Label::new("Jump to Edit")),
9558 )
9559 }
9560 EditPrediction::MoveOutside { snapshot, .. } => {
9561 let file_name = snapshot
9562 .file()
9563 .map(|file| file.file_name(cx))
9564 .unwrap_or("untitled");
9565 Some(
9566 h_flex()
9567 .px_2()
9568 .gap_2()
9569 .flex_1()
9570 .child(Icon::new(IconName::ZedPredict))
9571 .child(Label::new(format!("Jump to {file_name}"))),
9572 )
9573 }
9574 EditPrediction::Edit {
9575 edits,
9576 edit_preview,
9577 snapshot,
9578 display_mode: _,
9579 } => {
9580 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9581
9582 let (highlighted_edits, has_more_lines) =
9583 if let Some(edit_preview) = edit_preview.as_ref() {
9584 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9585 .first_line_preview()
9586 } else {
9587 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9588 };
9589
9590 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9591 .with_default_highlights(&style.text, highlighted_edits.highlights);
9592
9593 let preview = h_flex()
9594 .gap_1()
9595 .min_w_16()
9596 .child(styled_text)
9597 .when(has_more_lines, |parent| parent.child("…"));
9598
9599 let left = if supports_jump && first_edit_row != cursor_point.row {
9600 render_relative_row_jump("", cursor_point.row, first_edit_row)
9601 .into_any_element()
9602 } else {
9603 let icon_name =
9604 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9605 Icon::new(icon_name).into_any_element()
9606 };
9607
9608 Some(
9609 h_flex()
9610 .h_full()
9611 .flex_1()
9612 .gap_2()
9613 .pr_1()
9614 .overflow_x_hidden()
9615 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9616 .child(left)
9617 .child(preview),
9618 )
9619 }
9620 }
9621 }
9622
9623 pub fn render_context_menu(
9624 &self,
9625 style: &EditorStyle,
9626 max_height_in_lines: u32,
9627 window: &mut Window,
9628 cx: &mut Context<Editor>,
9629 ) -> Option<AnyElement> {
9630 let menu = self.context_menu.borrow();
9631 let menu = menu.as_ref()?;
9632 if !menu.visible() {
9633 return None;
9634 };
9635 Some(menu.render(style, max_height_in_lines, window, cx))
9636 }
9637
9638 fn render_context_menu_aside(
9639 &mut self,
9640 max_size: Size<Pixels>,
9641 window: &mut Window,
9642 cx: &mut Context<Editor>,
9643 ) -> Option<AnyElement> {
9644 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9645 if menu.visible() {
9646 menu.render_aside(max_size, window, cx)
9647 } else {
9648 None
9649 }
9650 })
9651 }
9652
9653 fn hide_context_menu(
9654 &mut self,
9655 window: &mut Window,
9656 cx: &mut Context<Self>,
9657 ) -> Option<CodeContextMenu> {
9658 cx.notify();
9659 self.completion_tasks.clear();
9660 let context_menu = self.context_menu.borrow_mut().take();
9661 self.stale_edit_prediction_in_menu.take();
9662 self.update_visible_edit_prediction(window, cx);
9663 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9664 && let Some(completion_provider) = &self.completion_provider
9665 {
9666 completion_provider.selection_changed(None, window, cx);
9667 }
9668 context_menu
9669 }
9670
9671 fn show_snippet_choices(
9672 &mut self,
9673 choices: &Vec<String>,
9674 selection: Range<Anchor>,
9675 cx: &mut Context<Self>,
9676 ) {
9677 let Some((_, buffer, _)) = self
9678 .buffer()
9679 .read(cx)
9680 .excerpt_containing(selection.start, cx)
9681 else {
9682 return;
9683 };
9684 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9685 else {
9686 return;
9687 };
9688 if buffer != end_buffer {
9689 log::error!("expected anchor range to have matching buffer IDs");
9690 return;
9691 }
9692
9693 let id = post_inc(&mut self.next_completion_id);
9694 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9695 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9696 CompletionsMenu::new_snippet_choices(
9697 id,
9698 true,
9699 choices,
9700 selection,
9701 buffer,
9702 snippet_sort_order,
9703 ),
9704 ));
9705 }
9706
9707 pub fn insert_snippet(
9708 &mut self,
9709 insertion_ranges: &[Range<usize>],
9710 snippet: Snippet,
9711 window: &mut Window,
9712 cx: &mut Context<Self>,
9713 ) -> Result<()> {
9714 struct Tabstop<T> {
9715 is_end_tabstop: bool,
9716 ranges: Vec<Range<T>>,
9717 choices: Option<Vec<String>>,
9718 }
9719
9720 let tabstops = self.buffer.update(cx, |buffer, cx| {
9721 let snippet_text: Arc<str> = snippet.text.clone().into();
9722 let edits = insertion_ranges
9723 .iter()
9724 .cloned()
9725 .map(|range| (range, snippet_text.clone()));
9726 let autoindent_mode = AutoindentMode::Block {
9727 original_indent_columns: Vec::new(),
9728 };
9729 buffer.edit(edits, Some(autoindent_mode), cx);
9730
9731 let snapshot = &*buffer.read(cx);
9732 let snippet = &snippet;
9733 snippet
9734 .tabstops
9735 .iter()
9736 .map(|tabstop| {
9737 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9738 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9739 });
9740 let mut tabstop_ranges = tabstop
9741 .ranges
9742 .iter()
9743 .flat_map(|tabstop_range| {
9744 let mut delta = 0_isize;
9745 insertion_ranges.iter().map(move |insertion_range| {
9746 let insertion_start = insertion_range.start as isize + delta;
9747 delta +=
9748 snippet.text.len() as isize - insertion_range.len() as isize;
9749
9750 let start = ((insertion_start + tabstop_range.start) as usize)
9751 .min(snapshot.len());
9752 let end = ((insertion_start + tabstop_range.end) as usize)
9753 .min(snapshot.len());
9754 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9755 })
9756 })
9757 .collect::<Vec<_>>();
9758 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9759
9760 Tabstop {
9761 is_end_tabstop,
9762 ranges: tabstop_ranges,
9763 choices: tabstop.choices.clone(),
9764 }
9765 })
9766 .collect::<Vec<_>>()
9767 });
9768 if let Some(tabstop) = tabstops.first() {
9769 self.change_selections(Default::default(), window, cx, |s| {
9770 // Reverse order so that the first range is the newest created selection.
9771 // Completions will use it and autoscroll will prioritize it.
9772 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9773 });
9774
9775 if let Some(choices) = &tabstop.choices
9776 && let Some(selection) = tabstop.ranges.first()
9777 {
9778 self.show_snippet_choices(choices, selection.clone(), cx)
9779 }
9780
9781 // If we're already at the last tabstop and it's at the end of the snippet,
9782 // we're done, we don't need to keep the state around.
9783 if !tabstop.is_end_tabstop {
9784 let choices = tabstops
9785 .iter()
9786 .map(|tabstop| tabstop.choices.clone())
9787 .collect();
9788
9789 let ranges = tabstops
9790 .into_iter()
9791 .map(|tabstop| tabstop.ranges)
9792 .collect::<Vec<_>>();
9793
9794 self.snippet_stack.push(SnippetState {
9795 active_index: 0,
9796 ranges,
9797 choices,
9798 });
9799 }
9800
9801 // Check whether the just-entered snippet ends with an auto-closable bracket.
9802 if self.autoclose_regions.is_empty() {
9803 let snapshot = self.buffer.read(cx).snapshot(cx);
9804 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9805 let selection_head = selection.head();
9806 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9807 continue;
9808 };
9809
9810 let mut bracket_pair = None;
9811 let max_lookup_length = scope
9812 .brackets()
9813 .map(|(pair, _)| {
9814 pair.start
9815 .as_str()
9816 .chars()
9817 .count()
9818 .max(pair.end.as_str().chars().count())
9819 })
9820 .max();
9821 if let Some(max_lookup_length) = max_lookup_length {
9822 let next_text = snapshot
9823 .chars_at(selection_head)
9824 .take(max_lookup_length)
9825 .collect::<String>();
9826 let prev_text = snapshot
9827 .reversed_chars_at(selection_head)
9828 .take(max_lookup_length)
9829 .collect::<String>();
9830
9831 for (pair, enabled) in scope.brackets() {
9832 if enabled
9833 && pair.close
9834 && prev_text.starts_with(pair.start.as_str())
9835 && next_text.starts_with(pair.end.as_str())
9836 {
9837 bracket_pair = Some(pair.clone());
9838 break;
9839 }
9840 }
9841 }
9842
9843 if let Some(pair) = bracket_pair {
9844 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9845 let autoclose_enabled =
9846 self.use_autoclose && snapshot_settings.use_autoclose;
9847 if autoclose_enabled {
9848 let start = snapshot.anchor_after(selection_head);
9849 let end = snapshot.anchor_after(selection_head);
9850 self.autoclose_regions.push(AutocloseRegion {
9851 selection_id: selection.id,
9852 range: start..end,
9853 pair,
9854 });
9855 }
9856 }
9857 }
9858 }
9859 }
9860 Ok(())
9861 }
9862
9863 pub fn move_to_next_snippet_tabstop(
9864 &mut self,
9865 window: &mut Window,
9866 cx: &mut Context<Self>,
9867 ) -> bool {
9868 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9869 }
9870
9871 pub fn move_to_prev_snippet_tabstop(
9872 &mut self,
9873 window: &mut Window,
9874 cx: &mut Context<Self>,
9875 ) -> bool {
9876 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9877 }
9878
9879 pub fn move_to_snippet_tabstop(
9880 &mut self,
9881 bias: Bias,
9882 window: &mut Window,
9883 cx: &mut Context<Self>,
9884 ) -> bool {
9885 if let Some(mut snippet) = self.snippet_stack.pop() {
9886 match bias {
9887 Bias::Left => {
9888 if snippet.active_index > 0 {
9889 snippet.active_index -= 1;
9890 } else {
9891 self.snippet_stack.push(snippet);
9892 return false;
9893 }
9894 }
9895 Bias::Right => {
9896 if snippet.active_index + 1 < snippet.ranges.len() {
9897 snippet.active_index += 1;
9898 } else {
9899 self.snippet_stack.push(snippet);
9900 return false;
9901 }
9902 }
9903 }
9904 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9905 self.change_selections(Default::default(), window, cx, |s| {
9906 // Reverse order so that the first range is the newest created selection.
9907 // Completions will use it and autoscroll will prioritize it.
9908 s.select_ranges(current_ranges.iter().rev().cloned())
9909 });
9910
9911 if let Some(choices) = &snippet.choices[snippet.active_index]
9912 && let Some(selection) = current_ranges.first()
9913 {
9914 self.show_snippet_choices(choices, selection.clone(), cx);
9915 }
9916
9917 // If snippet state is not at the last tabstop, push it back on the stack
9918 if snippet.active_index + 1 < snippet.ranges.len() {
9919 self.snippet_stack.push(snippet);
9920 }
9921 return true;
9922 }
9923 }
9924
9925 false
9926 }
9927
9928 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9929 self.transact(window, cx, |this, window, cx| {
9930 this.select_all(&SelectAll, window, cx);
9931 this.insert("", window, cx);
9932 });
9933 }
9934
9935 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9936 if self.read_only(cx) {
9937 return;
9938 }
9939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9940 self.transact(window, cx, |this, window, cx| {
9941 this.select_autoclose_pair(window, cx);
9942
9943 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9944
9945 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9946 if !this.linked_edit_ranges.is_empty() {
9947 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9948 let snapshot = this.buffer.read(cx).snapshot(cx);
9949
9950 for selection in selections.iter() {
9951 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9952 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9953 if selection_start.buffer_id != selection_end.buffer_id {
9954 continue;
9955 }
9956 if let Some(ranges) =
9957 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9958 {
9959 for (buffer, entries) in ranges {
9960 linked_ranges.entry(buffer).or_default().extend(entries);
9961 }
9962 }
9963 }
9964 }
9965
9966 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9967 for selection in &mut selections {
9968 if selection.is_empty() {
9969 let old_head = selection.head();
9970 let mut new_head =
9971 movement::left(&display_map, old_head.to_display_point(&display_map))
9972 .to_point(&display_map);
9973 if let Some((buffer, line_buffer_range)) = display_map
9974 .buffer_snapshot()
9975 .buffer_line_for_row(MultiBufferRow(old_head.row))
9976 {
9977 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9978 let indent_len = match indent_size.kind {
9979 IndentKind::Space => {
9980 buffer.settings_at(line_buffer_range.start, cx).tab_size
9981 }
9982 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9983 };
9984 if old_head.column <= indent_size.len && old_head.column > 0 {
9985 let indent_len = indent_len.get();
9986 new_head = cmp::min(
9987 new_head,
9988 MultiBufferPoint::new(
9989 old_head.row,
9990 ((old_head.column - 1) / indent_len) * indent_len,
9991 ),
9992 );
9993 }
9994 }
9995
9996 selection.set_head(new_head, SelectionGoal::None);
9997 }
9998 }
9999
10000 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10001 this.insert("", window, cx);
10002 let empty_str: Arc<str> = Arc::from("");
10003 for (buffer, edits) in linked_ranges {
10004 let snapshot = buffer.read(cx).snapshot();
10005 use text::ToPoint as TP;
10006
10007 let edits = edits
10008 .into_iter()
10009 .map(|range| {
10010 let end_point = TP::to_point(&range.end, &snapshot);
10011 let mut start_point = TP::to_point(&range.start, &snapshot);
10012
10013 if end_point == start_point {
10014 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10015 .saturating_sub(1);
10016 start_point =
10017 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10018 };
10019
10020 (start_point..end_point, empty_str.clone())
10021 })
10022 .sorted_by_key(|(range, _)| range.start)
10023 .collect::<Vec<_>>();
10024 buffer.update(cx, |this, cx| {
10025 this.edit(edits, None, cx);
10026 })
10027 }
10028 this.refresh_edit_prediction(true, false, window, cx);
10029 refresh_linked_ranges(this, window, cx);
10030 });
10031 }
10032
10033 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10034 if self.read_only(cx) {
10035 return;
10036 }
10037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10038 self.transact(window, cx, |this, window, cx| {
10039 this.change_selections(Default::default(), window, cx, |s| {
10040 s.move_with(|map, selection| {
10041 if selection.is_empty() {
10042 let cursor = movement::right(map, selection.head());
10043 selection.end = cursor;
10044 selection.reversed = true;
10045 selection.goal = SelectionGoal::None;
10046 }
10047 })
10048 });
10049 this.insert("", window, cx);
10050 this.refresh_edit_prediction(true, false, window, cx);
10051 });
10052 }
10053
10054 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10055 if self.mode.is_single_line() {
10056 cx.propagate();
10057 return;
10058 }
10059
10060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10061 if self.move_to_prev_snippet_tabstop(window, cx) {
10062 return;
10063 }
10064 self.outdent(&Outdent, window, cx);
10065 }
10066
10067 pub fn next_snippet_tabstop(
10068 &mut self,
10069 _: &NextSnippetTabstop,
10070 window: &mut Window,
10071 cx: &mut Context<Self>,
10072 ) {
10073 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10074 cx.propagate();
10075 return;
10076 }
10077
10078 if self.move_to_next_snippet_tabstop(window, cx) {
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 return;
10081 }
10082 cx.propagate();
10083 }
10084
10085 pub fn previous_snippet_tabstop(
10086 &mut self,
10087 _: &PreviousSnippetTabstop,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) {
10091 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10092 cx.propagate();
10093 return;
10094 }
10095
10096 if self.move_to_prev_snippet_tabstop(window, cx) {
10097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10098 return;
10099 }
10100 cx.propagate();
10101 }
10102
10103 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10104 if self.mode.is_single_line() {
10105 cx.propagate();
10106 return;
10107 }
10108
10109 if self.move_to_next_snippet_tabstop(window, cx) {
10110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10111 return;
10112 }
10113 if self.read_only(cx) {
10114 return;
10115 }
10116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10117 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10118 let buffer = self.buffer.read(cx);
10119 let snapshot = buffer.snapshot(cx);
10120 let rows_iter = selections.iter().map(|s| s.head().row);
10121 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10122
10123 let has_some_cursor_in_whitespace = selections
10124 .iter()
10125 .filter(|selection| selection.is_empty())
10126 .any(|selection| {
10127 let cursor = selection.head();
10128 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10129 cursor.column < current_indent.len
10130 });
10131
10132 let mut edits = Vec::new();
10133 let mut prev_edited_row = 0;
10134 let mut row_delta = 0;
10135 for selection in &mut selections {
10136 if selection.start.row != prev_edited_row {
10137 row_delta = 0;
10138 }
10139 prev_edited_row = selection.end.row;
10140
10141 // If the selection is non-empty, then increase the indentation of the selected lines.
10142 if !selection.is_empty() {
10143 row_delta =
10144 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10145 continue;
10146 }
10147
10148 let cursor = selection.head();
10149 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10150 if let Some(suggested_indent) =
10151 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10152 {
10153 // Don't do anything if already at suggested indent
10154 // and there is any other cursor which is not
10155 if has_some_cursor_in_whitespace
10156 && cursor.column == current_indent.len
10157 && current_indent.len == suggested_indent.len
10158 {
10159 continue;
10160 }
10161
10162 // Adjust line and move cursor to suggested indent
10163 // if cursor is not at suggested indent
10164 if cursor.column < suggested_indent.len
10165 && cursor.column <= current_indent.len
10166 && current_indent.len <= suggested_indent.len
10167 {
10168 selection.start = Point::new(cursor.row, suggested_indent.len);
10169 selection.end = selection.start;
10170 if row_delta == 0 {
10171 edits.extend(Buffer::edit_for_indent_size_adjustment(
10172 cursor.row,
10173 current_indent,
10174 suggested_indent,
10175 ));
10176 row_delta = suggested_indent.len - current_indent.len;
10177 }
10178 continue;
10179 }
10180
10181 // If current indent is more than suggested indent
10182 // only move cursor to current indent and skip indent
10183 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10184 selection.start = Point::new(cursor.row, current_indent.len);
10185 selection.end = selection.start;
10186 continue;
10187 }
10188 }
10189
10190 // Otherwise, insert a hard or soft tab.
10191 let settings = buffer.language_settings_at(cursor, cx);
10192 let tab_size = if settings.hard_tabs {
10193 IndentSize::tab()
10194 } else {
10195 let tab_size = settings.tab_size.get();
10196 let indent_remainder = snapshot
10197 .text_for_range(Point::new(cursor.row, 0)..cursor)
10198 .flat_map(str::chars)
10199 .fold(row_delta % tab_size, |counter: u32, c| {
10200 if c == '\t' {
10201 0
10202 } else {
10203 (counter + 1) % tab_size
10204 }
10205 });
10206
10207 let chars_to_next_tab_stop = tab_size - indent_remainder;
10208 IndentSize::spaces(chars_to_next_tab_stop)
10209 };
10210 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10211 selection.end = selection.start;
10212 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10213 row_delta += tab_size.len;
10214 }
10215
10216 self.transact(window, cx, |this, window, cx| {
10217 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10218 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10219 this.refresh_edit_prediction(true, false, window, cx);
10220 });
10221 }
10222
10223 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10224 if self.read_only(cx) {
10225 return;
10226 }
10227 if self.mode.is_single_line() {
10228 cx.propagate();
10229 return;
10230 }
10231
10232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10233 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10234 let mut prev_edited_row = 0;
10235 let mut row_delta = 0;
10236 let mut edits = Vec::new();
10237 let buffer = self.buffer.read(cx);
10238 let snapshot = buffer.snapshot(cx);
10239 for selection in &mut selections {
10240 if selection.start.row != prev_edited_row {
10241 row_delta = 0;
10242 }
10243 prev_edited_row = selection.end.row;
10244
10245 row_delta =
10246 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10247 }
10248
10249 self.transact(window, cx, |this, window, cx| {
10250 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10251 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10252 });
10253 }
10254
10255 fn indent_selection(
10256 buffer: &MultiBuffer,
10257 snapshot: &MultiBufferSnapshot,
10258 selection: &mut Selection<Point>,
10259 edits: &mut Vec<(Range<Point>, String)>,
10260 delta_for_start_row: u32,
10261 cx: &App,
10262 ) -> u32 {
10263 let settings = buffer.language_settings_at(selection.start, cx);
10264 let tab_size = settings.tab_size.get();
10265 let indent_kind = if settings.hard_tabs {
10266 IndentKind::Tab
10267 } else {
10268 IndentKind::Space
10269 };
10270 let mut start_row = selection.start.row;
10271 let mut end_row = selection.end.row + 1;
10272
10273 // If a selection ends at the beginning of a line, don't indent
10274 // that last line.
10275 if selection.end.column == 0 && selection.end.row > selection.start.row {
10276 end_row -= 1;
10277 }
10278
10279 // Avoid re-indenting a row that has already been indented by a
10280 // previous selection, but still update this selection's column
10281 // to reflect that indentation.
10282 if delta_for_start_row > 0 {
10283 start_row += 1;
10284 selection.start.column += delta_for_start_row;
10285 if selection.end.row == selection.start.row {
10286 selection.end.column += delta_for_start_row;
10287 }
10288 }
10289
10290 let mut delta_for_end_row = 0;
10291 let has_multiple_rows = start_row + 1 != end_row;
10292 for row in start_row..end_row {
10293 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10294 let indent_delta = match (current_indent.kind, indent_kind) {
10295 (IndentKind::Space, IndentKind::Space) => {
10296 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10297 IndentSize::spaces(columns_to_next_tab_stop)
10298 }
10299 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10300 (_, IndentKind::Tab) => IndentSize::tab(),
10301 };
10302
10303 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10304 0
10305 } else {
10306 selection.start.column
10307 };
10308 let row_start = Point::new(row, start);
10309 edits.push((
10310 row_start..row_start,
10311 indent_delta.chars().collect::<String>(),
10312 ));
10313
10314 // Update this selection's endpoints to reflect the indentation.
10315 if row == selection.start.row {
10316 selection.start.column += indent_delta.len;
10317 }
10318 if row == selection.end.row {
10319 selection.end.column += indent_delta.len;
10320 delta_for_end_row = indent_delta.len;
10321 }
10322 }
10323
10324 if selection.start.row == selection.end.row {
10325 delta_for_start_row + delta_for_end_row
10326 } else {
10327 delta_for_end_row
10328 }
10329 }
10330
10331 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10332 if self.read_only(cx) {
10333 return;
10334 }
10335 if self.mode.is_single_line() {
10336 cx.propagate();
10337 return;
10338 }
10339
10340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10342 let selections = self.selections.all::<Point>(&display_map);
10343 let mut deletion_ranges = Vec::new();
10344 let mut last_outdent = None;
10345 {
10346 let buffer = self.buffer.read(cx);
10347 let snapshot = buffer.snapshot(cx);
10348 for selection in &selections {
10349 let settings = buffer.language_settings_at(selection.start, cx);
10350 let tab_size = settings.tab_size.get();
10351 let mut rows = selection.spanned_rows(false, &display_map);
10352
10353 // Avoid re-outdenting a row that has already been outdented by a
10354 // previous selection.
10355 if let Some(last_row) = last_outdent
10356 && last_row == rows.start
10357 {
10358 rows.start = rows.start.next_row();
10359 }
10360 let has_multiple_rows = rows.len() > 1;
10361 for row in rows.iter_rows() {
10362 let indent_size = snapshot.indent_size_for_line(row);
10363 if indent_size.len > 0 {
10364 let deletion_len = match indent_size.kind {
10365 IndentKind::Space => {
10366 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10367 if columns_to_prev_tab_stop == 0 {
10368 tab_size
10369 } else {
10370 columns_to_prev_tab_stop
10371 }
10372 }
10373 IndentKind::Tab => 1,
10374 };
10375 let start = if has_multiple_rows
10376 || deletion_len > selection.start.column
10377 || indent_size.len < selection.start.column
10378 {
10379 0
10380 } else {
10381 selection.start.column - deletion_len
10382 };
10383 deletion_ranges.push(
10384 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10385 );
10386 last_outdent = Some(row);
10387 }
10388 }
10389 }
10390 }
10391
10392 self.transact(window, cx, |this, window, cx| {
10393 this.buffer.update(cx, |buffer, cx| {
10394 let empty_str: Arc<str> = Arc::default();
10395 buffer.edit(
10396 deletion_ranges
10397 .into_iter()
10398 .map(|range| (range, empty_str.clone())),
10399 None,
10400 cx,
10401 );
10402 });
10403 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10404 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10405 });
10406 }
10407
10408 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10409 if self.read_only(cx) {
10410 return;
10411 }
10412 if self.mode.is_single_line() {
10413 cx.propagate();
10414 return;
10415 }
10416
10417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10418 let selections = self
10419 .selections
10420 .all::<usize>(&self.display_snapshot(cx))
10421 .into_iter()
10422 .map(|s| s.range());
10423
10424 self.transact(window, cx, |this, window, cx| {
10425 this.buffer.update(cx, |buffer, cx| {
10426 buffer.autoindent_ranges(selections, cx);
10427 });
10428 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10429 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10430 });
10431 }
10432
10433 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10436 let selections = self.selections.all::<Point>(&display_map);
10437
10438 let mut new_cursors = Vec::new();
10439 let mut edit_ranges = Vec::new();
10440 let mut selections = selections.iter().peekable();
10441 while let Some(selection) = selections.next() {
10442 let mut rows = selection.spanned_rows(false, &display_map);
10443
10444 // Accumulate contiguous regions of rows that we want to delete.
10445 while let Some(next_selection) = selections.peek() {
10446 let next_rows = next_selection.spanned_rows(false, &display_map);
10447 if next_rows.start <= rows.end {
10448 rows.end = next_rows.end;
10449 selections.next().unwrap();
10450 } else {
10451 break;
10452 }
10453 }
10454
10455 let buffer = display_map.buffer_snapshot();
10456 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10457 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10458 // If there's a line after the range, delete the \n from the end of the row range
10459 (
10460 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10461 rows.end,
10462 )
10463 } else {
10464 // If there isn't a line after the range, delete the \n from the line before the
10465 // start of the row range
10466 edit_start = edit_start.saturating_sub(1);
10467 (buffer.len(), rows.start.previous_row())
10468 };
10469
10470 let text_layout_details = self.text_layout_details(window);
10471 let x = display_map.x_for_display_point(
10472 selection.head().to_display_point(&display_map),
10473 &text_layout_details,
10474 );
10475 let row = Point::new(target_row.0, 0)
10476 .to_display_point(&display_map)
10477 .row();
10478 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10479
10480 new_cursors.push((
10481 selection.id,
10482 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10483 SelectionGoal::None,
10484 ));
10485 edit_ranges.push(edit_start..edit_end);
10486 }
10487
10488 self.transact(window, cx, |this, window, cx| {
10489 let buffer = this.buffer.update(cx, |buffer, cx| {
10490 let empty_str: Arc<str> = Arc::default();
10491 buffer.edit(
10492 edit_ranges
10493 .into_iter()
10494 .map(|range| (range, empty_str.clone())),
10495 None,
10496 cx,
10497 );
10498 buffer.snapshot(cx)
10499 });
10500 let new_selections = new_cursors
10501 .into_iter()
10502 .map(|(id, cursor, goal)| {
10503 let cursor = cursor.to_point(&buffer);
10504 Selection {
10505 id,
10506 start: cursor,
10507 end: cursor,
10508 reversed: false,
10509 goal,
10510 }
10511 })
10512 .collect();
10513
10514 this.change_selections(Default::default(), window, cx, |s| {
10515 s.select(new_selections);
10516 });
10517 });
10518 }
10519
10520 pub fn join_lines_impl(
10521 &mut self,
10522 insert_whitespace: bool,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) {
10526 if self.read_only(cx) {
10527 return;
10528 }
10529 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10530 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10531 let start = MultiBufferRow(selection.start.row);
10532 // Treat single line selections as if they include the next line. Otherwise this action
10533 // would do nothing for single line selections individual cursors.
10534 let end = if selection.start.row == selection.end.row {
10535 MultiBufferRow(selection.start.row + 1)
10536 } else {
10537 MultiBufferRow(selection.end.row)
10538 };
10539
10540 if let Some(last_row_range) = row_ranges.last_mut()
10541 && start <= last_row_range.end
10542 {
10543 last_row_range.end = end;
10544 continue;
10545 }
10546 row_ranges.push(start..end);
10547 }
10548
10549 let snapshot = self.buffer.read(cx).snapshot(cx);
10550 let mut cursor_positions = Vec::new();
10551 for row_range in &row_ranges {
10552 let anchor = snapshot.anchor_before(Point::new(
10553 row_range.end.previous_row().0,
10554 snapshot.line_len(row_range.end.previous_row()),
10555 ));
10556 cursor_positions.push(anchor..anchor);
10557 }
10558
10559 self.transact(window, cx, |this, window, cx| {
10560 for row_range in row_ranges.into_iter().rev() {
10561 for row in row_range.iter_rows().rev() {
10562 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10563 let next_line_row = row.next_row();
10564 let indent = snapshot.indent_size_for_line(next_line_row);
10565 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10566
10567 let replace =
10568 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10569 " "
10570 } else {
10571 ""
10572 };
10573
10574 this.buffer.update(cx, |buffer, cx| {
10575 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10576 });
10577 }
10578 }
10579
10580 this.change_selections(Default::default(), window, cx, |s| {
10581 s.select_anchor_ranges(cursor_positions)
10582 });
10583 });
10584 }
10585
10586 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10587 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10588 self.join_lines_impl(true, window, cx);
10589 }
10590
10591 pub fn sort_lines_case_sensitive(
10592 &mut self,
10593 _: &SortLinesCaseSensitive,
10594 window: &mut Window,
10595 cx: &mut Context<Self>,
10596 ) {
10597 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10598 }
10599
10600 pub fn sort_lines_by_length(
10601 &mut self,
10602 _: &SortLinesByLength,
10603 window: &mut Window,
10604 cx: &mut Context<Self>,
10605 ) {
10606 self.manipulate_immutable_lines(window, cx, |lines| {
10607 lines.sort_by_key(|&line| line.chars().count())
10608 })
10609 }
10610
10611 pub fn sort_lines_case_insensitive(
10612 &mut self,
10613 _: &SortLinesCaseInsensitive,
10614 window: &mut Window,
10615 cx: &mut Context<Self>,
10616 ) {
10617 self.manipulate_immutable_lines(window, cx, |lines| {
10618 lines.sort_by_key(|line| line.to_lowercase())
10619 })
10620 }
10621
10622 pub fn unique_lines_case_insensitive(
10623 &mut self,
10624 _: &UniqueLinesCaseInsensitive,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) {
10628 self.manipulate_immutable_lines(window, cx, |lines| {
10629 let mut seen = HashSet::default();
10630 lines.retain(|line| seen.insert(line.to_lowercase()));
10631 })
10632 }
10633
10634 pub fn unique_lines_case_sensitive(
10635 &mut self,
10636 _: &UniqueLinesCaseSensitive,
10637 window: &mut Window,
10638 cx: &mut Context<Self>,
10639 ) {
10640 self.manipulate_immutable_lines(window, cx, |lines| {
10641 let mut seen = HashSet::default();
10642 lines.retain(|line| seen.insert(*line));
10643 })
10644 }
10645
10646 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10647 let snapshot = self.buffer.read(cx).snapshot(cx);
10648 for selection in self.selections.disjoint_anchors_arc().iter() {
10649 if snapshot
10650 .language_at(selection.start)
10651 .and_then(|lang| lang.config().wrap_characters.as_ref())
10652 .is_some()
10653 {
10654 return true;
10655 }
10656 }
10657 false
10658 }
10659
10660 fn wrap_selections_in_tag(
10661 &mut self,
10662 _: &WrapSelectionsInTag,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10667
10668 let snapshot = self.buffer.read(cx).snapshot(cx);
10669
10670 let mut edits = Vec::new();
10671 let mut boundaries = Vec::new();
10672
10673 for selection in self
10674 .selections
10675 .all_adjusted(&self.display_snapshot(cx))
10676 .iter()
10677 {
10678 let Some(wrap_config) = snapshot
10679 .language_at(selection.start)
10680 .and_then(|lang| lang.config().wrap_characters.clone())
10681 else {
10682 continue;
10683 };
10684
10685 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10686 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10687
10688 let start_before = snapshot.anchor_before(selection.start);
10689 let end_after = snapshot.anchor_after(selection.end);
10690
10691 edits.push((start_before..start_before, open_tag));
10692 edits.push((end_after..end_after, close_tag));
10693
10694 boundaries.push((
10695 start_before,
10696 end_after,
10697 wrap_config.start_prefix.len(),
10698 wrap_config.end_suffix.len(),
10699 ));
10700 }
10701
10702 if edits.is_empty() {
10703 return;
10704 }
10705
10706 self.transact(window, cx, |this, window, cx| {
10707 let buffer = this.buffer.update(cx, |buffer, cx| {
10708 buffer.edit(edits, None, cx);
10709 buffer.snapshot(cx)
10710 });
10711
10712 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10713 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10714 boundaries.into_iter()
10715 {
10716 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10717 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10718 new_selections.push(open_offset..open_offset);
10719 new_selections.push(close_offset..close_offset);
10720 }
10721
10722 this.change_selections(Default::default(), window, cx, |s| {
10723 s.select_ranges(new_selections);
10724 });
10725
10726 this.request_autoscroll(Autoscroll::fit(), cx);
10727 });
10728 }
10729
10730 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10731 let Some(project) = self.project.clone() else {
10732 return;
10733 };
10734 self.reload(project, window, cx)
10735 .detach_and_notify_err(window, cx);
10736 }
10737
10738 pub fn restore_file(
10739 &mut self,
10740 _: &::git::RestoreFile,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) {
10744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10745 let mut buffer_ids = HashSet::default();
10746 let snapshot = self.buffer().read(cx).snapshot(cx);
10747 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10748 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10749 }
10750
10751 let buffer = self.buffer().read(cx);
10752 let ranges = buffer_ids
10753 .into_iter()
10754 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10755 .collect::<Vec<_>>();
10756
10757 self.restore_hunks_in_ranges(ranges, window, cx);
10758 }
10759
10760 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10762 let selections = self
10763 .selections
10764 .all(&self.display_snapshot(cx))
10765 .into_iter()
10766 .map(|s| s.range())
10767 .collect();
10768 self.restore_hunks_in_ranges(selections, window, cx);
10769 }
10770
10771 pub fn restore_hunks_in_ranges(
10772 &mut self,
10773 ranges: Vec<Range<Point>>,
10774 window: &mut Window,
10775 cx: &mut Context<Editor>,
10776 ) {
10777 let mut revert_changes = HashMap::default();
10778 let chunk_by = self
10779 .snapshot(window, cx)
10780 .hunks_for_ranges(ranges)
10781 .into_iter()
10782 .chunk_by(|hunk| hunk.buffer_id);
10783 for (buffer_id, hunks) in &chunk_by {
10784 let hunks = hunks.collect::<Vec<_>>();
10785 for hunk in &hunks {
10786 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10787 }
10788 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10789 }
10790 drop(chunk_by);
10791 if !revert_changes.is_empty() {
10792 self.transact(window, cx, |editor, window, cx| {
10793 editor.restore(revert_changes, window, cx);
10794 });
10795 }
10796 }
10797
10798 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10799 if let Some(status) = self
10800 .addons
10801 .iter()
10802 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10803 {
10804 return Some(status);
10805 }
10806 self.project
10807 .as_ref()?
10808 .read(cx)
10809 .status_for_buffer_id(buffer_id, cx)
10810 }
10811
10812 pub fn open_active_item_in_terminal(
10813 &mut self,
10814 _: &OpenInTerminal,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 ) {
10818 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10819 let project_path = buffer.read(cx).project_path(cx)?;
10820 let project = self.project()?.read(cx);
10821 let entry = project.entry_for_path(&project_path, cx)?;
10822 let parent = match &entry.canonical_path {
10823 Some(canonical_path) => canonical_path.to_path_buf(),
10824 None => project.absolute_path(&project_path, cx)?,
10825 }
10826 .parent()?
10827 .to_path_buf();
10828 Some(parent)
10829 }) {
10830 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10831 }
10832 }
10833
10834 fn set_breakpoint_context_menu(
10835 &mut self,
10836 display_row: DisplayRow,
10837 position: Option<Anchor>,
10838 clicked_point: gpui::Point<Pixels>,
10839 window: &mut Window,
10840 cx: &mut Context<Self>,
10841 ) {
10842 let source = self
10843 .buffer
10844 .read(cx)
10845 .snapshot(cx)
10846 .anchor_before(Point::new(display_row.0, 0u32));
10847
10848 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10849
10850 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10851 self,
10852 source,
10853 clicked_point,
10854 context_menu,
10855 window,
10856 cx,
10857 );
10858 }
10859
10860 fn add_edit_breakpoint_block(
10861 &mut self,
10862 anchor: Anchor,
10863 breakpoint: &Breakpoint,
10864 edit_action: BreakpointPromptEditAction,
10865 window: &mut Window,
10866 cx: &mut Context<Self>,
10867 ) {
10868 let weak_editor = cx.weak_entity();
10869 let bp_prompt = cx.new(|cx| {
10870 BreakpointPromptEditor::new(
10871 weak_editor,
10872 anchor,
10873 breakpoint.clone(),
10874 edit_action,
10875 window,
10876 cx,
10877 )
10878 });
10879
10880 let height = bp_prompt.update(cx, |this, cx| {
10881 this.prompt
10882 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10883 });
10884 let cloned_prompt = bp_prompt.clone();
10885 let blocks = vec![BlockProperties {
10886 style: BlockStyle::Sticky,
10887 placement: BlockPlacement::Above(anchor),
10888 height: Some(height),
10889 render: Arc::new(move |cx| {
10890 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10891 cloned_prompt.clone().into_any_element()
10892 }),
10893 priority: 0,
10894 }];
10895
10896 let focus_handle = bp_prompt.focus_handle(cx);
10897 window.focus(&focus_handle);
10898
10899 let block_ids = self.insert_blocks(blocks, None, cx);
10900 bp_prompt.update(cx, |prompt, _| {
10901 prompt.add_block_ids(block_ids);
10902 });
10903 }
10904
10905 pub(crate) fn breakpoint_at_row(
10906 &self,
10907 row: u32,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) -> Option<(Anchor, Breakpoint)> {
10911 let snapshot = self.snapshot(window, cx);
10912 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10913
10914 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10915 }
10916
10917 pub(crate) fn breakpoint_at_anchor(
10918 &self,
10919 breakpoint_position: Anchor,
10920 snapshot: &EditorSnapshot,
10921 cx: &mut Context<Self>,
10922 ) -> Option<(Anchor, Breakpoint)> {
10923 let buffer = self
10924 .buffer
10925 .read(cx)
10926 .buffer_for_anchor(breakpoint_position, cx)?;
10927
10928 let enclosing_excerpt = breakpoint_position.excerpt_id;
10929 let buffer_snapshot = buffer.read(cx).snapshot();
10930
10931 let row = buffer_snapshot
10932 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10933 .row;
10934
10935 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10936 let anchor_end = snapshot
10937 .buffer_snapshot()
10938 .anchor_after(Point::new(row, line_len));
10939
10940 self.breakpoint_store
10941 .as_ref()?
10942 .read_with(cx, |breakpoint_store, cx| {
10943 breakpoint_store
10944 .breakpoints(
10945 &buffer,
10946 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10947 &buffer_snapshot,
10948 cx,
10949 )
10950 .next()
10951 .and_then(|(bp, _)| {
10952 let breakpoint_row = buffer_snapshot
10953 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10954 .row;
10955
10956 if breakpoint_row == row {
10957 snapshot
10958 .buffer_snapshot()
10959 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10960 .map(|position| (position, bp.bp.clone()))
10961 } else {
10962 None
10963 }
10964 })
10965 })
10966 }
10967
10968 pub fn edit_log_breakpoint(
10969 &mut self,
10970 _: &EditLogBreakpoint,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10975 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10976 message: None,
10977 state: BreakpointState::Enabled,
10978 condition: None,
10979 hit_condition: None,
10980 });
10981
10982 self.add_edit_breakpoint_block(
10983 anchor,
10984 &breakpoint,
10985 BreakpointPromptEditAction::Log,
10986 window,
10987 cx,
10988 );
10989 }
10990 }
10991
10992 fn breakpoints_at_cursors(
10993 &self,
10994 window: &mut Window,
10995 cx: &mut Context<Self>,
10996 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10997 let snapshot = self.snapshot(window, cx);
10998 let cursors = self
10999 .selections
11000 .disjoint_anchors_arc()
11001 .iter()
11002 .map(|selection| {
11003 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11004
11005 let breakpoint_position = self
11006 .breakpoint_at_row(cursor_position.row, window, cx)
11007 .map(|bp| bp.0)
11008 .unwrap_or_else(|| {
11009 snapshot
11010 .display_snapshot
11011 .buffer_snapshot()
11012 .anchor_after(Point::new(cursor_position.row, 0))
11013 });
11014
11015 let breakpoint = self
11016 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11017 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11018
11019 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11020 })
11021 // 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.
11022 .collect::<HashMap<Anchor, _>>();
11023
11024 cursors.into_iter().collect()
11025 }
11026
11027 pub fn enable_breakpoint(
11028 &mut self,
11029 _: &crate::actions::EnableBreakpoint,
11030 window: &mut Window,
11031 cx: &mut Context<Self>,
11032 ) {
11033 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11034 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11035 continue;
11036 };
11037 self.edit_breakpoint_at_anchor(
11038 anchor,
11039 breakpoint,
11040 BreakpointEditAction::InvertState,
11041 cx,
11042 );
11043 }
11044 }
11045
11046 pub fn disable_breakpoint(
11047 &mut self,
11048 _: &crate::actions::DisableBreakpoint,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11053 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11054 continue;
11055 };
11056 self.edit_breakpoint_at_anchor(
11057 anchor,
11058 breakpoint,
11059 BreakpointEditAction::InvertState,
11060 cx,
11061 );
11062 }
11063 }
11064
11065 pub fn toggle_breakpoint(
11066 &mut self,
11067 _: &crate::actions::ToggleBreakpoint,
11068 window: &mut Window,
11069 cx: &mut Context<Self>,
11070 ) {
11071 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11072 if let Some(breakpoint) = breakpoint {
11073 self.edit_breakpoint_at_anchor(
11074 anchor,
11075 breakpoint,
11076 BreakpointEditAction::Toggle,
11077 cx,
11078 );
11079 } else {
11080 self.edit_breakpoint_at_anchor(
11081 anchor,
11082 Breakpoint::new_standard(),
11083 BreakpointEditAction::Toggle,
11084 cx,
11085 );
11086 }
11087 }
11088 }
11089
11090 pub fn edit_breakpoint_at_anchor(
11091 &mut self,
11092 breakpoint_position: Anchor,
11093 breakpoint: Breakpoint,
11094 edit_action: BreakpointEditAction,
11095 cx: &mut Context<Self>,
11096 ) {
11097 let Some(breakpoint_store) = &self.breakpoint_store else {
11098 return;
11099 };
11100
11101 let Some(buffer) = self
11102 .buffer
11103 .read(cx)
11104 .buffer_for_anchor(breakpoint_position, cx)
11105 else {
11106 return;
11107 };
11108
11109 breakpoint_store.update(cx, |breakpoint_store, cx| {
11110 breakpoint_store.toggle_breakpoint(
11111 buffer,
11112 BreakpointWithPosition {
11113 position: breakpoint_position.text_anchor,
11114 bp: breakpoint,
11115 },
11116 edit_action,
11117 cx,
11118 );
11119 });
11120
11121 cx.notify();
11122 }
11123
11124 #[cfg(any(test, feature = "test-support"))]
11125 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11126 self.breakpoint_store.clone()
11127 }
11128
11129 pub fn prepare_restore_change(
11130 &self,
11131 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11132 hunk: &MultiBufferDiffHunk,
11133 cx: &mut App,
11134 ) -> Option<()> {
11135 if hunk.is_created_file() {
11136 return None;
11137 }
11138 let buffer = self.buffer.read(cx);
11139 let diff = buffer.diff_for(hunk.buffer_id)?;
11140 let buffer = buffer.buffer(hunk.buffer_id)?;
11141 let buffer = buffer.read(cx);
11142 let original_text = diff
11143 .read(cx)
11144 .base_text()
11145 .as_rope()
11146 .slice(hunk.diff_base_byte_range.clone());
11147 let buffer_snapshot = buffer.snapshot();
11148 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11149 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11150 probe
11151 .0
11152 .start
11153 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11154 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11155 }) {
11156 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11157 Some(())
11158 } else {
11159 None
11160 }
11161 }
11162
11163 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11164 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11165 }
11166
11167 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11168 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11169 }
11170
11171 fn manipulate_lines<M>(
11172 &mut self,
11173 window: &mut Window,
11174 cx: &mut Context<Self>,
11175 mut manipulate: M,
11176 ) where
11177 M: FnMut(&str) -> LineManipulationResult,
11178 {
11179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11180
11181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11182 let buffer = self.buffer.read(cx).snapshot(cx);
11183
11184 let mut edits = Vec::new();
11185
11186 let selections = self.selections.all::<Point>(&display_map);
11187 let mut selections = selections.iter().peekable();
11188 let mut contiguous_row_selections = Vec::new();
11189 let mut new_selections = Vec::new();
11190 let mut added_lines = 0;
11191 let mut removed_lines = 0;
11192
11193 while let Some(selection) = selections.next() {
11194 let (start_row, end_row) = consume_contiguous_rows(
11195 &mut contiguous_row_selections,
11196 selection,
11197 &display_map,
11198 &mut selections,
11199 );
11200
11201 let start_point = Point::new(start_row.0, 0);
11202 let end_point = Point::new(
11203 end_row.previous_row().0,
11204 buffer.line_len(end_row.previous_row()),
11205 );
11206 let text = buffer
11207 .text_for_range(start_point..end_point)
11208 .collect::<String>();
11209
11210 let LineManipulationResult {
11211 new_text,
11212 line_count_before,
11213 line_count_after,
11214 } = manipulate(&text);
11215
11216 edits.push((start_point..end_point, new_text));
11217
11218 // Selections must change based on added and removed line count
11219 let start_row =
11220 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11221 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11222 new_selections.push(Selection {
11223 id: selection.id,
11224 start: start_row,
11225 end: end_row,
11226 goal: SelectionGoal::None,
11227 reversed: selection.reversed,
11228 });
11229
11230 if line_count_after > line_count_before {
11231 added_lines += line_count_after - line_count_before;
11232 } else if line_count_before > line_count_after {
11233 removed_lines += line_count_before - line_count_after;
11234 }
11235 }
11236
11237 self.transact(window, cx, |this, window, cx| {
11238 let buffer = this.buffer.update(cx, |buffer, cx| {
11239 buffer.edit(edits, None, cx);
11240 buffer.snapshot(cx)
11241 });
11242
11243 // Recalculate offsets on newly edited buffer
11244 let new_selections = new_selections
11245 .iter()
11246 .map(|s| {
11247 let start_point = Point::new(s.start.0, 0);
11248 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11249 Selection {
11250 id: s.id,
11251 start: buffer.point_to_offset(start_point),
11252 end: buffer.point_to_offset(end_point),
11253 goal: s.goal,
11254 reversed: s.reversed,
11255 }
11256 })
11257 .collect();
11258
11259 this.change_selections(Default::default(), window, cx, |s| {
11260 s.select(new_selections);
11261 });
11262
11263 this.request_autoscroll(Autoscroll::fit(), cx);
11264 });
11265 }
11266
11267 fn manipulate_immutable_lines<Fn>(
11268 &mut self,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 mut callback: Fn,
11272 ) where
11273 Fn: FnMut(&mut Vec<&str>),
11274 {
11275 self.manipulate_lines(window, cx, |text| {
11276 let mut lines: Vec<&str> = text.split('\n').collect();
11277 let line_count_before = lines.len();
11278
11279 callback(&mut lines);
11280
11281 LineManipulationResult {
11282 new_text: lines.join("\n"),
11283 line_count_before,
11284 line_count_after: lines.len(),
11285 }
11286 });
11287 }
11288
11289 fn manipulate_mutable_lines<Fn>(
11290 &mut self,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 mut callback: Fn,
11294 ) where
11295 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11296 {
11297 self.manipulate_lines(window, cx, |text| {
11298 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11299 let line_count_before = lines.len();
11300
11301 callback(&mut lines);
11302
11303 LineManipulationResult {
11304 new_text: lines.join("\n"),
11305 line_count_before,
11306 line_count_after: lines.len(),
11307 }
11308 });
11309 }
11310
11311 pub fn convert_indentation_to_spaces(
11312 &mut self,
11313 _: &ConvertIndentationToSpaces,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 let settings = self.buffer.read(cx).language_settings(cx);
11318 let tab_size = settings.tab_size.get() as usize;
11319
11320 self.manipulate_mutable_lines(window, cx, |lines| {
11321 // Allocates a reasonably sized scratch buffer once for the whole loop
11322 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11323 // Avoids recomputing spaces that could be inserted many times
11324 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11325 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11326 .collect();
11327
11328 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11329 let mut chars = line.as_ref().chars();
11330 let mut col = 0;
11331 let mut changed = false;
11332
11333 for ch in chars.by_ref() {
11334 match ch {
11335 ' ' => {
11336 reindented_line.push(' ');
11337 col += 1;
11338 }
11339 '\t' => {
11340 // \t are converted to spaces depending on the current column
11341 let spaces_len = tab_size - (col % tab_size);
11342 reindented_line.extend(&space_cache[spaces_len - 1]);
11343 col += spaces_len;
11344 changed = true;
11345 }
11346 _ => {
11347 // If we dont append before break, the character is consumed
11348 reindented_line.push(ch);
11349 break;
11350 }
11351 }
11352 }
11353
11354 if !changed {
11355 reindented_line.clear();
11356 continue;
11357 }
11358 // Append the rest of the line and replace old reference with new one
11359 reindented_line.extend(chars);
11360 *line = Cow::Owned(reindented_line.clone());
11361 reindented_line.clear();
11362 }
11363 });
11364 }
11365
11366 pub fn convert_indentation_to_tabs(
11367 &mut self,
11368 _: &ConvertIndentationToTabs,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 ) {
11372 let settings = self.buffer.read(cx).language_settings(cx);
11373 let tab_size = settings.tab_size.get() as usize;
11374
11375 self.manipulate_mutable_lines(window, cx, |lines| {
11376 // Allocates a reasonably sized buffer once for the whole loop
11377 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11378 // Avoids recomputing spaces that could be inserted many times
11379 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11380 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11381 .collect();
11382
11383 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11384 let mut chars = line.chars();
11385 let mut spaces_count = 0;
11386 let mut first_non_indent_char = None;
11387 let mut changed = false;
11388
11389 for ch in chars.by_ref() {
11390 match ch {
11391 ' ' => {
11392 // Keep track of spaces. Append \t when we reach tab_size
11393 spaces_count += 1;
11394 changed = true;
11395 if spaces_count == tab_size {
11396 reindented_line.push('\t');
11397 spaces_count = 0;
11398 }
11399 }
11400 '\t' => {
11401 reindented_line.push('\t');
11402 spaces_count = 0;
11403 }
11404 _ => {
11405 // Dont append it yet, we might have remaining spaces
11406 first_non_indent_char = Some(ch);
11407 break;
11408 }
11409 }
11410 }
11411
11412 if !changed {
11413 reindented_line.clear();
11414 continue;
11415 }
11416 // Remaining spaces that didn't make a full tab stop
11417 if spaces_count > 0 {
11418 reindented_line.extend(&space_cache[spaces_count - 1]);
11419 }
11420 // If we consume an extra character that was not indentation, add it back
11421 if let Some(extra_char) = first_non_indent_char {
11422 reindented_line.push(extra_char);
11423 }
11424 // Append the rest of the line and replace old reference with new one
11425 reindented_line.extend(chars);
11426 *line = Cow::Owned(reindented_line.clone());
11427 reindented_line.clear();
11428 }
11429 });
11430 }
11431
11432 pub fn convert_to_upper_case(
11433 &mut self,
11434 _: &ConvertToUpperCase,
11435 window: &mut Window,
11436 cx: &mut Context<Self>,
11437 ) {
11438 self.manipulate_text(window, cx, |text| text.to_uppercase())
11439 }
11440
11441 pub fn convert_to_lower_case(
11442 &mut self,
11443 _: &ConvertToLowerCase,
11444 window: &mut Window,
11445 cx: &mut Context<Self>,
11446 ) {
11447 self.manipulate_text(window, cx, |text| text.to_lowercase())
11448 }
11449
11450 pub fn convert_to_title_case(
11451 &mut self,
11452 _: &ConvertToTitleCase,
11453 window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 self.manipulate_text(window, cx, |text| {
11457 text.split('\n')
11458 .map(|line| line.to_case(Case::Title))
11459 .join("\n")
11460 })
11461 }
11462
11463 pub fn convert_to_snake_case(
11464 &mut self,
11465 _: &ConvertToSnakeCase,
11466 window: &mut Window,
11467 cx: &mut Context<Self>,
11468 ) {
11469 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11470 }
11471
11472 pub fn convert_to_kebab_case(
11473 &mut self,
11474 _: &ConvertToKebabCase,
11475 window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11479 }
11480
11481 pub fn convert_to_upper_camel_case(
11482 &mut self,
11483 _: &ConvertToUpperCamelCase,
11484 window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 self.manipulate_text(window, cx, |text| {
11488 text.split('\n')
11489 .map(|line| line.to_case(Case::UpperCamel))
11490 .join("\n")
11491 })
11492 }
11493
11494 pub fn convert_to_lower_camel_case(
11495 &mut self,
11496 _: &ConvertToLowerCamelCase,
11497 window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11501 }
11502
11503 pub fn convert_to_opposite_case(
11504 &mut self,
11505 _: &ConvertToOppositeCase,
11506 window: &mut Window,
11507 cx: &mut Context<Self>,
11508 ) {
11509 self.manipulate_text(window, cx, |text| {
11510 text.chars()
11511 .fold(String::with_capacity(text.len()), |mut t, c| {
11512 if c.is_uppercase() {
11513 t.extend(c.to_lowercase());
11514 } else {
11515 t.extend(c.to_uppercase());
11516 }
11517 t
11518 })
11519 })
11520 }
11521
11522 pub fn convert_to_sentence_case(
11523 &mut self,
11524 _: &ConvertToSentenceCase,
11525 window: &mut Window,
11526 cx: &mut Context<Self>,
11527 ) {
11528 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11529 }
11530
11531 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11532 self.manipulate_text(window, cx, |text| {
11533 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11534 if has_upper_case_characters {
11535 text.to_lowercase()
11536 } else {
11537 text.to_uppercase()
11538 }
11539 })
11540 }
11541
11542 pub fn convert_to_rot13(
11543 &mut self,
11544 _: &ConvertToRot13,
11545 window: &mut Window,
11546 cx: &mut Context<Self>,
11547 ) {
11548 self.manipulate_text(window, cx, |text| {
11549 text.chars()
11550 .map(|c| match c {
11551 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11552 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11553 _ => c,
11554 })
11555 .collect()
11556 })
11557 }
11558
11559 pub fn convert_to_rot47(
11560 &mut self,
11561 _: &ConvertToRot47,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 self.manipulate_text(window, cx, |text| {
11566 text.chars()
11567 .map(|c| {
11568 let code_point = c as u32;
11569 if code_point >= 33 && code_point <= 126 {
11570 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11571 }
11572 c
11573 })
11574 .collect()
11575 })
11576 }
11577
11578 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11579 where
11580 Fn: FnMut(&str) -> String,
11581 {
11582 let buffer = self.buffer.read(cx).snapshot(cx);
11583
11584 let mut new_selections = Vec::new();
11585 let mut edits = Vec::new();
11586 let mut selection_adjustment = 0i32;
11587
11588 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11589 let selection_is_empty = selection.is_empty();
11590
11591 let (start, end) = if selection_is_empty {
11592 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11593 (word_range.start, word_range.end)
11594 } else {
11595 (
11596 buffer.point_to_offset(selection.start),
11597 buffer.point_to_offset(selection.end),
11598 )
11599 };
11600
11601 let text = buffer.text_for_range(start..end).collect::<String>();
11602 let old_length = text.len() as i32;
11603 let text = callback(&text);
11604
11605 new_selections.push(Selection {
11606 start: (start as i32 - selection_adjustment) as usize,
11607 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11608 goal: SelectionGoal::None,
11609 id: selection.id,
11610 reversed: selection.reversed,
11611 });
11612
11613 selection_adjustment += old_length - text.len() as i32;
11614
11615 edits.push((start..end, text));
11616 }
11617
11618 self.transact(window, cx, |this, window, cx| {
11619 this.buffer.update(cx, |buffer, cx| {
11620 buffer.edit(edits, None, cx);
11621 });
11622
11623 this.change_selections(Default::default(), window, cx, |s| {
11624 s.select(new_selections);
11625 });
11626
11627 this.request_autoscroll(Autoscroll::fit(), cx);
11628 });
11629 }
11630
11631 pub fn move_selection_on_drop(
11632 &mut self,
11633 selection: &Selection<Anchor>,
11634 target: DisplayPoint,
11635 is_cut: bool,
11636 window: &mut Window,
11637 cx: &mut Context<Self>,
11638 ) {
11639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11640 let buffer = display_map.buffer_snapshot();
11641 let mut edits = Vec::new();
11642 let insert_point = display_map
11643 .clip_point(target, Bias::Left)
11644 .to_point(&display_map);
11645 let text = buffer
11646 .text_for_range(selection.start..selection.end)
11647 .collect::<String>();
11648 if is_cut {
11649 edits.push(((selection.start..selection.end), String::new()));
11650 }
11651 let insert_anchor = buffer.anchor_before(insert_point);
11652 edits.push(((insert_anchor..insert_anchor), text));
11653 let last_edit_start = insert_anchor.bias_left(buffer);
11654 let last_edit_end = insert_anchor.bias_right(buffer);
11655 self.transact(window, cx, |this, window, cx| {
11656 this.buffer.update(cx, |buffer, cx| {
11657 buffer.edit(edits, None, cx);
11658 });
11659 this.change_selections(Default::default(), window, cx, |s| {
11660 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11661 });
11662 });
11663 }
11664
11665 pub fn clear_selection_drag_state(&mut self) {
11666 self.selection_drag_state = SelectionDragState::None;
11667 }
11668
11669 pub fn duplicate(
11670 &mut self,
11671 upwards: bool,
11672 whole_lines: bool,
11673 window: &mut Window,
11674 cx: &mut Context<Self>,
11675 ) {
11676 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11677
11678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11679 let buffer = display_map.buffer_snapshot();
11680 let selections = self.selections.all::<Point>(&display_map);
11681
11682 let mut edits = Vec::new();
11683 let mut selections_iter = selections.iter().peekable();
11684 while let Some(selection) = selections_iter.next() {
11685 let mut rows = selection.spanned_rows(false, &display_map);
11686 // duplicate line-wise
11687 if whole_lines || selection.start == selection.end {
11688 // Avoid duplicating the same lines twice.
11689 while let Some(next_selection) = selections_iter.peek() {
11690 let next_rows = next_selection.spanned_rows(false, &display_map);
11691 if next_rows.start < rows.end {
11692 rows.end = next_rows.end;
11693 selections_iter.next().unwrap();
11694 } else {
11695 break;
11696 }
11697 }
11698
11699 // Copy the text from the selected row region and splice it either at the start
11700 // or end of the region.
11701 let start = Point::new(rows.start.0, 0);
11702 let end = Point::new(
11703 rows.end.previous_row().0,
11704 buffer.line_len(rows.end.previous_row()),
11705 );
11706
11707 let mut text = buffer.text_for_range(start..end).collect::<String>();
11708
11709 let insert_location = if upwards {
11710 // When duplicating upward, we need to insert before the current line.
11711 // If we're on the last line and it doesn't end with a newline,
11712 // we need to add a newline before the duplicated content.
11713 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11714 && buffer.max_point().column > 0
11715 && !text.ends_with('\n');
11716
11717 if needs_leading_newline {
11718 text.insert(0, '\n');
11719 end
11720 } else {
11721 text.push('\n');
11722 Point::new(rows.start.0, 0)
11723 }
11724 } else {
11725 text.push('\n');
11726 start
11727 };
11728 edits.push((insert_location..insert_location, text));
11729 } else {
11730 // duplicate character-wise
11731 let start = selection.start;
11732 let end = selection.end;
11733 let text = buffer.text_for_range(start..end).collect::<String>();
11734 edits.push((selection.end..selection.end, text));
11735 }
11736 }
11737
11738 self.transact(window, cx, |this, window, cx| {
11739 this.buffer.update(cx, |buffer, cx| {
11740 buffer.edit(edits, None, cx);
11741 });
11742
11743 // When duplicating upward with whole lines, move the cursor to the duplicated line
11744 if upwards && whole_lines {
11745 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11746
11747 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11748 let mut new_ranges = Vec::new();
11749 let selections = s.all::<Point>(&display_map);
11750 let mut selections_iter = selections.iter().peekable();
11751
11752 while let Some(first_selection) = selections_iter.next() {
11753 // Group contiguous selections together to find the total row span
11754 let mut group_selections = vec![first_selection];
11755 let mut rows = first_selection.spanned_rows(false, &display_map);
11756
11757 while let Some(next_selection) = selections_iter.peek() {
11758 let next_rows = next_selection.spanned_rows(false, &display_map);
11759 if next_rows.start < rows.end {
11760 rows.end = next_rows.end;
11761 group_selections.push(selections_iter.next().unwrap());
11762 } else {
11763 break;
11764 }
11765 }
11766
11767 let row_count = rows.end.0 - rows.start.0;
11768
11769 // Move all selections in this group up by the total number of duplicated rows
11770 for selection in group_selections {
11771 let new_start = Point::new(
11772 selection.start.row.saturating_sub(row_count),
11773 selection.start.column,
11774 );
11775
11776 let new_end = Point::new(
11777 selection.end.row.saturating_sub(row_count),
11778 selection.end.column,
11779 );
11780
11781 new_ranges.push(new_start..new_end);
11782 }
11783 }
11784
11785 s.select_ranges(new_ranges);
11786 });
11787 }
11788
11789 this.request_autoscroll(Autoscroll::fit(), cx);
11790 });
11791 }
11792
11793 pub fn duplicate_line_up(
11794 &mut self,
11795 _: &DuplicateLineUp,
11796 window: &mut Window,
11797 cx: &mut Context<Self>,
11798 ) {
11799 self.duplicate(true, true, window, cx);
11800 }
11801
11802 pub fn duplicate_line_down(
11803 &mut self,
11804 _: &DuplicateLineDown,
11805 window: &mut Window,
11806 cx: &mut Context<Self>,
11807 ) {
11808 self.duplicate(false, true, window, cx);
11809 }
11810
11811 pub fn duplicate_selection(
11812 &mut self,
11813 _: &DuplicateSelection,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 self.duplicate(false, false, window, cx);
11818 }
11819
11820 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11822 if self.mode.is_single_line() {
11823 cx.propagate();
11824 return;
11825 }
11826
11827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11828 let buffer = self.buffer.read(cx).snapshot(cx);
11829
11830 let mut edits = Vec::new();
11831 let mut unfold_ranges = Vec::new();
11832 let mut refold_creases = Vec::new();
11833
11834 let selections = self.selections.all::<Point>(&display_map);
11835 let mut selections = selections.iter().peekable();
11836 let mut contiguous_row_selections = Vec::new();
11837 let mut new_selections = Vec::new();
11838
11839 while let Some(selection) = selections.next() {
11840 // Find all the selections that span a contiguous row range
11841 let (start_row, end_row) = consume_contiguous_rows(
11842 &mut contiguous_row_selections,
11843 selection,
11844 &display_map,
11845 &mut selections,
11846 );
11847
11848 // Move the text spanned by the row range to be before the line preceding the row range
11849 if start_row.0 > 0 {
11850 let range_to_move = Point::new(
11851 start_row.previous_row().0,
11852 buffer.line_len(start_row.previous_row()),
11853 )
11854 ..Point::new(
11855 end_row.previous_row().0,
11856 buffer.line_len(end_row.previous_row()),
11857 );
11858 let insertion_point = display_map
11859 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11860 .0;
11861
11862 // Don't move lines across excerpts
11863 if buffer
11864 .excerpt_containing(insertion_point..range_to_move.end)
11865 .is_some()
11866 {
11867 let text = buffer
11868 .text_for_range(range_to_move.clone())
11869 .flat_map(|s| s.chars())
11870 .skip(1)
11871 .chain(['\n'])
11872 .collect::<String>();
11873
11874 edits.push((
11875 buffer.anchor_after(range_to_move.start)
11876 ..buffer.anchor_before(range_to_move.end),
11877 String::new(),
11878 ));
11879 let insertion_anchor = buffer.anchor_after(insertion_point);
11880 edits.push((insertion_anchor..insertion_anchor, text));
11881
11882 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11883
11884 // Move selections up
11885 new_selections.extend(contiguous_row_selections.drain(..).map(
11886 |mut selection| {
11887 selection.start.row -= row_delta;
11888 selection.end.row -= row_delta;
11889 selection
11890 },
11891 ));
11892
11893 // Move folds up
11894 unfold_ranges.push(range_to_move.clone());
11895 for fold in display_map.folds_in_range(
11896 buffer.anchor_before(range_to_move.start)
11897 ..buffer.anchor_after(range_to_move.end),
11898 ) {
11899 let mut start = fold.range.start.to_point(&buffer);
11900 let mut end = fold.range.end.to_point(&buffer);
11901 start.row -= row_delta;
11902 end.row -= row_delta;
11903 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11904 }
11905 }
11906 }
11907
11908 // If we didn't move line(s), preserve the existing selections
11909 new_selections.append(&mut contiguous_row_selections);
11910 }
11911
11912 self.transact(window, cx, |this, window, cx| {
11913 this.unfold_ranges(&unfold_ranges, true, true, cx);
11914 this.buffer.update(cx, |buffer, cx| {
11915 for (range, text) in edits {
11916 buffer.edit([(range, text)], None, cx);
11917 }
11918 });
11919 this.fold_creases(refold_creases, true, window, cx);
11920 this.change_selections(Default::default(), window, cx, |s| {
11921 s.select(new_selections);
11922 })
11923 });
11924 }
11925
11926 pub fn move_line_down(
11927 &mut self,
11928 _: &MoveLineDown,
11929 window: &mut Window,
11930 cx: &mut Context<Self>,
11931 ) {
11932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11933 if self.mode.is_single_line() {
11934 cx.propagate();
11935 return;
11936 }
11937
11938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11939 let buffer = self.buffer.read(cx).snapshot(cx);
11940
11941 let mut edits = Vec::new();
11942 let mut unfold_ranges = Vec::new();
11943 let mut refold_creases = Vec::new();
11944
11945 let selections = self.selections.all::<Point>(&display_map);
11946 let mut selections = selections.iter().peekable();
11947 let mut contiguous_row_selections = Vec::new();
11948 let mut new_selections = Vec::new();
11949
11950 while let Some(selection) = selections.next() {
11951 // Find all the selections that span a contiguous row range
11952 let (start_row, end_row) = consume_contiguous_rows(
11953 &mut contiguous_row_selections,
11954 selection,
11955 &display_map,
11956 &mut selections,
11957 );
11958
11959 // Move the text spanned by the row range to be after the last line of the row range
11960 if end_row.0 <= buffer.max_point().row {
11961 let range_to_move =
11962 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11963 let insertion_point = display_map
11964 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11965 .0;
11966
11967 // Don't move lines across excerpt boundaries
11968 if buffer
11969 .excerpt_containing(range_to_move.start..insertion_point)
11970 .is_some()
11971 {
11972 let mut text = String::from("\n");
11973 text.extend(buffer.text_for_range(range_to_move.clone()));
11974 text.pop(); // Drop trailing newline
11975 edits.push((
11976 buffer.anchor_after(range_to_move.start)
11977 ..buffer.anchor_before(range_to_move.end),
11978 String::new(),
11979 ));
11980 let insertion_anchor = buffer.anchor_after(insertion_point);
11981 edits.push((insertion_anchor..insertion_anchor, text));
11982
11983 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11984
11985 // Move selections down
11986 new_selections.extend(contiguous_row_selections.drain(..).map(
11987 |mut selection| {
11988 selection.start.row += row_delta;
11989 selection.end.row += row_delta;
11990 selection
11991 },
11992 ));
11993
11994 // Move folds down
11995 unfold_ranges.push(range_to_move.clone());
11996 for fold in display_map.folds_in_range(
11997 buffer.anchor_before(range_to_move.start)
11998 ..buffer.anchor_after(range_to_move.end),
11999 ) {
12000 let mut start = fold.range.start.to_point(&buffer);
12001 let mut end = fold.range.end.to_point(&buffer);
12002 start.row += row_delta;
12003 end.row += row_delta;
12004 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12005 }
12006 }
12007 }
12008
12009 // If we didn't move line(s), preserve the existing selections
12010 new_selections.append(&mut contiguous_row_selections);
12011 }
12012
12013 self.transact(window, cx, |this, window, cx| {
12014 this.unfold_ranges(&unfold_ranges, true, true, cx);
12015 this.buffer.update(cx, |buffer, cx| {
12016 for (range, text) in edits {
12017 buffer.edit([(range, text)], None, cx);
12018 }
12019 });
12020 this.fold_creases(refold_creases, true, window, cx);
12021 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12022 });
12023 }
12024
12025 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12027 let text_layout_details = &self.text_layout_details(window);
12028 self.transact(window, cx, |this, window, cx| {
12029 let edits = this.change_selections(Default::default(), window, cx, |s| {
12030 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12031 s.move_with(|display_map, selection| {
12032 if !selection.is_empty() {
12033 return;
12034 }
12035
12036 let mut head = selection.head();
12037 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12038 if head.column() == display_map.line_len(head.row()) {
12039 transpose_offset = display_map
12040 .buffer_snapshot()
12041 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12042 }
12043
12044 if transpose_offset == 0 {
12045 return;
12046 }
12047
12048 *head.column_mut() += 1;
12049 head = display_map.clip_point(head, Bias::Right);
12050 let goal = SelectionGoal::HorizontalPosition(
12051 display_map
12052 .x_for_display_point(head, text_layout_details)
12053 .into(),
12054 );
12055 selection.collapse_to(head, goal);
12056
12057 let transpose_start = display_map
12058 .buffer_snapshot()
12059 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12060 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12061 let transpose_end = display_map
12062 .buffer_snapshot()
12063 .clip_offset(transpose_offset + 1, Bias::Right);
12064 if let Some(ch) = display_map
12065 .buffer_snapshot()
12066 .chars_at(transpose_start)
12067 .next()
12068 {
12069 edits.push((transpose_start..transpose_offset, String::new()));
12070 edits.push((transpose_end..transpose_end, ch.to_string()));
12071 }
12072 }
12073 });
12074 edits
12075 });
12076 this.buffer
12077 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12078 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12079 this.change_selections(Default::default(), window, cx, |s| {
12080 s.select(selections);
12081 });
12082 });
12083 }
12084
12085 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12087 if self.mode.is_single_line() {
12088 cx.propagate();
12089 return;
12090 }
12091
12092 self.rewrap_impl(RewrapOptions::default(), cx)
12093 }
12094
12095 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12096 let buffer = self.buffer.read(cx).snapshot(cx);
12097 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12098
12099 #[derive(Clone, Debug, PartialEq)]
12100 enum CommentFormat {
12101 /// single line comment, with prefix for line
12102 Line(String),
12103 /// single line within a block comment, with prefix for line
12104 BlockLine(String),
12105 /// a single line of a block comment that includes the initial delimiter
12106 BlockCommentWithStart(BlockCommentConfig),
12107 /// a single line of a block comment that includes the ending delimiter
12108 BlockCommentWithEnd(BlockCommentConfig),
12109 }
12110
12111 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12112 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12113 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12114 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12115 .peekable();
12116
12117 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12118 row
12119 } else {
12120 return Vec::new();
12121 };
12122
12123 let language_settings = buffer.language_settings_at(selection.head(), cx);
12124 let language_scope = buffer.language_scope_at(selection.head());
12125
12126 let indent_and_prefix_for_row =
12127 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12128 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12129 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12130 &language_scope
12131 {
12132 let indent_end = Point::new(row, indent.len);
12133 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12134 let line_text_after_indent = buffer
12135 .text_for_range(indent_end..line_end)
12136 .collect::<String>();
12137
12138 let is_within_comment_override = buffer
12139 .language_scope_at(indent_end)
12140 .is_some_and(|scope| scope.override_name() == Some("comment"));
12141 let comment_delimiters = if is_within_comment_override {
12142 // we are within a comment syntax node, but we don't
12143 // yet know what kind of comment: block, doc or line
12144 match (
12145 language_scope.documentation_comment(),
12146 language_scope.block_comment(),
12147 ) {
12148 (Some(config), _) | (_, Some(config))
12149 if buffer.contains_str_at(indent_end, &config.start) =>
12150 {
12151 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12152 }
12153 (Some(config), _) | (_, Some(config))
12154 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12155 {
12156 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12157 }
12158 (Some(config), _) | (_, Some(config))
12159 if buffer.contains_str_at(indent_end, &config.prefix) =>
12160 {
12161 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12162 }
12163 (_, _) => language_scope
12164 .line_comment_prefixes()
12165 .iter()
12166 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12167 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12168 }
12169 } else {
12170 // we not in an overridden comment node, but we may
12171 // be within a non-overridden line comment node
12172 language_scope
12173 .line_comment_prefixes()
12174 .iter()
12175 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12176 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12177 };
12178
12179 let rewrap_prefix = language_scope
12180 .rewrap_prefixes()
12181 .iter()
12182 .find_map(|prefix_regex| {
12183 prefix_regex.find(&line_text_after_indent).map(|mat| {
12184 if mat.start() == 0 {
12185 Some(mat.as_str().to_string())
12186 } else {
12187 None
12188 }
12189 })
12190 })
12191 .flatten();
12192 (comment_delimiters, rewrap_prefix)
12193 } else {
12194 (None, None)
12195 };
12196 (indent, comment_prefix, rewrap_prefix)
12197 };
12198
12199 let mut ranges = Vec::new();
12200 let from_empty_selection = selection.is_empty();
12201
12202 let mut current_range_start = first_row;
12203 let mut prev_row = first_row;
12204 let (
12205 mut current_range_indent,
12206 mut current_range_comment_delimiters,
12207 mut current_range_rewrap_prefix,
12208 ) = indent_and_prefix_for_row(first_row);
12209
12210 for row in non_blank_rows_iter.skip(1) {
12211 let has_paragraph_break = row > prev_row + 1;
12212
12213 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12214 indent_and_prefix_for_row(row);
12215
12216 let has_indent_change = row_indent != current_range_indent;
12217 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12218
12219 let has_boundary_change = has_comment_change
12220 || row_rewrap_prefix.is_some()
12221 || (has_indent_change && current_range_comment_delimiters.is_some());
12222
12223 if has_paragraph_break || has_boundary_change {
12224 ranges.push((
12225 language_settings.clone(),
12226 Point::new(current_range_start, 0)
12227 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12228 current_range_indent,
12229 current_range_comment_delimiters.clone(),
12230 current_range_rewrap_prefix.clone(),
12231 from_empty_selection,
12232 ));
12233 current_range_start = row;
12234 current_range_indent = row_indent;
12235 current_range_comment_delimiters = row_comment_delimiters;
12236 current_range_rewrap_prefix = row_rewrap_prefix;
12237 }
12238 prev_row = row;
12239 }
12240
12241 ranges.push((
12242 language_settings.clone(),
12243 Point::new(current_range_start, 0)
12244 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12245 current_range_indent,
12246 current_range_comment_delimiters,
12247 current_range_rewrap_prefix,
12248 from_empty_selection,
12249 ));
12250
12251 ranges
12252 });
12253
12254 let mut edits = Vec::new();
12255 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12256
12257 for (
12258 language_settings,
12259 wrap_range,
12260 mut indent_size,
12261 comment_prefix,
12262 rewrap_prefix,
12263 from_empty_selection,
12264 ) in wrap_ranges
12265 {
12266 let mut start_row = wrap_range.start.row;
12267 let mut end_row = wrap_range.end.row;
12268
12269 // Skip selections that overlap with a range that has already been rewrapped.
12270 let selection_range = start_row..end_row;
12271 if rewrapped_row_ranges
12272 .iter()
12273 .any(|range| range.overlaps(&selection_range))
12274 {
12275 continue;
12276 }
12277
12278 let tab_size = language_settings.tab_size;
12279
12280 let (line_prefix, inside_comment) = match &comment_prefix {
12281 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12282 (Some(prefix.as_str()), true)
12283 }
12284 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12285 (Some(prefix.as_ref()), true)
12286 }
12287 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12288 start: _,
12289 end: _,
12290 prefix,
12291 tab_size,
12292 })) => {
12293 indent_size.len += tab_size;
12294 (Some(prefix.as_ref()), true)
12295 }
12296 None => (None, false),
12297 };
12298 let indent_prefix = indent_size.chars().collect::<String>();
12299 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12300
12301 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12302 RewrapBehavior::InComments => inside_comment,
12303 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12304 RewrapBehavior::Anywhere => true,
12305 };
12306
12307 let should_rewrap = options.override_language_settings
12308 || allow_rewrap_based_on_language
12309 || self.hard_wrap.is_some();
12310 if !should_rewrap {
12311 continue;
12312 }
12313
12314 if from_empty_selection {
12315 'expand_upwards: while start_row > 0 {
12316 let prev_row = start_row - 1;
12317 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12318 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12319 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12320 {
12321 start_row = prev_row;
12322 } else {
12323 break 'expand_upwards;
12324 }
12325 }
12326
12327 'expand_downwards: while end_row < buffer.max_point().row {
12328 let next_row = end_row + 1;
12329 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12330 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12331 && !buffer.is_line_blank(MultiBufferRow(next_row))
12332 {
12333 end_row = next_row;
12334 } else {
12335 break 'expand_downwards;
12336 }
12337 }
12338 }
12339
12340 let start = Point::new(start_row, 0);
12341 let start_offset = ToOffset::to_offset(&start, &buffer);
12342 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12343 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12344 let mut first_line_delimiter = None;
12345 let mut last_line_delimiter = None;
12346 let Some(lines_without_prefixes) = selection_text
12347 .lines()
12348 .enumerate()
12349 .map(|(ix, line)| {
12350 let line_trimmed = line.trim_start();
12351 if rewrap_prefix.is_some() && ix > 0 {
12352 Ok(line_trimmed)
12353 } else if let Some(
12354 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12355 start,
12356 prefix,
12357 end,
12358 tab_size,
12359 })
12360 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12361 start,
12362 prefix,
12363 end,
12364 tab_size,
12365 }),
12366 ) = &comment_prefix
12367 {
12368 let line_trimmed = line_trimmed
12369 .strip_prefix(start.as_ref())
12370 .map(|s| {
12371 let mut indent_size = indent_size;
12372 indent_size.len -= tab_size;
12373 let indent_prefix: String = indent_size.chars().collect();
12374 first_line_delimiter = Some((indent_prefix, start));
12375 s.trim_start()
12376 })
12377 .unwrap_or(line_trimmed);
12378 let line_trimmed = line_trimmed
12379 .strip_suffix(end.as_ref())
12380 .map(|s| {
12381 last_line_delimiter = Some(end);
12382 s.trim_end()
12383 })
12384 .unwrap_or(line_trimmed);
12385 let line_trimmed = line_trimmed
12386 .strip_prefix(prefix.as_ref())
12387 .unwrap_or(line_trimmed);
12388 Ok(line_trimmed)
12389 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12390 line_trimmed.strip_prefix(prefix).with_context(|| {
12391 format!("line did not start with prefix {prefix:?}: {line:?}")
12392 })
12393 } else {
12394 line_trimmed
12395 .strip_prefix(&line_prefix.trim_start())
12396 .with_context(|| {
12397 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12398 })
12399 }
12400 })
12401 .collect::<Result<Vec<_>, _>>()
12402 .log_err()
12403 else {
12404 continue;
12405 };
12406
12407 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12408 buffer
12409 .language_settings_at(Point::new(start_row, 0), cx)
12410 .preferred_line_length as usize
12411 });
12412
12413 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12414 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12415 } else {
12416 line_prefix.clone()
12417 };
12418
12419 let wrapped_text = {
12420 let mut wrapped_text = wrap_with_prefix(
12421 line_prefix,
12422 subsequent_lines_prefix,
12423 lines_without_prefixes.join("\n"),
12424 wrap_column,
12425 tab_size,
12426 options.preserve_existing_whitespace,
12427 );
12428
12429 if let Some((indent, delimiter)) = first_line_delimiter {
12430 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12431 }
12432 if let Some(last_line) = last_line_delimiter {
12433 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12434 }
12435
12436 wrapped_text
12437 };
12438
12439 // TODO: should always use char-based diff while still supporting cursor behavior that
12440 // matches vim.
12441 let mut diff_options = DiffOptions::default();
12442 if options.override_language_settings {
12443 diff_options.max_word_diff_len = 0;
12444 diff_options.max_word_diff_line_count = 0;
12445 } else {
12446 diff_options.max_word_diff_len = usize::MAX;
12447 diff_options.max_word_diff_line_count = usize::MAX;
12448 }
12449
12450 for (old_range, new_text) in
12451 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12452 {
12453 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12454 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12455 edits.push((edit_start..edit_end, new_text));
12456 }
12457
12458 rewrapped_row_ranges.push(start_row..=end_row);
12459 }
12460
12461 self.buffer
12462 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12463 }
12464
12465 pub fn cut_common(
12466 &mut self,
12467 cut_no_selection_line: bool,
12468 window: &mut Window,
12469 cx: &mut Context<Self>,
12470 ) -> ClipboardItem {
12471 let mut text = String::new();
12472 let buffer = self.buffer.read(cx).snapshot(cx);
12473 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12474 let mut clipboard_selections = Vec::with_capacity(selections.len());
12475 {
12476 let max_point = buffer.max_point();
12477 let mut is_first = true;
12478 for selection in &mut selections {
12479 let is_entire_line =
12480 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12481 if is_entire_line {
12482 selection.start = Point::new(selection.start.row, 0);
12483 if !selection.is_empty() && selection.end.column == 0 {
12484 selection.end = cmp::min(max_point, selection.end);
12485 } else {
12486 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12487 }
12488 selection.goal = SelectionGoal::None;
12489 }
12490 if is_first {
12491 is_first = false;
12492 } else {
12493 text += "\n";
12494 }
12495 let mut len = 0;
12496 for chunk in buffer.text_for_range(selection.start..selection.end) {
12497 text.push_str(chunk);
12498 len += chunk.len();
12499 }
12500 clipboard_selections.push(ClipboardSelection {
12501 len,
12502 is_entire_line,
12503 first_line_indent: buffer
12504 .indent_size_for_line(MultiBufferRow(selection.start.row))
12505 .len,
12506 });
12507 }
12508 }
12509
12510 self.transact(window, cx, |this, window, cx| {
12511 this.change_selections(Default::default(), window, cx, |s| {
12512 s.select(selections);
12513 });
12514 this.insert("", window, cx);
12515 });
12516 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12517 }
12518
12519 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12521 let item = self.cut_common(true, window, cx);
12522 cx.write_to_clipboard(item);
12523 }
12524
12525 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12527 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12528 s.move_with(|snapshot, sel| {
12529 if sel.is_empty() {
12530 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12531 }
12532 if sel.is_empty() {
12533 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12534 }
12535 });
12536 });
12537 let item = self.cut_common(false, window, cx);
12538 cx.set_global(KillRing(item))
12539 }
12540
12541 pub fn kill_ring_yank(
12542 &mut self,
12543 _: &KillRingYank,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 ) {
12547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12548 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12549 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12550 (kill_ring.text().to_string(), kill_ring.metadata_json())
12551 } else {
12552 return;
12553 }
12554 } else {
12555 return;
12556 };
12557 self.do_paste(&text, metadata, false, window, cx);
12558 }
12559
12560 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12561 self.do_copy(true, cx);
12562 }
12563
12564 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12565 self.do_copy(false, cx);
12566 }
12567
12568 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12569 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12570 let buffer = self.buffer.read(cx).read(cx);
12571 let mut text = String::new();
12572
12573 let mut clipboard_selections = Vec::with_capacity(selections.len());
12574 {
12575 let max_point = buffer.max_point();
12576 let mut is_first = true;
12577 for selection in &selections {
12578 let mut start = selection.start;
12579 let mut end = selection.end;
12580 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12581 let mut add_trailing_newline = false;
12582 if is_entire_line {
12583 start = Point::new(start.row, 0);
12584 let next_line_start = Point::new(end.row + 1, 0);
12585 if next_line_start <= max_point {
12586 end = next_line_start;
12587 } else {
12588 // We're on the last line without a trailing newline.
12589 // Copy to the end of the line and add a newline afterwards.
12590 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12591 add_trailing_newline = true;
12592 }
12593 }
12594
12595 let mut trimmed_selections = Vec::new();
12596 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12597 let row = MultiBufferRow(start.row);
12598 let first_indent = buffer.indent_size_for_line(row);
12599 if first_indent.len == 0 || start.column > first_indent.len {
12600 trimmed_selections.push(start..end);
12601 } else {
12602 trimmed_selections.push(
12603 Point::new(row.0, first_indent.len)
12604 ..Point::new(row.0, buffer.line_len(row)),
12605 );
12606 for row in start.row + 1..=end.row {
12607 let mut line_len = buffer.line_len(MultiBufferRow(row));
12608 if row == end.row {
12609 line_len = end.column;
12610 }
12611 if line_len == 0 {
12612 trimmed_selections
12613 .push(Point::new(row, 0)..Point::new(row, line_len));
12614 continue;
12615 }
12616 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12617 if row_indent_size.len >= first_indent.len {
12618 trimmed_selections.push(
12619 Point::new(row, first_indent.len)..Point::new(row, line_len),
12620 );
12621 } else {
12622 trimmed_selections.clear();
12623 trimmed_selections.push(start..end);
12624 break;
12625 }
12626 }
12627 }
12628 } else {
12629 trimmed_selections.push(start..end);
12630 }
12631
12632 for trimmed_range in trimmed_selections {
12633 if is_first {
12634 is_first = false;
12635 } else {
12636 text += "\n";
12637 }
12638 let mut len = 0;
12639 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12640 text.push_str(chunk);
12641 len += chunk.len();
12642 }
12643 if add_trailing_newline {
12644 text.push('\n');
12645 len += 1;
12646 }
12647 clipboard_selections.push(ClipboardSelection {
12648 len,
12649 is_entire_line,
12650 first_line_indent: buffer
12651 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12652 .len,
12653 });
12654 }
12655 }
12656 }
12657
12658 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12659 text,
12660 clipboard_selections,
12661 ));
12662 }
12663
12664 pub fn do_paste(
12665 &mut self,
12666 text: &String,
12667 clipboard_selections: Option<Vec<ClipboardSelection>>,
12668 handle_entire_lines: bool,
12669 window: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) {
12672 if self.read_only(cx) {
12673 return;
12674 }
12675
12676 let clipboard_text = Cow::Borrowed(text.as_str());
12677
12678 self.transact(window, cx, |this, window, cx| {
12679 let had_active_edit_prediction = this.has_active_edit_prediction();
12680 let display_map = this.display_snapshot(cx);
12681 let old_selections = this.selections.all::<usize>(&display_map);
12682 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12683
12684 if let Some(mut clipboard_selections) = clipboard_selections {
12685 let all_selections_were_entire_line =
12686 clipboard_selections.iter().all(|s| s.is_entire_line);
12687 let first_selection_indent_column =
12688 clipboard_selections.first().map(|s| s.first_line_indent);
12689 if clipboard_selections.len() != old_selections.len() {
12690 clipboard_selections.drain(..);
12691 }
12692 let mut auto_indent_on_paste = true;
12693
12694 this.buffer.update(cx, |buffer, cx| {
12695 let snapshot = buffer.read(cx);
12696 auto_indent_on_paste = snapshot
12697 .language_settings_at(cursor_offset, cx)
12698 .auto_indent_on_paste;
12699
12700 let mut start_offset = 0;
12701 let mut edits = Vec::new();
12702 let mut original_indent_columns = Vec::new();
12703 for (ix, selection) in old_selections.iter().enumerate() {
12704 let to_insert;
12705 let entire_line;
12706 let original_indent_column;
12707 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12708 let end_offset = start_offset + clipboard_selection.len;
12709 to_insert = &clipboard_text[start_offset..end_offset];
12710 entire_line = clipboard_selection.is_entire_line;
12711 start_offset = end_offset + 1;
12712 original_indent_column = Some(clipboard_selection.first_line_indent);
12713 } else {
12714 to_insert = &*clipboard_text;
12715 entire_line = all_selections_were_entire_line;
12716 original_indent_column = first_selection_indent_column
12717 }
12718
12719 let (range, to_insert) =
12720 if selection.is_empty() && handle_entire_lines && entire_line {
12721 // If the corresponding selection was empty when this slice of the
12722 // clipboard text was written, then the entire line containing the
12723 // selection was copied. If this selection is also currently empty,
12724 // then paste the line before the current line of the buffer.
12725 let column = selection.start.to_point(&snapshot).column as usize;
12726 let line_start = selection.start - column;
12727 (line_start..line_start, Cow::Borrowed(to_insert))
12728 } else {
12729 let language = snapshot.language_at(selection.head());
12730 let range = selection.range();
12731 if let Some(language) = language
12732 && language.name() == "Markdown".into()
12733 {
12734 edit_for_markdown_paste(
12735 &snapshot,
12736 range,
12737 to_insert,
12738 url::Url::parse(to_insert).ok(),
12739 )
12740 } else {
12741 (range, Cow::Borrowed(to_insert))
12742 }
12743 };
12744
12745 edits.push((range, to_insert));
12746 original_indent_columns.push(original_indent_column);
12747 }
12748 drop(snapshot);
12749
12750 buffer.edit(
12751 edits,
12752 if auto_indent_on_paste {
12753 Some(AutoindentMode::Block {
12754 original_indent_columns,
12755 })
12756 } else {
12757 None
12758 },
12759 cx,
12760 );
12761 });
12762
12763 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12764 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12765 } else {
12766 let url = url::Url::parse(&clipboard_text).ok();
12767
12768 let auto_indent_mode = if !clipboard_text.is_empty() {
12769 Some(AutoindentMode::Block {
12770 original_indent_columns: Vec::new(),
12771 })
12772 } else {
12773 None
12774 };
12775
12776 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12777 let snapshot = buffer.snapshot(cx);
12778
12779 let anchors = old_selections
12780 .iter()
12781 .map(|s| {
12782 let anchor = snapshot.anchor_after(s.head());
12783 s.map(|_| anchor)
12784 })
12785 .collect::<Vec<_>>();
12786
12787 let mut edits = Vec::new();
12788
12789 for selection in old_selections.iter() {
12790 let language = snapshot.language_at(selection.head());
12791 let range = selection.range();
12792
12793 let (edit_range, edit_text) = if let Some(language) = language
12794 && language.name() == "Markdown".into()
12795 {
12796 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12797 } else {
12798 (range, clipboard_text.clone())
12799 };
12800
12801 edits.push((edit_range, edit_text));
12802 }
12803
12804 drop(snapshot);
12805 buffer.edit(edits, auto_indent_mode, cx);
12806
12807 anchors
12808 });
12809
12810 this.change_selections(Default::default(), window, cx, |s| {
12811 s.select_anchors(selection_anchors);
12812 });
12813 }
12814
12815 let trigger_in_words =
12816 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12817
12818 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12819 });
12820 }
12821
12822 pub fn diff_clipboard_with_selection(
12823 &mut self,
12824 _: &DiffClipboardWithSelection,
12825 window: &mut Window,
12826 cx: &mut Context<Self>,
12827 ) {
12828 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12829
12830 if selections.is_empty() {
12831 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12832 return;
12833 };
12834
12835 let clipboard_text = match cx.read_from_clipboard() {
12836 Some(item) => match item.entries().first() {
12837 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12838 _ => None,
12839 },
12840 None => None,
12841 };
12842
12843 let Some(clipboard_text) = clipboard_text else {
12844 log::warn!("Clipboard doesn't contain text.");
12845 return;
12846 };
12847
12848 window.dispatch_action(
12849 Box::new(DiffClipboardWithSelectionData {
12850 clipboard_text,
12851 editor: cx.entity(),
12852 }),
12853 cx,
12854 );
12855 }
12856
12857 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12859 if let Some(item) = cx.read_from_clipboard() {
12860 let entries = item.entries();
12861
12862 match entries.first() {
12863 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12864 // of all the pasted entries.
12865 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12866 .do_paste(
12867 clipboard_string.text(),
12868 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12869 true,
12870 window,
12871 cx,
12872 ),
12873 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12874 }
12875 }
12876 }
12877
12878 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12879 if self.read_only(cx) {
12880 return;
12881 }
12882
12883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12884
12885 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12886 if let Some((selections, _)) =
12887 self.selection_history.transaction(transaction_id).cloned()
12888 {
12889 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12890 s.select_anchors(selections.to_vec());
12891 });
12892 } else {
12893 log::error!(
12894 "No entry in selection_history found for undo. \
12895 This may correspond to a bug where undo does not update the selection. \
12896 If this is occurring, please add details to \
12897 https://github.com/zed-industries/zed/issues/22692"
12898 );
12899 }
12900 self.request_autoscroll(Autoscroll::fit(), cx);
12901 self.unmark_text(window, cx);
12902 self.refresh_edit_prediction(true, false, window, cx);
12903 cx.emit(EditorEvent::Edited { transaction_id });
12904 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12905 }
12906 }
12907
12908 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12909 if self.read_only(cx) {
12910 return;
12911 }
12912
12913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12914
12915 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12916 if let Some((_, Some(selections))) =
12917 self.selection_history.transaction(transaction_id).cloned()
12918 {
12919 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12920 s.select_anchors(selections.to_vec());
12921 });
12922 } else {
12923 log::error!(
12924 "No entry in selection_history found for redo. \
12925 This may correspond to a bug where undo does not update the selection. \
12926 If this is occurring, please add details to \
12927 https://github.com/zed-industries/zed/issues/22692"
12928 );
12929 }
12930 self.request_autoscroll(Autoscroll::fit(), cx);
12931 self.unmark_text(window, cx);
12932 self.refresh_edit_prediction(true, false, window, cx);
12933 cx.emit(EditorEvent::Edited { transaction_id });
12934 }
12935 }
12936
12937 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12938 self.buffer
12939 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12940 }
12941
12942 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12943 self.buffer
12944 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12945 }
12946
12947 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12949 self.change_selections(Default::default(), window, cx, |s| {
12950 s.move_with(|map, selection| {
12951 let cursor = if selection.is_empty() {
12952 movement::left(map, selection.start)
12953 } else {
12954 selection.start
12955 };
12956 selection.collapse_to(cursor, SelectionGoal::None);
12957 });
12958 })
12959 }
12960
12961 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12963 self.change_selections(Default::default(), window, cx, |s| {
12964 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12965 })
12966 }
12967
12968 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12970 self.change_selections(Default::default(), window, cx, |s| {
12971 s.move_with(|map, selection| {
12972 let cursor = if selection.is_empty() {
12973 movement::right(map, selection.end)
12974 } else {
12975 selection.end
12976 };
12977 selection.collapse_to(cursor, SelectionGoal::None)
12978 });
12979 })
12980 }
12981
12982 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12986 });
12987 }
12988
12989 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12990 if self.take_rename(true, window, cx).is_some() {
12991 return;
12992 }
12993
12994 if self.mode.is_single_line() {
12995 cx.propagate();
12996 return;
12997 }
12998
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000
13001 let text_layout_details = &self.text_layout_details(window);
13002 let selection_count = self.selections.count();
13003 let first_selection = self.selections.first_anchor();
13004
13005 self.change_selections(Default::default(), window, cx, |s| {
13006 s.move_with(|map, selection| {
13007 if !selection.is_empty() {
13008 selection.goal = SelectionGoal::None;
13009 }
13010 let (cursor, goal) = movement::up(
13011 map,
13012 selection.start,
13013 selection.goal,
13014 false,
13015 text_layout_details,
13016 );
13017 selection.collapse_to(cursor, goal);
13018 });
13019 });
13020
13021 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13022 {
13023 cx.propagate();
13024 }
13025 }
13026
13027 pub fn move_up_by_lines(
13028 &mut self,
13029 action: &MoveUpByLines,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 if self.take_rename(true, window, cx).is_some() {
13034 return;
13035 }
13036
13037 if self.mode.is_single_line() {
13038 cx.propagate();
13039 return;
13040 }
13041
13042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13043
13044 let text_layout_details = &self.text_layout_details(window);
13045
13046 self.change_selections(Default::default(), window, cx, |s| {
13047 s.move_with(|map, selection| {
13048 if !selection.is_empty() {
13049 selection.goal = SelectionGoal::None;
13050 }
13051 let (cursor, goal) = movement::up_by_rows(
13052 map,
13053 selection.start,
13054 action.lines,
13055 selection.goal,
13056 false,
13057 text_layout_details,
13058 );
13059 selection.collapse_to(cursor, goal);
13060 });
13061 })
13062 }
13063
13064 pub fn move_down_by_lines(
13065 &mut self,
13066 action: &MoveDownByLines,
13067 window: &mut Window,
13068 cx: &mut Context<Self>,
13069 ) {
13070 if self.take_rename(true, window, cx).is_some() {
13071 return;
13072 }
13073
13074 if self.mode.is_single_line() {
13075 cx.propagate();
13076 return;
13077 }
13078
13079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13080
13081 let text_layout_details = &self.text_layout_details(window);
13082
13083 self.change_selections(Default::default(), window, cx, |s| {
13084 s.move_with(|map, selection| {
13085 if !selection.is_empty() {
13086 selection.goal = SelectionGoal::None;
13087 }
13088 let (cursor, goal) = movement::down_by_rows(
13089 map,
13090 selection.start,
13091 action.lines,
13092 selection.goal,
13093 false,
13094 text_layout_details,
13095 );
13096 selection.collapse_to(cursor, goal);
13097 });
13098 })
13099 }
13100
13101 pub fn select_down_by_lines(
13102 &mut self,
13103 action: &SelectDownByLines,
13104 window: &mut Window,
13105 cx: &mut Context<Self>,
13106 ) {
13107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13108 let text_layout_details = &self.text_layout_details(window);
13109 self.change_selections(Default::default(), window, cx, |s| {
13110 s.move_heads_with(|map, head, goal| {
13111 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13112 })
13113 })
13114 }
13115
13116 pub fn select_up_by_lines(
13117 &mut self,
13118 action: &SelectUpByLines,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) {
13122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13123 let text_layout_details = &self.text_layout_details(window);
13124 self.change_selections(Default::default(), window, cx, |s| {
13125 s.move_heads_with(|map, head, goal| {
13126 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13127 })
13128 })
13129 }
13130
13131 pub fn select_page_up(
13132 &mut self,
13133 _: &SelectPageUp,
13134 window: &mut Window,
13135 cx: &mut Context<Self>,
13136 ) {
13137 let Some(row_count) = self.visible_row_count() else {
13138 return;
13139 };
13140
13141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13142
13143 let text_layout_details = &self.text_layout_details(window);
13144
13145 self.change_selections(Default::default(), window, cx, |s| {
13146 s.move_heads_with(|map, head, goal| {
13147 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13148 })
13149 })
13150 }
13151
13152 pub fn move_page_up(
13153 &mut self,
13154 action: &MovePageUp,
13155 window: &mut Window,
13156 cx: &mut Context<Self>,
13157 ) {
13158 if self.take_rename(true, window, cx).is_some() {
13159 return;
13160 }
13161
13162 if self
13163 .context_menu
13164 .borrow_mut()
13165 .as_mut()
13166 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13167 .unwrap_or(false)
13168 {
13169 return;
13170 }
13171
13172 if matches!(self.mode, EditorMode::SingleLine) {
13173 cx.propagate();
13174 return;
13175 }
13176
13177 let Some(row_count) = self.visible_row_count() else {
13178 return;
13179 };
13180
13181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13182
13183 let effects = if action.center_cursor {
13184 SelectionEffects::scroll(Autoscroll::center())
13185 } else {
13186 SelectionEffects::default()
13187 };
13188
13189 let text_layout_details = &self.text_layout_details(window);
13190
13191 self.change_selections(effects, window, cx, |s| {
13192 s.move_with(|map, selection| {
13193 if !selection.is_empty() {
13194 selection.goal = SelectionGoal::None;
13195 }
13196 let (cursor, goal) = movement::up_by_rows(
13197 map,
13198 selection.end,
13199 row_count,
13200 selection.goal,
13201 false,
13202 text_layout_details,
13203 );
13204 selection.collapse_to(cursor, goal);
13205 });
13206 });
13207 }
13208
13209 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13211 let text_layout_details = &self.text_layout_details(window);
13212 self.change_selections(Default::default(), window, cx, |s| {
13213 s.move_heads_with(|map, head, goal| {
13214 movement::up(map, head, goal, false, text_layout_details)
13215 })
13216 })
13217 }
13218
13219 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13220 self.take_rename(true, window, cx);
13221
13222 if self.mode.is_single_line() {
13223 cx.propagate();
13224 return;
13225 }
13226
13227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13228
13229 let text_layout_details = &self.text_layout_details(window);
13230 let selection_count = self.selections.count();
13231 let first_selection = self.selections.first_anchor();
13232
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_with(|map, selection| {
13235 if !selection.is_empty() {
13236 selection.goal = SelectionGoal::None;
13237 }
13238 let (cursor, goal) = movement::down(
13239 map,
13240 selection.end,
13241 selection.goal,
13242 false,
13243 text_layout_details,
13244 );
13245 selection.collapse_to(cursor, goal);
13246 });
13247 });
13248
13249 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13250 {
13251 cx.propagate();
13252 }
13253 }
13254
13255 pub fn select_page_down(
13256 &mut self,
13257 _: &SelectPageDown,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 let Some(row_count) = self.visible_row_count() else {
13262 return;
13263 };
13264
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266
13267 let text_layout_details = &self.text_layout_details(window);
13268
13269 self.change_selections(Default::default(), window, cx, |s| {
13270 s.move_heads_with(|map, head, goal| {
13271 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13272 })
13273 })
13274 }
13275
13276 pub fn move_page_down(
13277 &mut self,
13278 action: &MovePageDown,
13279 window: &mut Window,
13280 cx: &mut Context<Self>,
13281 ) {
13282 if self.take_rename(true, window, cx).is_some() {
13283 return;
13284 }
13285
13286 if self
13287 .context_menu
13288 .borrow_mut()
13289 .as_mut()
13290 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13291 .unwrap_or(false)
13292 {
13293 return;
13294 }
13295
13296 if matches!(self.mode, EditorMode::SingleLine) {
13297 cx.propagate();
13298 return;
13299 }
13300
13301 let Some(row_count) = self.visible_row_count() else {
13302 return;
13303 };
13304
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306
13307 let effects = if action.center_cursor {
13308 SelectionEffects::scroll(Autoscroll::center())
13309 } else {
13310 SelectionEffects::default()
13311 };
13312
13313 let text_layout_details = &self.text_layout_details(window);
13314 self.change_selections(effects, window, cx, |s| {
13315 s.move_with(|map, selection| {
13316 if !selection.is_empty() {
13317 selection.goal = SelectionGoal::None;
13318 }
13319 let (cursor, goal) = movement::down_by_rows(
13320 map,
13321 selection.end,
13322 row_count,
13323 selection.goal,
13324 false,
13325 text_layout_details,
13326 );
13327 selection.collapse_to(cursor, goal);
13328 });
13329 });
13330 }
13331
13332 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13334 let text_layout_details = &self.text_layout_details(window);
13335 self.change_selections(Default::default(), window, cx, |s| {
13336 s.move_heads_with(|map, head, goal| {
13337 movement::down(map, head, goal, false, text_layout_details)
13338 })
13339 });
13340 }
13341
13342 pub fn context_menu_first(
13343 &mut self,
13344 _: &ContextMenuFirst,
13345 window: &mut Window,
13346 cx: &mut Context<Self>,
13347 ) {
13348 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13349 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13350 }
13351 }
13352
13353 pub fn context_menu_prev(
13354 &mut self,
13355 _: &ContextMenuPrevious,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) {
13359 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13360 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13361 }
13362 }
13363
13364 pub fn context_menu_next(
13365 &mut self,
13366 _: &ContextMenuNext,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13371 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13372 }
13373 }
13374
13375 pub fn context_menu_last(
13376 &mut self,
13377 _: &ContextMenuLast,
13378 window: &mut Window,
13379 cx: &mut Context<Self>,
13380 ) {
13381 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13382 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13383 }
13384 }
13385
13386 pub fn signature_help_prev(
13387 &mut self,
13388 _: &SignatureHelpPrevious,
13389 _: &mut Window,
13390 cx: &mut Context<Self>,
13391 ) {
13392 if let Some(popover) = self.signature_help_state.popover_mut() {
13393 if popover.current_signature == 0 {
13394 popover.current_signature = popover.signatures.len() - 1;
13395 } else {
13396 popover.current_signature -= 1;
13397 }
13398 cx.notify();
13399 }
13400 }
13401
13402 pub fn signature_help_next(
13403 &mut self,
13404 _: &SignatureHelpNext,
13405 _: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 if let Some(popover) = self.signature_help_state.popover_mut() {
13409 if popover.current_signature + 1 == popover.signatures.len() {
13410 popover.current_signature = 0;
13411 } else {
13412 popover.current_signature += 1;
13413 }
13414 cx.notify();
13415 }
13416 }
13417
13418 pub fn move_to_previous_word_start(
13419 &mut self,
13420 _: &MoveToPreviousWordStart,
13421 window: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13425 self.change_selections(Default::default(), window, cx, |s| {
13426 s.move_cursors_with(|map, head, _| {
13427 (
13428 movement::previous_word_start(map, head),
13429 SelectionGoal::None,
13430 )
13431 });
13432 })
13433 }
13434
13435 pub fn move_to_previous_subword_start(
13436 &mut self,
13437 _: &MoveToPreviousSubwordStart,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13442 self.change_selections(Default::default(), window, cx, |s| {
13443 s.move_cursors_with(|map, head, _| {
13444 (
13445 movement::previous_subword_start(map, head),
13446 SelectionGoal::None,
13447 )
13448 });
13449 })
13450 }
13451
13452 pub fn select_to_previous_word_start(
13453 &mut self,
13454 _: &SelectToPreviousWordStart,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_heads_with(|map, head, _| {
13461 (
13462 movement::previous_word_start(map, head),
13463 SelectionGoal::None,
13464 )
13465 });
13466 })
13467 }
13468
13469 pub fn select_to_previous_subword_start(
13470 &mut self,
13471 _: &SelectToPreviousSubwordStart,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) {
13475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13476 self.change_selections(Default::default(), window, cx, |s| {
13477 s.move_heads_with(|map, head, _| {
13478 (
13479 movement::previous_subword_start(map, head),
13480 SelectionGoal::None,
13481 )
13482 });
13483 })
13484 }
13485
13486 pub fn delete_to_previous_word_start(
13487 &mut self,
13488 action: &DeleteToPreviousWordStart,
13489 window: &mut Window,
13490 cx: &mut Context<Self>,
13491 ) {
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13493 self.transact(window, cx, |this, window, cx| {
13494 this.select_autoclose_pair(window, cx);
13495 this.change_selections(Default::default(), window, cx, |s| {
13496 s.move_with(|map, selection| {
13497 if selection.is_empty() {
13498 let mut cursor = if action.ignore_newlines {
13499 movement::previous_word_start(map, selection.head())
13500 } else {
13501 movement::previous_word_start_or_newline(map, selection.head())
13502 };
13503 cursor = movement::adjust_greedy_deletion(
13504 map,
13505 selection.head(),
13506 cursor,
13507 action.ignore_brackets,
13508 );
13509 selection.set_head(cursor, SelectionGoal::None);
13510 }
13511 });
13512 });
13513 this.insert("", window, cx);
13514 });
13515 }
13516
13517 pub fn delete_to_previous_subword_start(
13518 &mut self,
13519 _: &DeleteToPreviousSubwordStart,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13524 self.transact(window, cx, |this, window, cx| {
13525 this.select_autoclose_pair(window, cx);
13526 this.change_selections(Default::default(), window, cx, |s| {
13527 s.move_with(|map, selection| {
13528 if selection.is_empty() {
13529 let mut cursor = movement::previous_subword_start(map, selection.head());
13530 cursor =
13531 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13532 selection.set_head(cursor, SelectionGoal::None);
13533 }
13534 });
13535 });
13536 this.insert("", window, cx);
13537 });
13538 }
13539
13540 pub fn move_to_next_word_end(
13541 &mut self,
13542 _: &MoveToNextWordEnd,
13543 window: &mut Window,
13544 cx: &mut Context<Self>,
13545 ) {
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547 self.change_selections(Default::default(), window, cx, |s| {
13548 s.move_cursors_with(|map, head, _| {
13549 (movement::next_word_end(map, head), SelectionGoal::None)
13550 });
13551 })
13552 }
13553
13554 pub fn move_to_next_subword_end(
13555 &mut self,
13556 _: &MoveToNextSubwordEnd,
13557 window: &mut Window,
13558 cx: &mut Context<Self>,
13559 ) {
13560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13561 self.change_selections(Default::default(), window, cx, |s| {
13562 s.move_cursors_with(|map, head, _| {
13563 (movement::next_subword_end(map, head), SelectionGoal::None)
13564 });
13565 })
13566 }
13567
13568 pub fn select_to_next_word_end(
13569 &mut self,
13570 _: &SelectToNextWordEnd,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13575 self.change_selections(Default::default(), window, cx, |s| {
13576 s.move_heads_with(|map, head, _| {
13577 (movement::next_word_end(map, head), SelectionGoal::None)
13578 });
13579 })
13580 }
13581
13582 pub fn select_to_next_subword_end(
13583 &mut self,
13584 _: &SelectToNextSubwordEnd,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.move_heads_with(|map, head, _| {
13591 (movement::next_subword_end(map, head), SelectionGoal::None)
13592 });
13593 })
13594 }
13595
13596 pub fn delete_to_next_word_end(
13597 &mut self,
13598 action: &DeleteToNextWordEnd,
13599 window: &mut Window,
13600 cx: &mut Context<Self>,
13601 ) {
13602 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13603 self.transact(window, cx, |this, window, cx| {
13604 this.change_selections(Default::default(), window, cx, |s| {
13605 s.move_with(|map, selection| {
13606 if selection.is_empty() {
13607 let mut cursor = if action.ignore_newlines {
13608 movement::next_word_end(map, selection.head())
13609 } else {
13610 movement::next_word_end_or_newline(map, selection.head())
13611 };
13612 cursor = movement::adjust_greedy_deletion(
13613 map,
13614 selection.head(),
13615 cursor,
13616 action.ignore_brackets,
13617 );
13618 selection.set_head(cursor, SelectionGoal::None);
13619 }
13620 });
13621 });
13622 this.insert("", window, cx);
13623 });
13624 }
13625
13626 pub fn delete_to_next_subword_end(
13627 &mut self,
13628 _: &DeleteToNextSubwordEnd,
13629 window: &mut Window,
13630 cx: &mut Context<Self>,
13631 ) {
13632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13633 self.transact(window, cx, |this, window, cx| {
13634 this.change_selections(Default::default(), window, cx, |s| {
13635 s.move_with(|map, selection| {
13636 if selection.is_empty() {
13637 let mut cursor = movement::next_subword_end(map, selection.head());
13638 cursor =
13639 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13640 selection.set_head(cursor, SelectionGoal::None);
13641 }
13642 });
13643 });
13644 this.insert("", window, cx);
13645 });
13646 }
13647
13648 pub fn move_to_beginning_of_line(
13649 &mut self,
13650 action: &MoveToBeginningOfLine,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) {
13654 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13655 self.change_selections(Default::default(), window, cx, |s| {
13656 s.move_cursors_with(|map, head, _| {
13657 (
13658 movement::indented_line_beginning(
13659 map,
13660 head,
13661 action.stop_at_soft_wraps,
13662 action.stop_at_indent,
13663 ),
13664 SelectionGoal::None,
13665 )
13666 });
13667 })
13668 }
13669
13670 pub fn select_to_beginning_of_line(
13671 &mut self,
13672 action: &SelectToBeginningOfLine,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) {
13676 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13677 self.change_selections(Default::default(), window, cx, |s| {
13678 s.move_heads_with(|map, head, _| {
13679 (
13680 movement::indented_line_beginning(
13681 map,
13682 head,
13683 action.stop_at_soft_wraps,
13684 action.stop_at_indent,
13685 ),
13686 SelectionGoal::None,
13687 )
13688 });
13689 });
13690 }
13691
13692 pub fn delete_to_beginning_of_line(
13693 &mut self,
13694 action: &DeleteToBeginningOfLine,
13695 window: &mut Window,
13696 cx: &mut Context<Self>,
13697 ) {
13698 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13699 self.transact(window, cx, |this, window, cx| {
13700 this.change_selections(Default::default(), window, cx, |s| {
13701 s.move_with(|_, selection| {
13702 selection.reversed = true;
13703 });
13704 });
13705
13706 this.select_to_beginning_of_line(
13707 &SelectToBeginningOfLine {
13708 stop_at_soft_wraps: false,
13709 stop_at_indent: action.stop_at_indent,
13710 },
13711 window,
13712 cx,
13713 );
13714 this.backspace(&Backspace, window, cx);
13715 });
13716 }
13717
13718 pub fn move_to_end_of_line(
13719 &mut self,
13720 action: &MoveToEndOfLine,
13721 window: &mut Window,
13722 cx: &mut Context<Self>,
13723 ) {
13724 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13725 self.change_selections(Default::default(), window, cx, |s| {
13726 s.move_cursors_with(|map, head, _| {
13727 (
13728 movement::line_end(map, head, action.stop_at_soft_wraps),
13729 SelectionGoal::None,
13730 )
13731 });
13732 })
13733 }
13734
13735 pub fn select_to_end_of_line(
13736 &mut self,
13737 action: &SelectToEndOfLine,
13738 window: &mut Window,
13739 cx: &mut Context<Self>,
13740 ) {
13741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13742 self.change_selections(Default::default(), window, cx, |s| {
13743 s.move_heads_with(|map, head, _| {
13744 (
13745 movement::line_end(map, head, action.stop_at_soft_wraps),
13746 SelectionGoal::None,
13747 )
13748 });
13749 })
13750 }
13751
13752 pub fn delete_to_end_of_line(
13753 &mut self,
13754 _: &DeleteToEndOfLine,
13755 window: &mut Window,
13756 cx: &mut Context<Self>,
13757 ) {
13758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13759 self.transact(window, cx, |this, window, cx| {
13760 this.select_to_end_of_line(
13761 &SelectToEndOfLine {
13762 stop_at_soft_wraps: false,
13763 },
13764 window,
13765 cx,
13766 );
13767 this.delete(&Delete, window, cx);
13768 });
13769 }
13770
13771 pub fn cut_to_end_of_line(
13772 &mut self,
13773 action: &CutToEndOfLine,
13774 window: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13778 self.transact(window, cx, |this, window, cx| {
13779 this.select_to_end_of_line(
13780 &SelectToEndOfLine {
13781 stop_at_soft_wraps: false,
13782 },
13783 window,
13784 cx,
13785 );
13786 if !action.stop_at_newlines {
13787 this.change_selections(Default::default(), window, cx, |s| {
13788 s.move_with(|_, sel| {
13789 if sel.is_empty() {
13790 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13791 }
13792 });
13793 });
13794 }
13795 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13796 let item = this.cut_common(false, window, cx);
13797 cx.write_to_clipboard(item);
13798 });
13799 }
13800
13801 pub fn move_to_start_of_paragraph(
13802 &mut self,
13803 _: &MoveToStartOfParagraph,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 if matches!(self.mode, EditorMode::SingleLine) {
13808 cx.propagate();
13809 return;
13810 }
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_with(|map, selection| {
13814 selection.collapse_to(
13815 movement::start_of_paragraph(map, selection.head(), 1),
13816 SelectionGoal::None,
13817 )
13818 });
13819 })
13820 }
13821
13822 pub fn move_to_end_of_paragraph(
13823 &mut self,
13824 _: &MoveToEndOfParagraph,
13825 window: &mut Window,
13826 cx: &mut Context<Self>,
13827 ) {
13828 if matches!(self.mode, EditorMode::SingleLine) {
13829 cx.propagate();
13830 return;
13831 }
13832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13833 self.change_selections(Default::default(), window, cx, |s| {
13834 s.move_with(|map, selection| {
13835 selection.collapse_to(
13836 movement::end_of_paragraph(map, selection.head(), 1),
13837 SelectionGoal::None,
13838 )
13839 });
13840 })
13841 }
13842
13843 pub fn select_to_start_of_paragraph(
13844 &mut self,
13845 _: &SelectToStartOfParagraph,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) {
13849 if matches!(self.mode, EditorMode::SingleLine) {
13850 cx.propagate();
13851 return;
13852 }
13853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13854 self.change_selections(Default::default(), window, cx, |s| {
13855 s.move_heads_with(|map, head, _| {
13856 (
13857 movement::start_of_paragraph(map, head, 1),
13858 SelectionGoal::None,
13859 )
13860 });
13861 })
13862 }
13863
13864 pub fn select_to_end_of_paragraph(
13865 &mut self,
13866 _: &SelectToEndOfParagraph,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 if matches!(self.mode, EditorMode::SingleLine) {
13871 cx.propagate();
13872 return;
13873 }
13874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13875 self.change_selections(Default::default(), window, cx, |s| {
13876 s.move_heads_with(|map, head, _| {
13877 (
13878 movement::end_of_paragraph(map, head, 1),
13879 SelectionGoal::None,
13880 )
13881 });
13882 })
13883 }
13884
13885 pub fn move_to_start_of_excerpt(
13886 &mut self,
13887 _: &MoveToStartOfExcerpt,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 if matches!(self.mode, EditorMode::SingleLine) {
13892 cx.propagate();
13893 return;
13894 }
13895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13896 self.change_selections(Default::default(), window, cx, |s| {
13897 s.move_with(|map, selection| {
13898 selection.collapse_to(
13899 movement::start_of_excerpt(
13900 map,
13901 selection.head(),
13902 workspace::searchable::Direction::Prev,
13903 ),
13904 SelectionGoal::None,
13905 )
13906 });
13907 })
13908 }
13909
13910 pub fn move_to_start_of_next_excerpt(
13911 &mut self,
13912 _: &MoveToStartOfNextExcerpt,
13913 window: &mut Window,
13914 cx: &mut Context<Self>,
13915 ) {
13916 if matches!(self.mode, EditorMode::SingleLine) {
13917 cx.propagate();
13918 return;
13919 }
13920
13921 self.change_selections(Default::default(), window, cx, |s| {
13922 s.move_with(|map, selection| {
13923 selection.collapse_to(
13924 movement::start_of_excerpt(
13925 map,
13926 selection.head(),
13927 workspace::searchable::Direction::Next,
13928 ),
13929 SelectionGoal::None,
13930 )
13931 });
13932 })
13933 }
13934
13935 pub fn move_to_end_of_excerpt(
13936 &mut self,
13937 _: &MoveToEndOfExcerpt,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) {
13941 if matches!(self.mode, EditorMode::SingleLine) {
13942 cx.propagate();
13943 return;
13944 }
13945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13946 self.change_selections(Default::default(), window, cx, |s| {
13947 s.move_with(|map, selection| {
13948 selection.collapse_to(
13949 movement::end_of_excerpt(
13950 map,
13951 selection.head(),
13952 workspace::searchable::Direction::Next,
13953 ),
13954 SelectionGoal::None,
13955 )
13956 });
13957 })
13958 }
13959
13960 pub fn move_to_end_of_previous_excerpt(
13961 &mut self,
13962 _: &MoveToEndOfPreviousExcerpt,
13963 window: &mut Window,
13964 cx: &mut Context<Self>,
13965 ) {
13966 if matches!(self.mode, EditorMode::SingleLine) {
13967 cx.propagate();
13968 return;
13969 }
13970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13971 self.change_selections(Default::default(), window, cx, |s| {
13972 s.move_with(|map, selection| {
13973 selection.collapse_to(
13974 movement::end_of_excerpt(
13975 map,
13976 selection.head(),
13977 workspace::searchable::Direction::Prev,
13978 ),
13979 SelectionGoal::None,
13980 )
13981 });
13982 })
13983 }
13984
13985 pub fn select_to_start_of_excerpt(
13986 &mut self,
13987 _: &SelectToStartOfExcerpt,
13988 window: &mut Window,
13989 cx: &mut Context<Self>,
13990 ) {
13991 if matches!(self.mode, EditorMode::SingleLine) {
13992 cx.propagate();
13993 return;
13994 }
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_heads_with(|map, head, _| {
13998 (
13999 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14000 SelectionGoal::None,
14001 )
14002 });
14003 })
14004 }
14005
14006 pub fn select_to_start_of_next_excerpt(
14007 &mut self,
14008 _: &SelectToStartOfNextExcerpt,
14009 window: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if matches!(self.mode, EditorMode::SingleLine) {
14013 cx.propagate();
14014 return;
14015 }
14016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14017 self.change_selections(Default::default(), window, cx, |s| {
14018 s.move_heads_with(|map, head, _| {
14019 (
14020 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14021 SelectionGoal::None,
14022 )
14023 });
14024 })
14025 }
14026
14027 pub fn select_to_end_of_excerpt(
14028 &mut self,
14029 _: &SelectToEndOfExcerpt,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 if matches!(self.mode, EditorMode::SingleLine) {
14034 cx.propagate();
14035 return;
14036 }
14037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14038 self.change_selections(Default::default(), window, cx, |s| {
14039 s.move_heads_with(|map, head, _| {
14040 (
14041 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14042 SelectionGoal::None,
14043 )
14044 });
14045 })
14046 }
14047
14048 pub fn select_to_end_of_previous_excerpt(
14049 &mut self,
14050 _: &SelectToEndOfPreviousExcerpt,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 if matches!(self.mode, EditorMode::SingleLine) {
14055 cx.propagate();
14056 return;
14057 }
14058 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14059 self.change_selections(Default::default(), window, cx, |s| {
14060 s.move_heads_with(|map, head, _| {
14061 (
14062 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14063 SelectionGoal::None,
14064 )
14065 });
14066 })
14067 }
14068
14069 pub fn move_to_beginning(
14070 &mut self,
14071 _: &MoveToBeginning,
14072 window: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 if matches!(self.mode, EditorMode::SingleLine) {
14076 cx.propagate();
14077 return;
14078 }
14079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14080 self.change_selections(Default::default(), window, cx, |s| {
14081 s.select_ranges(vec![0..0]);
14082 });
14083 }
14084
14085 pub fn select_to_beginning(
14086 &mut self,
14087 _: &SelectToBeginning,
14088 window: &mut Window,
14089 cx: &mut Context<Self>,
14090 ) {
14091 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14092 selection.set_head(Point::zero(), SelectionGoal::None);
14093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14094 self.change_selections(Default::default(), window, cx, |s| {
14095 s.select(vec![selection]);
14096 });
14097 }
14098
14099 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14100 if matches!(self.mode, EditorMode::SingleLine) {
14101 cx.propagate();
14102 return;
14103 }
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 let cursor = self.buffer.read(cx).read(cx).len();
14106 self.change_selections(Default::default(), window, cx, |s| {
14107 s.select_ranges(vec![cursor..cursor])
14108 });
14109 }
14110
14111 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14112 self.nav_history = nav_history;
14113 }
14114
14115 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14116 self.nav_history.as_ref()
14117 }
14118
14119 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14120 self.push_to_nav_history(
14121 self.selections.newest_anchor().head(),
14122 None,
14123 false,
14124 true,
14125 cx,
14126 );
14127 }
14128
14129 fn push_to_nav_history(
14130 &mut self,
14131 cursor_anchor: Anchor,
14132 new_position: Option<Point>,
14133 is_deactivate: bool,
14134 always: bool,
14135 cx: &mut Context<Self>,
14136 ) {
14137 if let Some(nav_history) = self.nav_history.as_mut() {
14138 let buffer = self.buffer.read(cx).read(cx);
14139 let cursor_position = cursor_anchor.to_point(&buffer);
14140 let scroll_state = self.scroll_manager.anchor();
14141 let scroll_top_row = scroll_state.top_row(&buffer);
14142 drop(buffer);
14143
14144 if let Some(new_position) = new_position {
14145 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14146 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14147 return;
14148 }
14149 }
14150
14151 nav_history.push(
14152 Some(NavigationData {
14153 cursor_anchor,
14154 cursor_position,
14155 scroll_anchor: scroll_state,
14156 scroll_top_row,
14157 }),
14158 cx,
14159 );
14160 cx.emit(EditorEvent::PushedToNavHistory {
14161 anchor: cursor_anchor,
14162 is_deactivate,
14163 })
14164 }
14165 }
14166
14167 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14169 let buffer = self.buffer.read(cx).snapshot(cx);
14170 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14171 selection.set_head(buffer.len(), SelectionGoal::None);
14172 self.change_selections(Default::default(), window, cx, |s| {
14173 s.select(vec![selection]);
14174 });
14175 }
14176
14177 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14179 let end = self.buffer.read(cx).read(cx).len();
14180 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14181 s.select_ranges(vec![0..end]);
14182 });
14183 }
14184
14185 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14188 let mut selections = self.selections.all::<Point>(&display_map);
14189 let max_point = display_map.buffer_snapshot().max_point();
14190 for selection in &mut selections {
14191 let rows = selection.spanned_rows(true, &display_map);
14192 selection.start = Point::new(rows.start.0, 0);
14193 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14194 selection.reversed = false;
14195 }
14196 self.change_selections(Default::default(), window, cx, |s| {
14197 s.select(selections);
14198 });
14199 }
14200
14201 pub fn split_selection_into_lines(
14202 &mut self,
14203 action: &SplitSelectionIntoLines,
14204 window: &mut Window,
14205 cx: &mut Context<Self>,
14206 ) {
14207 let selections = self
14208 .selections
14209 .all::<Point>(&self.display_snapshot(cx))
14210 .into_iter()
14211 .map(|selection| selection.start..selection.end)
14212 .collect::<Vec<_>>();
14213 self.unfold_ranges(&selections, true, true, cx);
14214
14215 let mut new_selection_ranges = Vec::new();
14216 {
14217 let buffer = self.buffer.read(cx).read(cx);
14218 for selection in selections {
14219 for row in selection.start.row..selection.end.row {
14220 let line_start = Point::new(row, 0);
14221 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14222
14223 if action.keep_selections {
14224 // Keep the selection range for each line
14225 let selection_start = if row == selection.start.row {
14226 selection.start
14227 } else {
14228 line_start
14229 };
14230 new_selection_ranges.push(selection_start..line_end);
14231 } else {
14232 // Collapse to cursor at end of line
14233 new_selection_ranges.push(line_end..line_end);
14234 }
14235 }
14236
14237 let is_multiline_selection = selection.start.row != selection.end.row;
14238 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14239 // so this action feels more ergonomic when paired with other selection operations
14240 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14241 if !should_skip_last {
14242 if action.keep_selections {
14243 if is_multiline_selection {
14244 let line_start = Point::new(selection.end.row, 0);
14245 new_selection_ranges.push(line_start..selection.end);
14246 } else {
14247 new_selection_ranges.push(selection.start..selection.end);
14248 }
14249 } else {
14250 new_selection_ranges.push(selection.end..selection.end);
14251 }
14252 }
14253 }
14254 }
14255 self.change_selections(Default::default(), window, cx, |s| {
14256 s.select_ranges(new_selection_ranges);
14257 });
14258 }
14259
14260 pub fn add_selection_above(
14261 &mut self,
14262 action: &AddSelectionAbove,
14263 window: &mut Window,
14264 cx: &mut Context<Self>,
14265 ) {
14266 self.add_selection(true, action.skip_soft_wrap, window, cx);
14267 }
14268
14269 pub fn add_selection_below(
14270 &mut self,
14271 action: &AddSelectionBelow,
14272 window: &mut Window,
14273 cx: &mut Context<Self>,
14274 ) {
14275 self.add_selection(false, action.skip_soft_wrap, window, cx);
14276 }
14277
14278 fn add_selection(
14279 &mut self,
14280 above: bool,
14281 skip_soft_wrap: bool,
14282 window: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14286
14287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14288 let all_selections = self.selections.all::<Point>(&display_map);
14289 let text_layout_details = self.text_layout_details(window);
14290
14291 let (mut columnar_selections, new_selections_to_columnarize) = {
14292 if let Some(state) = self.add_selections_state.as_ref() {
14293 let columnar_selection_ids: HashSet<_> = state
14294 .groups
14295 .iter()
14296 .flat_map(|group| group.stack.iter())
14297 .copied()
14298 .collect();
14299
14300 all_selections
14301 .into_iter()
14302 .partition(|s| columnar_selection_ids.contains(&s.id))
14303 } else {
14304 (Vec::new(), all_selections)
14305 }
14306 };
14307
14308 let mut state = self
14309 .add_selections_state
14310 .take()
14311 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14312
14313 for selection in new_selections_to_columnarize {
14314 let range = selection.display_range(&display_map).sorted();
14315 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14316 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14317 let positions = start_x.min(end_x)..start_x.max(end_x);
14318 let mut stack = Vec::new();
14319 for row in range.start.row().0..=range.end.row().0 {
14320 if let Some(selection) = self.selections.build_columnar_selection(
14321 &display_map,
14322 DisplayRow(row),
14323 &positions,
14324 selection.reversed,
14325 &text_layout_details,
14326 ) {
14327 stack.push(selection.id);
14328 columnar_selections.push(selection);
14329 }
14330 }
14331 if !stack.is_empty() {
14332 if above {
14333 stack.reverse();
14334 }
14335 state.groups.push(AddSelectionsGroup { above, stack });
14336 }
14337 }
14338
14339 let mut final_selections = Vec::new();
14340 let end_row = if above {
14341 DisplayRow(0)
14342 } else {
14343 display_map.max_point().row()
14344 };
14345
14346 let mut last_added_item_per_group = HashMap::default();
14347 for group in state.groups.iter_mut() {
14348 if let Some(last_id) = group.stack.last() {
14349 last_added_item_per_group.insert(*last_id, group);
14350 }
14351 }
14352
14353 for selection in columnar_selections {
14354 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14355 if above == group.above {
14356 let range = selection.display_range(&display_map).sorted();
14357 debug_assert_eq!(range.start.row(), range.end.row());
14358 let mut row = range.start.row();
14359 let positions =
14360 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14361 Pixels::from(start)..Pixels::from(end)
14362 } else {
14363 let start_x =
14364 display_map.x_for_display_point(range.start, &text_layout_details);
14365 let end_x =
14366 display_map.x_for_display_point(range.end, &text_layout_details);
14367 start_x.min(end_x)..start_x.max(end_x)
14368 };
14369
14370 let mut maybe_new_selection = None;
14371 let direction = if above { -1 } else { 1 };
14372
14373 while row != end_row {
14374 if skip_soft_wrap {
14375 row = display_map
14376 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14377 .row();
14378 } else if above {
14379 row.0 -= 1;
14380 } else {
14381 row.0 += 1;
14382 }
14383
14384 if let Some(new_selection) = self.selections.build_columnar_selection(
14385 &display_map,
14386 row,
14387 &positions,
14388 selection.reversed,
14389 &text_layout_details,
14390 ) {
14391 maybe_new_selection = Some(new_selection);
14392 break;
14393 }
14394 }
14395
14396 if let Some(new_selection) = maybe_new_selection {
14397 group.stack.push(new_selection.id);
14398 if above {
14399 final_selections.push(new_selection);
14400 final_selections.push(selection);
14401 } else {
14402 final_selections.push(selection);
14403 final_selections.push(new_selection);
14404 }
14405 } else {
14406 final_selections.push(selection);
14407 }
14408 } else {
14409 group.stack.pop();
14410 }
14411 } else {
14412 final_selections.push(selection);
14413 }
14414 }
14415
14416 self.change_selections(Default::default(), window, cx, |s| {
14417 s.select(final_selections);
14418 });
14419
14420 let final_selection_ids: HashSet<_> = self
14421 .selections
14422 .all::<Point>(&display_map)
14423 .iter()
14424 .map(|s| s.id)
14425 .collect();
14426 state.groups.retain_mut(|group| {
14427 // selections might get merged above so we remove invalid items from stacks
14428 group.stack.retain(|id| final_selection_ids.contains(id));
14429
14430 // single selection in stack can be treated as initial state
14431 group.stack.len() > 1
14432 });
14433
14434 if !state.groups.is_empty() {
14435 self.add_selections_state = Some(state);
14436 }
14437 }
14438
14439 fn select_match_ranges(
14440 &mut self,
14441 range: Range<usize>,
14442 reversed: bool,
14443 replace_newest: bool,
14444 auto_scroll: Option<Autoscroll>,
14445 window: &mut Window,
14446 cx: &mut Context<Editor>,
14447 ) {
14448 self.unfold_ranges(
14449 std::slice::from_ref(&range),
14450 false,
14451 auto_scroll.is_some(),
14452 cx,
14453 );
14454 let effects = if let Some(scroll) = auto_scroll {
14455 SelectionEffects::scroll(scroll)
14456 } else {
14457 SelectionEffects::no_scroll()
14458 };
14459 self.change_selections(effects, window, cx, |s| {
14460 if replace_newest {
14461 s.delete(s.newest_anchor().id);
14462 }
14463 if reversed {
14464 s.insert_range(range.end..range.start);
14465 } else {
14466 s.insert_range(range);
14467 }
14468 });
14469 }
14470
14471 pub fn select_next_match_internal(
14472 &mut self,
14473 display_map: &DisplaySnapshot,
14474 replace_newest: bool,
14475 autoscroll: Option<Autoscroll>,
14476 window: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) -> Result<()> {
14479 let buffer = display_map.buffer_snapshot();
14480 let mut selections = self.selections.all::<usize>(&display_map);
14481 if let Some(mut select_next_state) = self.select_next_state.take() {
14482 let query = &select_next_state.query;
14483 if !select_next_state.done {
14484 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14485 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14486 let mut next_selected_range = None;
14487
14488 let bytes_after_last_selection =
14489 buffer.bytes_in_range(last_selection.end..buffer.len());
14490 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14491 let query_matches = query
14492 .stream_find_iter(bytes_after_last_selection)
14493 .map(|result| (last_selection.end, result))
14494 .chain(
14495 query
14496 .stream_find_iter(bytes_before_first_selection)
14497 .map(|result| (0, result)),
14498 );
14499
14500 for (start_offset, query_match) in query_matches {
14501 let query_match = query_match.unwrap(); // can only fail due to I/O
14502 let offset_range =
14503 start_offset + query_match.start()..start_offset + query_match.end();
14504
14505 if !select_next_state.wordwise
14506 || (!buffer.is_inside_word(offset_range.start, None)
14507 && !buffer.is_inside_word(offset_range.end, None))
14508 {
14509 let idx = selections
14510 .partition_point(|selection| selection.end <= offset_range.start);
14511 let overlaps = selections
14512 .get(idx)
14513 .map_or(false, |selection| selection.start < offset_range.end);
14514
14515 if !overlaps {
14516 next_selected_range = Some(offset_range);
14517 break;
14518 }
14519 }
14520 }
14521
14522 if let Some(next_selected_range) = next_selected_range {
14523 self.select_match_ranges(
14524 next_selected_range,
14525 last_selection.reversed,
14526 replace_newest,
14527 autoscroll,
14528 window,
14529 cx,
14530 );
14531 } else {
14532 select_next_state.done = true;
14533 }
14534 }
14535
14536 self.select_next_state = Some(select_next_state);
14537 } else {
14538 let mut only_carets = true;
14539 let mut same_text_selected = true;
14540 let mut selected_text = None;
14541
14542 let mut selections_iter = selections.iter().peekable();
14543 while let Some(selection) = selections_iter.next() {
14544 if selection.start != selection.end {
14545 only_carets = false;
14546 }
14547
14548 if same_text_selected {
14549 if selected_text.is_none() {
14550 selected_text =
14551 Some(buffer.text_for_range(selection.range()).collect::<String>());
14552 }
14553
14554 if let Some(next_selection) = selections_iter.peek() {
14555 if next_selection.range().len() == selection.range().len() {
14556 let next_selected_text = buffer
14557 .text_for_range(next_selection.range())
14558 .collect::<String>();
14559 if Some(next_selected_text) != selected_text {
14560 same_text_selected = false;
14561 selected_text = None;
14562 }
14563 } else {
14564 same_text_selected = false;
14565 selected_text = None;
14566 }
14567 }
14568 }
14569 }
14570
14571 if only_carets {
14572 for selection in &mut selections {
14573 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14574 selection.start = word_range.start;
14575 selection.end = word_range.end;
14576 selection.goal = SelectionGoal::None;
14577 selection.reversed = false;
14578 self.select_match_ranges(
14579 selection.start..selection.end,
14580 selection.reversed,
14581 replace_newest,
14582 autoscroll,
14583 window,
14584 cx,
14585 );
14586 }
14587
14588 if selections.len() == 1 {
14589 let selection = selections
14590 .last()
14591 .expect("ensured that there's only one selection");
14592 let query = buffer
14593 .text_for_range(selection.start..selection.end)
14594 .collect::<String>();
14595 let is_empty = query.is_empty();
14596 let select_state = SelectNextState {
14597 query: AhoCorasick::new(&[query])?,
14598 wordwise: true,
14599 done: is_empty,
14600 };
14601 self.select_next_state = Some(select_state);
14602 } else {
14603 self.select_next_state = None;
14604 }
14605 } else if let Some(selected_text) = selected_text {
14606 self.select_next_state = Some(SelectNextState {
14607 query: AhoCorasick::new(&[selected_text])?,
14608 wordwise: false,
14609 done: false,
14610 });
14611 self.select_next_match_internal(
14612 display_map,
14613 replace_newest,
14614 autoscroll,
14615 window,
14616 cx,
14617 )?;
14618 }
14619 }
14620 Ok(())
14621 }
14622
14623 pub fn select_all_matches(
14624 &mut self,
14625 _action: &SelectAllMatches,
14626 window: &mut Window,
14627 cx: &mut Context<Self>,
14628 ) -> Result<()> {
14629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14630
14631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14632
14633 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14634 let Some(select_next_state) = self.select_next_state.as_mut() else {
14635 return Ok(());
14636 };
14637 if select_next_state.done {
14638 return Ok(());
14639 }
14640
14641 let mut new_selections = Vec::new();
14642
14643 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14644 let buffer = display_map.buffer_snapshot();
14645 let query_matches = select_next_state
14646 .query
14647 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14648
14649 for query_match in query_matches.into_iter() {
14650 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14651 let offset_range = if reversed {
14652 query_match.end()..query_match.start()
14653 } else {
14654 query_match.start()..query_match.end()
14655 };
14656
14657 if !select_next_state.wordwise
14658 || (!buffer.is_inside_word(offset_range.start, None)
14659 && !buffer.is_inside_word(offset_range.end, None))
14660 {
14661 new_selections.push(offset_range.start..offset_range.end);
14662 }
14663 }
14664
14665 select_next_state.done = true;
14666
14667 if new_selections.is_empty() {
14668 log::error!("bug: new_selections is empty in select_all_matches");
14669 return Ok(());
14670 }
14671
14672 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14673 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14674 selections.select_ranges(new_selections)
14675 });
14676
14677 Ok(())
14678 }
14679
14680 pub fn select_next(
14681 &mut self,
14682 action: &SelectNext,
14683 window: &mut Window,
14684 cx: &mut Context<Self>,
14685 ) -> Result<()> {
14686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14688 self.select_next_match_internal(
14689 &display_map,
14690 action.replace_newest,
14691 Some(Autoscroll::newest()),
14692 window,
14693 cx,
14694 )?;
14695 Ok(())
14696 }
14697
14698 pub fn select_previous(
14699 &mut self,
14700 action: &SelectPrevious,
14701 window: &mut Window,
14702 cx: &mut Context<Self>,
14703 ) -> Result<()> {
14704 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14706 let buffer = display_map.buffer_snapshot();
14707 let mut selections = self.selections.all::<usize>(&display_map);
14708 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14709 let query = &select_prev_state.query;
14710 if !select_prev_state.done {
14711 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14712 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14713 let mut next_selected_range = None;
14714 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14715 let bytes_before_last_selection =
14716 buffer.reversed_bytes_in_range(0..last_selection.start);
14717 let bytes_after_first_selection =
14718 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14719 let query_matches = query
14720 .stream_find_iter(bytes_before_last_selection)
14721 .map(|result| (last_selection.start, result))
14722 .chain(
14723 query
14724 .stream_find_iter(bytes_after_first_selection)
14725 .map(|result| (buffer.len(), result)),
14726 );
14727 for (end_offset, query_match) in query_matches {
14728 let query_match = query_match.unwrap(); // can only fail due to I/O
14729 let offset_range =
14730 end_offset - query_match.end()..end_offset - query_match.start();
14731
14732 if !select_prev_state.wordwise
14733 || (!buffer.is_inside_word(offset_range.start, None)
14734 && !buffer.is_inside_word(offset_range.end, None))
14735 {
14736 next_selected_range = Some(offset_range);
14737 break;
14738 }
14739 }
14740
14741 if let Some(next_selected_range) = next_selected_range {
14742 self.select_match_ranges(
14743 next_selected_range,
14744 last_selection.reversed,
14745 action.replace_newest,
14746 Some(Autoscroll::newest()),
14747 window,
14748 cx,
14749 );
14750 } else {
14751 select_prev_state.done = true;
14752 }
14753 }
14754
14755 self.select_prev_state = Some(select_prev_state);
14756 } else {
14757 let mut only_carets = true;
14758 let mut same_text_selected = true;
14759 let mut selected_text = None;
14760
14761 let mut selections_iter = selections.iter().peekable();
14762 while let Some(selection) = selections_iter.next() {
14763 if selection.start != selection.end {
14764 only_carets = false;
14765 }
14766
14767 if same_text_selected {
14768 if selected_text.is_none() {
14769 selected_text =
14770 Some(buffer.text_for_range(selection.range()).collect::<String>());
14771 }
14772
14773 if let Some(next_selection) = selections_iter.peek() {
14774 if next_selection.range().len() == selection.range().len() {
14775 let next_selected_text = buffer
14776 .text_for_range(next_selection.range())
14777 .collect::<String>();
14778 if Some(next_selected_text) != selected_text {
14779 same_text_selected = false;
14780 selected_text = None;
14781 }
14782 } else {
14783 same_text_selected = false;
14784 selected_text = None;
14785 }
14786 }
14787 }
14788 }
14789
14790 if only_carets {
14791 for selection in &mut selections {
14792 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14793 selection.start = word_range.start;
14794 selection.end = word_range.end;
14795 selection.goal = SelectionGoal::None;
14796 selection.reversed = false;
14797 self.select_match_ranges(
14798 selection.start..selection.end,
14799 selection.reversed,
14800 action.replace_newest,
14801 Some(Autoscroll::newest()),
14802 window,
14803 cx,
14804 );
14805 }
14806 if selections.len() == 1 {
14807 let selection = selections
14808 .last()
14809 .expect("ensured that there's only one selection");
14810 let query = buffer
14811 .text_for_range(selection.start..selection.end)
14812 .collect::<String>();
14813 let is_empty = query.is_empty();
14814 let select_state = SelectNextState {
14815 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14816 wordwise: true,
14817 done: is_empty,
14818 };
14819 self.select_prev_state = Some(select_state);
14820 } else {
14821 self.select_prev_state = None;
14822 }
14823 } else if let Some(selected_text) = selected_text {
14824 self.select_prev_state = Some(SelectNextState {
14825 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14826 wordwise: false,
14827 done: false,
14828 });
14829 self.select_previous(action, window, cx)?;
14830 }
14831 }
14832 Ok(())
14833 }
14834
14835 pub fn find_next_match(
14836 &mut self,
14837 _: &FindNextMatch,
14838 window: &mut Window,
14839 cx: &mut Context<Self>,
14840 ) -> Result<()> {
14841 let selections = self.selections.disjoint_anchors_arc();
14842 match selections.first() {
14843 Some(first) if selections.len() >= 2 => {
14844 self.change_selections(Default::default(), window, cx, |s| {
14845 s.select_ranges([first.range()]);
14846 });
14847 }
14848 _ => self.select_next(
14849 &SelectNext {
14850 replace_newest: true,
14851 },
14852 window,
14853 cx,
14854 )?,
14855 }
14856 Ok(())
14857 }
14858
14859 pub fn find_previous_match(
14860 &mut self,
14861 _: &FindPreviousMatch,
14862 window: &mut Window,
14863 cx: &mut Context<Self>,
14864 ) -> Result<()> {
14865 let selections = self.selections.disjoint_anchors_arc();
14866 match selections.last() {
14867 Some(last) if selections.len() >= 2 => {
14868 self.change_selections(Default::default(), window, cx, |s| {
14869 s.select_ranges([last.range()]);
14870 });
14871 }
14872 _ => self.select_previous(
14873 &SelectPrevious {
14874 replace_newest: true,
14875 },
14876 window,
14877 cx,
14878 )?,
14879 }
14880 Ok(())
14881 }
14882
14883 pub fn toggle_comments(
14884 &mut self,
14885 action: &ToggleComments,
14886 window: &mut Window,
14887 cx: &mut Context<Self>,
14888 ) {
14889 if self.read_only(cx) {
14890 return;
14891 }
14892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14893 let text_layout_details = &self.text_layout_details(window);
14894 self.transact(window, cx, |this, window, cx| {
14895 let mut selections = this
14896 .selections
14897 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14898 let mut edits = Vec::new();
14899 let mut selection_edit_ranges = Vec::new();
14900 let mut last_toggled_row = None;
14901 let snapshot = this.buffer.read(cx).read(cx);
14902 let empty_str: Arc<str> = Arc::default();
14903 let mut suffixes_inserted = Vec::new();
14904 let ignore_indent = action.ignore_indent;
14905
14906 fn comment_prefix_range(
14907 snapshot: &MultiBufferSnapshot,
14908 row: MultiBufferRow,
14909 comment_prefix: &str,
14910 comment_prefix_whitespace: &str,
14911 ignore_indent: bool,
14912 ) -> Range<Point> {
14913 let indent_size = if ignore_indent {
14914 0
14915 } else {
14916 snapshot.indent_size_for_line(row).len
14917 };
14918
14919 let start = Point::new(row.0, indent_size);
14920
14921 let mut line_bytes = snapshot
14922 .bytes_in_range(start..snapshot.max_point())
14923 .flatten()
14924 .copied();
14925
14926 // If this line currently begins with the line comment prefix, then record
14927 // the range containing the prefix.
14928 if line_bytes
14929 .by_ref()
14930 .take(comment_prefix.len())
14931 .eq(comment_prefix.bytes())
14932 {
14933 // Include any whitespace that matches the comment prefix.
14934 let matching_whitespace_len = line_bytes
14935 .zip(comment_prefix_whitespace.bytes())
14936 .take_while(|(a, b)| a == b)
14937 .count() as u32;
14938 let end = Point::new(
14939 start.row,
14940 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14941 );
14942 start..end
14943 } else {
14944 start..start
14945 }
14946 }
14947
14948 fn comment_suffix_range(
14949 snapshot: &MultiBufferSnapshot,
14950 row: MultiBufferRow,
14951 comment_suffix: &str,
14952 comment_suffix_has_leading_space: bool,
14953 ) -> Range<Point> {
14954 let end = Point::new(row.0, snapshot.line_len(row));
14955 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14956
14957 let mut line_end_bytes = snapshot
14958 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14959 .flatten()
14960 .copied();
14961
14962 let leading_space_len = if suffix_start_column > 0
14963 && line_end_bytes.next() == Some(b' ')
14964 && comment_suffix_has_leading_space
14965 {
14966 1
14967 } else {
14968 0
14969 };
14970
14971 // If this line currently begins with the line comment prefix, then record
14972 // the range containing the prefix.
14973 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14974 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14975 start..end
14976 } else {
14977 end..end
14978 }
14979 }
14980
14981 // TODO: Handle selections that cross excerpts
14982 for selection in &mut selections {
14983 let start_column = snapshot
14984 .indent_size_for_line(MultiBufferRow(selection.start.row))
14985 .len;
14986 let language = if let Some(language) =
14987 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14988 {
14989 language
14990 } else {
14991 continue;
14992 };
14993
14994 selection_edit_ranges.clear();
14995
14996 // If multiple selections contain a given row, avoid processing that
14997 // row more than once.
14998 let mut start_row = MultiBufferRow(selection.start.row);
14999 if last_toggled_row == Some(start_row) {
15000 start_row = start_row.next_row();
15001 }
15002 let end_row =
15003 if selection.end.row > selection.start.row && selection.end.column == 0 {
15004 MultiBufferRow(selection.end.row - 1)
15005 } else {
15006 MultiBufferRow(selection.end.row)
15007 };
15008 last_toggled_row = Some(end_row);
15009
15010 if start_row > end_row {
15011 continue;
15012 }
15013
15014 // If the language has line comments, toggle those.
15015 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15016
15017 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15018 if ignore_indent {
15019 full_comment_prefixes = full_comment_prefixes
15020 .into_iter()
15021 .map(|s| Arc::from(s.trim_end()))
15022 .collect();
15023 }
15024
15025 if !full_comment_prefixes.is_empty() {
15026 let first_prefix = full_comment_prefixes
15027 .first()
15028 .expect("prefixes is non-empty");
15029 let prefix_trimmed_lengths = full_comment_prefixes
15030 .iter()
15031 .map(|p| p.trim_end_matches(' ').len())
15032 .collect::<SmallVec<[usize; 4]>>();
15033
15034 let mut all_selection_lines_are_comments = true;
15035
15036 for row in start_row.0..=end_row.0 {
15037 let row = MultiBufferRow(row);
15038 if start_row < end_row && snapshot.is_line_blank(row) {
15039 continue;
15040 }
15041
15042 let prefix_range = full_comment_prefixes
15043 .iter()
15044 .zip(prefix_trimmed_lengths.iter().copied())
15045 .map(|(prefix, trimmed_prefix_len)| {
15046 comment_prefix_range(
15047 snapshot.deref(),
15048 row,
15049 &prefix[..trimmed_prefix_len],
15050 &prefix[trimmed_prefix_len..],
15051 ignore_indent,
15052 )
15053 })
15054 .max_by_key(|range| range.end.column - range.start.column)
15055 .expect("prefixes is non-empty");
15056
15057 if prefix_range.is_empty() {
15058 all_selection_lines_are_comments = false;
15059 }
15060
15061 selection_edit_ranges.push(prefix_range);
15062 }
15063
15064 if all_selection_lines_are_comments {
15065 edits.extend(
15066 selection_edit_ranges
15067 .iter()
15068 .cloned()
15069 .map(|range| (range, empty_str.clone())),
15070 );
15071 } else {
15072 let min_column = selection_edit_ranges
15073 .iter()
15074 .map(|range| range.start.column)
15075 .min()
15076 .unwrap_or(0);
15077 edits.extend(selection_edit_ranges.iter().map(|range| {
15078 let position = Point::new(range.start.row, min_column);
15079 (position..position, first_prefix.clone())
15080 }));
15081 }
15082 } else if let Some(BlockCommentConfig {
15083 start: full_comment_prefix,
15084 end: comment_suffix,
15085 ..
15086 }) = language.block_comment()
15087 {
15088 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15089 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15090 let prefix_range = comment_prefix_range(
15091 snapshot.deref(),
15092 start_row,
15093 comment_prefix,
15094 comment_prefix_whitespace,
15095 ignore_indent,
15096 );
15097 let suffix_range = comment_suffix_range(
15098 snapshot.deref(),
15099 end_row,
15100 comment_suffix.trim_start_matches(' '),
15101 comment_suffix.starts_with(' '),
15102 );
15103
15104 if prefix_range.is_empty() || suffix_range.is_empty() {
15105 edits.push((
15106 prefix_range.start..prefix_range.start,
15107 full_comment_prefix.clone(),
15108 ));
15109 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15110 suffixes_inserted.push((end_row, comment_suffix.len()));
15111 } else {
15112 edits.push((prefix_range, empty_str.clone()));
15113 edits.push((suffix_range, empty_str.clone()));
15114 }
15115 } else {
15116 continue;
15117 }
15118 }
15119
15120 drop(snapshot);
15121 this.buffer.update(cx, |buffer, cx| {
15122 buffer.edit(edits, None, cx);
15123 });
15124
15125 // Adjust selections so that they end before any comment suffixes that
15126 // were inserted.
15127 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15128 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15129 let snapshot = this.buffer.read(cx).read(cx);
15130 for selection in &mut selections {
15131 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15132 match row.cmp(&MultiBufferRow(selection.end.row)) {
15133 Ordering::Less => {
15134 suffixes_inserted.next();
15135 continue;
15136 }
15137 Ordering::Greater => break,
15138 Ordering::Equal => {
15139 if selection.end.column == snapshot.line_len(row) {
15140 if selection.is_empty() {
15141 selection.start.column -= suffix_len as u32;
15142 }
15143 selection.end.column -= suffix_len as u32;
15144 }
15145 break;
15146 }
15147 }
15148 }
15149 }
15150
15151 drop(snapshot);
15152 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15153
15154 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15155 let selections_on_single_row = selections.windows(2).all(|selections| {
15156 selections[0].start.row == selections[1].start.row
15157 && selections[0].end.row == selections[1].end.row
15158 && selections[0].start.row == selections[0].end.row
15159 });
15160 let selections_selecting = selections
15161 .iter()
15162 .any(|selection| selection.start != selection.end);
15163 let advance_downwards = action.advance_downwards
15164 && selections_on_single_row
15165 && !selections_selecting
15166 && !matches!(this.mode, EditorMode::SingleLine);
15167
15168 if advance_downwards {
15169 let snapshot = this.buffer.read(cx).snapshot(cx);
15170
15171 this.change_selections(Default::default(), window, cx, |s| {
15172 s.move_cursors_with(|display_snapshot, display_point, _| {
15173 let mut point = display_point.to_point(display_snapshot);
15174 point.row += 1;
15175 point = snapshot.clip_point(point, Bias::Left);
15176 let display_point = point.to_display_point(display_snapshot);
15177 let goal = SelectionGoal::HorizontalPosition(
15178 display_snapshot
15179 .x_for_display_point(display_point, text_layout_details)
15180 .into(),
15181 );
15182 (display_point, goal)
15183 })
15184 });
15185 }
15186 });
15187 }
15188
15189 pub fn select_enclosing_symbol(
15190 &mut self,
15191 _: &SelectEnclosingSymbol,
15192 window: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) {
15195 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15196
15197 let buffer = self.buffer.read(cx).snapshot(cx);
15198 let old_selections = self
15199 .selections
15200 .all::<usize>(&self.display_snapshot(cx))
15201 .into_boxed_slice();
15202
15203 fn update_selection(
15204 selection: &Selection<usize>,
15205 buffer_snap: &MultiBufferSnapshot,
15206 ) -> Option<Selection<usize>> {
15207 let cursor = selection.head();
15208 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15209 for symbol in symbols.iter().rev() {
15210 let start = symbol.range.start.to_offset(buffer_snap);
15211 let end = symbol.range.end.to_offset(buffer_snap);
15212 let new_range = start..end;
15213 if start < selection.start || end > selection.end {
15214 return Some(Selection {
15215 id: selection.id,
15216 start: new_range.start,
15217 end: new_range.end,
15218 goal: SelectionGoal::None,
15219 reversed: selection.reversed,
15220 });
15221 }
15222 }
15223 None
15224 }
15225
15226 let mut selected_larger_symbol = false;
15227 let new_selections = old_selections
15228 .iter()
15229 .map(|selection| match update_selection(selection, &buffer) {
15230 Some(new_selection) => {
15231 if new_selection.range() != selection.range() {
15232 selected_larger_symbol = true;
15233 }
15234 new_selection
15235 }
15236 None => selection.clone(),
15237 })
15238 .collect::<Vec<_>>();
15239
15240 if selected_larger_symbol {
15241 self.change_selections(Default::default(), window, cx, |s| {
15242 s.select(new_selections);
15243 });
15244 }
15245 }
15246
15247 pub fn select_larger_syntax_node(
15248 &mut self,
15249 _: &SelectLargerSyntaxNode,
15250 window: &mut Window,
15251 cx: &mut Context<Self>,
15252 ) {
15253 let Some(visible_row_count) = self.visible_row_count() else {
15254 return;
15255 };
15256 let old_selections: Box<[_]> = self
15257 .selections
15258 .all::<usize>(&self.display_snapshot(cx))
15259 .into();
15260 if old_selections.is_empty() {
15261 return;
15262 }
15263
15264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15265
15266 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15267 let buffer = self.buffer.read(cx).snapshot(cx);
15268
15269 let mut selected_larger_node = false;
15270 let mut new_selections = old_selections
15271 .iter()
15272 .map(|selection| {
15273 let old_range = selection.start..selection.end;
15274
15275 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15276 // manually select word at selection
15277 if ["string_content", "inline"].contains(&node.kind()) {
15278 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15279 // ignore if word is already selected
15280 if !word_range.is_empty() && old_range != word_range {
15281 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15282 // only select word if start and end point belongs to same word
15283 if word_range == last_word_range {
15284 selected_larger_node = true;
15285 return Selection {
15286 id: selection.id,
15287 start: word_range.start,
15288 end: word_range.end,
15289 goal: SelectionGoal::None,
15290 reversed: selection.reversed,
15291 };
15292 }
15293 }
15294 }
15295 }
15296
15297 let mut new_range = old_range.clone();
15298 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15299 new_range = range;
15300 if !node.is_named() {
15301 continue;
15302 }
15303 if !display_map.intersects_fold(new_range.start)
15304 && !display_map.intersects_fold(new_range.end)
15305 {
15306 break;
15307 }
15308 }
15309
15310 selected_larger_node |= new_range != old_range;
15311 Selection {
15312 id: selection.id,
15313 start: new_range.start,
15314 end: new_range.end,
15315 goal: SelectionGoal::None,
15316 reversed: selection.reversed,
15317 }
15318 })
15319 .collect::<Vec<_>>();
15320
15321 if !selected_larger_node {
15322 return; // don't put this call in the history
15323 }
15324
15325 // scroll based on transformation done to the last selection created by the user
15326 let (last_old, last_new) = old_selections
15327 .last()
15328 .zip(new_selections.last().cloned())
15329 .expect("old_selections isn't empty");
15330
15331 // revert selection
15332 let is_selection_reversed = {
15333 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15334 new_selections.last_mut().expect("checked above").reversed =
15335 should_newest_selection_be_reversed;
15336 should_newest_selection_be_reversed
15337 };
15338
15339 if selected_larger_node {
15340 self.select_syntax_node_history.disable_clearing = true;
15341 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15342 s.select(new_selections.clone());
15343 });
15344 self.select_syntax_node_history.disable_clearing = false;
15345 }
15346
15347 let start_row = last_new.start.to_display_point(&display_map).row().0;
15348 let end_row = last_new.end.to_display_point(&display_map).row().0;
15349 let selection_height = end_row - start_row + 1;
15350 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15351
15352 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15353 let scroll_behavior = if fits_on_the_screen {
15354 self.request_autoscroll(Autoscroll::fit(), cx);
15355 SelectSyntaxNodeScrollBehavior::FitSelection
15356 } else if is_selection_reversed {
15357 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15358 SelectSyntaxNodeScrollBehavior::CursorTop
15359 } else {
15360 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15361 SelectSyntaxNodeScrollBehavior::CursorBottom
15362 };
15363
15364 self.select_syntax_node_history.push((
15365 old_selections,
15366 scroll_behavior,
15367 is_selection_reversed,
15368 ));
15369 }
15370
15371 pub fn select_smaller_syntax_node(
15372 &mut self,
15373 _: &SelectSmallerSyntaxNode,
15374 window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) {
15377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15378
15379 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15380 self.select_syntax_node_history.pop()
15381 {
15382 if let Some(selection) = selections.last_mut() {
15383 selection.reversed = is_selection_reversed;
15384 }
15385
15386 self.select_syntax_node_history.disable_clearing = true;
15387 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15388 s.select(selections.to_vec());
15389 });
15390 self.select_syntax_node_history.disable_clearing = false;
15391
15392 match scroll_behavior {
15393 SelectSyntaxNodeScrollBehavior::CursorTop => {
15394 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15395 }
15396 SelectSyntaxNodeScrollBehavior::FitSelection => {
15397 self.request_autoscroll(Autoscroll::fit(), cx);
15398 }
15399 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15400 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15401 }
15402 }
15403 }
15404 }
15405
15406 pub fn unwrap_syntax_node(
15407 &mut self,
15408 _: &UnwrapSyntaxNode,
15409 window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15413
15414 let buffer = self.buffer.read(cx).snapshot(cx);
15415 let selections = self
15416 .selections
15417 .all::<usize>(&self.display_snapshot(cx))
15418 .into_iter()
15419 // subtracting the offset requires sorting
15420 .sorted_by_key(|i| i.start);
15421
15422 let full_edits = selections
15423 .into_iter()
15424 .filter_map(|selection| {
15425 let child = if selection.is_empty()
15426 && let Some((_, ancestor_range)) =
15427 buffer.syntax_ancestor(selection.start..selection.end)
15428 {
15429 ancestor_range
15430 } else {
15431 selection.range()
15432 };
15433
15434 let mut parent = child.clone();
15435 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15436 parent = ancestor_range;
15437 if parent.start < child.start || parent.end > child.end {
15438 break;
15439 }
15440 }
15441
15442 if parent == child {
15443 return None;
15444 }
15445 let text = buffer.text_for_range(child).collect::<String>();
15446 Some((selection.id, parent, text))
15447 })
15448 .collect::<Vec<_>>();
15449 if full_edits.is_empty() {
15450 return;
15451 }
15452
15453 self.transact(window, cx, |this, window, cx| {
15454 this.buffer.update(cx, |buffer, cx| {
15455 buffer.edit(
15456 full_edits
15457 .iter()
15458 .map(|(_, p, t)| (p.clone(), t.clone()))
15459 .collect::<Vec<_>>(),
15460 None,
15461 cx,
15462 );
15463 });
15464 this.change_selections(Default::default(), window, cx, |s| {
15465 let mut offset = 0;
15466 let mut selections = vec![];
15467 for (id, parent, text) in full_edits {
15468 let start = parent.start - offset;
15469 offset += parent.len() - text.len();
15470 selections.push(Selection {
15471 id,
15472 start,
15473 end: start + text.len(),
15474 reversed: false,
15475 goal: Default::default(),
15476 });
15477 }
15478 s.select(selections);
15479 });
15480 });
15481 }
15482
15483 pub fn select_next_syntax_node(
15484 &mut self,
15485 _: &SelectNextSyntaxNode,
15486 window: &mut Window,
15487 cx: &mut Context<Self>,
15488 ) {
15489 let old_selections: Box<[_]> = self
15490 .selections
15491 .all::<usize>(&self.display_snapshot(cx))
15492 .into();
15493 if old_selections.is_empty() {
15494 return;
15495 }
15496
15497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15498
15499 let buffer = self.buffer.read(cx).snapshot(cx);
15500 let mut selected_sibling = false;
15501
15502 let new_selections = old_selections
15503 .iter()
15504 .map(|selection| {
15505 let old_range = selection.start..selection.end;
15506
15507 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15508 let new_range = node.byte_range();
15509 selected_sibling = true;
15510 Selection {
15511 id: selection.id,
15512 start: new_range.start,
15513 end: new_range.end,
15514 goal: SelectionGoal::None,
15515 reversed: selection.reversed,
15516 }
15517 } else {
15518 selection.clone()
15519 }
15520 })
15521 .collect::<Vec<_>>();
15522
15523 if selected_sibling {
15524 self.change_selections(
15525 SelectionEffects::scroll(Autoscroll::fit()),
15526 window,
15527 cx,
15528 |s| {
15529 s.select(new_selections);
15530 },
15531 );
15532 }
15533 }
15534
15535 pub fn select_prev_syntax_node(
15536 &mut self,
15537 _: &SelectPreviousSyntaxNode,
15538 window: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) {
15541 let old_selections: Box<[_]> = self
15542 .selections
15543 .all::<usize>(&self.display_snapshot(cx))
15544 .into();
15545 if old_selections.is_empty() {
15546 return;
15547 }
15548
15549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15550
15551 let buffer = self.buffer.read(cx).snapshot(cx);
15552 let mut selected_sibling = false;
15553
15554 let new_selections = old_selections
15555 .iter()
15556 .map(|selection| {
15557 let old_range = selection.start..selection.end;
15558
15559 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15560 let new_range = node.byte_range();
15561 selected_sibling = true;
15562 Selection {
15563 id: selection.id,
15564 start: new_range.start,
15565 end: new_range.end,
15566 goal: SelectionGoal::None,
15567 reversed: selection.reversed,
15568 }
15569 } else {
15570 selection.clone()
15571 }
15572 })
15573 .collect::<Vec<_>>();
15574
15575 if selected_sibling {
15576 self.change_selections(
15577 SelectionEffects::scroll(Autoscroll::fit()),
15578 window,
15579 cx,
15580 |s| {
15581 s.select(new_selections);
15582 },
15583 );
15584 }
15585 }
15586
15587 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15588 if !EditorSettings::get_global(cx).gutter.runnables {
15589 self.clear_tasks();
15590 return Task::ready(());
15591 }
15592 let project = self.project().map(Entity::downgrade);
15593 let task_sources = self.lsp_task_sources(cx);
15594 let multi_buffer = self.buffer.downgrade();
15595 cx.spawn_in(window, async move |editor, cx| {
15596 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15597 let Some(project) = project.and_then(|p| p.upgrade()) else {
15598 return;
15599 };
15600 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15601 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15602 }) else {
15603 return;
15604 };
15605
15606 let hide_runnables = project
15607 .update(cx, |project, _| project.is_via_collab())
15608 .unwrap_or(true);
15609 if hide_runnables {
15610 return;
15611 }
15612 let new_rows =
15613 cx.background_spawn({
15614 let snapshot = display_snapshot.clone();
15615 async move {
15616 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15617 }
15618 })
15619 .await;
15620 let Ok(lsp_tasks) =
15621 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15622 else {
15623 return;
15624 };
15625 let lsp_tasks = lsp_tasks.await;
15626
15627 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15628 lsp_tasks
15629 .into_iter()
15630 .flat_map(|(kind, tasks)| {
15631 tasks.into_iter().filter_map(move |(location, task)| {
15632 Some((kind.clone(), location?, task))
15633 })
15634 })
15635 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15636 let buffer = location.target.buffer;
15637 let buffer_snapshot = buffer.read(cx).snapshot();
15638 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15639 |(excerpt_id, snapshot, _)| {
15640 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15641 display_snapshot
15642 .buffer_snapshot()
15643 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15644 } else {
15645 None
15646 }
15647 },
15648 );
15649 if let Some(offset) = offset {
15650 let task_buffer_range =
15651 location.target.range.to_point(&buffer_snapshot);
15652 let context_buffer_range =
15653 task_buffer_range.to_offset(&buffer_snapshot);
15654 let context_range = BufferOffset(context_buffer_range.start)
15655 ..BufferOffset(context_buffer_range.end);
15656
15657 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15658 .or_insert_with(|| RunnableTasks {
15659 templates: Vec::new(),
15660 offset,
15661 column: task_buffer_range.start.column,
15662 extra_variables: HashMap::default(),
15663 context_range,
15664 })
15665 .templates
15666 .push((kind, task.original_task().clone()));
15667 }
15668
15669 acc
15670 })
15671 }) else {
15672 return;
15673 };
15674
15675 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15676 buffer.language_settings(cx).tasks.prefer_lsp
15677 }) else {
15678 return;
15679 };
15680
15681 let rows = Self::runnable_rows(
15682 project,
15683 display_snapshot,
15684 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15685 new_rows,
15686 cx.clone(),
15687 )
15688 .await;
15689 editor
15690 .update(cx, |editor, _| {
15691 editor.clear_tasks();
15692 for (key, mut value) in rows {
15693 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15694 value.templates.extend(lsp_tasks.templates);
15695 }
15696
15697 editor.insert_tasks(key, value);
15698 }
15699 for (key, value) in lsp_tasks_by_rows {
15700 editor.insert_tasks(key, value);
15701 }
15702 })
15703 .ok();
15704 })
15705 }
15706 fn fetch_runnable_ranges(
15707 snapshot: &DisplaySnapshot,
15708 range: Range<Anchor>,
15709 ) -> Vec<language::RunnableRange> {
15710 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15711 }
15712
15713 fn runnable_rows(
15714 project: Entity<Project>,
15715 snapshot: DisplaySnapshot,
15716 prefer_lsp: bool,
15717 runnable_ranges: Vec<RunnableRange>,
15718 cx: AsyncWindowContext,
15719 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15720 cx.spawn(async move |cx| {
15721 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15722 for mut runnable in runnable_ranges {
15723 let Some(tasks) = cx
15724 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15725 .ok()
15726 else {
15727 continue;
15728 };
15729 let mut tasks = tasks.await;
15730
15731 if prefer_lsp {
15732 tasks.retain(|(task_kind, _)| {
15733 !matches!(task_kind, TaskSourceKind::Language { .. })
15734 });
15735 }
15736 if tasks.is_empty() {
15737 continue;
15738 }
15739
15740 let point = runnable
15741 .run_range
15742 .start
15743 .to_point(&snapshot.buffer_snapshot());
15744 let Some(row) = snapshot
15745 .buffer_snapshot()
15746 .buffer_line_for_row(MultiBufferRow(point.row))
15747 .map(|(_, range)| range.start.row)
15748 else {
15749 continue;
15750 };
15751
15752 let context_range =
15753 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15754 runnable_rows.push((
15755 (runnable.buffer_id, row),
15756 RunnableTasks {
15757 templates: tasks,
15758 offset: snapshot
15759 .buffer_snapshot()
15760 .anchor_before(runnable.run_range.start),
15761 context_range,
15762 column: point.column,
15763 extra_variables: runnable.extra_captures,
15764 },
15765 ));
15766 }
15767 runnable_rows
15768 })
15769 }
15770
15771 fn templates_with_tags(
15772 project: &Entity<Project>,
15773 runnable: &mut Runnable,
15774 cx: &mut App,
15775 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15776 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15777 let (worktree_id, file) = project
15778 .buffer_for_id(runnable.buffer, cx)
15779 .and_then(|buffer| buffer.read(cx).file())
15780 .map(|file| (file.worktree_id(cx), file.clone()))
15781 .unzip();
15782
15783 (
15784 project.task_store().read(cx).task_inventory().cloned(),
15785 worktree_id,
15786 file,
15787 )
15788 });
15789
15790 let tags = mem::take(&mut runnable.tags);
15791 let language = runnable.language.clone();
15792 cx.spawn(async move |cx| {
15793 let mut templates_with_tags = Vec::new();
15794 if let Some(inventory) = inventory {
15795 for RunnableTag(tag) in tags {
15796 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15797 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15798 }) else {
15799 return templates_with_tags;
15800 };
15801 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15802 move |(_, template)| {
15803 template.tags.iter().any(|source_tag| source_tag == &tag)
15804 },
15805 ));
15806 }
15807 }
15808 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15809
15810 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15811 // Strongest source wins; if we have worktree tag binding, prefer that to
15812 // global and language bindings;
15813 // if we have a global binding, prefer that to language binding.
15814 let first_mismatch = templates_with_tags
15815 .iter()
15816 .position(|(tag_source, _)| tag_source != leading_tag_source);
15817 if let Some(index) = first_mismatch {
15818 templates_with_tags.truncate(index);
15819 }
15820 }
15821
15822 templates_with_tags
15823 })
15824 }
15825
15826 pub fn move_to_enclosing_bracket(
15827 &mut self,
15828 _: &MoveToEnclosingBracket,
15829 window: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15833 self.change_selections(Default::default(), window, cx, |s| {
15834 s.move_offsets_with(|snapshot, selection| {
15835 let Some(enclosing_bracket_ranges) =
15836 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15837 else {
15838 return;
15839 };
15840
15841 let mut best_length = usize::MAX;
15842 let mut best_inside = false;
15843 let mut best_in_bracket_range = false;
15844 let mut best_destination = None;
15845 for (open, close) in enclosing_bracket_ranges {
15846 let close = close.to_inclusive();
15847 let length = close.end() - open.start;
15848 let inside = selection.start >= open.end && selection.end <= *close.start();
15849 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15850 || close.contains(&selection.head());
15851
15852 // If best is next to a bracket and current isn't, skip
15853 if !in_bracket_range && best_in_bracket_range {
15854 continue;
15855 }
15856
15857 // Prefer smaller lengths unless best is inside and current isn't
15858 if length > best_length && (best_inside || !inside) {
15859 continue;
15860 }
15861
15862 best_length = length;
15863 best_inside = inside;
15864 best_in_bracket_range = in_bracket_range;
15865 best_destination = Some(
15866 if close.contains(&selection.start) && close.contains(&selection.end) {
15867 if inside { open.end } else { open.start }
15868 } else if inside {
15869 *close.start()
15870 } else {
15871 *close.end()
15872 },
15873 );
15874 }
15875
15876 if let Some(destination) = best_destination {
15877 selection.collapse_to(destination, SelectionGoal::None);
15878 }
15879 })
15880 });
15881 }
15882
15883 pub fn undo_selection(
15884 &mut self,
15885 _: &UndoSelection,
15886 window: &mut Window,
15887 cx: &mut Context<Self>,
15888 ) {
15889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15890 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15891 self.selection_history.mode = SelectionHistoryMode::Undoing;
15892 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15893 this.end_selection(window, cx);
15894 this.change_selections(
15895 SelectionEffects::scroll(Autoscroll::newest()),
15896 window,
15897 cx,
15898 |s| s.select_anchors(entry.selections.to_vec()),
15899 );
15900 });
15901 self.selection_history.mode = SelectionHistoryMode::Normal;
15902
15903 self.select_next_state = entry.select_next_state;
15904 self.select_prev_state = entry.select_prev_state;
15905 self.add_selections_state = entry.add_selections_state;
15906 }
15907 }
15908
15909 pub fn redo_selection(
15910 &mut self,
15911 _: &RedoSelection,
15912 window: &mut Window,
15913 cx: &mut Context<Self>,
15914 ) {
15915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15916 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15917 self.selection_history.mode = SelectionHistoryMode::Redoing;
15918 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15919 this.end_selection(window, cx);
15920 this.change_selections(
15921 SelectionEffects::scroll(Autoscroll::newest()),
15922 window,
15923 cx,
15924 |s| s.select_anchors(entry.selections.to_vec()),
15925 );
15926 });
15927 self.selection_history.mode = SelectionHistoryMode::Normal;
15928
15929 self.select_next_state = entry.select_next_state;
15930 self.select_prev_state = entry.select_prev_state;
15931 self.add_selections_state = entry.add_selections_state;
15932 }
15933 }
15934
15935 pub fn expand_excerpts(
15936 &mut self,
15937 action: &ExpandExcerpts,
15938 _: &mut Window,
15939 cx: &mut Context<Self>,
15940 ) {
15941 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15942 }
15943
15944 pub fn expand_excerpts_down(
15945 &mut self,
15946 action: &ExpandExcerptsDown,
15947 _: &mut Window,
15948 cx: &mut Context<Self>,
15949 ) {
15950 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15951 }
15952
15953 pub fn expand_excerpts_up(
15954 &mut self,
15955 action: &ExpandExcerptsUp,
15956 _: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) {
15959 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15960 }
15961
15962 pub fn expand_excerpts_for_direction(
15963 &mut self,
15964 lines: u32,
15965 direction: ExpandExcerptDirection,
15966
15967 cx: &mut Context<Self>,
15968 ) {
15969 let selections = self.selections.disjoint_anchors_arc();
15970
15971 let lines = if lines == 0 {
15972 EditorSettings::get_global(cx).expand_excerpt_lines
15973 } else {
15974 lines
15975 };
15976
15977 self.buffer.update(cx, |buffer, cx| {
15978 let snapshot = buffer.snapshot(cx);
15979 let mut excerpt_ids = selections
15980 .iter()
15981 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15982 .collect::<Vec<_>>();
15983 excerpt_ids.sort();
15984 excerpt_ids.dedup();
15985 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15986 })
15987 }
15988
15989 pub fn expand_excerpt(
15990 &mut self,
15991 excerpt: ExcerptId,
15992 direction: ExpandExcerptDirection,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 let current_scroll_position = self.scroll_position(cx);
15997 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15998 let mut scroll = None;
15999
16000 if direction == ExpandExcerptDirection::Down {
16001 let multi_buffer = self.buffer.read(cx);
16002 let snapshot = multi_buffer.snapshot(cx);
16003 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16004 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16005 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16006 {
16007 let buffer_snapshot = buffer.read(cx).snapshot();
16008 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16009 let last_row = buffer_snapshot.max_point().row;
16010 let lines_below = last_row.saturating_sub(excerpt_end_row);
16011 if lines_below >= lines_to_expand {
16012 scroll = Some(
16013 current_scroll_position
16014 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16015 );
16016 }
16017 }
16018 }
16019 if direction == ExpandExcerptDirection::Up
16020 && self
16021 .buffer
16022 .read(cx)
16023 .snapshot(cx)
16024 .excerpt_before(excerpt)
16025 .is_none()
16026 {
16027 scroll = Some(current_scroll_position);
16028 }
16029
16030 self.buffer.update(cx, |buffer, cx| {
16031 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16032 });
16033
16034 if let Some(new_scroll_position) = scroll {
16035 self.set_scroll_position(new_scroll_position, window, cx);
16036 }
16037 }
16038
16039 pub fn go_to_singleton_buffer_point(
16040 &mut self,
16041 point: Point,
16042 window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) {
16045 self.go_to_singleton_buffer_range(point..point, window, cx);
16046 }
16047
16048 pub fn go_to_singleton_buffer_range(
16049 &mut self,
16050 range: Range<Point>,
16051 window: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 let multibuffer = self.buffer().read(cx);
16055 let Some(buffer) = multibuffer.as_singleton() else {
16056 return;
16057 };
16058 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16059 return;
16060 };
16061 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16062 return;
16063 };
16064 self.change_selections(
16065 SelectionEffects::default().nav_history(true),
16066 window,
16067 cx,
16068 |s| s.select_anchor_ranges([start..end]),
16069 );
16070 }
16071
16072 pub fn go_to_diagnostic(
16073 &mut self,
16074 action: &GoToDiagnostic,
16075 window: &mut Window,
16076 cx: &mut Context<Self>,
16077 ) {
16078 if !self.diagnostics_enabled() {
16079 return;
16080 }
16081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16082 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16083 }
16084
16085 pub fn go_to_prev_diagnostic(
16086 &mut self,
16087 action: &GoToPreviousDiagnostic,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 if !self.diagnostics_enabled() {
16092 return;
16093 }
16094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16095 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16096 }
16097
16098 pub fn go_to_diagnostic_impl(
16099 &mut self,
16100 direction: Direction,
16101 severity: GoToDiagnosticSeverityFilter,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 let buffer = self.buffer.read(cx).snapshot(cx);
16106 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16107
16108 let mut active_group_id = None;
16109 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16110 && active_group.active_range.start.to_offset(&buffer) == selection.start
16111 {
16112 active_group_id = Some(active_group.group_id);
16113 }
16114
16115 fn filtered<'a>(
16116 severity: GoToDiagnosticSeverityFilter,
16117 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16118 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16119 diagnostics
16120 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16121 .filter(|entry| entry.range.start != entry.range.end)
16122 .filter(|entry| !entry.diagnostic.is_unnecessary)
16123 }
16124
16125 let before = filtered(
16126 severity,
16127 buffer
16128 .diagnostics_in_range(0..selection.start)
16129 .filter(|entry| entry.range.start <= selection.start),
16130 );
16131 let after = filtered(
16132 severity,
16133 buffer
16134 .diagnostics_in_range(selection.start..buffer.len())
16135 .filter(|entry| entry.range.start >= selection.start),
16136 );
16137
16138 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16139 if direction == Direction::Prev {
16140 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16141 {
16142 for diagnostic in prev_diagnostics.into_iter().rev() {
16143 if diagnostic.range.start != selection.start
16144 || active_group_id
16145 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16146 {
16147 found = Some(diagnostic);
16148 break 'outer;
16149 }
16150 }
16151 }
16152 } else {
16153 for diagnostic in after.chain(before) {
16154 if diagnostic.range.start != selection.start
16155 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16156 {
16157 found = Some(diagnostic);
16158 break;
16159 }
16160 }
16161 }
16162 let Some(next_diagnostic) = found else {
16163 return;
16164 };
16165
16166 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16167 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16168 return;
16169 };
16170 let snapshot = self.snapshot(window, cx);
16171 if snapshot.intersects_fold(next_diagnostic.range.start) {
16172 self.unfold_ranges(
16173 std::slice::from_ref(&next_diagnostic.range),
16174 true,
16175 false,
16176 cx,
16177 );
16178 }
16179 self.change_selections(Default::default(), window, cx, |s| {
16180 s.select_ranges(vec![
16181 next_diagnostic.range.start..next_diagnostic.range.start,
16182 ])
16183 });
16184 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16185 self.refresh_edit_prediction(false, true, window, cx);
16186 }
16187
16188 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16190 let snapshot = self.snapshot(window, cx);
16191 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16192 self.go_to_hunk_before_or_after_position(
16193 &snapshot,
16194 selection.head(),
16195 Direction::Next,
16196 window,
16197 cx,
16198 );
16199 }
16200
16201 pub fn go_to_hunk_before_or_after_position(
16202 &mut self,
16203 snapshot: &EditorSnapshot,
16204 position: Point,
16205 direction: Direction,
16206 window: &mut Window,
16207 cx: &mut Context<Editor>,
16208 ) {
16209 let row = if direction == Direction::Next {
16210 self.hunk_after_position(snapshot, position)
16211 .map(|hunk| hunk.row_range.start)
16212 } else {
16213 self.hunk_before_position(snapshot, position)
16214 };
16215
16216 if let Some(row) = row {
16217 let destination = Point::new(row.0, 0);
16218 let autoscroll = Autoscroll::center();
16219
16220 self.unfold_ranges(&[destination..destination], false, false, cx);
16221 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16222 s.select_ranges([destination..destination]);
16223 });
16224 }
16225 }
16226
16227 fn hunk_after_position(
16228 &mut self,
16229 snapshot: &EditorSnapshot,
16230 position: Point,
16231 ) -> Option<MultiBufferDiffHunk> {
16232 snapshot
16233 .buffer_snapshot()
16234 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16235 .find(|hunk| hunk.row_range.start.0 > position.row)
16236 .or_else(|| {
16237 snapshot
16238 .buffer_snapshot()
16239 .diff_hunks_in_range(Point::zero()..position)
16240 .find(|hunk| hunk.row_range.end.0 < position.row)
16241 })
16242 }
16243
16244 fn go_to_prev_hunk(
16245 &mut self,
16246 _: &GoToPreviousHunk,
16247 window: &mut Window,
16248 cx: &mut Context<Self>,
16249 ) {
16250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16251 let snapshot = self.snapshot(window, cx);
16252 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16253 self.go_to_hunk_before_or_after_position(
16254 &snapshot,
16255 selection.head(),
16256 Direction::Prev,
16257 window,
16258 cx,
16259 );
16260 }
16261
16262 fn hunk_before_position(
16263 &mut self,
16264 snapshot: &EditorSnapshot,
16265 position: Point,
16266 ) -> Option<MultiBufferRow> {
16267 snapshot
16268 .buffer_snapshot()
16269 .diff_hunk_before(position)
16270 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16271 }
16272
16273 fn go_to_next_change(
16274 &mut self,
16275 _: &GoToNextChange,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 if let Some(selections) = self
16280 .change_list
16281 .next_change(1, Direction::Next)
16282 .map(|s| s.to_vec())
16283 {
16284 self.change_selections(Default::default(), window, cx, |s| {
16285 let map = s.display_snapshot();
16286 s.select_display_ranges(selections.iter().map(|a| {
16287 let point = a.to_display_point(&map);
16288 point..point
16289 }))
16290 })
16291 }
16292 }
16293
16294 fn go_to_previous_change(
16295 &mut self,
16296 _: &GoToPreviousChange,
16297 window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) {
16300 if let Some(selections) = self
16301 .change_list
16302 .next_change(1, Direction::Prev)
16303 .map(|s| s.to_vec())
16304 {
16305 self.change_selections(Default::default(), window, cx, |s| {
16306 let map = s.display_snapshot();
16307 s.select_display_ranges(selections.iter().map(|a| {
16308 let point = a.to_display_point(&map);
16309 point..point
16310 }))
16311 })
16312 }
16313 }
16314
16315 pub fn go_to_next_document_highlight(
16316 &mut self,
16317 _: &GoToNextDocumentHighlight,
16318 window: &mut Window,
16319 cx: &mut Context<Self>,
16320 ) {
16321 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16322 }
16323
16324 pub fn go_to_prev_document_highlight(
16325 &mut self,
16326 _: &GoToPreviousDocumentHighlight,
16327 window: &mut Window,
16328 cx: &mut Context<Self>,
16329 ) {
16330 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16331 }
16332
16333 pub fn go_to_document_highlight_before_or_after_position(
16334 &mut self,
16335 direction: Direction,
16336 window: &mut Window,
16337 cx: &mut Context<Editor>,
16338 ) {
16339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16340 let snapshot = self.snapshot(window, cx);
16341 let buffer = &snapshot.buffer_snapshot();
16342 let position = self
16343 .selections
16344 .newest::<Point>(&snapshot.display_snapshot)
16345 .head();
16346 let anchor_position = buffer.anchor_after(position);
16347
16348 // Get all document highlights (both read and write)
16349 let mut all_highlights = Vec::new();
16350
16351 if let Some((_, read_highlights)) = self
16352 .background_highlights
16353 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16354 {
16355 all_highlights.extend(read_highlights.iter());
16356 }
16357
16358 if let Some((_, write_highlights)) = self
16359 .background_highlights
16360 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16361 {
16362 all_highlights.extend(write_highlights.iter());
16363 }
16364
16365 if all_highlights.is_empty() {
16366 return;
16367 }
16368
16369 // Sort highlights by position
16370 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16371
16372 let target_highlight = match direction {
16373 Direction::Next => {
16374 // Find the first highlight after the current position
16375 all_highlights
16376 .iter()
16377 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16378 }
16379 Direction::Prev => {
16380 // Find the last highlight before the current position
16381 all_highlights
16382 .iter()
16383 .rev()
16384 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16385 }
16386 };
16387
16388 if let Some(highlight) = target_highlight {
16389 let destination = highlight.start.to_point(buffer);
16390 let autoscroll = Autoscroll::center();
16391
16392 self.unfold_ranges(&[destination..destination], false, false, cx);
16393 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16394 s.select_ranges([destination..destination]);
16395 });
16396 }
16397 }
16398
16399 fn go_to_line<T: 'static>(
16400 &mut self,
16401 position: Anchor,
16402 highlight_color: Option<Hsla>,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) {
16406 let snapshot = self.snapshot(window, cx).display_snapshot;
16407 let position = position.to_point(&snapshot.buffer_snapshot());
16408 let start = snapshot
16409 .buffer_snapshot()
16410 .clip_point(Point::new(position.row, 0), Bias::Left);
16411 let end = start + Point::new(1, 0);
16412 let start = snapshot.buffer_snapshot().anchor_before(start);
16413 let end = snapshot.buffer_snapshot().anchor_before(end);
16414
16415 self.highlight_rows::<T>(
16416 start..end,
16417 highlight_color
16418 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16419 Default::default(),
16420 cx,
16421 );
16422
16423 if self.buffer.read(cx).is_singleton() {
16424 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16425 }
16426 }
16427
16428 pub fn go_to_definition(
16429 &mut self,
16430 _: &GoToDefinition,
16431 window: &mut Window,
16432 cx: &mut Context<Self>,
16433 ) -> Task<Result<Navigated>> {
16434 let definition =
16435 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16436 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16437 cx.spawn_in(window, async move |editor, cx| {
16438 if definition.await? == Navigated::Yes {
16439 return Ok(Navigated::Yes);
16440 }
16441 match fallback_strategy {
16442 GoToDefinitionFallback::None => Ok(Navigated::No),
16443 GoToDefinitionFallback::FindAllReferences => {
16444 match editor.update_in(cx, |editor, window, cx| {
16445 editor.find_all_references(&FindAllReferences, window, cx)
16446 })? {
16447 Some(references) => references.await,
16448 None => Ok(Navigated::No),
16449 }
16450 }
16451 }
16452 })
16453 }
16454
16455 pub fn go_to_declaration(
16456 &mut self,
16457 _: &GoToDeclaration,
16458 window: &mut Window,
16459 cx: &mut Context<Self>,
16460 ) -> Task<Result<Navigated>> {
16461 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16462 }
16463
16464 pub fn go_to_declaration_split(
16465 &mut self,
16466 _: &GoToDeclaration,
16467 window: &mut Window,
16468 cx: &mut Context<Self>,
16469 ) -> Task<Result<Navigated>> {
16470 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16471 }
16472
16473 pub fn go_to_implementation(
16474 &mut self,
16475 _: &GoToImplementation,
16476 window: &mut Window,
16477 cx: &mut Context<Self>,
16478 ) -> Task<Result<Navigated>> {
16479 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16480 }
16481
16482 pub fn go_to_implementation_split(
16483 &mut self,
16484 _: &GoToImplementationSplit,
16485 window: &mut Window,
16486 cx: &mut Context<Self>,
16487 ) -> Task<Result<Navigated>> {
16488 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16489 }
16490
16491 pub fn go_to_type_definition(
16492 &mut self,
16493 _: &GoToTypeDefinition,
16494 window: &mut Window,
16495 cx: &mut Context<Self>,
16496 ) -> Task<Result<Navigated>> {
16497 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16498 }
16499
16500 pub fn go_to_definition_split(
16501 &mut self,
16502 _: &GoToDefinitionSplit,
16503 window: &mut Window,
16504 cx: &mut Context<Self>,
16505 ) -> Task<Result<Navigated>> {
16506 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16507 }
16508
16509 pub fn go_to_type_definition_split(
16510 &mut self,
16511 _: &GoToTypeDefinitionSplit,
16512 window: &mut Window,
16513 cx: &mut Context<Self>,
16514 ) -> Task<Result<Navigated>> {
16515 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16516 }
16517
16518 fn go_to_definition_of_kind(
16519 &mut self,
16520 kind: GotoDefinitionKind,
16521 split: bool,
16522 window: &mut Window,
16523 cx: &mut Context<Self>,
16524 ) -> Task<Result<Navigated>> {
16525 let Some(provider) = self.semantics_provider.clone() else {
16526 return Task::ready(Ok(Navigated::No));
16527 };
16528 let head = self
16529 .selections
16530 .newest::<usize>(&self.display_snapshot(cx))
16531 .head();
16532 let buffer = self.buffer.read(cx);
16533 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16534 return Task::ready(Ok(Navigated::No));
16535 };
16536 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16537 return Task::ready(Ok(Navigated::No));
16538 };
16539
16540 cx.spawn_in(window, async move |editor, cx| {
16541 let Some(definitions) = definitions.await? else {
16542 return Ok(Navigated::No);
16543 };
16544 let navigated = editor
16545 .update_in(cx, |editor, window, cx| {
16546 editor.navigate_to_hover_links(
16547 Some(kind),
16548 definitions
16549 .into_iter()
16550 .filter(|location| {
16551 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16552 })
16553 .map(HoverLink::Text)
16554 .collect::<Vec<_>>(),
16555 split,
16556 window,
16557 cx,
16558 )
16559 })?
16560 .await?;
16561 anyhow::Ok(navigated)
16562 })
16563 }
16564
16565 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16566 let selection = self.selections.newest_anchor();
16567 let head = selection.head();
16568 let tail = selection.tail();
16569
16570 let Some((buffer, start_position)) =
16571 self.buffer.read(cx).text_anchor_for_position(head, cx)
16572 else {
16573 return;
16574 };
16575
16576 let end_position = if head != tail {
16577 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16578 return;
16579 };
16580 Some(pos)
16581 } else {
16582 None
16583 };
16584
16585 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16586 let url = if let Some(end_pos) = end_position {
16587 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16588 } else {
16589 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16590 };
16591
16592 if let Some(url) = url {
16593 cx.update(|window, cx| {
16594 if parse_zed_link(&url, cx).is_some() {
16595 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16596 } else {
16597 cx.open_url(&url);
16598 }
16599 })?;
16600 }
16601
16602 anyhow::Ok(())
16603 });
16604
16605 url_finder.detach();
16606 }
16607
16608 pub fn open_selected_filename(
16609 &mut self,
16610 _: &OpenSelectedFilename,
16611 window: &mut Window,
16612 cx: &mut Context<Self>,
16613 ) {
16614 let Some(workspace) = self.workspace() else {
16615 return;
16616 };
16617
16618 let position = self.selections.newest_anchor().head();
16619
16620 let Some((buffer, buffer_position)) =
16621 self.buffer.read(cx).text_anchor_for_position(position, cx)
16622 else {
16623 return;
16624 };
16625
16626 let project = self.project.clone();
16627
16628 cx.spawn_in(window, async move |_, cx| {
16629 let result = find_file(&buffer, project, buffer_position, cx).await;
16630
16631 if let Some((_, path)) = result {
16632 workspace
16633 .update_in(cx, |workspace, window, cx| {
16634 workspace.open_resolved_path(path, window, cx)
16635 })?
16636 .await?;
16637 }
16638 anyhow::Ok(())
16639 })
16640 .detach();
16641 }
16642
16643 pub(crate) fn navigate_to_hover_links(
16644 &mut self,
16645 kind: Option<GotoDefinitionKind>,
16646 definitions: Vec<HoverLink>,
16647 split: bool,
16648 window: &mut Window,
16649 cx: &mut Context<Editor>,
16650 ) -> Task<Result<Navigated>> {
16651 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16652 let mut first_url_or_file = None;
16653 let definitions: Vec<_> = definitions
16654 .into_iter()
16655 .filter_map(|def| match def {
16656 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16657 HoverLink::InlayHint(lsp_location, server_id) => {
16658 let computation =
16659 self.compute_target_location(lsp_location, server_id, window, cx);
16660 Some(cx.background_spawn(computation))
16661 }
16662 HoverLink::Url(url) => {
16663 first_url_or_file = Some(Either::Left(url));
16664 None
16665 }
16666 HoverLink::File(path) => {
16667 first_url_or_file = Some(Either::Right(path));
16668 None
16669 }
16670 })
16671 .collect();
16672
16673 let workspace = self.workspace();
16674
16675 cx.spawn_in(window, async move |editor, cx| {
16676 let locations: Vec<Location> = future::join_all(definitions)
16677 .await
16678 .into_iter()
16679 .filter_map(|location| location.transpose())
16680 .collect::<Result<_>>()
16681 .context("location tasks")?;
16682 let mut locations = cx.update(|_, cx| {
16683 locations
16684 .into_iter()
16685 .map(|location| {
16686 let buffer = location.buffer.read(cx);
16687 (location.buffer, location.range.to_point(buffer))
16688 })
16689 .into_group_map()
16690 })?;
16691 let mut num_locations = 0;
16692 for ranges in locations.values_mut() {
16693 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16694 ranges.dedup();
16695 num_locations += ranges.len();
16696 }
16697
16698 if num_locations > 1 {
16699 let Some(workspace) = workspace else {
16700 return Ok(Navigated::No);
16701 };
16702
16703 let tab_kind = match kind {
16704 Some(GotoDefinitionKind::Implementation) => "Implementations",
16705 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16706 Some(GotoDefinitionKind::Declaration) => "Declarations",
16707 Some(GotoDefinitionKind::Type) => "Types",
16708 };
16709 let title = editor
16710 .update_in(cx, |_, _, cx| {
16711 let target = locations
16712 .iter()
16713 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16714 .map(|(buffer, location)| {
16715 buffer
16716 .read(cx)
16717 .text_for_range(location.clone())
16718 .collect::<String>()
16719 })
16720 .filter(|text| !text.contains('\n'))
16721 .unique()
16722 .take(3)
16723 .join(", ");
16724 if target.is_empty() {
16725 tab_kind.to_owned()
16726 } else {
16727 format!("{tab_kind} for {target}")
16728 }
16729 })
16730 .context("buffer title")?;
16731
16732 let opened = workspace
16733 .update_in(cx, |workspace, window, cx| {
16734 Self::open_locations_in_multibuffer(
16735 workspace,
16736 locations,
16737 title,
16738 split,
16739 MultibufferSelectionMode::First,
16740 window,
16741 cx,
16742 )
16743 })
16744 .is_ok();
16745
16746 anyhow::Ok(Navigated::from_bool(opened))
16747 } else if num_locations == 0 {
16748 // If there is one url or file, open it directly
16749 match first_url_or_file {
16750 Some(Either::Left(url)) => {
16751 cx.update(|_, cx| cx.open_url(&url))?;
16752 Ok(Navigated::Yes)
16753 }
16754 Some(Either::Right(path)) => {
16755 let Some(workspace) = workspace else {
16756 return Ok(Navigated::No);
16757 };
16758
16759 workspace
16760 .update_in(cx, |workspace, window, cx| {
16761 workspace.open_resolved_path(path, window, cx)
16762 })?
16763 .await?;
16764 Ok(Navigated::Yes)
16765 }
16766 None => Ok(Navigated::No),
16767 }
16768 } else {
16769 let Some(workspace) = workspace else {
16770 return Ok(Navigated::No);
16771 };
16772
16773 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16774 let target_range = target_ranges.first().unwrap().clone();
16775
16776 editor.update_in(cx, |editor, window, cx| {
16777 let range = target_range.to_point(target_buffer.read(cx));
16778 let range = editor.range_for_match(&range, false);
16779 let range = collapse_multiline_range(range);
16780
16781 if !split
16782 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16783 {
16784 editor.go_to_singleton_buffer_range(range, window, cx);
16785 } else {
16786 let pane = workspace.read(cx).active_pane().clone();
16787 window.defer(cx, move |window, cx| {
16788 let target_editor: Entity<Self> =
16789 workspace.update(cx, |workspace, cx| {
16790 let pane = if split {
16791 workspace.adjacent_pane(window, cx)
16792 } else {
16793 workspace.active_pane().clone()
16794 };
16795
16796 workspace.open_project_item(
16797 pane,
16798 target_buffer.clone(),
16799 true,
16800 true,
16801 window,
16802 cx,
16803 )
16804 });
16805 target_editor.update(cx, |target_editor, cx| {
16806 // When selecting a definition in a different buffer, disable the nav history
16807 // to avoid creating a history entry at the previous cursor location.
16808 pane.update(cx, |pane, _| pane.disable_history());
16809 target_editor.go_to_singleton_buffer_range(range, window, cx);
16810 pane.update(cx, |pane, _| pane.enable_history());
16811 });
16812 });
16813 }
16814 Navigated::Yes
16815 })
16816 }
16817 })
16818 }
16819
16820 fn compute_target_location(
16821 &self,
16822 lsp_location: lsp::Location,
16823 server_id: LanguageServerId,
16824 window: &mut Window,
16825 cx: &mut Context<Self>,
16826 ) -> Task<anyhow::Result<Option<Location>>> {
16827 let Some(project) = self.project.clone() else {
16828 return Task::ready(Ok(None));
16829 };
16830
16831 cx.spawn_in(window, async move |editor, cx| {
16832 let location_task = editor.update(cx, |_, cx| {
16833 project.update(cx, |project, cx| {
16834 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16835 })
16836 })?;
16837 let location = Some({
16838 let target_buffer_handle = location_task.await.context("open local buffer")?;
16839 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16840 let target_start = target_buffer
16841 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16842 let target_end = target_buffer
16843 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16844 target_buffer.anchor_after(target_start)
16845 ..target_buffer.anchor_before(target_end)
16846 })?;
16847 Location {
16848 buffer: target_buffer_handle,
16849 range,
16850 }
16851 });
16852 Ok(location)
16853 })
16854 }
16855
16856 fn go_to_next_reference(
16857 &mut self,
16858 _: &GoToNextReference,
16859 window: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16863 if let Some(task) = task {
16864 task.detach();
16865 };
16866 }
16867
16868 fn go_to_prev_reference(
16869 &mut self,
16870 _: &GoToPreviousReference,
16871 window: &mut Window,
16872 cx: &mut Context<Self>,
16873 ) {
16874 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16875 if let Some(task) = task {
16876 task.detach();
16877 };
16878 }
16879
16880 pub fn go_to_reference_before_or_after_position(
16881 &mut self,
16882 direction: Direction,
16883 count: usize,
16884 window: &mut Window,
16885 cx: &mut Context<Self>,
16886 ) -> Option<Task<Result<()>>> {
16887 let selection = self.selections.newest_anchor();
16888 let head = selection.head();
16889
16890 let multi_buffer = self.buffer.read(cx);
16891
16892 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16893 let workspace = self.workspace()?;
16894 let project = workspace.read(cx).project().clone();
16895 let references =
16896 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16897 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16898 let Some(locations) = references.await? else {
16899 return Ok(());
16900 };
16901
16902 if locations.is_empty() {
16903 // totally normal - the cursor may be on something which is not
16904 // a symbol (e.g. a keyword)
16905 log::info!("no references found under cursor");
16906 return Ok(());
16907 }
16908
16909 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16910
16911 let multi_buffer_snapshot =
16912 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16913
16914 let (locations, current_location_index) =
16915 multi_buffer.update(cx, |multi_buffer, cx| {
16916 let mut locations = locations
16917 .into_iter()
16918 .filter_map(|loc| {
16919 let start = multi_buffer.buffer_anchor_to_anchor(
16920 &loc.buffer,
16921 loc.range.start,
16922 cx,
16923 )?;
16924 let end = multi_buffer.buffer_anchor_to_anchor(
16925 &loc.buffer,
16926 loc.range.end,
16927 cx,
16928 )?;
16929 Some(start..end)
16930 })
16931 .collect::<Vec<_>>();
16932
16933 // There is an O(n) implementation, but given this list will be
16934 // small (usually <100 items), the extra O(log(n)) factor isn't
16935 // worth the (surprisingly large amount of) extra complexity.
16936 locations
16937 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16938
16939 let head_offset = head.to_offset(&multi_buffer_snapshot);
16940
16941 let current_location_index = locations.iter().position(|loc| {
16942 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16943 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16944 });
16945
16946 (locations, current_location_index)
16947 })?;
16948
16949 let Some(current_location_index) = current_location_index else {
16950 // This indicates something has gone wrong, because we already
16951 // handle the "no references" case above
16952 log::error!(
16953 "failed to find current reference under cursor. Total references: {}",
16954 locations.len()
16955 );
16956 return Ok(());
16957 };
16958
16959 let destination_location_index = match direction {
16960 Direction::Next => (current_location_index + count) % locations.len(),
16961 Direction::Prev => {
16962 (current_location_index + locations.len() - count % locations.len())
16963 % locations.len()
16964 }
16965 };
16966
16967 // TODO(cameron): is this needed?
16968 // the thinking is to avoid "jumping to the current location" (avoid
16969 // polluting "jumplist" in vim terms)
16970 if current_location_index == destination_location_index {
16971 return Ok(());
16972 }
16973
16974 let Range { start, end } = locations[destination_location_index];
16975
16976 editor.update_in(cx, |editor, window, cx| {
16977 let effects = SelectionEffects::default();
16978
16979 editor.unfold_ranges(&[start..end], false, false, cx);
16980 editor.change_selections(effects, window, cx, |s| {
16981 s.select_ranges([start..start]);
16982 });
16983 })?;
16984
16985 Ok(())
16986 }))
16987 }
16988
16989 pub fn find_all_references(
16990 &mut self,
16991 _: &FindAllReferences,
16992 window: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) -> Option<Task<Result<Navigated>>> {
16995 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16996 let multi_buffer = self.buffer.read(cx);
16997 let head = selection.head();
16998
16999 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17000 let head_anchor = multi_buffer_snapshot.anchor_at(
17001 head,
17002 if head < selection.tail() {
17003 Bias::Right
17004 } else {
17005 Bias::Left
17006 },
17007 );
17008
17009 match self
17010 .find_all_references_task_sources
17011 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17012 {
17013 Ok(_) => {
17014 log::info!(
17015 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17016 );
17017 return None;
17018 }
17019 Err(i) => {
17020 self.find_all_references_task_sources.insert(i, head_anchor);
17021 }
17022 }
17023
17024 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17025 let workspace = self.workspace()?;
17026 let project = workspace.read(cx).project().clone();
17027 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17028 Some(cx.spawn_in(window, async move |editor, cx| {
17029 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17030 if let Ok(i) = editor
17031 .find_all_references_task_sources
17032 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17033 {
17034 editor.find_all_references_task_sources.remove(i);
17035 }
17036 });
17037
17038 let Some(locations) = references.await? else {
17039 return anyhow::Ok(Navigated::No);
17040 };
17041 let mut locations = cx.update(|_, cx| {
17042 locations
17043 .into_iter()
17044 .map(|location| {
17045 let buffer = location.buffer.read(cx);
17046 (location.buffer, location.range.to_point(buffer))
17047 })
17048 .into_group_map()
17049 })?;
17050 if locations.is_empty() {
17051 return anyhow::Ok(Navigated::No);
17052 }
17053 for ranges in locations.values_mut() {
17054 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17055 ranges.dedup();
17056 }
17057
17058 workspace.update_in(cx, |workspace, window, cx| {
17059 let target = locations
17060 .iter()
17061 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17062 .map(|(buffer, location)| {
17063 buffer
17064 .read(cx)
17065 .text_for_range(location.clone())
17066 .collect::<String>()
17067 })
17068 .filter(|text| !text.contains('\n'))
17069 .unique()
17070 .take(3)
17071 .join(", ");
17072 let title = if target.is_empty() {
17073 "References".to_owned()
17074 } else {
17075 format!("References to {target}")
17076 };
17077 Self::open_locations_in_multibuffer(
17078 workspace,
17079 locations,
17080 title,
17081 false,
17082 MultibufferSelectionMode::First,
17083 window,
17084 cx,
17085 );
17086 Navigated::Yes
17087 })
17088 }))
17089 }
17090
17091 /// Opens a multibuffer with the given project locations in it
17092 pub fn open_locations_in_multibuffer(
17093 workspace: &mut Workspace,
17094 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17095 title: String,
17096 split: bool,
17097 multibuffer_selection_mode: MultibufferSelectionMode,
17098 window: &mut Window,
17099 cx: &mut Context<Workspace>,
17100 ) {
17101 if locations.is_empty() {
17102 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17103 return;
17104 }
17105
17106 let capability = workspace.project().read(cx).capability();
17107 let mut ranges = <Vec<Range<Anchor>>>::new();
17108
17109 // a key to find existing multibuffer editors with the same set of locations
17110 // to prevent us from opening more and more multibuffer tabs for searches and the like
17111 let mut key = (title.clone(), vec![]);
17112 let excerpt_buffer = cx.new(|cx| {
17113 let key = &mut key.1;
17114 let mut multibuffer = MultiBuffer::new(capability);
17115 for (buffer, mut ranges_for_buffer) in locations {
17116 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17117 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17118 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17119 PathKey::for_buffer(&buffer, cx),
17120 buffer.clone(),
17121 ranges_for_buffer,
17122 multibuffer_context_lines(cx),
17123 cx,
17124 );
17125 ranges.extend(new_ranges)
17126 }
17127
17128 multibuffer.with_title(title)
17129 });
17130 let existing = workspace.active_pane().update(cx, |pane, cx| {
17131 pane.items()
17132 .filter_map(|item| item.downcast::<Editor>())
17133 .find(|editor| {
17134 editor
17135 .read(cx)
17136 .lookup_key
17137 .as_ref()
17138 .and_then(|it| {
17139 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17140 })
17141 .is_some_and(|it| *it == key)
17142 })
17143 });
17144 let editor = existing.unwrap_or_else(|| {
17145 cx.new(|cx| {
17146 let mut editor = Editor::for_multibuffer(
17147 excerpt_buffer,
17148 Some(workspace.project().clone()),
17149 window,
17150 cx,
17151 );
17152 editor.lookup_key = Some(Box::new(key));
17153 editor
17154 })
17155 });
17156 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17157 MultibufferSelectionMode::First => {
17158 if let Some(first_range) = ranges.first() {
17159 editor.change_selections(
17160 SelectionEffects::no_scroll(),
17161 window,
17162 cx,
17163 |selections| {
17164 selections.clear_disjoint();
17165 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17166 },
17167 );
17168 }
17169 editor.highlight_background::<Self>(
17170 &ranges,
17171 |theme| theme.colors().editor_highlighted_line_background,
17172 cx,
17173 );
17174 }
17175 MultibufferSelectionMode::All => {
17176 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17177 selections.clear_disjoint();
17178 selections.select_anchor_ranges(ranges);
17179 });
17180 }
17181 });
17182
17183 let item = Box::new(editor);
17184 let item_id = item.item_id();
17185
17186 if split {
17187 let pane = workspace.adjacent_pane(window, cx);
17188 workspace.add_item(pane, item, None, true, true, window, cx);
17189 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17190 let (preview_item_id, preview_item_idx) =
17191 workspace.active_pane().read_with(cx, |pane, _| {
17192 (pane.preview_item_id(), pane.preview_item_idx())
17193 });
17194
17195 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17196
17197 if let Some(preview_item_id) = preview_item_id {
17198 workspace.active_pane().update(cx, |pane, cx| {
17199 pane.remove_item(preview_item_id, false, false, window, cx);
17200 });
17201 }
17202 } else {
17203 workspace.add_item_to_active_pane(item, None, true, window, cx);
17204 }
17205 workspace.active_pane().update(cx, |pane, cx| {
17206 pane.set_preview_item_id(Some(item_id), cx);
17207 });
17208 }
17209
17210 pub fn rename(
17211 &mut self,
17212 _: &Rename,
17213 window: &mut Window,
17214 cx: &mut Context<Self>,
17215 ) -> Option<Task<Result<()>>> {
17216 use language::ToOffset as _;
17217
17218 let provider = self.semantics_provider.clone()?;
17219 let selection = self.selections.newest_anchor().clone();
17220 let (cursor_buffer, cursor_buffer_position) = self
17221 .buffer
17222 .read(cx)
17223 .text_anchor_for_position(selection.head(), cx)?;
17224 let (tail_buffer, cursor_buffer_position_end) = self
17225 .buffer
17226 .read(cx)
17227 .text_anchor_for_position(selection.tail(), cx)?;
17228 if tail_buffer != cursor_buffer {
17229 return None;
17230 }
17231
17232 let snapshot = cursor_buffer.read(cx).snapshot();
17233 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17234 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17235 let prepare_rename = provider
17236 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17237 .unwrap_or_else(|| Task::ready(Ok(None)));
17238 drop(snapshot);
17239
17240 Some(cx.spawn_in(window, async move |this, cx| {
17241 let rename_range = if let Some(range) = prepare_rename.await? {
17242 Some(range)
17243 } else {
17244 this.update(cx, |this, cx| {
17245 let buffer = this.buffer.read(cx).snapshot(cx);
17246 let mut buffer_highlights = this
17247 .document_highlights_for_position(selection.head(), &buffer)
17248 .filter(|highlight| {
17249 highlight.start.excerpt_id == selection.head().excerpt_id
17250 && highlight.end.excerpt_id == selection.head().excerpt_id
17251 });
17252 buffer_highlights
17253 .next()
17254 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17255 })?
17256 };
17257 if let Some(rename_range) = rename_range {
17258 this.update_in(cx, |this, window, cx| {
17259 let snapshot = cursor_buffer.read(cx).snapshot();
17260 let rename_buffer_range = rename_range.to_offset(&snapshot);
17261 let cursor_offset_in_rename_range =
17262 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17263 let cursor_offset_in_rename_range_end =
17264 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17265
17266 this.take_rename(false, window, cx);
17267 let buffer = this.buffer.read(cx).read(cx);
17268 let cursor_offset = selection.head().to_offset(&buffer);
17269 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17270 let rename_end = rename_start + rename_buffer_range.len();
17271 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17272 let mut old_highlight_id = None;
17273 let old_name: Arc<str> = buffer
17274 .chunks(rename_start..rename_end, true)
17275 .map(|chunk| {
17276 if old_highlight_id.is_none() {
17277 old_highlight_id = chunk.syntax_highlight_id;
17278 }
17279 chunk.text
17280 })
17281 .collect::<String>()
17282 .into();
17283
17284 drop(buffer);
17285
17286 // Position the selection in the rename editor so that it matches the current selection.
17287 this.show_local_selections = false;
17288 let rename_editor = cx.new(|cx| {
17289 let mut editor = Editor::single_line(window, cx);
17290 editor.buffer.update(cx, |buffer, cx| {
17291 buffer.edit([(0..0, old_name.clone())], None, cx)
17292 });
17293 let rename_selection_range = match cursor_offset_in_rename_range
17294 .cmp(&cursor_offset_in_rename_range_end)
17295 {
17296 Ordering::Equal => {
17297 editor.select_all(&SelectAll, window, cx);
17298 return editor;
17299 }
17300 Ordering::Less => {
17301 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17302 }
17303 Ordering::Greater => {
17304 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17305 }
17306 };
17307 if rename_selection_range.end > old_name.len() {
17308 editor.select_all(&SelectAll, window, cx);
17309 } else {
17310 editor.change_selections(Default::default(), window, cx, |s| {
17311 s.select_ranges([rename_selection_range]);
17312 });
17313 }
17314 editor
17315 });
17316 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17317 if e == &EditorEvent::Focused {
17318 cx.emit(EditorEvent::FocusedIn)
17319 }
17320 })
17321 .detach();
17322
17323 let write_highlights =
17324 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17325 let read_highlights =
17326 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17327 let ranges = write_highlights
17328 .iter()
17329 .flat_map(|(_, ranges)| ranges.iter())
17330 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17331 .cloned()
17332 .collect();
17333
17334 this.highlight_text::<Rename>(
17335 ranges,
17336 HighlightStyle {
17337 fade_out: Some(0.6),
17338 ..Default::default()
17339 },
17340 cx,
17341 );
17342 let rename_focus_handle = rename_editor.focus_handle(cx);
17343 window.focus(&rename_focus_handle);
17344 let block_id = this.insert_blocks(
17345 [BlockProperties {
17346 style: BlockStyle::Flex,
17347 placement: BlockPlacement::Below(range.start),
17348 height: Some(1),
17349 render: Arc::new({
17350 let rename_editor = rename_editor.clone();
17351 move |cx: &mut BlockContext| {
17352 let mut text_style = cx.editor_style.text.clone();
17353 if let Some(highlight_style) = old_highlight_id
17354 .and_then(|h| h.style(&cx.editor_style.syntax))
17355 {
17356 text_style = text_style.highlight(highlight_style);
17357 }
17358 div()
17359 .block_mouse_except_scroll()
17360 .pl(cx.anchor_x)
17361 .child(EditorElement::new(
17362 &rename_editor,
17363 EditorStyle {
17364 background: cx.theme().system().transparent,
17365 local_player: cx.editor_style.local_player,
17366 text: text_style,
17367 scrollbar_width: cx.editor_style.scrollbar_width,
17368 syntax: cx.editor_style.syntax.clone(),
17369 status: cx.editor_style.status.clone(),
17370 inlay_hints_style: HighlightStyle {
17371 font_weight: Some(FontWeight::BOLD),
17372 ..make_inlay_hints_style(cx.app)
17373 },
17374 edit_prediction_styles: make_suggestion_styles(
17375 cx.app,
17376 ),
17377 ..EditorStyle::default()
17378 },
17379 ))
17380 .into_any_element()
17381 }
17382 }),
17383 priority: 0,
17384 }],
17385 Some(Autoscroll::fit()),
17386 cx,
17387 )[0];
17388 this.pending_rename = Some(RenameState {
17389 range,
17390 old_name,
17391 editor: rename_editor,
17392 block_id,
17393 });
17394 })?;
17395 }
17396
17397 Ok(())
17398 }))
17399 }
17400
17401 pub fn confirm_rename(
17402 &mut self,
17403 _: &ConfirmRename,
17404 window: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) -> Option<Task<Result<()>>> {
17407 let rename = self.take_rename(false, window, cx)?;
17408 let workspace = self.workspace()?.downgrade();
17409 let (buffer, start) = self
17410 .buffer
17411 .read(cx)
17412 .text_anchor_for_position(rename.range.start, cx)?;
17413 let (end_buffer, _) = self
17414 .buffer
17415 .read(cx)
17416 .text_anchor_for_position(rename.range.end, cx)?;
17417 if buffer != end_buffer {
17418 return None;
17419 }
17420
17421 let old_name = rename.old_name;
17422 let new_name = rename.editor.read(cx).text(cx);
17423
17424 let rename = self.semantics_provider.as_ref()?.perform_rename(
17425 &buffer,
17426 start,
17427 new_name.clone(),
17428 cx,
17429 )?;
17430
17431 Some(cx.spawn_in(window, async move |editor, cx| {
17432 let project_transaction = rename.await?;
17433 Self::open_project_transaction(
17434 &editor,
17435 workspace,
17436 project_transaction,
17437 format!("Rename: {} → {}", old_name, new_name),
17438 cx,
17439 )
17440 .await?;
17441
17442 editor.update(cx, |editor, cx| {
17443 editor.refresh_document_highlights(cx);
17444 })?;
17445 Ok(())
17446 }))
17447 }
17448
17449 fn take_rename(
17450 &mut self,
17451 moving_cursor: bool,
17452 window: &mut Window,
17453 cx: &mut Context<Self>,
17454 ) -> Option<RenameState> {
17455 let rename = self.pending_rename.take()?;
17456 if rename.editor.focus_handle(cx).is_focused(window) {
17457 window.focus(&self.focus_handle);
17458 }
17459
17460 self.remove_blocks(
17461 [rename.block_id].into_iter().collect(),
17462 Some(Autoscroll::fit()),
17463 cx,
17464 );
17465 self.clear_highlights::<Rename>(cx);
17466 self.show_local_selections = true;
17467
17468 if moving_cursor {
17469 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17470 editor
17471 .selections
17472 .newest::<usize>(&editor.display_snapshot(cx))
17473 .head()
17474 });
17475
17476 // Update the selection to match the position of the selection inside
17477 // the rename editor.
17478 let snapshot = self.buffer.read(cx).read(cx);
17479 let rename_range = rename.range.to_offset(&snapshot);
17480 let cursor_in_editor = snapshot
17481 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17482 .min(rename_range.end);
17483 drop(snapshot);
17484
17485 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17486 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17487 });
17488 } else {
17489 self.refresh_document_highlights(cx);
17490 }
17491
17492 Some(rename)
17493 }
17494
17495 pub fn pending_rename(&self) -> Option<&RenameState> {
17496 self.pending_rename.as_ref()
17497 }
17498
17499 fn format(
17500 &mut self,
17501 _: &Format,
17502 window: &mut Window,
17503 cx: &mut Context<Self>,
17504 ) -> Option<Task<Result<()>>> {
17505 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17506
17507 let project = match &self.project {
17508 Some(project) => project.clone(),
17509 None => return None,
17510 };
17511
17512 Some(self.perform_format(
17513 project,
17514 FormatTrigger::Manual,
17515 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17516 window,
17517 cx,
17518 ))
17519 }
17520
17521 fn format_selections(
17522 &mut self,
17523 _: &FormatSelections,
17524 window: &mut Window,
17525 cx: &mut Context<Self>,
17526 ) -> Option<Task<Result<()>>> {
17527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17528
17529 let project = match &self.project {
17530 Some(project) => project.clone(),
17531 None => return None,
17532 };
17533
17534 let ranges = self
17535 .selections
17536 .all_adjusted(&self.display_snapshot(cx))
17537 .into_iter()
17538 .map(|selection| selection.range())
17539 .collect_vec();
17540
17541 Some(self.perform_format(
17542 project,
17543 FormatTrigger::Manual,
17544 FormatTarget::Ranges(ranges),
17545 window,
17546 cx,
17547 ))
17548 }
17549
17550 fn perform_format(
17551 &mut self,
17552 project: Entity<Project>,
17553 trigger: FormatTrigger,
17554 target: FormatTarget,
17555 window: &mut Window,
17556 cx: &mut Context<Self>,
17557 ) -> Task<Result<()>> {
17558 let buffer = self.buffer.clone();
17559 let (buffers, target) = match target {
17560 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17561 FormatTarget::Ranges(selection_ranges) => {
17562 let multi_buffer = buffer.read(cx);
17563 let snapshot = multi_buffer.read(cx);
17564 let mut buffers = HashSet::default();
17565 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17566 BTreeMap::new();
17567 for selection_range in selection_ranges {
17568 for (buffer, buffer_range, _) in
17569 snapshot.range_to_buffer_ranges(selection_range)
17570 {
17571 let buffer_id = buffer.remote_id();
17572 let start = buffer.anchor_before(buffer_range.start);
17573 let end = buffer.anchor_after(buffer_range.end);
17574 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17575 buffer_id_to_ranges
17576 .entry(buffer_id)
17577 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17578 .or_insert_with(|| vec![start..end]);
17579 }
17580 }
17581 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17582 }
17583 };
17584
17585 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17586 let selections_prev = transaction_id_prev
17587 .and_then(|transaction_id_prev| {
17588 // default to selections as they were after the last edit, if we have them,
17589 // instead of how they are now.
17590 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17591 // will take you back to where you made the last edit, instead of staying where you scrolled
17592 self.selection_history
17593 .transaction(transaction_id_prev)
17594 .map(|t| t.0.clone())
17595 })
17596 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17597
17598 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17599 let format = project.update(cx, |project, cx| {
17600 project.format(buffers, target, true, trigger, cx)
17601 });
17602
17603 cx.spawn_in(window, async move |editor, cx| {
17604 let transaction = futures::select_biased! {
17605 transaction = format.log_err().fuse() => transaction,
17606 () = timeout => {
17607 log::warn!("timed out waiting for formatting");
17608 None
17609 }
17610 };
17611
17612 buffer
17613 .update(cx, |buffer, cx| {
17614 if let Some(transaction) = transaction
17615 && !buffer.is_singleton()
17616 {
17617 buffer.push_transaction(&transaction.0, cx);
17618 }
17619 cx.notify();
17620 })
17621 .ok();
17622
17623 if let Some(transaction_id_now) =
17624 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17625 {
17626 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17627 if has_new_transaction {
17628 _ = editor.update(cx, |editor, _| {
17629 editor
17630 .selection_history
17631 .insert_transaction(transaction_id_now, selections_prev);
17632 });
17633 }
17634 }
17635
17636 Ok(())
17637 })
17638 }
17639
17640 fn organize_imports(
17641 &mut self,
17642 _: &OrganizeImports,
17643 window: &mut Window,
17644 cx: &mut Context<Self>,
17645 ) -> Option<Task<Result<()>>> {
17646 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17647 let project = match &self.project {
17648 Some(project) => project.clone(),
17649 None => return None,
17650 };
17651 Some(self.perform_code_action_kind(
17652 project,
17653 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17654 window,
17655 cx,
17656 ))
17657 }
17658
17659 fn perform_code_action_kind(
17660 &mut self,
17661 project: Entity<Project>,
17662 kind: CodeActionKind,
17663 window: &mut Window,
17664 cx: &mut Context<Self>,
17665 ) -> Task<Result<()>> {
17666 let buffer = self.buffer.clone();
17667 let buffers = buffer.read(cx).all_buffers();
17668 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17669 let apply_action = project.update(cx, |project, cx| {
17670 project.apply_code_action_kind(buffers, kind, true, cx)
17671 });
17672 cx.spawn_in(window, async move |_, cx| {
17673 let transaction = futures::select_biased! {
17674 () = timeout => {
17675 log::warn!("timed out waiting for executing code action");
17676 None
17677 }
17678 transaction = apply_action.log_err().fuse() => transaction,
17679 };
17680 buffer
17681 .update(cx, |buffer, cx| {
17682 // check if we need this
17683 if let Some(transaction) = transaction
17684 && !buffer.is_singleton()
17685 {
17686 buffer.push_transaction(&transaction.0, cx);
17687 }
17688 cx.notify();
17689 })
17690 .ok();
17691 Ok(())
17692 })
17693 }
17694
17695 pub fn restart_language_server(
17696 &mut self,
17697 _: &RestartLanguageServer,
17698 _: &mut Window,
17699 cx: &mut Context<Self>,
17700 ) {
17701 if let Some(project) = self.project.clone() {
17702 self.buffer.update(cx, |multi_buffer, cx| {
17703 project.update(cx, |project, cx| {
17704 project.restart_language_servers_for_buffers(
17705 multi_buffer.all_buffers().into_iter().collect(),
17706 HashSet::default(),
17707 cx,
17708 );
17709 });
17710 })
17711 }
17712 }
17713
17714 pub fn stop_language_server(
17715 &mut self,
17716 _: &StopLanguageServer,
17717 _: &mut Window,
17718 cx: &mut Context<Self>,
17719 ) {
17720 if let Some(project) = self.project.clone() {
17721 self.buffer.update(cx, |multi_buffer, cx| {
17722 project.update(cx, |project, cx| {
17723 project.stop_language_servers_for_buffers(
17724 multi_buffer.all_buffers().into_iter().collect(),
17725 HashSet::default(),
17726 cx,
17727 );
17728 });
17729 });
17730 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17731 }
17732 }
17733
17734 fn cancel_language_server_work(
17735 workspace: &mut Workspace,
17736 _: &actions::CancelLanguageServerWork,
17737 _: &mut Window,
17738 cx: &mut Context<Workspace>,
17739 ) {
17740 let project = workspace.project();
17741 let buffers = workspace
17742 .active_item(cx)
17743 .and_then(|item| item.act_as::<Editor>(cx))
17744 .map_or(HashSet::default(), |editor| {
17745 editor.read(cx).buffer.read(cx).all_buffers()
17746 });
17747 project.update(cx, |project, cx| {
17748 project.cancel_language_server_work_for_buffers(buffers, cx);
17749 });
17750 }
17751
17752 fn show_character_palette(
17753 &mut self,
17754 _: &ShowCharacterPalette,
17755 window: &mut Window,
17756 _: &mut Context<Self>,
17757 ) {
17758 window.show_character_palette();
17759 }
17760
17761 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17762 if !self.diagnostics_enabled() {
17763 return;
17764 }
17765
17766 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17767 let buffer = self.buffer.read(cx).snapshot(cx);
17768 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17769 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17770 let is_valid = buffer
17771 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17772 .any(|entry| {
17773 entry.diagnostic.is_primary
17774 && !entry.range.is_empty()
17775 && entry.range.start == primary_range_start
17776 && entry.diagnostic.message == active_diagnostics.active_message
17777 });
17778
17779 if !is_valid {
17780 self.dismiss_diagnostics(cx);
17781 }
17782 }
17783 }
17784
17785 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17786 match &self.active_diagnostics {
17787 ActiveDiagnostic::Group(group) => Some(group),
17788 _ => None,
17789 }
17790 }
17791
17792 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17793 if !self.diagnostics_enabled() {
17794 return;
17795 }
17796 self.dismiss_diagnostics(cx);
17797 self.active_diagnostics = ActiveDiagnostic::All;
17798 }
17799
17800 fn activate_diagnostics(
17801 &mut self,
17802 buffer_id: BufferId,
17803 diagnostic: DiagnosticEntryRef<'_, usize>,
17804 window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) {
17807 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17808 return;
17809 }
17810 self.dismiss_diagnostics(cx);
17811 let snapshot = self.snapshot(window, cx);
17812 let buffer = self.buffer.read(cx).snapshot(cx);
17813 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17814 return;
17815 };
17816
17817 let diagnostic_group = buffer
17818 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17819 .collect::<Vec<_>>();
17820
17821 let blocks =
17822 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17823
17824 let blocks = self.display_map.update(cx, |display_map, cx| {
17825 display_map.insert_blocks(blocks, cx).into_iter().collect()
17826 });
17827 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17828 active_range: buffer.anchor_before(diagnostic.range.start)
17829 ..buffer.anchor_after(diagnostic.range.end),
17830 active_message: diagnostic.diagnostic.message.clone(),
17831 group_id: diagnostic.diagnostic.group_id,
17832 blocks,
17833 });
17834 cx.notify();
17835 }
17836
17837 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17838 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17839 return;
17840 };
17841
17842 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17843 if let ActiveDiagnostic::Group(group) = prev {
17844 self.display_map.update(cx, |display_map, cx| {
17845 display_map.remove_blocks(group.blocks, cx);
17846 });
17847 cx.notify();
17848 }
17849 }
17850
17851 /// Disable inline diagnostics rendering for this editor.
17852 pub fn disable_inline_diagnostics(&mut self) {
17853 self.inline_diagnostics_enabled = false;
17854 self.inline_diagnostics_update = Task::ready(());
17855 self.inline_diagnostics.clear();
17856 }
17857
17858 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17859 self.diagnostics_enabled = false;
17860 self.dismiss_diagnostics(cx);
17861 self.inline_diagnostics_update = Task::ready(());
17862 self.inline_diagnostics.clear();
17863 }
17864
17865 pub fn disable_word_completions(&mut self) {
17866 self.word_completions_enabled = false;
17867 }
17868
17869 pub fn diagnostics_enabled(&self) -> bool {
17870 self.diagnostics_enabled && self.mode.is_full()
17871 }
17872
17873 pub fn inline_diagnostics_enabled(&self) -> bool {
17874 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17875 }
17876
17877 pub fn show_inline_diagnostics(&self) -> bool {
17878 self.show_inline_diagnostics
17879 }
17880
17881 pub fn toggle_inline_diagnostics(
17882 &mut self,
17883 _: &ToggleInlineDiagnostics,
17884 window: &mut Window,
17885 cx: &mut Context<Editor>,
17886 ) {
17887 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17888 self.refresh_inline_diagnostics(false, window, cx);
17889 }
17890
17891 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17892 self.diagnostics_max_severity = severity;
17893 self.display_map.update(cx, |display_map, _| {
17894 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17895 });
17896 }
17897
17898 pub fn toggle_diagnostics(
17899 &mut self,
17900 _: &ToggleDiagnostics,
17901 window: &mut Window,
17902 cx: &mut Context<Editor>,
17903 ) {
17904 if !self.diagnostics_enabled() {
17905 return;
17906 }
17907
17908 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17909 EditorSettings::get_global(cx)
17910 .diagnostics_max_severity
17911 .filter(|severity| severity != &DiagnosticSeverity::Off)
17912 .unwrap_or(DiagnosticSeverity::Hint)
17913 } else {
17914 DiagnosticSeverity::Off
17915 };
17916 self.set_max_diagnostics_severity(new_severity, cx);
17917 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17918 self.active_diagnostics = ActiveDiagnostic::None;
17919 self.inline_diagnostics_update = Task::ready(());
17920 self.inline_diagnostics.clear();
17921 } else {
17922 self.refresh_inline_diagnostics(false, window, cx);
17923 }
17924
17925 cx.notify();
17926 }
17927
17928 pub fn toggle_minimap(
17929 &mut self,
17930 _: &ToggleMinimap,
17931 window: &mut Window,
17932 cx: &mut Context<Editor>,
17933 ) {
17934 if self.supports_minimap(cx) {
17935 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17936 }
17937 }
17938
17939 fn refresh_inline_diagnostics(
17940 &mut self,
17941 debounce: bool,
17942 window: &mut Window,
17943 cx: &mut Context<Self>,
17944 ) {
17945 let max_severity = ProjectSettings::get_global(cx)
17946 .diagnostics
17947 .inline
17948 .max_severity
17949 .unwrap_or(self.diagnostics_max_severity);
17950
17951 if !self.inline_diagnostics_enabled()
17952 || !self.diagnostics_enabled()
17953 || !self.show_inline_diagnostics
17954 || max_severity == DiagnosticSeverity::Off
17955 {
17956 self.inline_diagnostics_update = Task::ready(());
17957 self.inline_diagnostics.clear();
17958 return;
17959 }
17960
17961 let debounce_ms = ProjectSettings::get_global(cx)
17962 .diagnostics
17963 .inline
17964 .update_debounce_ms;
17965 let debounce = if debounce && debounce_ms > 0 {
17966 Some(Duration::from_millis(debounce_ms))
17967 } else {
17968 None
17969 };
17970 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17971 if let Some(debounce) = debounce {
17972 cx.background_executor().timer(debounce).await;
17973 }
17974 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17975 editor
17976 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17977 .ok()
17978 }) else {
17979 return;
17980 };
17981
17982 let new_inline_diagnostics = cx
17983 .background_spawn(async move {
17984 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17985 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17986 let message = diagnostic_entry
17987 .diagnostic
17988 .message
17989 .split_once('\n')
17990 .map(|(line, _)| line)
17991 .map(SharedString::new)
17992 .unwrap_or_else(|| {
17993 SharedString::new(&*diagnostic_entry.diagnostic.message)
17994 });
17995 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17996 let (Ok(i) | Err(i)) = inline_diagnostics
17997 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17998 inline_diagnostics.insert(
17999 i,
18000 (
18001 start_anchor,
18002 InlineDiagnostic {
18003 message,
18004 group_id: diagnostic_entry.diagnostic.group_id,
18005 start: diagnostic_entry.range.start.to_point(&snapshot),
18006 is_primary: diagnostic_entry.diagnostic.is_primary,
18007 severity: diagnostic_entry.diagnostic.severity,
18008 },
18009 ),
18010 );
18011 }
18012 inline_diagnostics
18013 })
18014 .await;
18015
18016 editor
18017 .update(cx, |editor, cx| {
18018 editor.inline_diagnostics = new_inline_diagnostics;
18019 cx.notify();
18020 })
18021 .ok();
18022 });
18023 }
18024
18025 fn pull_diagnostics(
18026 &mut self,
18027 buffer_id: Option<BufferId>,
18028 window: &Window,
18029 cx: &mut Context<Self>,
18030 ) -> Option<()> {
18031 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18032 return None;
18033 }
18034 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18035 .diagnostics
18036 .lsp_pull_diagnostics;
18037 if !pull_diagnostics_settings.enabled {
18038 return None;
18039 }
18040 let project = self.project()?.downgrade();
18041 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18042 let mut buffers = self.buffer.read(cx).all_buffers();
18043 buffers.retain(|buffer| {
18044 let buffer_id_to_retain = buffer.read(cx).remote_id();
18045 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18046 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18047 });
18048 if buffers.is_empty() {
18049 self.pull_diagnostics_task = Task::ready(());
18050 return None;
18051 }
18052
18053 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18054 cx.background_executor().timer(debounce).await;
18055
18056 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18057 buffers
18058 .into_iter()
18059 .filter_map(|buffer| {
18060 project
18061 .update(cx, |project, cx| {
18062 project.lsp_store().update(cx, |lsp_store, cx| {
18063 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18064 })
18065 })
18066 .ok()
18067 })
18068 .collect::<FuturesUnordered<_>>()
18069 }) else {
18070 return;
18071 };
18072
18073 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18074 match pull_task {
18075 Ok(()) => {
18076 if editor
18077 .update_in(cx, |editor, window, cx| {
18078 editor.update_diagnostics_state(window, cx);
18079 })
18080 .is_err()
18081 {
18082 return;
18083 }
18084 }
18085 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18086 }
18087 }
18088 });
18089
18090 Some(())
18091 }
18092
18093 pub fn set_selections_from_remote(
18094 &mut self,
18095 selections: Vec<Selection<Anchor>>,
18096 pending_selection: Option<Selection<Anchor>>,
18097 window: &mut Window,
18098 cx: &mut Context<Self>,
18099 ) {
18100 let old_cursor_position = self.selections.newest_anchor().head();
18101 self.selections
18102 .change_with(&self.display_snapshot(cx), |s| {
18103 s.select_anchors(selections);
18104 if let Some(pending_selection) = pending_selection {
18105 s.set_pending(pending_selection, SelectMode::Character);
18106 } else {
18107 s.clear_pending();
18108 }
18109 });
18110 self.selections_did_change(
18111 false,
18112 &old_cursor_position,
18113 SelectionEffects::default(),
18114 window,
18115 cx,
18116 );
18117 }
18118
18119 pub fn transact(
18120 &mut self,
18121 window: &mut Window,
18122 cx: &mut Context<Self>,
18123 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18124 ) -> Option<TransactionId> {
18125 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18126 this.start_transaction_at(Instant::now(), window, cx);
18127 update(this, window, cx);
18128 this.end_transaction_at(Instant::now(), cx)
18129 })
18130 }
18131
18132 pub fn start_transaction_at(
18133 &mut self,
18134 now: Instant,
18135 window: &mut Window,
18136 cx: &mut Context<Self>,
18137 ) -> Option<TransactionId> {
18138 self.end_selection(window, cx);
18139 if let Some(tx_id) = self
18140 .buffer
18141 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18142 {
18143 self.selection_history
18144 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18145 cx.emit(EditorEvent::TransactionBegun {
18146 transaction_id: tx_id,
18147 });
18148 Some(tx_id)
18149 } else {
18150 None
18151 }
18152 }
18153
18154 pub fn end_transaction_at(
18155 &mut self,
18156 now: Instant,
18157 cx: &mut Context<Self>,
18158 ) -> Option<TransactionId> {
18159 if let Some(transaction_id) = self
18160 .buffer
18161 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18162 {
18163 if let Some((_, end_selections)) =
18164 self.selection_history.transaction_mut(transaction_id)
18165 {
18166 *end_selections = Some(self.selections.disjoint_anchors_arc());
18167 } else {
18168 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18169 }
18170
18171 cx.emit(EditorEvent::Edited { transaction_id });
18172 Some(transaction_id)
18173 } else {
18174 None
18175 }
18176 }
18177
18178 pub fn modify_transaction_selection_history(
18179 &mut self,
18180 transaction_id: TransactionId,
18181 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18182 ) -> bool {
18183 self.selection_history
18184 .transaction_mut(transaction_id)
18185 .map(modify)
18186 .is_some()
18187 }
18188
18189 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18190 if self.selection_mark_mode {
18191 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18192 s.move_with(|_, sel| {
18193 sel.collapse_to(sel.head(), SelectionGoal::None);
18194 });
18195 })
18196 }
18197 self.selection_mark_mode = true;
18198 cx.notify();
18199 }
18200
18201 pub fn swap_selection_ends(
18202 &mut self,
18203 _: &actions::SwapSelectionEnds,
18204 window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) {
18207 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18208 s.move_with(|_, sel| {
18209 if sel.start != sel.end {
18210 sel.reversed = !sel.reversed
18211 }
18212 });
18213 });
18214 self.request_autoscroll(Autoscroll::newest(), cx);
18215 cx.notify();
18216 }
18217
18218 pub fn toggle_focus(
18219 workspace: &mut Workspace,
18220 _: &actions::ToggleFocus,
18221 window: &mut Window,
18222 cx: &mut Context<Workspace>,
18223 ) {
18224 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18225 return;
18226 };
18227 workspace.activate_item(&item, true, true, window, cx);
18228 }
18229
18230 pub fn toggle_fold(
18231 &mut self,
18232 _: &actions::ToggleFold,
18233 window: &mut Window,
18234 cx: &mut Context<Self>,
18235 ) {
18236 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18237 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18238 let selection = self.selections.newest::<Point>(&display_map);
18239
18240 let range = if selection.is_empty() {
18241 let point = selection.head().to_display_point(&display_map);
18242 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18243 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18244 .to_point(&display_map);
18245 start..end
18246 } else {
18247 selection.range()
18248 };
18249 if display_map.folds_in_range(range).next().is_some() {
18250 self.unfold_lines(&Default::default(), window, cx)
18251 } else {
18252 self.fold(&Default::default(), window, cx)
18253 }
18254 } else {
18255 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18256 let buffer_ids: HashSet<_> = self
18257 .selections
18258 .disjoint_anchor_ranges()
18259 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18260 .collect();
18261
18262 let should_unfold = buffer_ids
18263 .iter()
18264 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18265
18266 for buffer_id in buffer_ids {
18267 if should_unfold {
18268 self.unfold_buffer(buffer_id, cx);
18269 } else {
18270 self.fold_buffer(buffer_id, cx);
18271 }
18272 }
18273 }
18274 }
18275
18276 pub fn toggle_fold_recursive(
18277 &mut self,
18278 _: &actions::ToggleFoldRecursive,
18279 window: &mut Window,
18280 cx: &mut Context<Self>,
18281 ) {
18282 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18283
18284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18285 let range = if selection.is_empty() {
18286 let point = selection.head().to_display_point(&display_map);
18287 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18288 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18289 .to_point(&display_map);
18290 start..end
18291 } else {
18292 selection.range()
18293 };
18294 if display_map.folds_in_range(range).next().is_some() {
18295 self.unfold_recursive(&Default::default(), window, cx)
18296 } else {
18297 self.fold_recursive(&Default::default(), window, cx)
18298 }
18299 }
18300
18301 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18302 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18303 let mut to_fold = Vec::new();
18304 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18305 let selections = self.selections.all_adjusted(&display_map);
18306
18307 for selection in selections {
18308 let range = selection.range().sorted();
18309 let buffer_start_row = range.start.row;
18310
18311 if range.start.row != range.end.row {
18312 let mut found = false;
18313 let mut row = range.start.row;
18314 while row <= range.end.row {
18315 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18316 {
18317 found = true;
18318 row = crease.range().end.row + 1;
18319 to_fold.push(crease);
18320 } else {
18321 row += 1
18322 }
18323 }
18324 if found {
18325 continue;
18326 }
18327 }
18328
18329 for row in (0..=range.start.row).rev() {
18330 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18331 && crease.range().end.row >= buffer_start_row
18332 {
18333 to_fold.push(crease);
18334 if row <= range.start.row {
18335 break;
18336 }
18337 }
18338 }
18339 }
18340
18341 self.fold_creases(to_fold, true, window, cx);
18342 } else {
18343 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18344 let buffer_ids = self
18345 .selections
18346 .disjoint_anchor_ranges()
18347 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18348 .collect::<HashSet<_>>();
18349 for buffer_id in buffer_ids {
18350 self.fold_buffer(buffer_id, cx);
18351 }
18352 }
18353 }
18354
18355 pub fn toggle_fold_all(
18356 &mut self,
18357 _: &actions::ToggleFoldAll,
18358 window: &mut Window,
18359 cx: &mut Context<Self>,
18360 ) {
18361 if self.buffer.read(cx).is_singleton() {
18362 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18363 let has_folds = display_map
18364 .folds_in_range(0..display_map.buffer_snapshot().len())
18365 .next()
18366 .is_some();
18367
18368 if has_folds {
18369 self.unfold_all(&actions::UnfoldAll, window, cx);
18370 } else {
18371 self.fold_all(&actions::FoldAll, window, cx);
18372 }
18373 } else {
18374 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18375 let should_unfold = buffer_ids
18376 .iter()
18377 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18378
18379 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18380 editor
18381 .update_in(cx, |editor, _, cx| {
18382 for buffer_id in buffer_ids {
18383 if should_unfold {
18384 editor.unfold_buffer(buffer_id, cx);
18385 } else {
18386 editor.fold_buffer(buffer_id, cx);
18387 }
18388 }
18389 })
18390 .ok();
18391 });
18392 }
18393 }
18394
18395 fn fold_at_level(
18396 &mut self,
18397 fold_at: &FoldAtLevel,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 if !self.buffer.read(cx).is_singleton() {
18402 return;
18403 }
18404
18405 let fold_at_level = fold_at.0;
18406 let snapshot = self.buffer.read(cx).snapshot(cx);
18407 let mut to_fold = Vec::new();
18408 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18409
18410 let row_ranges_to_keep: Vec<Range<u32>> = self
18411 .selections
18412 .all::<Point>(&self.display_snapshot(cx))
18413 .into_iter()
18414 .map(|sel| sel.start.row..sel.end.row)
18415 .collect();
18416
18417 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18418 while start_row < end_row {
18419 match self
18420 .snapshot(window, cx)
18421 .crease_for_buffer_row(MultiBufferRow(start_row))
18422 {
18423 Some(crease) => {
18424 let nested_start_row = crease.range().start.row + 1;
18425 let nested_end_row = crease.range().end.row;
18426
18427 if current_level < fold_at_level {
18428 stack.push((nested_start_row, nested_end_row, current_level + 1));
18429 } else if current_level == fold_at_level {
18430 // Fold iff there is no selection completely contained within the fold region
18431 if !row_ranges_to_keep.iter().any(|selection| {
18432 selection.end >= nested_start_row
18433 && selection.start <= nested_end_row
18434 }) {
18435 to_fold.push(crease);
18436 }
18437 }
18438
18439 start_row = nested_end_row + 1;
18440 }
18441 None => start_row += 1,
18442 }
18443 }
18444 }
18445
18446 self.fold_creases(to_fold, true, window, cx);
18447 }
18448
18449 pub fn fold_at_level_1(
18450 &mut self,
18451 _: &actions::FoldAtLevel1,
18452 window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) {
18455 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18456 }
18457
18458 pub fn fold_at_level_2(
18459 &mut self,
18460 _: &actions::FoldAtLevel2,
18461 window: &mut Window,
18462 cx: &mut Context<Self>,
18463 ) {
18464 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18465 }
18466
18467 pub fn fold_at_level_3(
18468 &mut self,
18469 _: &actions::FoldAtLevel3,
18470 window: &mut Window,
18471 cx: &mut Context<Self>,
18472 ) {
18473 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18474 }
18475
18476 pub fn fold_at_level_4(
18477 &mut self,
18478 _: &actions::FoldAtLevel4,
18479 window: &mut Window,
18480 cx: &mut Context<Self>,
18481 ) {
18482 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18483 }
18484
18485 pub fn fold_at_level_5(
18486 &mut self,
18487 _: &actions::FoldAtLevel5,
18488 window: &mut Window,
18489 cx: &mut Context<Self>,
18490 ) {
18491 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18492 }
18493
18494 pub fn fold_at_level_6(
18495 &mut self,
18496 _: &actions::FoldAtLevel6,
18497 window: &mut Window,
18498 cx: &mut Context<Self>,
18499 ) {
18500 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18501 }
18502
18503 pub fn fold_at_level_7(
18504 &mut self,
18505 _: &actions::FoldAtLevel7,
18506 window: &mut Window,
18507 cx: &mut Context<Self>,
18508 ) {
18509 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18510 }
18511
18512 pub fn fold_at_level_8(
18513 &mut self,
18514 _: &actions::FoldAtLevel8,
18515 window: &mut Window,
18516 cx: &mut Context<Self>,
18517 ) {
18518 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18519 }
18520
18521 pub fn fold_at_level_9(
18522 &mut self,
18523 _: &actions::FoldAtLevel9,
18524 window: &mut Window,
18525 cx: &mut Context<Self>,
18526 ) {
18527 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18528 }
18529
18530 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18531 if self.buffer.read(cx).is_singleton() {
18532 let mut fold_ranges = Vec::new();
18533 let snapshot = self.buffer.read(cx).snapshot(cx);
18534
18535 for row in 0..snapshot.max_row().0 {
18536 if let Some(foldable_range) = self
18537 .snapshot(window, cx)
18538 .crease_for_buffer_row(MultiBufferRow(row))
18539 {
18540 fold_ranges.push(foldable_range);
18541 }
18542 }
18543
18544 self.fold_creases(fold_ranges, true, window, cx);
18545 } else {
18546 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18547 editor
18548 .update_in(cx, |editor, _, cx| {
18549 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18550 editor.fold_buffer(buffer_id, cx);
18551 }
18552 })
18553 .ok();
18554 });
18555 }
18556 }
18557
18558 pub fn fold_function_bodies(
18559 &mut self,
18560 _: &actions::FoldFunctionBodies,
18561 window: &mut Window,
18562 cx: &mut Context<Self>,
18563 ) {
18564 let snapshot = self.buffer.read(cx).snapshot(cx);
18565
18566 let ranges = snapshot
18567 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18568 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18569 .collect::<Vec<_>>();
18570
18571 let creases = ranges
18572 .into_iter()
18573 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18574 .collect();
18575
18576 self.fold_creases(creases, true, window, cx);
18577 }
18578
18579 pub fn fold_recursive(
18580 &mut self,
18581 _: &actions::FoldRecursive,
18582 window: &mut Window,
18583 cx: &mut Context<Self>,
18584 ) {
18585 let mut to_fold = Vec::new();
18586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18587 let selections = self.selections.all_adjusted(&display_map);
18588
18589 for selection in selections {
18590 let range = selection.range().sorted();
18591 let buffer_start_row = range.start.row;
18592
18593 if range.start.row != range.end.row {
18594 let mut found = false;
18595 for row in range.start.row..=range.end.row {
18596 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18597 found = true;
18598 to_fold.push(crease);
18599 }
18600 }
18601 if found {
18602 continue;
18603 }
18604 }
18605
18606 for row in (0..=range.start.row).rev() {
18607 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18608 if crease.range().end.row >= buffer_start_row {
18609 to_fold.push(crease);
18610 } else {
18611 break;
18612 }
18613 }
18614 }
18615 }
18616
18617 self.fold_creases(to_fold, true, window, cx);
18618 }
18619
18620 pub fn fold_at(
18621 &mut self,
18622 buffer_row: MultiBufferRow,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18627
18628 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18629 let autoscroll = self
18630 .selections
18631 .all::<Point>(&display_map)
18632 .iter()
18633 .any(|selection| crease.range().overlaps(&selection.range()));
18634
18635 self.fold_creases(vec![crease], autoscroll, window, cx);
18636 }
18637 }
18638
18639 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18640 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18642 let buffer = display_map.buffer_snapshot();
18643 let selections = self.selections.all::<Point>(&display_map);
18644 let ranges = selections
18645 .iter()
18646 .map(|s| {
18647 let range = s.display_range(&display_map).sorted();
18648 let mut start = range.start.to_point(&display_map);
18649 let mut end = range.end.to_point(&display_map);
18650 start.column = 0;
18651 end.column = buffer.line_len(MultiBufferRow(end.row));
18652 start..end
18653 })
18654 .collect::<Vec<_>>();
18655
18656 self.unfold_ranges(&ranges, true, true, cx);
18657 } else {
18658 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18659 let buffer_ids = self
18660 .selections
18661 .disjoint_anchor_ranges()
18662 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18663 .collect::<HashSet<_>>();
18664 for buffer_id in buffer_ids {
18665 self.unfold_buffer(buffer_id, cx);
18666 }
18667 }
18668 }
18669
18670 pub fn unfold_recursive(
18671 &mut self,
18672 _: &UnfoldRecursive,
18673 _window: &mut Window,
18674 cx: &mut Context<Self>,
18675 ) {
18676 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18677 let selections = self.selections.all::<Point>(&display_map);
18678 let ranges = selections
18679 .iter()
18680 .map(|s| {
18681 let mut range = s.display_range(&display_map).sorted();
18682 *range.start.column_mut() = 0;
18683 *range.end.column_mut() = display_map.line_len(range.end.row());
18684 let start = range.start.to_point(&display_map);
18685 let end = range.end.to_point(&display_map);
18686 start..end
18687 })
18688 .collect::<Vec<_>>();
18689
18690 self.unfold_ranges(&ranges, true, true, cx);
18691 }
18692
18693 pub fn unfold_at(
18694 &mut self,
18695 buffer_row: MultiBufferRow,
18696 _window: &mut Window,
18697 cx: &mut Context<Self>,
18698 ) {
18699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18700
18701 let intersection_range = Point::new(buffer_row.0, 0)
18702 ..Point::new(
18703 buffer_row.0,
18704 display_map.buffer_snapshot().line_len(buffer_row),
18705 );
18706
18707 let autoscroll = self
18708 .selections
18709 .all::<Point>(&display_map)
18710 .iter()
18711 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18712
18713 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18714 }
18715
18716 pub fn unfold_all(
18717 &mut self,
18718 _: &actions::UnfoldAll,
18719 _window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 if self.buffer.read(cx).is_singleton() {
18723 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18724 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18725 } else {
18726 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18727 editor
18728 .update(cx, |editor, cx| {
18729 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18730 editor.unfold_buffer(buffer_id, cx);
18731 }
18732 })
18733 .ok();
18734 });
18735 }
18736 }
18737
18738 pub fn fold_selected_ranges(
18739 &mut self,
18740 _: &FoldSelectedRanges,
18741 window: &mut Window,
18742 cx: &mut Context<Self>,
18743 ) {
18744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18745 let selections = self.selections.all_adjusted(&display_map);
18746 let ranges = selections
18747 .into_iter()
18748 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18749 .collect::<Vec<_>>();
18750 self.fold_creases(ranges, true, window, cx);
18751 }
18752
18753 pub fn fold_ranges<T: ToOffset + Clone>(
18754 &mut self,
18755 ranges: Vec<Range<T>>,
18756 auto_scroll: bool,
18757 window: &mut Window,
18758 cx: &mut Context<Self>,
18759 ) {
18760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18761 let ranges = ranges
18762 .into_iter()
18763 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18764 .collect::<Vec<_>>();
18765 self.fold_creases(ranges, auto_scroll, window, cx);
18766 }
18767
18768 pub fn fold_creases<T: ToOffset + Clone>(
18769 &mut self,
18770 creases: Vec<Crease<T>>,
18771 auto_scroll: bool,
18772 _window: &mut Window,
18773 cx: &mut Context<Self>,
18774 ) {
18775 if creases.is_empty() {
18776 return;
18777 }
18778
18779 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18780
18781 if auto_scroll {
18782 self.request_autoscroll(Autoscroll::fit(), cx);
18783 }
18784
18785 cx.notify();
18786
18787 self.scrollbar_marker_state.dirty = true;
18788 self.folds_did_change(cx);
18789 }
18790
18791 /// Removes any folds whose ranges intersect any of the given ranges.
18792 pub fn unfold_ranges<T: ToOffset + Clone>(
18793 &mut self,
18794 ranges: &[Range<T>],
18795 inclusive: bool,
18796 auto_scroll: bool,
18797 cx: &mut Context<Self>,
18798 ) {
18799 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18800 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18801 });
18802 self.folds_did_change(cx);
18803 }
18804
18805 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18806 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18807 return;
18808 }
18809 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18810 self.display_map.update(cx, |display_map, cx| {
18811 display_map.fold_buffers([buffer_id], cx)
18812 });
18813 cx.emit(EditorEvent::BufferFoldToggled {
18814 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18815 folded: true,
18816 });
18817 cx.notify();
18818 }
18819
18820 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18821 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18822 return;
18823 }
18824 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18825 self.display_map.update(cx, |display_map, cx| {
18826 display_map.unfold_buffers([buffer_id], cx);
18827 });
18828 cx.emit(EditorEvent::BufferFoldToggled {
18829 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18830 folded: false,
18831 });
18832 cx.notify();
18833 }
18834
18835 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18836 self.display_map.read(cx).is_buffer_folded(buffer)
18837 }
18838
18839 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18840 self.display_map.read(cx).folded_buffers()
18841 }
18842
18843 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18844 self.display_map.update(cx, |display_map, cx| {
18845 display_map.disable_header_for_buffer(buffer_id, cx);
18846 });
18847 cx.notify();
18848 }
18849
18850 /// Removes any folds with the given ranges.
18851 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18852 &mut self,
18853 ranges: &[Range<T>],
18854 type_id: TypeId,
18855 auto_scroll: bool,
18856 cx: &mut Context<Self>,
18857 ) {
18858 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18859 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18860 });
18861 self.folds_did_change(cx);
18862 }
18863
18864 fn remove_folds_with<T: ToOffset + Clone>(
18865 &mut self,
18866 ranges: &[Range<T>],
18867 auto_scroll: bool,
18868 cx: &mut Context<Self>,
18869 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18870 ) {
18871 if ranges.is_empty() {
18872 return;
18873 }
18874
18875 let mut buffers_affected = HashSet::default();
18876 let multi_buffer = self.buffer().read(cx);
18877 for range in ranges {
18878 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18879 buffers_affected.insert(buffer.read(cx).remote_id());
18880 };
18881 }
18882
18883 self.display_map.update(cx, update);
18884
18885 if auto_scroll {
18886 self.request_autoscroll(Autoscroll::fit(), cx);
18887 }
18888
18889 cx.notify();
18890 self.scrollbar_marker_state.dirty = true;
18891 self.active_indent_guides_state.dirty = true;
18892 }
18893
18894 pub fn update_renderer_widths(
18895 &mut self,
18896 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18897 cx: &mut Context<Self>,
18898 ) -> bool {
18899 self.display_map
18900 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18901 }
18902
18903 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18904 self.display_map.read(cx).fold_placeholder.clone()
18905 }
18906
18907 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18908 self.buffer.update(cx, |buffer, cx| {
18909 buffer.set_all_diff_hunks_expanded(cx);
18910 });
18911 }
18912
18913 pub fn expand_all_diff_hunks(
18914 &mut self,
18915 _: &ExpandAllDiffHunks,
18916 _window: &mut Window,
18917 cx: &mut Context<Self>,
18918 ) {
18919 self.buffer.update(cx, |buffer, cx| {
18920 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18921 });
18922 }
18923
18924 pub fn collapse_all_diff_hunks(
18925 &mut self,
18926 _: &CollapseAllDiffHunks,
18927 _window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) {
18930 self.buffer.update(cx, |buffer, cx| {
18931 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18932 });
18933 }
18934
18935 pub fn toggle_selected_diff_hunks(
18936 &mut self,
18937 _: &ToggleSelectedDiffHunks,
18938 _window: &mut Window,
18939 cx: &mut Context<Self>,
18940 ) {
18941 let ranges: Vec<_> = self
18942 .selections
18943 .disjoint_anchors()
18944 .iter()
18945 .map(|s| s.range())
18946 .collect();
18947 self.toggle_diff_hunks_in_ranges(ranges, cx);
18948 }
18949
18950 pub fn diff_hunks_in_ranges<'a>(
18951 &'a self,
18952 ranges: &'a [Range<Anchor>],
18953 buffer: &'a MultiBufferSnapshot,
18954 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18955 ranges.iter().flat_map(move |range| {
18956 let end_excerpt_id = range.end.excerpt_id;
18957 let range = range.to_point(buffer);
18958 let mut peek_end = range.end;
18959 if range.end.row < buffer.max_row().0 {
18960 peek_end = Point::new(range.end.row + 1, 0);
18961 }
18962 buffer
18963 .diff_hunks_in_range(range.start..peek_end)
18964 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18965 })
18966 }
18967
18968 pub fn has_stageable_diff_hunks_in_ranges(
18969 &self,
18970 ranges: &[Range<Anchor>],
18971 snapshot: &MultiBufferSnapshot,
18972 ) -> bool {
18973 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18974 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18975 }
18976
18977 pub fn toggle_staged_selected_diff_hunks(
18978 &mut self,
18979 _: &::git::ToggleStaged,
18980 _: &mut Window,
18981 cx: &mut Context<Self>,
18982 ) {
18983 let snapshot = self.buffer.read(cx).snapshot(cx);
18984 let ranges: Vec<_> = self
18985 .selections
18986 .disjoint_anchors()
18987 .iter()
18988 .map(|s| s.range())
18989 .collect();
18990 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18991 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18992 }
18993
18994 pub fn set_render_diff_hunk_controls(
18995 &mut self,
18996 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18997 cx: &mut Context<Self>,
18998 ) {
18999 self.render_diff_hunk_controls = render_diff_hunk_controls;
19000 cx.notify();
19001 }
19002
19003 pub fn stage_and_next(
19004 &mut self,
19005 _: &::git::StageAndNext,
19006 window: &mut Window,
19007 cx: &mut Context<Self>,
19008 ) {
19009 self.do_stage_or_unstage_and_next(true, window, cx);
19010 }
19011
19012 pub fn unstage_and_next(
19013 &mut self,
19014 _: &::git::UnstageAndNext,
19015 window: &mut Window,
19016 cx: &mut Context<Self>,
19017 ) {
19018 self.do_stage_or_unstage_and_next(false, window, cx);
19019 }
19020
19021 pub fn stage_or_unstage_diff_hunks(
19022 &mut self,
19023 stage: bool,
19024 ranges: Vec<Range<Anchor>>,
19025 cx: &mut Context<Self>,
19026 ) {
19027 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19028 cx.spawn(async move |this, cx| {
19029 task.await?;
19030 this.update(cx, |this, cx| {
19031 let snapshot = this.buffer.read(cx).snapshot(cx);
19032 let chunk_by = this
19033 .diff_hunks_in_ranges(&ranges, &snapshot)
19034 .chunk_by(|hunk| hunk.buffer_id);
19035 for (buffer_id, hunks) in &chunk_by {
19036 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19037 }
19038 })
19039 })
19040 .detach_and_log_err(cx);
19041 }
19042
19043 fn save_buffers_for_ranges_if_needed(
19044 &mut self,
19045 ranges: &[Range<Anchor>],
19046 cx: &mut Context<Editor>,
19047 ) -> Task<Result<()>> {
19048 let multibuffer = self.buffer.read(cx);
19049 let snapshot = multibuffer.read(cx);
19050 let buffer_ids: HashSet<_> = ranges
19051 .iter()
19052 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19053 .collect();
19054 drop(snapshot);
19055
19056 let mut buffers = HashSet::default();
19057 for buffer_id in buffer_ids {
19058 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19059 let buffer = buffer_entity.read(cx);
19060 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19061 {
19062 buffers.insert(buffer_entity);
19063 }
19064 }
19065 }
19066
19067 if let Some(project) = &self.project {
19068 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19069 } else {
19070 Task::ready(Ok(()))
19071 }
19072 }
19073
19074 fn do_stage_or_unstage_and_next(
19075 &mut self,
19076 stage: bool,
19077 window: &mut Window,
19078 cx: &mut Context<Self>,
19079 ) {
19080 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19081
19082 if ranges.iter().any(|range| range.start != range.end) {
19083 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19084 return;
19085 }
19086
19087 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19088 let snapshot = self.snapshot(window, cx);
19089 let position = self
19090 .selections
19091 .newest::<Point>(&snapshot.display_snapshot)
19092 .head();
19093 let mut row = snapshot
19094 .buffer_snapshot()
19095 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19096 .find(|hunk| hunk.row_range.start.0 > position.row)
19097 .map(|hunk| hunk.row_range.start);
19098
19099 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19100 // Outside of the project diff editor, wrap around to the beginning.
19101 if !all_diff_hunks_expanded {
19102 row = row.or_else(|| {
19103 snapshot
19104 .buffer_snapshot()
19105 .diff_hunks_in_range(Point::zero()..position)
19106 .find(|hunk| hunk.row_range.end.0 < position.row)
19107 .map(|hunk| hunk.row_range.start)
19108 });
19109 }
19110
19111 if let Some(row) = row {
19112 let destination = Point::new(row.0, 0);
19113 let autoscroll = Autoscroll::center();
19114
19115 self.unfold_ranges(&[destination..destination], false, false, cx);
19116 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19117 s.select_ranges([destination..destination]);
19118 });
19119 }
19120 }
19121
19122 fn do_stage_or_unstage(
19123 &self,
19124 stage: bool,
19125 buffer_id: BufferId,
19126 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19127 cx: &mut App,
19128 ) -> Option<()> {
19129 let project = self.project()?;
19130 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19131 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19132 let buffer_snapshot = buffer.read(cx).snapshot();
19133 let file_exists = buffer_snapshot
19134 .file()
19135 .is_some_and(|file| file.disk_state().exists());
19136 diff.update(cx, |diff, cx| {
19137 diff.stage_or_unstage_hunks(
19138 stage,
19139 &hunks
19140 .map(|hunk| buffer_diff::DiffHunk {
19141 buffer_range: hunk.buffer_range,
19142 diff_base_byte_range: hunk.diff_base_byte_range,
19143 secondary_status: hunk.secondary_status,
19144 range: Point::zero()..Point::zero(), // unused
19145 })
19146 .collect::<Vec<_>>(),
19147 &buffer_snapshot,
19148 file_exists,
19149 cx,
19150 )
19151 });
19152 None
19153 }
19154
19155 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19156 let ranges: Vec<_> = self
19157 .selections
19158 .disjoint_anchors()
19159 .iter()
19160 .map(|s| s.range())
19161 .collect();
19162 self.buffer
19163 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19164 }
19165
19166 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19167 self.buffer.update(cx, |buffer, cx| {
19168 let ranges = vec![Anchor::min()..Anchor::max()];
19169 if !buffer.all_diff_hunks_expanded()
19170 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19171 {
19172 buffer.collapse_diff_hunks(ranges, cx);
19173 true
19174 } else {
19175 false
19176 }
19177 })
19178 }
19179
19180 fn toggle_diff_hunks_in_ranges(
19181 &mut self,
19182 ranges: Vec<Range<Anchor>>,
19183 cx: &mut Context<Editor>,
19184 ) {
19185 self.buffer.update(cx, |buffer, cx| {
19186 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19187 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19188 })
19189 }
19190
19191 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19192 self.buffer.update(cx, |buffer, cx| {
19193 let snapshot = buffer.snapshot(cx);
19194 let excerpt_id = range.end.excerpt_id;
19195 let point_range = range.to_point(&snapshot);
19196 let expand = !buffer.single_hunk_is_expanded(range, cx);
19197 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19198 })
19199 }
19200
19201 pub(crate) fn apply_all_diff_hunks(
19202 &mut self,
19203 _: &ApplyAllDiffHunks,
19204 window: &mut Window,
19205 cx: &mut Context<Self>,
19206 ) {
19207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19208
19209 let buffers = self.buffer.read(cx).all_buffers();
19210 for branch_buffer in buffers {
19211 branch_buffer.update(cx, |branch_buffer, cx| {
19212 branch_buffer.merge_into_base(Vec::new(), cx);
19213 });
19214 }
19215
19216 if let Some(project) = self.project.clone() {
19217 self.save(
19218 SaveOptions {
19219 format: true,
19220 autosave: false,
19221 },
19222 project,
19223 window,
19224 cx,
19225 )
19226 .detach_and_log_err(cx);
19227 }
19228 }
19229
19230 pub(crate) fn apply_selected_diff_hunks(
19231 &mut self,
19232 _: &ApplyDiffHunk,
19233 window: &mut Window,
19234 cx: &mut Context<Self>,
19235 ) {
19236 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19237 let snapshot = self.snapshot(window, cx);
19238 let hunks = snapshot.hunks_for_ranges(
19239 self.selections
19240 .all(&snapshot.display_snapshot)
19241 .into_iter()
19242 .map(|selection| selection.range()),
19243 );
19244 let mut ranges_by_buffer = HashMap::default();
19245 self.transact(window, cx, |editor, _window, cx| {
19246 for hunk in hunks {
19247 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19248 ranges_by_buffer
19249 .entry(buffer.clone())
19250 .or_insert_with(Vec::new)
19251 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19252 }
19253 }
19254
19255 for (buffer, ranges) in ranges_by_buffer {
19256 buffer.update(cx, |buffer, cx| {
19257 buffer.merge_into_base(ranges, cx);
19258 });
19259 }
19260 });
19261
19262 if let Some(project) = self.project.clone() {
19263 self.save(
19264 SaveOptions {
19265 format: true,
19266 autosave: false,
19267 },
19268 project,
19269 window,
19270 cx,
19271 )
19272 .detach_and_log_err(cx);
19273 }
19274 }
19275
19276 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19277 if hovered != self.gutter_hovered {
19278 self.gutter_hovered = hovered;
19279 cx.notify();
19280 }
19281 }
19282
19283 pub fn insert_blocks(
19284 &mut self,
19285 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19286 autoscroll: Option<Autoscroll>,
19287 cx: &mut Context<Self>,
19288 ) -> Vec<CustomBlockId> {
19289 let blocks = self
19290 .display_map
19291 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19292 if let Some(autoscroll) = autoscroll {
19293 self.request_autoscroll(autoscroll, cx);
19294 }
19295 cx.notify();
19296 blocks
19297 }
19298
19299 pub fn resize_blocks(
19300 &mut self,
19301 heights: HashMap<CustomBlockId, u32>,
19302 autoscroll: Option<Autoscroll>,
19303 cx: &mut Context<Self>,
19304 ) {
19305 self.display_map
19306 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19307 if let Some(autoscroll) = autoscroll {
19308 self.request_autoscroll(autoscroll, cx);
19309 }
19310 cx.notify();
19311 }
19312
19313 pub fn replace_blocks(
19314 &mut self,
19315 renderers: HashMap<CustomBlockId, RenderBlock>,
19316 autoscroll: Option<Autoscroll>,
19317 cx: &mut Context<Self>,
19318 ) {
19319 self.display_map
19320 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19321 if let Some(autoscroll) = autoscroll {
19322 self.request_autoscroll(autoscroll, cx);
19323 }
19324 cx.notify();
19325 }
19326
19327 pub fn remove_blocks(
19328 &mut self,
19329 block_ids: HashSet<CustomBlockId>,
19330 autoscroll: Option<Autoscroll>,
19331 cx: &mut Context<Self>,
19332 ) {
19333 self.display_map.update(cx, |display_map, cx| {
19334 display_map.remove_blocks(block_ids, cx)
19335 });
19336 if let Some(autoscroll) = autoscroll {
19337 self.request_autoscroll(autoscroll, cx);
19338 }
19339 cx.notify();
19340 }
19341
19342 pub fn row_for_block(
19343 &self,
19344 block_id: CustomBlockId,
19345 cx: &mut Context<Self>,
19346 ) -> Option<DisplayRow> {
19347 self.display_map
19348 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19349 }
19350
19351 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19352 self.focused_block = Some(focused_block);
19353 }
19354
19355 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19356 self.focused_block.take()
19357 }
19358
19359 pub fn insert_creases(
19360 &mut self,
19361 creases: impl IntoIterator<Item = Crease<Anchor>>,
19362 cx: &mut Context<Self>,
19363 ) -> Vec<CreaseId> {
19364 self.display_map
19365 .update(cx, |map, cx| map.insert_creases(creases, cx))
19366 }
19367
19368 pub fn remove_creases(
19369 &mut self,
19370 ids: impl IntoIterator<Item = CreaseId>,
19371 cx: &mut Context<Self>,
19372 ) -> Vec<(CreaseId, Range<Anchor>)> {
19373 self.display_map
19374 .update(cx, |map, cx| map.remove_creases(ids, cx))
19375 }
19376
19377 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19378 self.display_map
19379 .update(cx, |map, cx| map.snapshot(cx))
19380 .longest_row()
19381 }
19382
19383 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19384 self.display_map
19385 .update(cx, |map, cx| map.snapshot(cx))
19386 .max_point()
19387 }
19388
19389 pub fn text(&self, cx: &App) -> String {
19390 self.buffer.read(cx).read(cx).text()
19391 }
19392
19393 pub fn is_empty(&self, cx: &App) -> bool {
19394 self.buffer.read(cx).read(cx).is_empty()
19395 }
19396
19397 pub fn text_option(&self, cx: &App) -> Option<String> {
19398 let text = self.text(cx);
19399 let text = text.trim();
19400
19401 if text.is_empty() {
19402 return None;
19403 }
19404
19405 Some(text.to_string())
19406 }
19407
19408 pub fn set_text(
19409 &mut self,
19410 text: impl Into<Arc<str>>,
19411 window: &mut Window,
19412 cx: &mut Context<Self>,
19413 ) {
19414 self.transact(window, cx, |this, _, cx| {
19415 this.buffer
19416 .read(cx)
19417 .as_singleton()
19418 .expect("you can only call set_text on editors for singleton buffers")
19419 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19420 });
19421 }
19422
19423 pub fn display_text(&self, cx: &mut App) -> String {
19424 self.display_map
19425 .update(cx, |map, cx| map.snapshot(cx))
19426 .text()
19427 }
19428
19429 fn create_minimap(
19430 &self,
19431 minimap_settings: MinimapSettings,
19432 window: &mut Window,
19433 cx: &mut Context<Self>,
19434 ) -> Option<Entity<Self>> {
19435 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19436 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19437 }
19438
19439 fn initialize_new_minimap(
19440 &self,
19441 minimap_settings: MinimapSettings,
19442 window: &mut Window,
19443 cx: &mut Context<Self>,
19444 ) -> Entity<Self> {
19445 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19446
19447 let mut minimap = Editor::new_internal(
19448 EditorMode::Minimap {
19449 parent: cx.weak_entity(),
19450 },
19451 self.buffer.clone(),
19452 None,
19453 Some(self.display_map.clone()),
19454 window,
19455 cx,
19456 );
19457 minimap.scroll_manager.clone_state(&self.scroll_manager);
19458 minimap.set_text_style_refinement(TextStyleRefinement {
19459 font_size: Some(MINIMAP_FONT_SIZE),
19460 font_weight: Some(MINIMAP_FONT_WEIGHT),
19461 ..Default::default()
19462 });
19463 minimap.update_minimap_configuration(minimap_settings, cx);
19464 cx.new(|_| minimap)
19465 }
19466
19467 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19468 let current_line_highlight = minimap_settings
19469 .current_line_highlight
19470 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19471 self.set_current_line_highlight(Some(current_line_highlight));
19472 }
19473
19474 pub fn minimap(&self) -> Option<&Entity<Self>> {
19475 self.minimap
19476 .as_ref()
19477 .filter(|_| self.minimap_visibility.visible())
19478 }
19479
19480 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19481 let mut wrap_guides = smallvec![];
19482
19483 if self.show_wrap_guides == Some(false) {
19484 return wrap_guides;
19485 }
19486
19487 let settings = self.buffer.read(cx).language_settings(cx);
19488 if settings.show_wrap_guides {
19489 match self.soft_wrap_mode(cx) {
19490 SoftWrap::Column(soft_wrap) => {
19491 wrap_guides.push((soft_wrap as usize, true));
19492 }
19493 SoftWrap::Bounded(soft_wrap) => {
19494 wrap_guides.push((soft_wrap as usize, true));
19495 }
19496 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19497 }
19498 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19499 }
19500
19501 wrap_guides
19502 }
19503
19504 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19505 let settings = self.buffer.read(cx).language_settings(cx);
19506 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19507 match mode {
19508 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19509 SoftWrap::None
19510 }
19511 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19512 language_settings::SoftWrap::PreferredLineLength => {
19513 SoftWrap::Column(settings.preferred_line_length)
19514 }
19515 language_settings::SoftWrap::Bounded => {
19516 SoftWrap::Bounded(settings.preferred_line_length)
19517 }
19518 }
19519 }
19520
19521 pub fn set_soft_wrap_mode(
19522 &mut self,
19523 mode: language_settings::SoftWrap,
19524
19525 cx: &mut Context<Self>,
19526 ) {
19527 self.soft_wrap_mode_override = Some(mode);
19528 cx.notify();
19529 }
19530
19531 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19532 self.hard_wrap = hard_wrap;
19533 cx.notify();
19534 }
19535
19536 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19537 self.text_style_refinement = Some(style);
19538 }
19539
19540 /// called by the Element so we know what style we were most recently rendered with.
19541 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19542 // We intentionally do not inform the display map about the minimap style
19543 // so that wrapping is not recalculated and stays consistent for the editor
19544 // and its linked minimap.
19545 if !self.mode.is_minimap() {
19546 let font = style.text.font();
19547 let font_size = style.text.font_size.to_pixels(window.rem_size());
19548 let display_map = self
19549 .placeholder_display_map
19550 .as_ref()
19551 .filter(|_| self.is_empty(cx))
19552 .unwrap_or(&self.display_map);
19553
19554 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19555 }
19556 self.style = Some(style);
19557 }
19558
19559 pub fn style(&self) -> Option<&EditorStyle> {
19560 self.style.as_ref()
19561 }
19562
19563 // Called by the element. This method is not designed to be called outside of the editor
19564 // element's layout code because it does not notify when rewrapping is computed synchronously.
19565 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19566 if self.is_empty(cx) {
19567 self.placeholder_display_map
19568 .as_ref()
19569 .map_or(false, |display_map| {
19570 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19571 })
19572 } else {
19573 self.display_map
19574 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19575 }
19576 }
19577
19578 pub fn set_soft_wrap(&mut self) {
19579 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19580 }
19581
19582 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19583 if self.soft_wrap_mode_override.is_some() {
19584 self.soft_wrap_mode_override.take();
19585 } else {
19586 let soft_wrap = match self.soft_wrap_mode(cx) {
19587 SoftWrap::GitDiff => return,
19588 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19589 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19590 language_settings::SoftWrap::None
19591 }
19592 };
19593 self.soft_wrap_mode_override = Some(soft_wrap);
19594 }
19595 cx.notify();
19596 }
19597
19598 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19599 let Some(workspace) = self.workspace() else {
19600 return;
19601 };
19602 let fs = workspace.read(cx).app_state().fs.clone();
19603 let current_show = TabBarSettings::get_global(cx).show;
19604 update_settings_file(fs, cx, move |setting, _| {
19605 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19606 });
19607 }
19608
19609 pub fn toggle_indent_guides(
19610 &mut self,
19611 _: &ToggleIndentGuides,
19612 _: &mut Window,
19613 cx: &mut Context<Self>,
19614 ) {
19615 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19616 self.buffer
19617 .read(cx)
19618 .language_settings(cx)
19619 .indent_guides
19620 .enabled
19621 });
19622 self.show_indent_guides = Some(!currently_enabled);
19623 cx.notify();
19624 }
19625
19626 fn should_show_indent_guides(&self) -> Option<bool> {
19627 self.show_indent_guides
19628 }
19629
19630 pub fn toggle_line_numbers(
19631 &mut self,
19632 _: &ToggleLineNumbers,
19633 _: &mut Window,
19634 cx: &mut Context<Self>,
19635 ) {
19636 let mut editor_settings = EditorSettings::get_global(cx).clone();
19637 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19638 EditorSettings::override_global(editor_settings, cx);
19639 }
19640
19641 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19642 if let Some(show_line_numbers) = self.show_line_numbers {
19643 return show_line_numbers;
19644 }
19645 EditorSettings::get_global(cx).gutter.line_numbers
19646 }
19647
19648 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19649 match (
19650 self.use_relative_line_numbers,
19651 EditorSettings::get_global(cx).relative_line_numbers,
19652 ) {
19653 (None, setting) => setting,
19654 (Some(false), _) => RelativeLineNumbers::Disabled,
19655 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19656 (Some(true), _) => RelativeLineNumbers::Enabled,
19657 }
19658 }
19659
19660 pub fn toggle_relative_line_numbers(
19661 &mut self,
19662 _: &ToggleRelativeLineNumbers,
19663 _: &mut Window,
19664 cx: &mut Context<Self>,
19665 ) {
19666 let is_relative = self.relative_line_numbers(cx);
19667 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19668 }
19669
19670 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19671 self.use_relative_line_numbers = is_relative;
19672 cx.notify();
19673 }
19674
19675 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19676 self.show_gutter = show_gutter;
19677 cx.notify();
19678 }
19679
19680 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19681 self.show_scrollbars = ScrollbarAxes {
19682 horizontal: show,
19683 vertical: show,
19684 };
19685 cx.notify();
19686 }
19687
19688 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19689 self.show_scrollbars.vertical = show;
19690 cx.notify();
19691 }
19692
19693 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19694 self.show_scrollbars.horizontal = show;
19695 cx.notify();
19696 }
19697
19698 pub fn set_minimap_visibility(
19699 &mut self,
19700 minimap_visibility: MinimapVisibility,
19701 window: &mut Window,
19702 cx: &mut Context<Self>,
19703 ) {
19704 if self.minimap_visibility != minimap_visibility {
19705 if minimap_visibility.visible() && self.minimap.is_none() {
19706 let minimap_settings = EditorSettings::get_global(cx).minimap;
19707 self.minimap =
19708 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19709 }
19710 self.minimap_visibility = minimap_visibility;
19711 cx.notify();
19712 }
19713 }
19714
19715 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19716 self.set_show_scrollbars(false, cx);
19717 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19718 }
19719
19720 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19721 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19722 }
19723
19724 /// Normally the text in full mode and auto height editors is padded on the
19725 /// left side by roughly half a character width for improved hit testing.
19726 ///
19727 /// Use this method to disable this for cases where this is not wanted (e.g.
19728 /// if you want to align the editor text with some other text above or below)
19729 /// or if you want to add this padding to single-line editors.
19730 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19731 self.offset_content = offset_content;
19732 cx.notify();
19733 }
19734
19735 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19736 self.show_line_numbers = Some(show_line_numbers);
19737 cx.notify();
19738 }
19739
19740 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19741 self.disable_expand_excerpt_buttons = true;
19742 cx.notify();
19743 }
19744
19745 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19746 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19747 cx.notify();
19748 }
19749
19750 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19751 self.show_code_actions = Some(show_code_actions);
19752 cx.notify();
19753 }
19754
19755 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19756 self.show_runnables = Some(show_runnables);
19757 cx.notify();
19758 }
19759
19760 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19761 self.show_breakpoints = Some(show_breakpoints);
19762 cx.notify();
19763 }
19764
19765 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19766 if self.display_map.read(cx).masked != masked {
19767 self.display_map.update(cx, |map, _| map.masked = masked);
19768 }
19769 cx.notify()
19770 }
19771
19772 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19773 self.show_wrap_guides = Some(show_wrap_guides);
19774 cx.notify();
19775 }
19776
19777 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19778 self.show_indent_guides = Some(show_indent_guides);
19779 cx.notify();
19780 }
19781
19782 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19783 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19784 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19785 && let Some(dir) = file.abs_path(cx).parent()
19786 {
19787 return Some(dir.to_owned());
19788 }
19789 }
19790
19791 None
19792 }
19793
19794 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19795 self.active_excerpt(cx)?
19796 .1
19797 .read(cx)
19798 .file()
19799 .and_then(|f| f.as_local())
19800 }
19801
19802 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19803 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19804 let buffer = buffer.read(cx);
19805 if let Some(project_path) = buffer.project_path(cx) {
19806 let project = self.project()?.read(cx);
19807 project.absolute_path(&project_path, cx)
19808 } else {
19809 buffer
19810 .file()
19811 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19812 }
19813 })
19814 }
19815
19816 pub fn reveal_in_finder(
19817 &mut self,
19818 _: &RevealInFileManager,
19819 _window: &mut Window,
19820 cx: &mut Context<Self>,
19821 ) {
19822 if let Some(target) = self.target_file(cx) {
19823 cx.reveal_path(&target.abs_path(cx));
19824 }
19825 }
19826
19827 pub fn copy_path(
19828 &mut self,
19829 _: &zed_actions::workspace::CopyPath,
19830 _window: &mut Window,
19831 cx: &mut Context<Self>,
19832 ) {
19833 if let Some(path) = self.target_file_abs_path(cx)
19834 && let Some(path) = path.to_str()
19835 {
19836 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19837 } else {
19838 cx.propagate();
19839 }
19840 }
19841
19842 pub fn copy_relative_path(
19843 &mut self,
19844 _: &zed_actions::workspace::CopyRelativePath,
19845 _window: &mut Window,
19846 cx: &mut Context<Self>,
19847 ) {
19848 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19849 let project = self.project()?.read(cx);
19850 let path = buffer.read(cx).file()?.path();
19851 let path = path.display(project.path_style(cx));
19852 Some(path)
19853 }) {
19854 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19855 } else {
19856 cx.propagate();
19857 }
19858 }
19859
19860 /// Returns the project path for the editor's buffer, if any buffer is
19861 /// opened in the editor.
19862 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19863 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19864 buffer.read(cx).project_path(cx)
19865 } else {
19866 None
19867 }
19868 }
19869
19870 // Returns true if the editor handled a go-to-line request
19871 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19872 maybe!({
19873 let breakpoint_store = self.breakpoint_store.as_ref()?;
19874
19875 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19876 else {
19877 self.clear_row_highlights::<ActiveDebugLine>();
19878 return None;
19879 };
19880
19881 let position = active_stack_frame.position;
19882 let buffer_id = position.buffer_id?;
19883 let snapshot = self
19884 .project
19885 .as_ref()?
19886 .read(cx)
19887 .buffer_for_id(buffer_id, cx)?
19888 .read(cx)
19889 .snapshot();
19890
19891 let mut handled = false;
19892 for (id, ExcerptRange { context, .. }) in
19893 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19894 {
19895 if context.start.cmp(&position, &snapshot).is_ge()
19896 || context.end.cmp(&position, &snapshot).is_lt()
19897 {
19898 continue;
19899 }
19900 let snapshot = self.buffer.read(cx).snapshot(cx);
19901 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19902
19903 handled = true;
19904 self.clear_row_highlights::<ActiveDebugLine>();
19905
19906 self.go_to_line::<ActiveDebugLine>(
19907 multibuffer_anchor,
19908 Some(cx.theme().colors().editor_debugger_active_line_background),
19909 window,
19910 cx,
19911 );
19912
19913 cx.notify();
19914 }
19915
19916 handled.then_some(())
19917 })
19918 .is_some()
19919 }
19920
19921 pub fn copy_file_name_without_extension(
19922 &mut self,
19923 _: &CopyFileNameWithoutExtension,
19924 _: &mut Window,
19925 cx: &mut Context<Self>,
19926 ) {
19927 if let Some(file) = self.target_file(cx)
19928 && let Some(file_stem) = file.path().file_stem()
19929 {
19930 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19931 }
19932 }
19933
19934 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19935 if let Some(file) = self.target_file(cx)
19936 && let Some(name) = file.path().file_name()
19937 {
19938 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19939 }
19940 }
19941
19942 pub fn toggle_git_blame(
19943 &mut self,
19944 _: &::git::Blame,
19945 window: &mut Window,
19946 cx: &mut Context<Self>,
19947 ) {
19948 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19949
19950 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19951 self.start_git_blame(true, window, cx);
19952 }
19953
19954 cx.notify();
19955 }
19956
19957 pub fn toggle_git_blame_inline(
19958 &mut self,
19959 _: &ToggleGitBlameInline,
19960 window: &mut Window,
19961 cx: &mut Context<Self>,
19962 ) {
19963 self.toggle_git_blame_inline_internal(true, window, cx);
19964 cx.notify();
19965 }
19966
19967 pub fn open_git_blame_commit(
19968 &mut self,
19969 _: &OpenGitBlameCommit,
19970 window: &mut Window,
19971 cx: &mut Context<Self>,
19972 ) {
19973 self.open_git_blame_commit_internal(window, cx);
19974 }
19975
19976 fn open_git_blame_commit_internal(
19977 &mut self,
19978 window: &mut Window,
19979 cx: &mut Context<Self>,
19980 ) -> Option<()> {
19981 let blame = self.blame.as_ref()?;
19982 let snapshot = self.snapshot(window, cx);
19983 let cursor = self
19984 .selections
19985 .newest::<Point>(&snapshot.display_snapshot)
19986 .head();
19987 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19988 let (_, blame_entry) = blame
19989 .update(cx, |blame, cx| {
19990 blame
19991 .blame_for_rows(
19992 &[RowInfo {
19993 buffer_id: Some(buffer.remote_id()),
19994 buffer_row: Some(point.row),
19995 ..Default::default()
19996 }],
19997 cx,
19998 )
19999 .next()
20000 })
20001 .flatten()?;
20002 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20003 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20004 let workspace = self.workspace()?.downgrade();
20005 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20006 None
20007 }
20008
20009 pub fn git_blame_inline_enabled(&self) -> bool {
20010 self.git_blame_inline_enabled
20011 }
20012
20013 pub fn toggle_selection_menu(
20014 &mut self,
20015 _: &ToggleSelectionMenu,
20016 _: &mut Window,
20017 cx: &mut Context<Self>,
20018 ) {
20019 self.show_selection_menu = self
20020 .show_selection_menu
20021 .map(|show_selections_menu| !show_selections_menu)
20022 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20023
20024 cx.notify();
20025 }
20026
20027 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20028 self.show_selection_menu
20029 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20030 }
20031
20032 fn start_git_blame(
20033 &mut self,
20034 user_triggered: bool,
20035 window: &mut Window,
20036 cx: &mut Context<Self>,
20037 ) {
20038 if let Some(project) = self.project() {
20039 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20040 && buffer.read(cx).file().is_none()
20041 {
20042 return;
20043 }
20044
20045 let focused = self.focus_handle(cx).contains_focused(window, cx);
20046
20047 let project = project.clone();
20048 let blame = cx
20049 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20050 self.blame_subscription =
20051 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20052 self.blame = Some(blame);
20053 }
20054 }
20055
20056 fn toggle_git_blame_inline_internal(
20057 &mut self,
20058 user_triggered: bool,
20059 window: &mut Window,
20060 cx: &mut Context<Self>,
20061 ) {
20062 if self.git_blame_inline_enabled {
20063 self.git_blame_inline_enabled = false;
20064 self.show_git_blame_inline = false;
20065 self.show_git_blame_inline_delay_task.take();
20066 } else {
20067 self.git_blame_inline_enabled = true;
20068 self.start_git_blame_inline(user_triggered, window, cx);
20069 }
20070
20071 cx.notify();
20072 }
20073
20074 fn start_git_blame_inline(
20075 &mut self,
20076 user_triggered: bool,
20077 window: &mut Window,
20078 cx: &mut Context<Self>,
20079 ) {
20080 self.start_git_blame(user_triggered, window, cx);
20081
20082 if ProjectSettings::get_global(cx)
20083 .git
20084 .inline_blame_delay()
20085 .is_some()
20086 {
20087 self.start_inline_blame_timer(window, cx);
20088 } else {
20089 self.show_git_blame_inline = true
20090 }
20091 }
20092
20093 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20094 self.blame.as_ref()
20095 }
20096
20097 pub fn show_git_blame_gutter(&self) -> bool {
20098 self.show_git_blame_gutter
20099 }
20100
20101 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20102 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20103 }
20104
20105 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20106 self.show_git_blame_inline
20107 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20108 && !self.newest_selection_head_on_empty_line(cx)
20109 && self.has_blame_entries(cx)
20110 }
20111
20112 fn has_blame_entries(&self, cx: &App) -> bool {
20113 self.blame()
20114 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20115 }
20116
20117 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20118 let cursor_anchor = self.selections.newest_anchor().head();
20119
20120 let snapshot = self.buffer.read(cx).snapshot(cx);
20121 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20122
20123 snapshot.line_len(buffer_row) == 0
20124 }
20125
20126 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20127 let buffer_and_selection = maybe!({
20128 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20129 let selection_range = selection.range();
20130
20131 let multi_buffer = self.buffer().read(cx);
20132 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20133 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20134
20135 let (buffer, range, _) = if selection.reversed {
20136 buffer_ranges.first()
20137 } else {
20138 buffer_ranges.last()
20139 }?;
20140
20141 let selection = text::ToPoint::to_point(&range.start, buffer).row
20142 ..text::ToPoint::to_point(&range.end, buffer).row;
20143 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20144 });
20145
20146 let Some((buffer, selection)) = buffer_and_selection else {
20147 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20148 };
20149
20150 let Some(project) = self.project() else {
20151 return Task::ready(Err(anyhow!("editor does not have project")));
20152 };
20153
20154 project.update(cx, |project, cx| {
20155 project.get_permalink_to_line(&buffer, selection, cx)
20156 })
20157 }
20158
20159 pub fn copy_permalink_to_line(
20160 &mut self,
20161 _: &CopyPermalinkToLine,
20162 window: &mut Window,
20163 cx: &mut Context<Self>,
20164 ) {
20165 let permalink_task = self.get_permalink_to_line(cx);
20166 let workspace = self.workspace();
20167
20168 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20169 Ok(permalink) => {
20170 cx.update(|_, cx| {
20171 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20172 })
20173 .ok();
20174 }
20175 Err(err) => {
20176 let message = format!("Failed to copy permalink: {err}");
20177
20178 anyhow::Result::<()>::Err(err).log_err();
20179
20180 if let Some(workspace) = workspace {
20181 workspace
20182 .update_in(cx, |workspace, _, cx| {
20183 struct CopyPermalinkToLine;
20184
20185 workspace.show_toast(
20186 Toast::new(
20187 NotificationId::unique::<CopyPermalinkToLine>(),
20188 message,
20189 ),
20190 cx,
20191 )
20192 })
20193 .ok();
20194 }
20195 }
20196 })
20197 .detach();
20198 }
20199
20200 pub fn copy_file_location(
20201 &mut self,
20202 _: &CopyFileLocation,
20203 _: &mut Window,
20204 cx: &mut Context<Self>,
20205 ) {
20206 let selection = self
20207 .selections
20208 .newest::<Point>(&self.display_snapshot(cx))
20209 .start
20210 .row
20211 + 1;
20212 if let Some(file) = self.target_file(cx) {
20213 let path = file.path().display(file.path_style(cx));
20214 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20215 }
20216 }
20217
20218 pub fn open_permalink_to_line(
20219 &mut self,
20220 _: &OpenPermalinkToLine,
20221 window: &mut Window,
20222 cx: &mut Context<Self>,
20223 ) {
20224 let permalink_task = self.get_permalink_to_line(cx);
20225 let workspace = self.workspace();
20226
20227 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20228 Ok(permalink) => {
20229 cx.update(|_, cx| {
20230 cx.open_url(permalink.as_ref());
20231 })
20232 .ok();
20233 }
20234 Err(err) => {
20235 let message = format!("Failed to open permalink: {err}");
20236
20237 anyhow::Result::<()>::Err(err).log_err();
20238
20239 if let Some(workspace) = workspace {
20240 workspace
20241 .update(cx, |workspace, cx| {
20242 struct OpenPermalinkToLine;
20243
20244 workspace.show_toast(
20245 Toast::new(
20246 NotificationId::unique::<OpenPermalinkToLine>(),
20247 message,
20248 ),
20249 cx,
20250 )
20251 })
20252 .ok();
20253 }
20254 }
20255 })
20256 .detach();
20257 }
20258
20259 pub fn insert_uuid_v4(
20260 &mut self,
20261 _: &InsertUuidV4,
20262 window: &mut Window,
20263 cx: &mut Context<Self>,
20264 ) {
20265 self.insert_uuid(UuidVersion::V4, window, cx);
20266 }
20267
20268 pub fn insert_uuid_v7(
20269 &mut self,
20270 _: &InsertUuidV7,
20271 window: &mut Window,
20272 cx: &mut Context<Self>,
20273 ) {
20274 self.insert_uuid(UuidVersion::V7, window, cx);
20275 }
20276
20277 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20279 self.transact(window, cx, |this, window, cx| {
20280 let edits = this
20281 .selections
20282 .all::<Point>(&this.display_snapshot(cx))
20283 .into_iter()
20284 .map(|selection| {
20285 let uuid = match version {
20286 UuidVersion::V4 => uuid::Uuid::new_v4(),
20287 UuidVersion::V7 => uuid::Uuid::now_v7(),
20288 };
20289
20290 (selection.range(), uuid.to_string())
20291 });
20292 this.edit(edits, cx);
20293 this.refresh_edit_prediction(true, false, window, cx);
20294 });
20295 }
20296
20297 pub fn open_selections_in_multibuffer(
20298 &mut self,
20299 _: &OpenSelectionsInMultibuffer,
20300 window: &mut Window,
20301 cx: &mut Context<Self>,
20302 ) {
20303 let multibuffer = self.buffer.read(cx);
20304
20305 let Some(buffer) = multibuffer.as_singleton() else {
20306 return;
20307 };
20308
20309 let Some(workspace) = self.workspace() else {
20310 return;
20311 };
20312
20313 let title = multibuffer.title(cx).to_string();
20314
20315 let locations = self
20316 .selections
20317 .all_anchors(&self.display_snapshot(cx))
20318 .iter()
20319 .map(|selection| {
20320 (
20321 buffer.clone(),
20322 (selection.start.text_anchor..selection.end.text_anchor)
20323 .to_point(buffer.read(cx)),
20324 )
20325 })
20326 .into_group_map();
20327
20328 cx.spawn_in(window, async move |_, cx| {
20329 workspace.update_in(cx, |workspace, window, cx| {
20330 Self::open_locations_in_multibuffer(
20331 workspace,
20332 locations,
20333 format!("Selections for '{title}'"),
20334 false,
20335 MultibufferSelectionMode::All,
20336 window,
20337 cx,
20338 );
20339 })
20340 })
20341 .detach();
20342 }
20343
20344 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20345 /// last highlight added will be used.
20346 ///
20347 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20348 pub fn highlight_rows<T: 'static>(
20349 &mut self,
20350 range: Range<Anchor>,
20351 color: Hsla,
20352 options: RowHighlightOptions,
20353 cx: &mut Context<Self>,
20354 ) {
20355 let snapshot = self.buffer().read(cx).snapshot(cx);
20356 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20357 let ix = row_highlights.binary_search_by(|highlight| {
20358 Ordering::Equal
20359 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20360 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20361 });
20362
20363 if let Err(mut ix) = ix {
20364 let index = post_inc(&mut self.highlight_order);
20365
20366 // If this range intersects with the preceding highlight, then merge it with
20367 // the preceding highlight. Otherwise insert a new highlight.
20368 let mut merged = false;
20369 if ix > 0 {
20370 let prev_highlight = &mut row_highlights[ix - 1];
20371 if prev_highlight
20372 .range
20373 .end
20374 .cmp(&range.start, &snapshot)
20375 .is_ge()
20376 {
20377 ix -= 1;
20378 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20379 prev_highlight.range.end = range.end;
20380 }
20381 merged = true;
20382 prev_highlight.index = index;
20383 prev_highlight.color = color;
20384 prev_highlight.options = options;
20385 }
20386 }
20387
20388 if !merged {
20389 row_highlights.insert(
20390 ix,
20391 RowHighlight {
20392 range,
20393 index,
20394 color,
20395 options,
20396 type_id: TypeId::of::<T>(),
20397 },
20398 );
20399 }
20400
20401 // If any of the following highlights intersect with this one, merge them.
20402 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20403 let highlight = &row_highlights[ix];
20404 if next_highlight
20405 .range
20406 .start
20407 .cmp(&highlight.range.end, &snapshot)
20408 .is_le()
20409 {
20410 if next_highlight
20411 .range
20412 .end
20413 .cmp(&highlight.range.end, &snapshot)
20414 .is_gt()
20415 {
20416 row_highlights[ix].range.end = next_highlight.range.end;
20417 }
20418 row_highlights.remove(ix + 1);
20419 } else {
20420 break;
20421 }
20422 }
20423 }
20424 }
20425
20426 /// Remove any highlighted row ranges of the given type that intersect the
20427 /// given ranges.
20428 pub fn remove_highlighted_rows<T: 'static>(
20429 &mut self,
20430 ranges_to_remove: Vec<Range<Anchor>>,
20431 cx: &mut Context<Self>,
20432 ) {
20433 let snapshot = self.buffer().read(cx).snapshot(cx);
20434 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20435 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20436 row_highlights.retain(|highlight| {
20437 while let Some(range_to_remove) = ranges_to_remove.peek() {
20438 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20439 Ordering::Less | Ordering::Equal => {
20440 ranges_to_remove.next();
20441 }
20442 Ordering::Greater => {
20443 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20444 Ordering::Less | Ordering::Equal => {
20445 return false;
20446 }
20447 Ordering::Greater => break,
20448 }
20449 }
20450 }
20451 }
20452
20453 true
20454 })
20455 }
20456
20457 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20458 pub fn clear_row_highlights<T: 'static>(&mut self) {
20459 self.highlighted_rows.remove(&TypeId::of::<T>());
20460 }
20461
20462 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20463 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20464 self.highlighted_rows
20465 .get(&TypeId::of::<T>())
20466 .map_or(&[] as &[_], |vec| vec.as_slice())
20467 .iter()
20468 .map(|highlight| (highlight.range.clone(), highlight.color))
20469 }
20470
20471 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20472 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20473 /// Allows to ignore certain kinds of highlights.
20474 pub fn highlighted_display_rows(
20475 &self,
20476 window: &mut Window,
20477 cx: &mut App,
20478 ) -> BTreeMap<DisplayRow, LineHighlight> {
20479 let snapshot = self.snapshot(window, cx);
20480 let mut used_highlight_orders = HashMap::default();
20481 self.highlighted_rows
20482 .iter()
20483 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20484 .fold(
20485 BTreeMap::<DisplayRow, LineHighlight>::new(),
20486 |mut unique_rows, highlight| {
20487 let start = highlight.range.start.to_display_point(&snapshot);
20488 let end = highlight.range.end.to_display_point(&snapshot);
20489 let start_row = start.row().0;
20490 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20491 && end.column() == 0
20492 {
20493 end.row().0.saturating_sub(1)
20494 } else {
20495 end.row().0
20496 };
20497 for row in start_row..=end_row {
20498 let used_index =
20499 used_highlight_orders.entry(row).or_insert(highlight.index);
20500 if highlight.index >= *used_index {
20501 *used_index = highlight.index;
20502 unique_rows.insert(
20503 DisplayRow(row),
20504 LineHighlight {
20505 include_gutter: highlight.options.include_gutter,
20506 border: None,
20507 background: highlight.color.into(),
20508 type_id: Some(highlight.type_id),
20509 },
20510 );
20511 }
20512 }
20513 unique_rows
20514 },
20515 )
20516 }
20517
20518 pub fn highlighted_display_row_for_autoscroll(
20519 &self,
20520 snapshot: &DisplaySnapshot,
20521 ) -> Option<DisplayRow> {
20522 self.highlighted_rows
20523 .values()
20524 .flat_map(|highlighted_rows| highlighted_rows.iter())
20525 .filter_map(|highlight| {
20526 if highlight.options.autoscroll {
20527 Some(highlight.range.start.to_display_point(snapshot).row())
20528 } else {
20529 None
20530 }
20531 })
20532 .min()
20533 }
20534
20535 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20536 self.highlight_background::<SearchWithinRange>(
20537 ranges,
20538 |colors| colors.colors().editor_document_highlight_read_background,
20539 cx,
20540 )
20541 }
20542
20543 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20544 self.breadcrumb_header = Some(new_header);
20545 }
20546
20547 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20548 self.clear_background_highlights::<SearchWithinRange>(cx);
20549 }
20550
20551 pub fn highlight_background<T: 'static>(
20552 &mut self,
20553 ranges: &[Range<Anchor>],
20554 color_fetcher: fn(&Theme) -> Hsla,
20555 cx: &mut Context<Self>,
20556 ) {
20557 self.background_highlights.insert(
20558 HighlightKey::Type(TypeId::of::<T>()),
20559 (color_fetcher, Arc::from(ranges)),
20560 );
20561 self.scrollbar_marker_state.dirty = true;
20562 cx.notify();
20563 }
20564
20565 pub fn highlight_background_key<T: 'static>(
20566 &mut self,
20567 key: usize,
20568 ranges: &[Range<Anchor>],
20569 color_fetcher: fn(&Theme) -> Hsla,
20570 cx: &mut Context<Self>,
20571 ) {
20572 self.background_highlights.insert(
20573 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20574 (color_fetcher, Arc::from(ranges)),
20575 );
20576 self.scrollbar_marker_state.dirty = true;
20577 cx.notify();
20578 }
20579
20580 pub fn clear_background_highlights<T: 'static>(
20581 &mut self,
20582 cx: &mut Context<Self>,
20583 ) -> Option<BackgroundHighlight> {
20584 let text_highlights = self
20585 .background_highlights
20586 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20587 if !text_highlights.1.is_empty() {
20588 self.scrollbar_marker_state.dirty = true;
20589 cx.notify();
20590 }
20591 Some(text_highlights)
20592 }
20593
20594 pub fn highlight_gutter<T: 'static>(
20595 &mut self,
20596 ranges: impl Into<Vec<Range<Anchor>>>,
20597 color_fetcher: fn(&App) -> Hsla,
20598 cx: &mut Context<Self>,
20599 ) {
20600 self.gutter_highlights
20601 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20602 cx.notify();
20603 }
20604
20605 pub fn clear_gutter_highlights<T: 'static>(
20606 &mut self,
20607 cx: &mut Context<Self>,
20608 ) -> Option<GutterHighlight> {
20609 cx.notify();
20610 self.gutter_highlights.remove(&TypeId::of::<T>())
20611 }
20612
20613 pub fn insert_gutter_highlight<T: 'static>(
20614 &mut self,
20615 range: Range<Anchor>,
20616 color_fetcher: fn(&App) -> Hsla,
20617 cx: &mut Context<Self>,
20618 ) {
20619 let snapshot = self.buffer().read(cx).snapshot(cx);
20620 let mut highlights = self
20621 .gutter_highlights
20622 .remove(&TypeId::of::<T>())
20623 .map(|(_, highlights)| highlights)
20624 .unwrap_or_default();
20625 let ix = highlights.binary_search_by(|highlight| {
20626 Ordering::Equal
20627 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20628 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20629 });
20630 if let Err(ix) = ix {
20631 highlights.insert(ix, range);
20632 }
20633 self.gutter_highlights
20634 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20635 }
20636
20637 pub fn remove_gutter_highlights<T: 'static>(
20638 &mut self,
20639 ranges_to_remove: Vec<Range<Anchor>>,
20640 cx: &mut Context<Self>,
20641 ) {
20642 let snapshot = self.buffer().read(cx).snapshot(cx);
20643 let Some((color_fetcher, mut gutter_highlights)) =
20644 self.gutter_highlights.remove(&TypeId::of::<T>())
20645 else {
20646 return;
20647 };
20648 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20649 gutter_highlights.retain(|highlight| {
20650 while let Some(range_to_remove) = ranges_to_remove.peek() {
20651 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20652 Ordering::Less | Ordering::Equal => {
20653 ranges_to_remove.next();
20654 }
20655 Ordering::Greater => {
20656 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20657 Ordering::Less | Ordering::Equal => {
20658 return false;
20659 }
20660 Ordering::Greater => break,
20661 }
20662 }
20663 }
20664 }
20665
20666 true
20667 });
20668 self.gutter_highlights
20669 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20670 }
20671
20672 #[cfg(feature = "test-support")]
20673 pub fn all_text_highlights(
20674 &self,
20675 window: &mut Window,
20676 cx: &mut Context<Self>,
20677 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20678 let snapshot = self.snapshot(window, cx);
20679 self.display_map.update(cx, |display_map, _| {
20680 display_map
20681 .all_text_highlights()
20682 .map(|highlight| {
20683 let (style, ranges) = highlight.as_ref();
20684 (
20685 *style,
20686 ranges
20687 .iter()
20688 .map(|range| range.clone().to_display_points(&snapshot))
20689 .collect(),
20690 )
20691 })
20692 .collect()
20693 })
20694 }
20695
20696 #[cfg(feature = "test-support")]
20697 pub fn all_text_background_highlights(
20698 &self,
20699 window: &mut Window,
20700 cx: &mut Context<Self>,
20701 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20702 let snapshot = self.snapshot(window, cx);
20703 let buffer = &snapshot.buffer_snapshot();
20704 let start = buffer.anchor_before(0);
20705 let end = buffer.anchor_after(buffer.len());
20706 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20707 }
20708
20709 #[cfg(any(test, feature = "test-support"))]
20710 pub fn sorted_background_highlights_in_range(
20711 &self,
20712 search_range: Range<Anchor>,
20713 display_snapshot: &DisplaySnapshot,
20714 theme: &Theme,
20715 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20716 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20717 res.sort_by(|a, b| {
20718 a.0.start
20719 .cmp(&b.0.start)
20720 .then_with(|| a.0.end.cmp(&b.0.end))
20721 .then_with(|| a.1.cmp(&b.1))
20722 });
20723 res
20724 }
20725
20726 #[cfg(feature = "test-support")]
20727 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20728 let snapshot = self.buffer().read(cx).snapshot(cx);
20729
20730 let highlights = self
20731 .background_highlights
20732 .get(&HighlightKey::Type(TypeId::of::<
20733 items::BufferSearchHighlights,
20734 >()));
20735
20736 if let Some((_color, ranges)) = highlights {
20737 ranges
20738 .iter()
20739 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20740 .collect_vec()
20741 } else {
20742 vec![]
20743 }
20744 }
20745
20746 fn document_highlights_for_position<'a>(
20747 &'a self,
20748 position: Anchor,
20749 buffer: &'a MultiBufferSnapshot,
20750 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20751 let read_highlights = self
20752 .background_highlights
20753 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20754 .map(|h| &h.1);
20755 let write_highlights = self
20756 .background_highlights
20757 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20758 .map(|h| &h.1);
20759 let left_position = position.bias_left(buffer);
20760 let right_position = position.bias_right(buffer);
20761 read_highlights
20762 .into_iter()
20763 .chain(write_highlights)
20764 .flat_map(move |ranges| {
20765 let start_ix = match ranges.binary_search_by(|probe| {
20766 let cmp = probe.end.cmp(&left_position, buffer);
20767 if cmp.is_ge() {
20768 Ordering::Greater
20769 } else {
20770 Ordering::Less
20771 }
20772 }) {
20773 Ok(i) | Err(i) => i,
20774 };
20775
20776 ranges[start_ix..]
20777 .iter()
20778 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20779 })
20780 }
20781
20782 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20783 self.background_highlights
20784 .get(&HighlightKey::Type(TypeId::of::<T>()))
20785 .is_some_and(|(_, highlights)| !highlights.is_empty())
20786 }
20787
20788 /// Returns all background highlights for a given range.
20789 ///
20790 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20791 pub fn background_highlights_in_range(
20792 &self,
20793 search_range: Range<Anchor>,
20794 display_snapshot: &DisplaySnapshot,
20795 theme: &Theme,
20796 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20797 let mut results = Vec::new();
20798 for (color_fetcher, ranges) in self.background_highlights.values() {
20799 let color = color_fetcher(theme);
20800 let start_ix = match ranges.binary_search_by(|probe| {
20801 let cmp = probe
20802 .end
20803 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20804 if cmp.is_gt() {
20805 Ordering::Greater
20806 } else {
20807 Ordering::Less
20808 }
20809 }) {
20810 Ok(i) | Err(i) => i,
20811 };
20812 for range in &ranges[start_ix..] {
20813 if range
20814 .start
20815 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20816 .is_ge()
20817 {
20818 break;
20819 }
20820
20821 let start = range.start.to_display_point(display_snapshot);
20822 let end = range.end.to_display_point(display_snapshot);
20823 results.push((start..end, color))
20824 }
20825 }
20826 results
20827 }
20828
20829 pub fn gutter_highlights_in_range(
20830 &self,
20831 search_range: Range<Anchor>,
20832 display_snapshot: &DisplaySnapshot,
20833 cx: &App,
20834 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20835 let mut results = Vec::new();
20836 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20837 let color = color_fetcher(cx);
20838 let start_ix = match ranges.binary_search_by(|probe| {
20839 let cmp = probe
20840 .end
20841 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20842 if cmp.is_gt() {
20843 Ordering::Greater
20844 } else {
20845 Ordering::Less
20846 }
20847 }) {
20848 Ok(i) | Err(i) => i,
20849 };
20850 for range in &ranges[start_ix..] {
20851 if range
20852 .start
20853 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20854 .is_ge()
20855 {
20856 break;
20857 }
20858
20859 let start = range.start.to_display_point(display_snapshot);
20860 let end = range.end.to_display_point(display_snapshot);
20861 results.push((start..end, color))
20862 }
20863 }
20864 results
20865 }
20866
20867 /// Get the text ranges corresponding to the redaction query
20868 pub fn redacted_ranges(
20869 &self,
20870 search_range: Range<Anchor>,
20871 display_snapshot: &DisplaySnapshot,
20872 cx: &App,
20873 ) -> Vec<Range<DisplayPoint>> {
20874 display_snapshot
20875 .buffer_snapshot()
20876 .redacted_ranges(search_range, |file| {
20877 if let Some(file) = file {
20878 file.is_private()
20879 && EditorSettings::get(
20880 Some(SettingsLocation {
20881 worktree_id: file.worktree_id(cx),
20882 path: file.path().as_ref(),
20883 }),
20884 cx,
20885 )
20886 .redact_private_values
20887 } else {
20888 false
20889 }
20890 })
20891 .map(|range| {
20892 range.start.to_display_point(display_snapshot)
20893 ..range.end.to_display_point(display_snapshot)
20894 })
20895 .collect()
20896 }
20897
20898 pub fn highlight_text_key<T: 'static>(
20899 &mut self,
20900 key: usize,
20901 ranges: Vec<Range<Anchor>>,
20902 style: HighlightStyle,
20903 cx: &mut Context<Self>,
20904 ) {
20905 self.display_map.update(cx, |map, _| {
20906 map.highlight_text(
20907 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20908 ranges,
20909 style,
20910 );
20911 });
20912 cx.notify();
20913 }
20914
20915 pub fn highlight_text<T: 'static>(
20916 &mut self,
20917 ranges: Vec<Range<Anchor>>,
20918 style: HighlightStyle,
20919 cx: &mut Context<Self>,
20920 ) {
20921 self.display_map.update(cx, |map, _| {
20922 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20923 });
20924 cx.notify();
20925 }
20926
20927 pub fn text_highlights<'a, T: 'static>(
20928 &'a self,
20929 cx: &'a App,
20930 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20931 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20932 }
20933
20934 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20935 let cleared = self
20936 .display_map
20937 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20938 if cleared {
20939 cx.notify();
20940 }
20941 }
20942
20943 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20944 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20945 && self.focus_handle.is_focused(window)
20946 }
20947
20948 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20949 self.show_cursor_when_unfocused = is_enabled;
20950 cx.notify();
20951 }
20952
20953 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20954 cx.notify();
20955 }
20956
20957 fn on_debug_session_event(
20958 &mut self,
20959 _session: Entity<Session>,
20960 event: &SessionEvent,
20961 cx: &mut Context<Self>,
20962 ) {
20963 if let SessionEvent::InvalidateInlineValue = event {
20964 self.refresh_inline_values(cx);
20965 }
20966 }
20967
20968 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20969 let Some(project) = self.project.clone() else {
20970 return;
20971 };
20972
20973 if !self.inline_value_cache.enabled {
20974 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20975 self.splice_inlays(&inlays, Vec::new(), cx);
20976 return;
20977 }
20978
20979 let current_execution_position = self
20980 .highlighted_rows
20981 .get(&TypeId::of::<ActiveDebugLine>())
20982 .and_then(|lines| lines.last().map(|line| line.range.end));
20983
20984 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20985 let inline_values = editor
20986 .update(cx, |editor, cx| {
20987 let Some(current_execution_position) = current_execution_position else {
20988 return Some(Task::ready(Ok(Vec::new())));
20989 };
20990
20991 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20992 let snapshot = buffer.snapshot(cx);
20993
20994 let excerpt = snapshot.excerpt_containing(
20995 current_execution_position..current_execution_position,
20996 )?;
20997
20998 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20999 })?;
21000
21001 let range =
21002 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21003
21004 project.inline_values(buffer, range, cx)
21005 })
21006 .ok()
21007 .flatten()?
21008 .await
21009 .context("refreshing debugger inlays")
21010 .log_err()?;
21011
21012 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21013
21014 for (buffer_id, inline_value) in inline_values
21015 .into_iter()
21016 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21017 {
21018 buffer_inline_values
21019 .entry(buffer_id)
21020 .or_default()
21021 .push(inline_value);
21022 }
21023
21024 editor
21025 .update(cx, |editor, cx| {
21026 let snapshot = editor.buffer.read(cx).snapshot(cx);
21027 let mut new_inlays = Vec::default();
21028
21029 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21030 let buffer_id = buffer_snapshot.remote_id();
21031 buffer_inline_values
21032 .get(&buffer_id)
21033 .into_iter()
21034 .flatten()
21035 .for_each(|hint| {
21036 let inlay = Inlay::debugger(
21037 post_inc(&mut editor.next_inlay_id),
21038 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21039 hint.text(),
21040 );
21041 if !inlay.text().chars().contains(&'\n') {
21042 new_inlays.push(inlay);
21043 }
21044 });
21045 }
21046
21047 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21048 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21049
21050 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21051 })
21052 .ok()?;
21053 Some(())
21054 });
21055 }
21056
21057 fn on_buffer_event(
21058 &mut self,
21059 multibuffer: &Entity<MultiBuffer>,
21060 event: &multi_buffer::Event,
21061 window: &mut Window,
21062 cx: &mut Context<Self>,
21063 ) {
21064 match event {
21065 multi_buffer::Event::Edited { edited_buffer } => {
21066 self.scrollbar_marker_state.dirty = true;
21067 self.active_indent_guides_state.dirty = true;
21068 self.refresh_active_diagnostics(cx);
21069 self.refresh_code_actions(window, cx);
21070 self.refresh_selected_text_highlights(true, window, cx);
21071 self.refresh_single_line_folds(window, cx);
21072 self.refresh_matching_bracket_highlights(window, cx);
21073 if self.has_active_edit_prediction() {
21074 self.update_visible_edit_prediction(window, cx);
21075 }
21076
21077 if let Some(buffer) = edited_buffer {
21078 if buffer.read(cx).file().is_none() {
21079 cx.emit(EditorEvent::TitleChanged);
21080 }
21081
21082 if self.project.is_some() {
21083 let buffer_id = buffer.read(cx).remote_id();
21084 self.register_buffer(buffer_id, cx);
21085 self.update_lsp_data(Some(buffer_id), window, cx);
21086 self.refresh_inlay_hints(
21087 InlayHintRefreshReason::BufferEdited(buffer_id),
21088 cx,
21089 );
21090 }
21091 }
21092
21093 cx.emit(EditorEvent::BufferEdited);
21094 cx.emit(SearchEvent::MatchesInvalidated);
21095
21096 let Some(project) = &self.project else { return };
21097 let (telemetry, is_via_ssh) = {
21098 let project = project.read(cx);
21099 let telemetry = project.client().telemetry().clone();
21100 let is_via_ssh = project.is_via_remote_server();
21101 (telemetry, is_via_ssh)
21102 };
21103 telemetry.log_edit_event("editor", is_via_ssh);
21104 }
21105 multi_buffer::Event::ExcerptsAdded {
21106 buffer,
21107 predecessor,
21108 excerpts,
21109 } => {
21110 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21111 let buffer_id = buffer.read(cx).remote_id();
21112 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21113 && let Some(project) = &self.project
21114 {
21115 update_uncommitted_diff_for_buffer(
21116 cx.entity(),
21117 project,
21118 [buffer.clone()],
21119 self.buffer.clone(),
21120 cx,
21121 )
21122 .detach();
21123 }
21124 self.update_lsp_data(Some(buffer_id), window, cx);
21125 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21126 cx.emit(EditorEvent::ExcerptsAdded {
21127 buffer: buffer.clone(),
21128 predecessor: *predecessor,
21129 excerpts: excerpts.clone(),
21130 });
21131 }
21132 multi_buffer::Event::ExcerptsRemoved {
21133 ids,
21134 removed_buffer_ids,
21135 } => {
21136 if let Some(inlay_hints) = &mut self.inlay_hints {
21137 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21138 }
21139 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21140 for buffer_id in removed_buffer_ids {
21141 self.registered_buffers.remove(buffer_id);
21142 }
21143 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21144 cx.emit(EditorEvent::ExcerptsRemoved {
21145 ids: ids.clone(),
21146 removed_buffer_ids: removed_buffer_ids.clone(),
21147 });
21148 }
21149 multi_buffer::Event::ExcerptsEdited {
21150 excerpt_ids,
21151 buffer_ids,
21152 } => {
21153 self.display_map.update(cx, |map, cx| {
21154 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21155 });
21156 cx.emit(EditorEvent::ExcerptsEdited {
21157 ids: excerpt_ids.clone(),
21158 });
21159 }
21160 multi_buffer::Event::ExcerptsExpanded { ids } => {
21161 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21162 self.refresh_document_highlights(cx);
21163 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21164 }
21165 multi_buffer::Event::Reparsed(buffer_id) => {
21166 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21167 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21168
21169 cx.emit(EditorEvent::Reparsed(*buffer_id));
21170 }
21171 multi_buffer::Event::DiffHunksToggled => {
21172 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21173 }
21174 multi_buffer::Event::LanguageChanged(buffer_id) => {
21175 self.registered_buffers.remove(&buffer_id);
21176 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21177 cx.emit(EditorEvent::Reparsed(*buffer_id));
21178 cx.notify();
21179 }
21180 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21181 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21182 multi_buffer::Event::FileHandleChanged
21183 | multi_buffer::Event::Reloaded
21184 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21185 multi_buffer::Event::DiagnosticsUpdated => {
21186 self.update_diagnostics_state(window, cx);
21187 }
21188 _ => {}
21189 };
21190 }
21191
21192 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21193 if !self.diagnostics_enabled() {
21194 return;
21195 }
21196 self.refresh_active_diagnostics(cx);
21197 self.refresh_inline_diagnostics(true, window, cx);
21198 self.scrollbar_marker_state.dirty = true;
21199 cx.notify();
21200 }
21201
21202 pub fn start_temporary_diff_override(&mut self) {
21203 self.load_diff_task.take();
21204 self.temporary_diff_override = true;
21205 }
21206
21207 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21208 self.temporary_diff_override = false;
21209 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21210 self.buffer.update(cx, |buffer, cx| {
21211 buffer.set_all_diff_hunks_collapsed(cx);
21212 });
21213
21214 if let Some(project) = self.project.clone() {
21215 self.load_diff_task = Some(
21216 update_uncommitted_diff_for_buffer(
21217 cx.entity(),
21218 &project,
21219 self.buffer.read(cx).all_buffers(),
21220 self.buffer.clone(),
21221 cx,
21222 )
21223 .shared(),
21224 );
21225 }
21226 }
21227
21228 fn on_display_map_changed(
21229 &mut self,
21230 _: Entity<DisplayMap>,
21231 _: &mut Window,
21232 cx: &mut Context<Self>,
21233 ) {
21234 cx.notify();
21235 }
21236
21237 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21238 if self.diagnostics_enabled() {
21239 let new_severity = EditorSettings::get_global(cx)
21240 .diagnostics_max_severity
21241 .unwrap_or(DiagnosticSeverity::Hint);
21242 self.set_max_diagnostics_severity(new_severity, cx);
21243 }
21244 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21245 self.update_edit_prediction_settings(cx);
21246 self.refresh_edit_prediction(true, false, window, cx);
21247 self.refresh_inline_values(cx);
21248 self.refresh_inlay_hints(
21249 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21250 self.selections.newest_anchor().head(),
21251 &self.buffer.read(cx).snapshot(cx),
21252 cx,
21253 )),
21254 cx,
21255 );
21256
21257 let old_cursor_shape = self.cursor_shape;
21258 let old_show_breadcrumbs = self.show_breadcrumbs;
21259
21260 {
21261 let editor_settings = EditorSettings::get_global(cx);
21262 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21263 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21264 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21265 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21266 }
21267
21268 if old_cursor_shape != self.cursor_shape {
21269 cx.emit(EditorEvent::CursorShapeChanged);
21270 }
21271
21272 if old_show_breadcrumbs != self.show_breadcrumbs {
21273 cx.emit(EditorEvent::BreadcrumbsChanged);
21274 }
21275
21276 let project_settings = ProjectSettings::get_global(cx);
21277 self.buffer_serialization = self
21278 .should_serialize_buffer()
21279 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21280
21281 if self.mode.is_full() {
21282 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21283 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21284 if self.show_inline_diagnostics != show_inline_diagnostics {
21285 self.show_inline_diagnostics = show_inline_diagnostics;
21286 self.refresh_inline_diagnostics(false, window, cx);
21287 }
21288
21289 if self.git_blame_inline_enabled != inline_blame_enabled {
21290 self.toggle_git_blame_inline_internal(false, window, cx);
21291 }
21292
21293 let minimap_settings = EditorSettings::get_global(cx).minimap;
21294 if self.minimap_visibility != MinimapVisibility::Disabled {
21295 if self.minimap_visibility.settings_visibility()
21296 != minimap_settings.minimap_enabled()
21297 {
21298 self.set_minimap_visibility(
21299 MinimapVisibility::for_mode(self.mode(), cx),
21300 window,
21301 cx,
21302 );
21303 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21304 minimap_entity.update(cx, |minimap_editor, cx| {
21305 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21306 })
21307 }
21308 }
21309 }
21310
21311 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21312 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21313 }) {
21314 if !inlay_splice.is_empty() {
21315 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21316 }
21317 self.refresh_colors_for_visible_range(None, window, cx);
21318 }
21319
21320 cx.notify();
21321 }
21322
21323 pub fn set_searchable(&mut self, searchable: bool) {
21324 self.searchable = searchable;
21325 }
21326
21327 pub fn searchable(&self) -> bool {
21328 self.searchable
21329 }
21330
21331 pub fn open_excerpts_in_split(
21332 &mut self,
21333 _: &OpenExcerptsSplit,
21334 window: &mut Window,
21335 cx: &mut Context<Self>,
21336 ) {
21337 self.open_excerpts_common(None, true, window, cx)
21338 }
21339
21340 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21341 self.open_excerpts_common(None, false, window, cx)
21342 }
21343
21344 fn open_excerpts_common(
21345 &mut self,
21346 jump_data: Option<JumpData>,
21347 split: bool,
21348 window: &mut Window,
21349 cx: &mut Context<Self>,
21350 ) {
21351 let Some(workspace) = self.workspace() else {
21352 cx.propagate();
21353 return;
21354 };
21355
21356 if self.buffer.read(cx).is_singleton() {
21357 cx.propagate();
21358 return;
21359 }
21360
21361 let mut new_selections_by_buffer = HashMap::default();
21362 match &jump_data {
21363 Some(JumpData::MultiBufferPoint {
21364 excerpt_id,
21365 position,
21366 anchor,
21367 line_offset_from_top,
21368 }) => {
21369 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21370 if let Some(buffer) = multi_buffer_snapshot
21371 .buffer_id_for_excerpt(*excerpt_id)
21372 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21373 {
21374 let buffer_snapshot = buffer.read(cx).snapshot();
21375 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21376 language::ToPoint::to_point(anchor, &buffer_snapshot)
21377 } else {
21378 buffer_snapshot.clip_point(*position, Bias::Left)
21379 };
21380 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21381 new_selections_by_buffer.insert(
21382 buffer,
21383 (
21384 vec![jump_to_offset..jump_to_offset],
21385 Some(*line_offset_from_top),
21386 ),
21387 );
21388 }
21389 }
21390 Some(JumpData::MultiBufferRow {
21391 row,
21392 line_offset_from_top,
21393 }) => {
21394 let point = MultiBufferPoint::new(row.0, 0);
21395 if let Some((buffer, buffer_point, _)) =
21396 self.buffer.read(cx).point_to_buffer_point(point, cx)
21397 {
21398 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21399 new_selections_by_buffer
21400 .entry(buffer)
21401 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21402 .0
21403 .push(buffer_offset..buffer_offset)
21404 }
21405 }
21406 None => {
21407 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21408 let multi_buffer = self.buffer.read(cx);
21409 for selection in selections {
21410 for (snapshot, range, _, anchor) in multi_buffer
21411 .snapshot(cx)
21412 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21413 {
21414 if let Some(anchor) = anchor {
21415 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21416 else {
21417 continue;
21418 };
21419 let offset = text::ToOffset::to_offset(
21420 &anchor.text_anchor,
21421 &buffer_handle.read(cx).snapshot(),
21422 );
21423 let range = offset..offset;
21424 new_selections_by_buffer
21425 .entry(buffer_handle)
21426 .or_insert((Vec::new(), None))
21427 .0
21428 .push(range)
21429 } else {
21430 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21431 else {
21432 continue;
21433 };
21434 new_selections_by_buffer
21435 .entry(buffer_handle)
21436 .or_insert((Vec::new(), None))
21437 .0
21438 .push(range)
21439 }
21440 }
21441 }
21442 }
21443 }
21444
21445 new_selections_by_buffer
21446 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21447
21448 if new_selections_by_buffer.is_empty() {
21449 return;
21450 }
21451
21452 // We defer the pane interaction because we ourselves are a workspace item
21453 // and activating a new item causes the pane to call a method on us reentrantly,
21454 // which panics if we're on the stack.
21455 window.defer(cx, move |window, cx| {
21456 workspace.update(cx, |workspace, cx| {
21457 let pane = if split {
21458 workspace.adjacent_pane(window, cx)
21459 } else {
21460 workspace.active_pane().clone()
21461 };
21462
21463 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21464 let editor = buffer
21465 .read(cx)
21466 .file()
21467 .is_none()
21468 .then(|| {
21469 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21470 // so `workspace.open_project_item` will never find them, always opening a new editor.
21471 // Instead, we try to activate the existing editor in the pane first.
21472 let (editor, pane_item_index) =
21473 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21474 let editor = item.downcast::<Editor>()?;
21475 let singleton_buffer =
21476 editor.read(cx).buffer().read(cx).as_singleton()?;
21477 if singleton_buffer == buffer {
21478 Some((editor, i))
21479 } else {
21480 None
21481 }
21482 })?;
21483 pane.update(cx, |pane, cx| {
21484 pane.activate_item(pane_item_index, true, true, window, cx)
21485 });
21486 Some(editor)
21487 })
21488 .flatten()
21489 .unwrap_or_else(|| {
21490 workspace.open_project_item::<Self>(
21491 pane.clone(),
21492 buffer,
21493 true,
21494 true,
21495 window,
21496 cx,
21497 )
21498 });
21499
21500 editor.update(cx, |editor, cx| {
21501 let autoscroll = match scroll_offset {
21502 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21503 None => Autoscroll::newest(),
21504 };
21505 let nav_history = editor.nav_history.take();
21506 editor.change_selections(
21507 SelectionEffects::scroll(autoscroll),
21508 window,
21509 cx,
21510 |s| {
21511 s.select_ranges(ranges);
21512 },
21513 );
21514 editor.nav_history = nav_history;
21515 });
21516 }
21517 })
21518 });
21519 }
21520
21521 // For now, don't allow opening excerpts in buffers that aren't backed by
21522 // regular project files.
21523 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21524 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21525 }
21526
21527 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21528 let snapshot = self.buffer.read(cx).read(cx);
21529 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21530 Some(
21531 ranges
21532 .iter()
21533 .map(move |range| {
21534 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21535 })
21536 .collect(),
21537 )
21538 }
21539
21540 fn selection_replacement_ranges(
21541 &self,
21542 range: Range<OffsetUtf16>,
21543 cx: &mut App,
21544 ) -> Vec<Range<OffsetUtf16>> {
21545 let selections = self
21546 .selections
21547 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21548 let newest_selection = selections
21549 .iter()
21550 .max_by_key(|selection| selection.id)
21551 .unwrap();
21552 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21553 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21554 let snapshot = self.buffer.read(cx).read(cx);
21555 selections
21556 .into_iter()
21557 .map(|mut selection| {
21558 selection.start.0 =
21559 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21560 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21561 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21562 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21563 })
21564 .collect()
21565 }
21566
21567 fn report_editor_event(
21568 &self,
21569 reported_event: ReportEditorEvent,
21570 file_extension: Option<String>,
21571 cx: &App,
21572 ) {
21573 if cfg!(any(test, feature = "test-support")) {
21574 return;
21575 }
21576
21577 let Some(project) = &self.project else { return };
21578
21579 // If None, we are in a file without an extension
21580 let file = self
21581 .buffer
21582 .read(cx)
21583 .as_singleton()
21584 .and_then(|b| b.read(cx).file());
21585 let file_extension = file_extension.or(file
21586 .as_ref()
21587 .and_then(|file| Path::new(file.file_name(cx)).extension())
21588 .and_then(|e| e.to_str())
21589 .map(|a| a.to_string()));
21590
21591 let vim_mode = vim_flavor(cx).is_some();
21592
21593 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21594 let copilot_enabled = edit_predictions_provider
21595 == language::language_settings::EditPredictionProvider::Copilot;
21596 let copilot_enabled_for_language = self
21597 .buffer
21598 .read(cx)
21599 .language_settings(cx)
21600 .show_edit_predictions;
21601
21602 let project = project.read(cx);
21603 let event_type = reported_event.event_type();
21604
21605 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21606 telemetry::event!(
21607 event_type,
21608 type = if auto_saved {"autosave"} else {"manual"},
21609 file_extension,
21610 vim_mode,
21611 copilot_enabled,
21612 copilot_enabled_for_language,
21613 edit_predictions_provider,
21614 is_via_ssh = project.is_via_remote_server(),
21615 );
21616 } else {
21617 telemetry::event!(
21618 event_type,
21619 file_extension,
21620 vim_mode,
21621 copilot_enabled,
21622 copilot_enabled_for_language,
21623 edit_predictions_provider,
21624 is_via_ssh = project.is_via_remote_server(),
21625 );
21626 };
21627 }
21628
21629 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21630 /// with each line being an array of {text, highlight} objects.
21631 fn copy_highlight_json(
21632 &mut self,
21633 _: &CopyHighlightJson,
21634 window: &mut Window,
21635 cx: &mut Context<Self>,
21636 ) {
21637 #[derive(Serialize)]
21638 struct Chunk<'a> {
21639 text: String,
21640 highlight: Option<&'a str>,
21641 }
21642
21643 let snapshot = self.buffer.read(cx).snapshot(cx);
21644 let range = self
21645 .selected_text_range(false, window, cx)
21646 .and_then(|selection| {
21647 if selection.range.is_empty() {
21648 None
21649 } else {
21650 Some(
21651 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21652 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21653 )
21654 }
21655 })
21656 .unwrap_or_else(|| 0..snapshot.len());
21657
21658 let chunks = snapshot.chunks(range, true);
21659 let mut lines = Vec::new();
21660 let mut line: VecDeque<Chunk> = VecDeque::new();
21661
21662 let Some(style) = self.style.as_ref() else {
21663 return;
21664 };
21665
21666 for chunk in chunks {
21667 let highlight = chunk
21668 .syntax_highlight_id
21669 .and_then(|id| id.name(&style.syntax));
21670 let mut chunk_lines = chunk.text.split('\n').peekable();
21671 while let Some(text) = chunk_lines.next() {
21672 let mut merged_with_last_token = false;
21673 if let Some(last_token) = line.back_mut()
21674 && last_token.highlight == highlight
21675 {
21676 last_token.text.push_str(text);
21677 merged_with_last_token = true;
21678 }
21679
21680 if !merged_with_last_token {
21681 line.push_back(Chunk {
21682 text: text.into(),
21683 highlight,
21684 });
21685 }
21686
21687 if chunk_lines.peek().is_some() {
21688 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21689 line.pop_front();
21690 }
21691 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21692 line.pop_back();
21693 }
21694
21695 lines.push(mem::take(&mut line));
21696 }
21697 }
21698 }
21699
21700 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21701 return;
21702 };
21703 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21704 }
21705
21706 pub fn open_context_menu(
21707 &mut self,
21708 _: &OpenContextMenu,
21709 window: &mut Window,
21710 cx: &mut Context<Self>,
21711 ) {
21712 self.request_autoscroll(Autoscroll::newest(), cx);
21713 let position = self
21714 .selections
21715 .newest_display(&self.display_snapshot(cx))
21716 .start;
21717 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21718 }
21719
21720 pub fn replay_insert_event(
21721 &mut self,
21722 text: &str,
21723 relative_utf16_range: Option<Range<isize>>,
21724 window: &mut Window,
21725 cx: &mut Context<Self>,
21726 ) {
21727 if !self.input_enabled {
21728 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21729 return;
21730 }
21731 if let Some(relative_utf16_range) = relative_utf16_range {
21732 let selections = self
21733 .selections
21734 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21735 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21736 let new_ranges = selections.into_iter().map(|range| {
21737 let start = OffsetUtf16(
21738 range
21739 .head()
21740 .0
21741 .saturating_add_signed(relative_utf16_range.start),
21742 );
21743 let end = OffsetUtf16(
21744 range
21745 .head()
21746 .0
21747 .saturating_add_signed(relative_utf16_range.end),
21748 );
21749 start..end
21750 });
21751 s.select_ranges(new_ranges);
21752 });
21753 }
21754
21755 self.handle_input(text, window, cx);
21756 }
21757
21758 pub fn is_focused(&self, window: &Window) -> bool {
21759 self.focus_handle.is_focused(window)
21760 }
21761
21762 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21763 cx.emit(EditorEvent::Focused);
21764
21765 if let Some(descendant) = self
21766 .last_focused_descendant
21767 .take()
21768 .and_then(|descendant| descendant.upgrade())
21769 {
21770 window.focus(&descendant);
21771 } else {
21772 if let Some(blame) = self.blame.as_ref() {
21773 blame.update(cx, GitBlame::focus)
21774 }
21775
21776 self.blink_manager.update(cx, BlinkManager::enable);
21777 self.show_cursor_names(window, cx);
21778 self.buffer.update(cx, |buffer, cx| {
21779 buffer.finalize_last_transaction(cx);
21780 if self.leader_id.is_none() {
21781 buffer.set_active_selections(
21782 &self.selections.disjoint_anchors_arc(),
21783 self.selections.line_mode(),
21784 self.cursor_shape,
21785 cx,
21786 );
21787 }
21788 });
21789 }
21790 }
21791
21792 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21793 cx.emit(EditorEvent::FocusedIn)
21794 }
21795
21796 fn handle_focus_out(
21797 &mut self,
21798 event: FocusOutEvent,
21799 _window: &mut Window,
21800 cx: &mut Context<Self>,
21801 ) {
21802 if event.blurred != self.focus_handle {
21803 self.last_focused_descendant = Some(event.blurred);
21804 }
21805 self.selection_drag_state = SelectionDragState::None;
21806 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21807 }
21808
21809 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21810 self.blink_manager.update(cx, BlinkManager::disable);
21811 self.buffer
21812 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21813
21814 if let Some(blame) = self.blame.as_ref() {
21815 blame.update(cx, GitBlame::blur)
21816 }
21817 if !self.hover_state.focused(window, cx) {
21818 hide_hover(self, cx);
21819 }
21820 if !self
21821 .context_menu
21822 .borrow()
21823 .as_ref()
21824 .is_some_and(|context_menu| context_menu.focused(window, cx))
21825 {
21826 self.hide_context_menu(window, cx);
21827 }
21828 self.take_active_edit_prediction(cx);
21829 cx.emit(EditorEvent::Blurred);
21830 cx.notify();
21831 }
21832
21833 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21834 let mut pending: String = window
21835 .pending_input_keystrokes()
21836 .into_iter()
21837 .flatten()
21838 .filter_map(|keystroke| {
21839 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21840 keystroke.key_char.clone()
21841 } else {
21842 None
21843 }
21844 })
21845 .collect();
21846
21847 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21848 pending = "".to_string();
21849 }
21850
21851 let existing_pending = self
21852 .text_highlights::<PendingInput>(cx)
21853 .map(|(_, ranges)| ranges.to_vec());
21854 if existing_pending.is_none() && pending.is_empty() {
21855 return;
21856 }
21857 let transaction =
21858 self.transact(window, cx, |this, window, cx| {
21859 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21860 let edits = selections
21861 .iter()
21862 .map(|selection| (selection.end..selection.end, pending.clone()));
21863 this.edit(edits, cx);
21864 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21865 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21866 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21867 }));
21868 });
21869 if let Some(existing_ranges) = existing_pending {
21870 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21871 this.edit(edits, cx);
21872 }
21873 });
21874
21875 let snapshot = self.snapshot(window, cx);
21876 let ranges = self
21877 .selections
21878 .all::<usize>(&snapshot.display_snapshot)
21879 .into_iter()
21880 .map(|selection| {
21881 snapshot.buffer_snapshot().anchor_after(selection.end)
21882 ..snapshot
21883 .buffer_snapshot()
21884 .anchor_before(selection.end + pending.len())
21885 })
21886 .collect();
21887
21888 if pending.is_empty() {
21889 self.clear_highlights::<PendingInput>(cx);
21890 } else {
21891 self.highlight_text::<PendingInput>(
21892 ranges,
21893 HighlightStyle {
21894 underline: Some(UnderlineStyle {
21895 thickness: px(1.),
21896 color: None,
21897 wavy: false,
21898 }),
21899 ..Default::default()
21900 },
21901 cx,
21902 );
21903 }
21904
21905 self.ime_transaction = self.ime_transaction.or(transaction);
21906 if let Some(transaction) = self.ime_transaction {
21907 self.buffer.update(cx, |buffer, cx| {
21908 buffer.group_until_transaction(transaction, cx);
21909 });
21910 }
21911
21912 if self.text_highlights::<PendingInput>(cx).is_none() {
21913 self.ime_transaction.take();
21914 }
21915 }
21916
21917 pub fn register_action_renderer(
21918 &mut self,
21919 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21920 ) -> Subscription {
21921 let id = self.next_editor_action_id.post_inc();
21922 self.editor_actions
21923 .borrow_mut()
21924 .insert(id, Box::new(listener));
21925
21926 let editor_actions = self.editor_actions.clone();
21927 Subscription::new(move || {
21928 editor_actions.borrow_mut().remove(&id);
21929 })
21930 }
21931
21932 pub fn register_action<A: Action>(
21933 &mut self,
21934 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21935 ) -> Subscription {
21936 let id = self.next_editor_action_id.post_inc();
21937 let listener = Arc::new(listener);
21938 self.editor_actions.borrow_mut().insert(
21939 id,
21940 Box::new(move |_, window, _| {
21941 let listener = listener.clone();
21942 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21943 let action = action.downcast_ref().unwrap();
21944 if phase == DispatchPhase::Bubble {
21945 listener(action, window, cx)
21946 }
21947 })
21948 }),
21949 );
21950
21951 let editor_actions = self.editor_actions.clone();
21952 Subscription::new(move || {
21953 editor_actions.borrow_mut().remove(&id);
21954 })
21955 }
21956
21957 pub fn file_header_size(&self) -> u32 {
21958 FILE_HEADER_HEIGHT
21959 }
21960
21961 pub fn restore(
21962 &mut self,
21963 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21964 window: &mut Window,
21965 cx: &mut Context<Self>,
21966 ) {
21967 let workspace = self.workspace();
21968 let project = self.project();
21969 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21970 let mut tasks = Vec::new();
21971 for (buffer_id, changes) in revert_changes {
21972 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21973 buffer.update(cx, |buffer, cx| {
21974 buffer.edit(
21975 changes
21976 .into_iter()
21977 .map(|(range, text)| (range, text.to_string())),
21978 None,
21979 cx,
21980 );
21981 });
21982
21983 if let Some(project) =
21984 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21985 {
21986 project.update(cx, |project, cx| {
21987 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21988 })
21989 }
21990 }
21991 }
21992 tasks
21993 });
21994 cx.spawn_in(window, async move |_, cx| {
21995 for (buffer, task) in save_tasks {
21996 let result = task.await;
21997 if result.is_err() {
21998 let Some(path) = buffer
21999 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22000 .ok()
22001 else {
22002 continue;
22003 };
22004 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22005 let Some(task) = cx
22006 .update_window_entity(workspace, |workspace, window, cx| {
22007 workspace
22008 .open_path_preview(path, None, false, false, false, window, cx)
22009 })
22010 .ok()
22011 else {
22012 continue;
22013 };
22014 task.await.log_err();
22015 }
22016 }
22017 }
22018 })
22019 .detach();
22020 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22021 selections.refresh()
22022 });
22023 }
22024
22025 pub fn to_pixel_point(
22026 &self,
22027 source: multi_buffer::Anchor,
22028 editor_snapshot: &EditorSnapshot,
22029 window: &mut Window,
22030 ) -> Option<gpui::Point<Pixels>> {
22031 let source_point = source.to_display_point(editor_snapshot);
22032 self.display_to_pixel_point(source_point, editor_snapshot, window)
22033 }
22034
22035 pub fn display_to_pixel_point(
22036 &self,
22037 source: DisplayPoint,
22038 editor_snapshot: &EditorSnapshot,
22039 window: &mut Window,
22040 ) -> Option<gpui::Point<Pixels>> {
22041 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22042 let text_layout_details = self.text_layout_details(window);
22043 let scroll_top = text_layout_details
22044 .scroll_anchor
22045 .scroll_position(editor_snapshot)
22046 .y;
22047
22048 if source.row().as_f64() < scroll_top.floor() {
22049 return None;
22050 }
22051 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22052 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22053 Some(gpui::Point::new(source_x, source_y))
22054 }
22055
22056 pub fn has_visible_completions_menu(&self) -> bool {
22057 !self.edit_prediction_preview_is_active()
22058 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22059 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22060 })
22061 }
22062
22063 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22064 if self.mode.is_minimap() {
22065 return;
22066 }
22067 self.addons
22068 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22069 }
22070
22071 pub fn unregister_addon<T: Addon>(&mut self) {
22072 self.addons.remove(&std::any::TypeId::of::<T>());
22073 }
22074
22075 pub fn addon<T: Addon>(&self) -> Option<&T> {
22076 let type_id = std::any::TypeId::of::<T>();
22077 self.addons
22078 .get(&type_id)
22079 .and_then(|item| item.to_any().downcast_ref::<T>())
22080 }
22081
22082 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22083 let type_id = std::any::TypeId::of::<T>();
22084 self.addons
22085 .get_mut(&type_id)
22086 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22087 }
22088
22089 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22090 let text_layout_details = self.text_layout_details(window);
22091 let style = &text_layout_details.editor_style;
22092 let font_id = window.text_system().resolve_font(&style.text.font());
22093 let font_size = style.text.font_size.to_pixels(window.rem_size());
22094 let line_height = style.text.line_height_in_pixels(window.rem_size());
22095 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22096 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22097
22098 CharacterDimensions {
22099 em_width,
22100 em_advance,
22101 line_height,
22102 }
22103 }
22104
22105 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22106 self.load_diff_task.clone()
22107 }
22108
22109 fn read_metadata_from_db(
22110 &mut self,
22111 item_id: u64,
22112 workspace_id: WorkspaceId,
22113 window: &mut Window,
22114 cx: &mut Context<Editor>,
22115 ) {
22116 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22117 && !self.mode.is_minimap()
22118 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22119 {
22120 let buffer_snapshot = OnceCell::new();
22121
22122 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22123 && !folds.is_empty()
22124 {
22125 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22126 self.fold_ranges(
22127 folds
22128 .into_iter()
22129 .map(|(start, end)| {
22130 snapshot.clip_offset(start, Bias::Left)
22131 ..snapshot.clip_offset(end, Bias::Right)
22132 })
22133 .collect(),
22134 false,
22135 window,
22136 cx,
22137 );
22138 }
22139
22140 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22141 && !selections.is_empty()
22142 {
22143 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22144 // skip adding the initial selection to selection history
22145 self.selection_history.mode = SelectionHistoryMode::Skipping;
22146 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22147 s.select_ranges(selections.into_iter().map(|(start, end)| {
22148 snapshot.clip_offset(start, Bias::Left)
22149 ..snapshot.clip_offset(end, Bias::Right)
22150 }));
22151 });
22152 self.selection_history.mode = SelectionHistoryMode::Normal;
22153 };
22154 }
22155
22156 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22157 }
22158
22159 fn update_lsp_data(
22160 &mut self,
22161 for_buffer: Option<BufferId>,
22162 window: &mut Window,
22163 cx: &mut Context<'_, Self>,
22164 ) {
22165 self.pull_diagnostics(for_buffer, window, cx);
22166 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22167 }
22168
22169 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22170 if self.ignore_lsp_data() {
22171 return;
22172 }
22173 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22174 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22175 }
22176 }
22177
22178 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22179 if self.ignore_lsp_data() {
22180 return;
22181 }
22182
22183 if !self.registered_buffers.contains_key(&buffer_id)
22184 && let Some(project) = self.project.as_ref()
22185 {
22186 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22187 project.update(cx, |project, cx| {
22188 self.registered_buffers.insert(
22189 buffer_id,
22190 project.register_buffer_with_language_servers(&buffer, cx),
22191 );
22192 });
22193 } else {
22194 self.registered_buffers.remove(&buffer_id);
22195 }
22196 }
22197 }
22198
22199 fn ignore_lsp_data(&self) -> bool {
22200 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22201 // skip any LSP updates for it.
22202 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22203 }
22204}
22205
22206fn edit_for_markdown_paste<'a>(
22207 buffer: &MultiBufferSnapshot,
22208 range: Range<usize>,
22209 to_insert: &'a str,
22210 url: Option<url::Url>,
22211) -> (Range<usize>, Cow<'a, str>) {
22212 if url.is_none() {
22213 return (range, Cow::Borrowed(to_insert));
22214 };
22215
22216 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22217
22218 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22219 Cow::Borrowed(to_insert)
22220 } else {
22221 Cow::Owned(format!("[{old_text}]({to_insert})"))
22222 };
22223 (range, new_text)
22224}
22225
22226#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22227pub enum VimFlavor {
22228 Vim,
22229 Helix,
22230}
22231
22232pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22233 if vim_mode_setting::HelixModeSetting::try_get(cx)
22234 .map(|helix_mode| helix_mode.0)
22235 .unwrap_or(false)
22236 {
22237 Some(VimFlavor::Helix)
22238 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22239 .map(|vim_mode| vim_mode.0)
22240 .unwrap_or(false)
22241 {
22242 Some(VimFlavor::Vim)
22243 } else {
22244 None // neither vim nor helix mode
22245 }
22246}
22247
22248fn process_completion_for_edit(
22249 completion: &Completion,
22250 intent: CompletionIntent,
22251 buffer: &Entity<Buffer>,
22252 cursor_position: &text::Anchor,
22253 cx: &mut Context<Editor>,
22254) -> CompletionEdit {
22255 let buffer = buffer.read(cx);
22256 let buffer_snapshot = buffer.snapshot();
22257 let (snippet, new_text) = if completion.is_snippet() {
22258 let mut snippet_source = completion.new_text.clone();
22259 // Workaround for typescript language server issues so that methods don't expand within
22260 // strings and functions with type expressions. The previous point is used because the query
22261 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22262 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22263 let previous_point = if previous_point.column > 0 {
22264 cursor_position.to_previous_offset(&buffer_snapshot)
22265 } else {
22266 cursor_position.to_offset(&buffer_snapshot)
22267 };
22268 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22269 && scope.prefers_label_for_snippet_in_completion()
22270 && let Some(label) = completion.label()
22271 && matches!(
22272 completion.kind(),
22273 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22274 )
22275 {
22276 snippet_source = label;
22277 }
22278 match Snippet::parse(&snippet_source).log_err() {
22279 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22280 None => (None, completion.new_text.clone()),
22281 }
22282 } else {
22283 (None, completion.new_text.clone())
22284 };
22285
22286 let mut range_to_replace = {
22287 let replace_range = &completion.replace_range;
22288 if let CompletionSource::Lsp {
22289 insert_range: Some(insert_range),
22290 ..
22291 } = &completion.source
22292 {
22293 debug_assert_eq!(
22294 insert_range.start, replace_range.start,
22295 "insert_range and replace_range should start at the same position"
22296 );
22297 debug_assert!(
22298 insert_range
22299 .start
22300 .cmp(cursor_position, &buffer_snapshot)
22301 .is_le(),
22302 "insert_range should start before or at cursor position"
22303 );
22304 debug_assert!(
22305 replace_range
22306 .start
22307 .cmp(cursor_position, &buffer_snapshot)
22308 .is_le(),
22309 "replace_range should start before or at cursor position"
22310 );
22311
22312 let should_replace = match intent {
22313 CompletionIntent::CompleteWithInsert => false,
22314 CompletionIntent::CompleteWithReplace => true,
22315 CompletionIntent::Complete | CompletionIntent::Compose => {
22316 let insert_mode =
22317 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22318 .completions
22319 .lsp_insert_mode;
22320 match insert_mode {
22321 LspInsertMode::Insert => false,
22322 LspInsertMode::Replace => true,
22323 LspInsertMode::ReplaceSubsequence => {
22324 let mut text_to_replace = buffer.chars_for_range(
22325 buffer.anchor_before(replace_range.start)
22326 ..buffer.anchor_after(replace_range.end),
22327 );
22328 let mut current_needle = text_to_replace.next();
22329 for haystack_ch in completion.label.text.chars() {
22330 if let Some(needle_ch) = current_needle
22331 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22332 {
22333 current_needle = text_to_replace.next();
22334 }
22335 }
22336 current_needle.is_none()
22337 }
22338 LspInsertMode::ReplaceSuffix => {
22339 if replace_range
22340 .end
22341 .cmp(cursor_position, &buffer_snapshot)
22342 .is_gt()
22343 {
22344 let range_after_cursor = *cursor_position..replace_range.end;
22345 let text_after_cursor = buffer
22346 .text_for_range(
22347 buffer.anchor_before(range_after_cursor.start)
22348 ..buffer.anchor_after(range_after_cursor.end),
22349 )
22350 .collect::<String>()
22351 .to_ascii_lowercase();
22352 completion
22353 .label
22354 .text
22355 .to_ascii_lowercase()
22356 .ends_with(&text_after_cursor)
22357 } else {
22358 true
22359 }
22360 }
22361 }
22362 }
22363 };
22364
22365 if should_replace {
22366 replace_range.clone()
22367 } else {
22368 insert_range.clone()
22369 }
22370 } else {
22371 replace_range.clone()
22372 }
22373 };
22374
22375 if range_to_replace
22376 .end
22377 .cmp(cursor_position, &buffer_snapshot)
22378 .is_lt()
22379 {
22380 range_to_replace.end = *cursor_position;
22381 }
22382
22383 CompletionEdit {
22384 new_text,
22385 replace_range: range_to_replace.to_offset(buffer),
22386 snippet,
22387 }
22388}
22389
22390struct CompletionEdit {
22391 new_text: String,
22392 replace_range: Range<usize>,
22393 snippet: Option<Snippet>,
22394}
22395
22396fn insert_extra_newline_brackets(
22397 buffer: &MultiBufferSnapshot,
22398 range: Range<usize>,
22399 language: &language::LanguageScope,
22400) -> bool {
22401 let leading_whitespace_len = buffer
22402 .reversed_chars_at(range.start)
22403 .take_while(|c| c.is_whitespace() && *c != '\n')
22404 .map(|c| c.len_utf8())
22405 .sum::<usize>();
22406 let trailing_whitespace_len = buffer
22407 .chars_at(range.end)
22408 .take_while(|c| c.is_whitespace() && *c != '\n')
22409 .map(|c| c.len_utf8())
22410 .sum::<usize>();
22411 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22412
22413 language.brackets().any(|(pair, enabled)| {
22414 let pair_start = pair.start.trim_end();
22415 let pair_end = pair.end.trim_start();
22416
22417 enabled
22418 && pair.newline
22419 && buffer.contains_str_at(range.end, pair_end)
22420 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22421 })
22422}
22423
22424fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22425 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22426 [(buffer, range, _)] => (*buffer, range.clone()),
22427 _ => return false,
22428 };
22429 let pair = {
22430 let mut result: Option<BracketMatch> = None;
22431
22432 for pair in buffer
22433 .all_bracket_ranges(range.clone())
22434 .filter(move |pair| {
22435 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22436 })
22437 {
22438 let len = pair.close_range.end - pair.open_range.start;
22439
22440 if let Some(existing) = &result {
22441 let existing_len = existing.close_range.end - existing.open_range.start;
22442 if len > existing_len {
22443 continue;
22444 }
22445 }
22446
22447 result = Some(pair);
22448 }
22449
22450 result
22451 };
22452 let Some(pair) = pair else {
22453 return false;
22454 };
22455 pair.newline_only
22456 && buffer
22457 .chars_for_range(pair.open_range.end..range.start)
22458 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22459 .all(|c| c.is_whitespace() && c != '\n')
22460}
22461
22462fn update_uncommitted_diff_for_buffer(
22463 editor: Entity<Editor>,
22464 project: &Entity<Project>,
22465 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22466 buffer: Entity<MultiBuffer>,
22467 cx: &mut App,
22468) -> Task<()> {
22469 let mut tasks = Vec::new();
22470 project.update(cx, |project, cx| {
22471 for buffer in buffers {
22472 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22473 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22474 }
22475 }
22476 });
22477 cx.spawn(async move |cx| {
22478 let diffs = future::join_all(tasks).await;
22479 if editor
22480 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22481 .unwrap_or(false)
22482 {
22483 return;
22484 }
22485
22486 buffer
22487 .update(cx, |buffer, cx| {
22488 for diff in diffs.into_iter().flatten() {
22489 buffer.add_diff(diff, cx);
22490 }
22491 })
22492 .ok();
22493 })
22494}
22495
22496fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22497 let tab_size = tab_size.get() as usize;
22498 let mut width = offset;
22499
22500 for ch in text.chars() {
22501 width += if ch == '\t' {
22502 tab_size - (width % tab_size)
22503 } else {
22504 1
22505 };
22506 }
22507
22508 width - offset
22509}
22510
22511#[cfg(test)]
22512mod tests {
22513 use super::*;
22514
22515 #[test]
22516 fn test_string_size_with_expanded_tabs() {
22517 let nz = |val| NonZeroU32::new(val).unwrap();
22518 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22519 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22520 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22521 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22522 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22523 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22524 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22525 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22526 }
22527}
22528
22529/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22530struct WordBreakingTokenizer<'a> {
22531 input: &'a str,
22532}
22533
22534impl<'a> WordBreakingTokenizer<'a> {
22535 fn new(input: &'a str) -> Self {
22536 Self { input }
22537 }
22538}
22539
22540fn is_char_ideographic(ch: char) -> bool {
22541 use unicode_script::Script::*;
22542 use unicode_script::UnicodeScript;
22543 matches!(ch.script(), Han | Tangut | Yi)
22544}
22545
22546fn is_grapheme_ideographic(text: &str) -> bool {
22547 text.chars().any(is_char_ideographic)
22548}
22549
22550fn is_grapheme_whitespace(text: &str) -> bool {
22551 text.chars().any(|x| x.is_whitespace())
22552}
22553
22554fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22555 text.chars()
22556 .next()
22557 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22558}
22559
22560#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22561enum WordBreakToken<'a> {
22562 Word { token: &'a str, grapheme_len: usize },
22563 InlineWhitespace { token: &'a str, grapheme_len: usize },
22564 Newline,
22565}
22566
22567impl<'a> Iterator for WordBreakingTokenizer<'a> {
22568 /// Yields a span, the count of graphemes in the token, and whether it was
22569 /// whitespace. Note that it also breaks at word boundaries.
22570 type Item = WordBreakToken<'a>;
22571
22572 fn next(&mut self) -> Option<Self::Item> {
22573 use unicode_segmentation::UnicodeSegmentation;
22574 if self.input.is_empty() {
22575 return None;
22576 }
22577
22578 let mut iter = self.input.graphemes(true).peekable();
22579 let mut offset = 0;
22580 let mut grapheme_len = 0;
22581 if let Some(first_grapheme) = iter.next() {
22582 let is_newline = first_grapheme == "\n";
22583 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22584 offset += first_grapheme.len();
22585 grapheme_len += 1;
22586 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22587 if let Some(grapheme) = iter.peek().copied()
22588 && should_stay_with_preceding_ideograph(grapheme)
22589 {
22590 offset += grapheme.len();
22591 grapheme_len += 1;
22592 }
22593 } else {
22594 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22595 let mut next_word_bound = words.peek().copied();
22596 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22597 next_word_bound = words.next();
22598 }
22599 while let Some(grapheme) = iter.peek().copied() {
22600 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22601 break;
22602 };
22603 if is_grapheme_whitespace(grapheme) != is_whitespace
22604 || (grapheme == "\n") != is_newline
22605 {
22606 break;
22607 };
22608 offset += grapheme.len();
22609 grapheme_len += 1;
22610 iter.next();
22611 }
22612 }
22613 let token = &self.input[..offset];
22614 self.input = &self.input[offset..];
22615 if token == "\n" {
22616 Some(WordBreakToken::Newline)
22617 } else if is_whitespace {
22618 Some(WordBreakToken::InlineWhitespace {
22619 token,
22620 grapheme_len,
22621 })
22622 } else {
22623 Some(WordBreakToken::Word {
22624 token,
22625 grapheme_len,
22626 })
22627 }
22628 } else {
22629 None
22630 }
22631 }
22632}
22633
22634#[test]
22635fn test_word_breaking_tokenizer() {
22636 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22637 ("", &[]),
22638 (" ", &[whitespace(" ", 2)]),
22639 ("Ʒ", &[word("Ʒ", 1)]),
22640 ("Ǽ", &[word("Ǽ", 1)]),
22641 ("⋑", &[word("⋑", 1)]),
22642 ("⋑⋑", &[word("⋑⋑", 2)]),
22643 (
22644 "原理,进而",
22645 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22646 ),
22647 (
22648 "hello world",
22649 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22650 ),
22651 (
22652 "hello, world",
22653 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22654 ),
22655 (
22656 " hello world",
22657 &[
22658 whitespace(" ", 2),
22659 word("hello", 5),
22660 whitespace(" ", 1),
22661 word("world", 5),
22662 ],
22663 ),
22664 (
22665 "这是什么 \n 钢笔",
22666 &[
22667 word("这", 1),
22668 word("是", 1),
22669 word("什", 1),
22670 word("么", 1),
22671 whitespace(" ", 1),
22672 newline(),
22673 whitespace(" ", 1),
22674 word("钢", 1),
22675 word("笔", 1),
22676 ],
22677 ),
22678 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22679 ];
22680
22681 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22682 WordBreakToken::Word {
22683 token,
22684 grapheme_len,
22685 }
22686 }
22687
22688 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22689 WordBreakToken::InlineWhitespace {
22690 token,
22691 grapheme_len,
22692 }
22693 }
22694
22695 fn newline() -> WordBreakToken<'static> {
22696 WordBreakToken::Newline
22697 }
22698
22699 for (input, result) in tests {
22700 assert_eq!(
22701 WordBreakingTokenizer::new(input)
22702 .collect::<Vec<_>>()
22703 .as_slice(),
22704 *result,
22705 );
22706 }
22707}
22708
22709fn wrap_with_prefix(
22710 first_line_prefix: String,
22711 subsequent_lines_prefix: String,
22712 unwrapped_text: String,
22713 wrap_column: usize,
22714 tab_size: NonZeroU32,
22715 preserve_existing_whitespace: bool,
22716) -> String {
22717 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22718 let subsequent_lines_prefix_len =
22719 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22720 let mut wrapped_text = String::new();
22721 let mut current_line = first_line_prefix;
22722 let mut is_first_line = true;
22723
22724 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22725 let mut current_line_len = first_line_prefix_len;
22726 let mut in_whitespace = false;
22727 for token in tokenizer {
22728 let have_preceding_whitespace = in_whitespace;
22729 match token {
22730 WordBreakToken::Word {
22731 token,
22732 grapheme_len,
22733 } => {
22734 in_whitespace = false;
22735 let current_prefix_len = if is_first_line {
22736 first_line_prefix_len
22737 } else {
22738 subsequent_lines_prefix_len
22739 };
22740 if current_line_len + grapheme_len > wrap_column
22741 && current_line_len != current_prefix_len
22742 {
22743 wrapped_text.push_str(current_line.trim_end());
22744 wrapped_text.push('\n');
22745 is_first_line = false;
22746 current_line = subsequent_lines_prefix.clone();
22747 current_line_len = subsequent_lines_prefix_len;
22748 }
22749 current_line.push_str(token);
22750 current_line_len += grapheme_len;
22751 }
22752 WordBreakToken::InlineWhitespace {
22753 mut token,
22754 mut grapheme_len,
22755 } => {
22756 in_whitespace = true;
22757 if have_preceding_whitespace && !preserve_existing_whitespace {
22758 continue;
22759 }
22760 if !preserve_existing_whitespace {
22761 // Keep a single whitespace grapheme as-is
22762 if let Some(first) =
22763 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22764 {
22765 token = first;
22766 } else {
22767 token = " ";
22768 }
22769 grapheme_len = 1;
22770 }
22771 let current_prefix_len = if is_first_line {
22772 first_line_prefix_len
22773 } else {
22774 subsequent_lines_prefix_len
22775 };
22776 if current_line_len + grapheme_len > wrap_column {
22777 wrapped_text.push_str(current_line.trim_end());
22778 wrapped_text.push('\n');
22779 is_first_line = false;
22780 current_line = subsequent_lines_prefix.clone();
22781 current_line_len = subsequent_lines_prefix_len;
22782 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22783 current_line.push_str(token);
22784 current_line_len += grapheme_len;
22785 }
22786 }
22787 WordBreakToken::Newline => {
22788 in_whitespace = true;
22789 let current_prefix_len = if is_first_line {
22790 first_line_prefix_len
22791 } else {
22792 subsequent_lines_prefix_len
22793 };
22794 if preserve_existing_whitespace {
22795 wrapped_text.push_str(current_line.trim_end());
22796 wrapped_text.push('\n');
22797 is_first_line = false;
22798 current_line = subsequent_lines_prefix.clone();
22799 current_line_len = subsequent_lines_prefix_len;
22800 } else if have_preceding_whitespace {
22801 continue;
22802 } else if current_line_len + 1 > wrap_column
22803 && current_line_len != current_prefix_len
22804 {
22805 wrapped_text.push_str(current_line.trim_end());
22806 wrapped_text.push('\n');
22807 is_first_line = false;
22808 current_line = subsequent_lines_prefix.clone();
22809 current_line_len = subsequent_lines_prefix_len;
22810 } else if current_line_len != current_prefix_len {
22811 current_line.push(' ');
22812 current_line_len += 1;
22813 }
22814 }
22815 }
22816 }
22817
22818 if !current_line.is_empty() {
22819 wrapped_text.push_str(¤t_line);
22820 }
22821 wrapped_text
22822}
22823
22824#[test]
22825fn test_wrap_with_prefix() {
22826 assert_eq!(
22827 wrap_with_prefix(
22828 "# ".to_string(),
22829 "# ".to_string(),
22830 "abcdefg".to_string(),
22831 4,
22832 NonZeroU32::new(4).unwrap(),
22833 false,
22834 ),
22835 "# abcdefg"
22836 );
22837 assert_eq!(
22838 wrap_with_prefix(
22839 "".to_string(),
22840 "".to_string(),
22841 "\thello world".to_string(),
22842 8,
22843 NonZeroU32::new(4).unwrap(),
22844 false,
22845 ),
22846 "hello\nworld"
22847 );
22848 assert_eq!(
22849 wrap_with_prefix(
22850 "// ".to_string(),
22851 "// ".to_string(),
22852 "xx \nyy zz aa bb cc".to_string(),
22853 12,
22854 NonZeroU32::new(4).unwrap(),
22855 false,
22856 ),
22857 "// xx yy zz\n// aa bb cc"
22858 );
22859 assert_eq!(
22860 wrap_with_prefix(
22861 String::new(),
22862 String::new(),
22863 "这是什么 \n 钢笔".to_string(),
22864 3,
22865 NonZeroU32::new(4).unwrap(),
22866 false,
22867 ),
22868 "这是什\n么 钢\n笔"
22869 );
22870 assert_eq!(
22871 wrap_with_prefix(
22872 String::new(),
22873 String::new(),
22874 format!("foo{}bar", '\u{2009}'), // thin space
22875 80,
22876 NonZeroU32::new(4).unwrap(),
22877 false,
22878 ),
22879 format!("foo{}bar", '\u{2009}')
22880 );
22881}
22882
22883pub trait CollaborationHub {
22884 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22885 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22886 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22887}
22888
22889impl CollaborationHub for Entity<Project> {
22890 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22891 self.read(cx).collaborators()
22892 }
22893
22894 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22895 self.read(cx).user_store().read(cx).participant_indices()
22896 }
22897
22898 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22899 let this = self.read(cx);
22900 let user_ids = this.collaborators().values().map(|c| c.user_id);
22901 this.user_store().read(cx).participant_names(user_ids, cx)
22902 }
22903}
22904
22905pub trait SemanticsProvider {
22906 fn hover(
22907 &self,
22908 buffer: &Entity<Buffer>,
22909 position: text::Anchor,
22910 cx: &mut App,
22911 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22912
22913 fn inline_values(
22914 &self,
22915 buffer_handle: Entity<Buffer>,
22916 range: Range<text::Anchor>,
22917 cx: &mut App,
22918 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22919
22920 fn applicable_inlay_chunks(
22921 &self,
22922 buffer: &Entity<Buffer>,
22923 ranges: &[Range<text::Anchor>],
22924 cx: &mut App,
22925 ) -> Vec<Range<BufferRow>>;
22926
22927 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22928
22929 fn inlay_hints(
22930 &self,
22931 invalidate: InvalidationStrategy,
22932 buffer: Entity<Buffer>,
22933 ranges: Vec<Range<text::Anchor>>,
22934 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22935 cx: &mut App,
22936 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22937
22938 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22939
22940 fn document_highlights(
22941 &self,
22942 buffer: &Entity<Buffer>,
22943 position: text::Anchor,
22944 cx: &mut App,
22945 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22946
22947 fn definitions(
22948 &self,
22949 buffer: &Entity<Buffer>,
22950 position: text::Anchor,
22951 kind: GotoDefinitionKind,
22952 cx: &mut App,
22953 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22954
22955 fn range_for_rename(
22956 &self,
22957 buffer: &Entity<Buffer>,
22958 position: text::Anchor,
22959 cx: &mut App,
22960 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22961
22962 fn perform_rename(
22963 &self,
22964 buffer: &Entity<Buffer>,
22965 position: text::Anchor,
22966 new_name: String,
22967 cx: &mut App,
22968 ) -> Option<Task<Result<ProjectTransaction>>>;
22969}
22970
22971pub trait CompletionProvider {
22972 fn completions(
22973 &self,
22974 excerpt_id: ExcerptId,
22975 buffer: &Entity<Buffer>,
22976 buffer_position: text::Anchor,
22977 trigger: CompletionContext,
22978 window: &mut Window,
22979 cx: &mut Context<Editor>,
22980 ) -> Task<Result<Vec<CompletionResponse>>>;
22981
22982 fn resolve_completions(
22983 &self,
22984 _buffer: Entity<Buffer>,
22985 _completion_indices: Vec<usize>,
22986 _completions: Rc<RefCell<Box<[Completion]>>>,
22987 _cx: &mut Context<Editor>,
22988 ) -> Task<Result<bool>> {
22989 Task::ready(Ok(false))
22990 }
22991
22992 fn apply_additional_edits_for_completion(
22993 &self,
22994 _buffer: Entity<Buffer>,
22995 _completions: Rc<RefCell<Box<[Completion]>>>,
22996 _completion_index: usize,
22997 _push_to_history: bool,
22998 _cx: &mut Context<Editor>,
22999 ) -> Task<Result<Option<language::Transaction>>> {
23000 Task::ready(Ok(None))
23001 }
23002
23003 fn is_completion_trigger(
23004 &self,
23005 buffer: &Entity<Buffer>,
23006 position: language::Anchor,
23007 text: &str,
23008 trigger_in_words: bool,
23009 menu_is_open: bool,
23010 cx: &mut Context<Editor>,
23011 ) -> bool;
23012
23013 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23014
23015 fn sort_completions(&self) -> bool {
23016 true
23017 }
23018
23019 fn filter_completions(&self) -> bool {
23020 true
23021 }
23022}
23023
23024pub trait CodeActionProvider {
23025 fn id(&self) -> Arc<str>;
23026
23027 fn code_actions(
23028 &self,
23029 buffer: &Entity<Buffer>,
23030 range: Range<text::Anchor>,
23031 window: &mut Window,
23032 cx: &mut App,
23033 ) -> Task<Result<Vec<CodeAction>>>;
23034
23035 fn apply_code_action(
23036 &self,
23037 buffer_handle: Entity<Buffer>,
23038 action: CodeAction,
23039 excerpt_id: ExcerptId,
23040 push_to_history: bool,
23041 window: &mut Window,
23042 cx: &mut App,
23043 ) -> Task<Result<ProjectTransaction>>;
23044}
23045
23046impl CodeActionProvider for Entity<Project> {
23047 fn id(&self) -> Arc<str> {
23048 "project".into()
23049 }
23050
23051 fn code_actions(
23052 &self,
23053 buffer: &Entity<Buffer>,
23054 range: Range<text::Anchor>,
23055 _window: &mut Window,
23056 cx: &mut App,
23057 ) -> Task<Result<Vec<CodeAction>>> {
23058 self.update(cx, |project, cx| {
23059 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23060 let code_actions = project.code_actions(buffer, range, None, cx);
23061 cx.background_spawn(async move {
23062 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23063 Ok(code_lens_actions
23064 .context("code lens fetch")?
23065 .into_iter()
23066 .flatten()
23067 .chain(
23068 code_actions
23069 .context("code action fetch")?
23070 .into_iter()
23071 .flatten(),
23072 )
23073 .collect())
23074 })
23075 })
23076 }
23077
23078 fn apply_code_action(
23079 &self,
23080 buffer_handle: Entity<Buffer>,
23081 action: CodeAction,
23082 _excerpt_id: ExcerptId,
23083 push_to_history: bool,
23084 _window: &mut Window,
23085 cx: &mut App,
23086 ) -> Task<Result<ProjectTransaction>> {
23087 self.update(cx, |project, cx| {
23088 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23089 })
23090 }
23091}
23092
23093fn snippet_completions(
23094 project: &Project,
23095 buffer: &Entity<Buffer>,
23096 buffer_position: text::Anchor,
23097 cx: &mut App,
23098) -> Task<Result<CompletionResponse>> {
23099 let languages = buffer.read(cx).languages_at(buffer_position);
23100 let snippet_store = project.snippets().read(cx);
23101
23102 let scopes: Vec<_> = languages
23103 .iter()
23104 .filter_map(|language| {
23105 let language_name = language.lsp_id();
23106 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23107
23108 if snippets.is_empty() {
23109 None
23110 } else {
23111 Some((language.default_scope(), snippets))
23112 }
23113 })
23114 .collect();
23115
23116 if scopes.is_empty() {
23117 return Task::ready(Ok(CompletionResponse {
23118 completions: vec![],
23119 display_options: CompletionDisplayOptions::default(),
23120 is_incomplete: false,
23121 }));
23122 }
23123
23124 let snapshot = buffer.read(cx).text_snapshot();
23125 let executor = cx.background_executor().clone();
23126
23127 cx.background_spawn(async move {
23128 let mut is_incomplete = false;
23129 let mut completions: Vec<Completion> = Vec::new();
23130 for (scope, snippets) in scopes.into_iter() {
23131 let classifier =
23132 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23133
23134 const MAX_WORD_PREFIX_LEN: usize = 128;
23135 let last_word: String = snapshot
23136 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23137 .take(MAX_WORD_PREFIX_LEN)
23138 .take_while(|c| classifier.is_word(*c))
23139 .collect::<String>()
23140 .chars()
23141 .rev()
23142 .collect();
23143
23144 if last_word.is_empty() {
23145 return Ok(CompletionResponse {
23146 completions: vec![],
23147 display_options: CompletionDisplayOptions::default(),
23148 is_incomplete: true,
23149 });
23150 }
23151
23152 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23153 let to_lsp = |point: &text::Anchor| {
23154 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23155 point_to_lsp(end)
23156 };
23157 let lsp_end = to_lsp(&buffer_position);
23158
23159 let candidates = snippets
23160 .iter()
23161 .enumerate()
23162 .flat_map(|(ix, snippet)| {
23163 snippet
23164 .prefix
23165 .iter()
23166 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23167 })
23168 .collect::<Vec<StringMatchCandidate>>();
23169
23170 const MAX_RESULTS: usize = 100;
23171 let mut matches = fuzzy::match_strings(
23172 &candidates,
23173 &last_word,
23174 last_word.chars().any(|c| c.is_uppercase()),
23175 true,
23176 MAX_RESULTS,
23177 &Default::default(),
23178 executor.clone(),
23179 )
23180 .await;
23181
23182 if matches.len() >= MAX_RESULTS {
23183 is_incomplete = true;
23184 }
23185
23186 // Remove all candidates where the query's start does not match the start of any word in the candidate
23187 if let Some(query_start) = last_word.chars().next() {
23188 matches.retain(|string_match| {
23189 split_words(&string_match.string).any(|word| {
23190 // Check that the first codepoint of the word as lowercase matches the first
23191 // codepoint of the query as lowercase
23192 word.chars()
23193 .flat_map(|codepoint| codepoint.to_lowercase())
23194 .zip(query_start.to_lowercase())
23195 .all(|(word_cp, query_cp)| word_cp == query_cp)
23196 })
23197 });
23198 }
23199
23200 let matched_strings = matches
23201 .into_iter()
23202 .map(|m| m.string)
23203 .collect::<HashSet<_>>();
23204
23205 completions.extend(snippets.iter().filter_map(|snippet| {
23206 let matching_prefix = snippet
23207 .prefix
23208 .iter()
23209 .find(|prefix| matched_strings.contains(*prefix))?;
23210 let start = as_offset - last_word.len();
23211 let start = snapshot.anchor_before(start);
23212 let range = start..buffer_position;
23213 let lsp_start = to_lsp(&start);
23214 let lsp_range = lsp::Range {
23215 start: lsp_start,
23216 end: lsp_end,
23217 };
23218 Some(Completion {
23219 replace_range: range,
23220 new_text: snippet.body.clone(),
23221 source: CompletionSource::Lsp {
23222 insert_range: None,
23223 server_id: LanguageServerId(usize::MAX),
23224 resolved: true,
23225 lsp_completion: Box::new(lsp::CompletionItem {
23226 label: snippet.prefix.first().unwrap().clone(),
23227 kind: Some(CompletionItemKind::SNIPPET),
23228 label_details: snippet.description.as_ref().map(|description| {
23229 lsp::CompletionItemLabelDetails {
23230 detail: Some(description.clone()),
23231 description: None,
23232 }
23233 }),
23234 insert_text_format: Some(InsertTextFormat::SNIPPET),
23235 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23236 lsp::InsertReplaceEdit {
23237 new_text: snippet.body.clone(),
23238 insert: lsp_range,
23239 replace: lsp_range,
23240 },
23241 )),
23242 filter_text: Some(snippet.body.clone()),
23243 sort_text: Some(char::MAX.to_string()),
23244 ..lsp::CompletionItem::default()
23245 }),
23246 lsp_defaults: None,
23247 },
23248 label: CodeLabel::plain(matching_prefix.clone(), None),
23249 icon_path: None,
23250 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23251 single_line: snippet.name.clone().into(),
23252 plain_text: snippet
23253 .description
23254 .clone()
23255 .map(|description| description.into()),
23256 }),
23257 insert_text_mode: None,
23258 confirm: None,
23259 })
23260 }))
23261 }
23262
23263 Ok(CompletionResponse {
23264 completions,
23265 display_options: CompletionDisplayOptions::default(),
23266 is_incomplete,
23267 })
23268 })
23269}
23270
23271impl CompletionProvider for Entity<Project> {
23272 fn completions(
23273 &self,
23274 _excerpt_id: ExcerptId,
23275 buffer: &Entity<Buffer>,
23276 buffer_position: text::Anchor,
23277 options: CompletionContext,
23278 _window: &mut Window,
23279 cx: &mut Context<Editor>,
23280 ) -> Task<Result<Vec<CompletionResponse>>> {
23281 self.update(cx, |project, cx| {
23282 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23283 let project_completions = project.completions(buffer, buffer_position, options, cx);
23284 cx.background_spawn(async move {
23285 let mut responses = project_completions.await?;
23286 let snippets = snippets.await?;
23287 if !snippets.completions.is_empty() {
23288 responses.push(snippets);
23289 }
23290 Ok(responses)
23291 })
23292 })
23293 }
23294
23295 fn resolve_completions(
23296 &self,
23297 buffer: Entity<Buffer>,
23298 completion_indices: Vec<usize>,
23299 completions: Rc<RefCell<Box<[Completion]>>>,
23300 cx: &mut Context<Editor>,
23301 ) -> Task<Result<bool>> {
23302 self.update(cx, |project, cx| {
23303 project.lsp_store().update(cx, |lsp_store, cx| {
23304 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23305 })
23306 })
23307 }
23308
23309 fn apply_additional_edits_for_completion(
23310 &self,
23311 buffer: Entity<Buffer>,
23312 completions: Rc<RefCell<Box<[Completion]>>>,
23313 completion_index: usize,
23314 push_to_history: bool,
23315 cx: &mut Context<Editor>,
23316 ) -> Task<Result<Option<language::Transaction>>> {
23317 self.update(cx, |project, cx| {
23318 project.lsp_store().update(cx, |lsp_store, cx| {
23319 lsp_store.apply_additional_edits_for_completion(
23320 buffer,
23321 completions,
23322 completion_index,
23323 push_to_history,
23324 cx,
23325 )
23326 })
23327 })
23328 }
23329
23330 fn is_completion_trigger(
23331 &self,
23332 buffer: &Entity<Buffer>,
23333 position: language::Anchor,
23334 text: &str,
23335 trigger_in_words: bool,
23336 menu_is_open: bool,
23337 cx: &mut Context<Editor>,
23338 ) -> bool {
23339 let mut chars = text.chars();
23340 let char = if let Some(char) = chars.next() {
23341 char
23342 } else {
23343 return false;
23344 };
23345 if chars.next().is_some() {
23346 return false;
23347 }
23348
23349 let buffer = buffer.read(cx);
23350 let snapshot = buffer.snapshot();
23351 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23352 return false;
23353 }
23354 let classifier = snapshot
23355 .char_classifier_at(position)
23356 .scope_context(Some(CharScopeContext::Completion));
23357 if trigger_in_words && classifier.is_word(char) {
23358 return true;
23359 }
23360
23361 buffer.completion_triggers().contains(text)
23362 }
23363}
23364
23365impl SemanticsProvider for Entity<Project> {
23366 fn hover(
23367 &self,
23368 buffer: &Entity<Buffer>,
23369 position: text::Anchor,
23370 cx: &mut App,
23371 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23372 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23373 }
23374
23375 fn document_highlights(
23376 &self,
23377 buffer: &Entity<Buffer>,
23378 position: text::Anchor,
23379 cx: &mut App,
23380 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23381 Some(self.update(cx, |project, cx| {
23382 project.document_highlights(buffer, position, cx)
23383 }))
23384 }
23385
23386 fn definitions(
23387 &self,
23388 buffer: &Entity<Buffer>,
23389 position: text::Anchor,
23390 kind: GotoDefinitionKind,
23391 cx: &mut App,
23392 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23393 Some(self.update(cx, |project, cx| match kind {
23394 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23395 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23396 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23397 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23398 }))
23399 }
23400
23401 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23402 self.update(cx, |project, cx| {
23403 if project
23404 .active_debug_session(cx)
23405 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23406 {
23407 return true;
23408 }
23409
23410 buffer.update(cx, |buffer, cx| {
23411 project.any_language_server_supports_inlay_hints(buffer, cx)
23412 })
23413 })
23414 }
23415
23416 fn inline_values(
23417 &self,
23418 buffer_handle: Entity<Buffer>,
23419 range: Range<text::Anchor>,
23420 cx: &mut App,
23421 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23422 self.update(cx, |project, cx| {
23423 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23424
23425 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23426 })
23427 }
23428
23429 fn applicable_inlay_chunks(
23430 &self,
23431 buffer: &Entity<Buffer>,
23432 ranges: &[Range<text::Anchor>],
23433 cx: &mut App,
23434 ) -> Vec<Range<BufferRow>> {
23435 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23436 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23437 })
23438 }
23439
23440 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23441 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23442 lsp_store.invalidate_inlay_hints(for_buffers)
23443 });
23444 }
23445
23446 fn inlay_hints(
23447 &self,
23448 invalidate: InvalidationStrategy,
23449 buffer: Entity<Buffer>,
23450 ranges: Vec<Range<text::Anchor>>,
23451 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23452 cx: &mut App,
23453 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23454 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23455 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23456 }))
23457 }
23458
23459 fn range_for_rename(
23460 &self,
23461 buffer: &Entity<Buffer>,
23462 position: text::Anchor,
23463 cx: &mut App,
23464 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23465 Some(self.update(cx, |project, cx| {
23466 let buffer = buffer.clone();
23467 let task = project.prepare_rename(buffer.clone(), position, cx);
23468 cx.spawn(async move |_, cx| {
23469 Ok(match task.await? {
23470 PrepareRenameResponse::Success(range) => Some(range),
23471 PrepareRenameResponse::InvalidPosition => None,
23472 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23473 // Fallback on using TreeSitter info to determine identifier range
23474 buffer.read_with(cx, |buffer, _| {
23475 let snapshot = buffer.snapshot();
23476 let (range, kind) = snapshot.surrounding_word(position, None);
23477 if kind != Some(CharKind::Word) {
23478 return None;
23479 }
23480 Some(
23481 snapshot.anchor_before(range.start)
23482 ..snapshot.anchor_after(range.end),
23483 )
23484 })?
23485 }
23486 })
23487 })
23488 }))
23489 }
23490
23491 fn perform_rename(
23492 &self,
23493 buffer: &Entity<Buffer>,
23494 position: text::Anchor,
23495 new_name: String,
23496 cx: &mut App,
23497 ) -> Option<Task<Result<ProjectTransaction>>> {
23498 Some(self.update(cx, |project, cx| {
23499 project.perform_rename(buffer.clone(), position, new_name, cx)
23500 }))
23501 }
23502}
23503
23504fn consume_contiguous_rows(
23505 contiguous_row_selections: &mut Vec<Selection<Point>>,
23506 selection: &Selection<Point>,
23507 display_map: &DisplaySnapshot,
23508 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23509) -> (MultiBufferRow, MultiBufferRow) {
23510 contiguous_row_selections.push(selection.clone());
23511 let start_row = starting_row(selection, display_map);
23512 let mut end_row = ending_row(selection, display_map);
23513
23514 while let Some(next_selection) = selections.peek() {
23515 if next_selection.start.row <= end_row.0 {
23516 end_row = ending_row(next_selection, display_map);
23517 contiguous_row_selections.push(selections.next().unwrap().clone());
23518 } else {
23519 break;
23520 }
23521 }
23522 (start_row, end_row)
23523}
23524
23525fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23526 if selection.start.column > 0 {
23527 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23528 } else {
23529 MultiBufferRow(selection.start.row)
23530 }
23531}
23532
23533fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23534 if next_selection.end.column > 0 || next_selection.is_empty() {
23535 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23536 } else {
23537 MultiBufferRow(next_selection.end.row)
23538 }
23539}
23540
23541impl EditorSnapshot {
23542 pub fn remote_selections_in_range<'a>(
23543 &'a self,
23544 range: &'a Range<Anchor>,
23545 collaboration_hub: &dyn CollaborationHub,
23546 cx: &'a App,
23547 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23548 let participant_names = collaboration_hub.user_names(cx);
23549 let participant_indices = collaboration_hub.user_participant_indices(cx);
23550 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23551 let collaborators_by_replica_id = collaborators_by_peer_id
23552 .values()
23553 .map(|collaborator| (collaborator.replica_id, collaborator))
23554 .collect::<HashMap<_, _>>();
23555 self.buffer_snapshot()
23556 .selections_in_range(range, false)
23557 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23558 if replica_id == ReplicaId::AGENT {
23559 Some(RemoteSelection {
23560 replica_id,
23561 selection,
23562 cursor_shape,
23563 line_mode,
23564 collaborator_id: CollaboratorId::Agent,
23565 user_name: Some("Agent".into()),
23566 color: cx.theme().players().agent(),
23567 })
23568 } else {
23569 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23570 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23571 let user_name = participant_names.get(&collaborator.user_id).cloned();
23572 Some(RemoteSelection {
23573 replica_id,
23574 selection,
23575 cursor_shape,
23576 line_mode,
23577 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23578 user_name,
23579 color: if let Some(index) = participant_index {
23580 cx.theme().players().color_for_participant(index.0)
23581 } else {
23582 cx.theme().players().absent()
23583 },
23584 })
23585 }
23586 })
23587 }
23588
23589 pub fn hunks_for_ranges(
23590 &self,
23591 ranges: impl IntoIterator<Item = Range<Point>>,
23592 ) -> Vec<MultiBufferDiffHunk> {
23593 let mut hunks = Vec::new();
23594 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23595 HashMap::default();
23596 for query_range in ranges {
23597 let query_rows =
23598 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23599 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23600 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23601 ) {
23602 // Include deleted hunks that are adjacent to the query range, because
23603 // otherwise they would be missed.
23604 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23605 if hunk.status().is_deleted() {
23606 intersects_range |= hunk.row_range.start == query_rows.end;
23607 intersects_range |= hunk.row_range.end == query_rows.start;
23608 }
23609 if intersects_range {
23610 if !processed_buffer_rows
23611 .entry(hunk.buffer_id)
23612 .or_default()
23613 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23614 {
23615 continue;
23616 }
23617 hunks.push(hunk);
23618 }
23619 }
23620 }
23621
23622 hunks
23623 }
23624
23625 fn display_diff_hunks_for_rows<'a>(
23626 &'a self,
23627 display_rows: Range<DisplayRow>,
23628 folded_buffers: &'a HashSet<BufferId>,
23629 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23630 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23631 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23632
23633 self.buffer_snapshot()
23634 .diff_hunks_in_range(buffer_start..buffer_end)
23635 .filter_map(|hunk| {
23636 if folded_buffers.contains(&hunk.buffer_id) {
23637 return None;
23638 }
23639
23640 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23641 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23642
23643 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23644 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23645
23646 let display_hunk = if hunk_display_start.column() != 0 {
23647 DisplayDiffHunk::Folded {
23648 display_row: hunk_display_start.row(),
23649 }
23650 } else {
23651 let mut end_row = hunk_display_end.row();
23652 if hunk_display_end.column() > 0 {
23653 end_row.0 += 1;
23654 }
23655 let is_created_file = hunk.is_created_file();
23656 DisplayDiffHunk::Unfolded {
23657 status: hunk.status(),
23658 diff_base_byte_range: hunk.diff_base_byte_range,
23659 display_row_range: hunk_display_start.row()..end_row,
23660 multi_buffer_range: Anchor::range_in_buffer(
23661 hunk.excerpt_id,
23662 hunk.buffer_id,
23663 hunk.buffer_range,
23664 ),
23665 is_created_file,
23666 }
23667 };
23668
23669 Some(display_hunk)
23670 })
23671 }
23672
23673 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23674 self.display_snapshot
23675 .buffer_snapshot()
23676 .language_at(position)
23677 }
23678
23679 pub fn is_focused(&self) -> bool {
23680 self.is_focused
23681 }
23682
23683 pub fn placeholder_text(&self) -> Option<String> {
23684 self.placeholder_display_snapshot
23685 .as_ref()
23686 .map(|display_map| display_map.text())
23687 }
23688
23689 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23690 self.scroll_anchor.scroll_position(&self.display_snapshot)
23691 }
23692
23693 fn gutter_dimensions(
23694 &self,
23695 font_id: FontId,
23696 font_size: Pixels,
23697 max_line_number_width: Pixels,
23698 cx: &App,
23699 ) -> Option<GutterDimensions> {
23700 if !self.show_gutter {
23701 return None;
23702 }
23703
23704 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23705 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23706
23707 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23708 matches!(
23709 ProjectSettings::get_global(cx).git.git_gutter,
23710 GitGutterSetting::TrackedFiles
23711 )
23712 });
23713 let gutter_settings = EditorSettings::get_global(cx).gutter;
23714 let show_line_numbers = self
23715 .show_line_numbers
23716 .unwrap_or(gutter_settings.line_numbers);
23717 let line_gutter_width = if show_line_numbers {
23718 // Avoid flicker-like gutter resizes when the line number gains another digit by
23719 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23720 let min_width_for_number_on_gutter =
23721 ch_advance * gutter_settings.min_line_number_digits as f32;
23722 max_line_number_width.max(min_width_for_number_on_gutter)
23723 } else {
23724 0.0.into()
23725 };
23726
23727 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23728 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23729
23730 let git_blame_entries_width =
23731 self.git_blame_gutter_max_author_length
23732 .map(|max_author_length| {
23733 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23734 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23735
23736 /// The number of characters to dedicate to gaps and margins.
23737 const SPACING_WIDTH: usize = 4;
23738
23739 let max_char_count = max_author_length.min(renderer.max_author_length())
23740 + ::git::SHORT_SHA_LENGTH
23741 + MAX_RELATIVE_TIMESTAMP.len()
23742 + SPACING_WIDTH;
23743
23744 ch_advance * max_char_count
23745 });
23746
23747 let is_singleton = self.buffer_snapshot().is_singleton();
23748
23749 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23750 left_padding += if !is_singleton {
23751 ch_width * 4.0
23752 } else if show_runnables || show_breakpoints {
23753 ch_width * 3.0
23754 } else if show_git_gutter && show_line_numbers {
23755 ch_width * 2.0
23756 } else if show_git_gutter || show_line_numbers {
23757 ch_width
23758 } else {
23759 px(0.)
23760 };
23761
23762 let shows_folds = is_singleton && gutter_settings.folds;
23763
23764 let right_padding = if shows_folds && show_line_numbers {
23765 ch_width * 4.0
23766 } else if shows_folds || (!is_singleton && show_line_numbers) {
23767 ch_width * 3.0
23768 } else if show_line_numbers {
23769 ch_width
23770 } else {
23771 px(0.)
23772 };
23773
23774 Some(GutterDimensions {
23775 left_padding,
23776 right_padding,
23777 width: line_gutter_width + left_padding + right_padding,
23778 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23779 git_blame_entries_width,
23780 })
23781 }
23782
23783 pub fn render_crease_toggle(
23784 &self,
23785 buffer_row: MultiBufferRow,
23786 row_contains_cursor: bool,
23787 editor: Entity<Editor>,
23788 window: &mut Window,
23789 cx: &mut App,
23790 ) -> Option<AnyElement> {
23791 let folded = self.is_line_folded(buffer_row);
23792 let mut is_foldable = false;
23793
23794 if let Some(crease) = self
23795 .crease_snapshot
23796 .query_row(buffer_row, self.buffer_snapshot())
23797 {
23798 is_foldable = true;
23799 match crease {
23800 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23801 if let Some(render_toggle) = render_toggle {
23802 let toggle_callback =
23803 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23804 if folded {
23805 editor.update(cx, |editor, cx| {
23806 editor.fold_at(buffer_row, window, cx)
23807 });
23808 } else {
23809 editor.update(cx, |editor, cx| {
23810 editor.unfold_at(buffer_row, window, cx)
23811 });
23812 }
23813 });
23814 return Some((render_toggle)(
23815 buffer_row,
23816 folded,
23817 toggle_callback,
23818 window,
23819 cx,
23820 ));
23821 }
23822 }
23823 }
23824 }
23825
23826 is_foldable |= self.starts_indent(buffer_row);
23827
23828 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23829 Some(
23830 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23831 .toggle_state(folded)
23832 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23833 if folded {
23834 this.unfold_at(buffer_row, window, cx);
23835 } else {
23836 this.fold_at(buffer_row, window, cx);
23837 }
23838 }))
23839 .into_any_element(),
23840 )
23841 } else {
23842 None
23843 }
23844 }
23845
23846 pub fn render_crease_trailer(
23847 &self,
23848 buffer_row: MultiBufferRow,
23849 window: &mut Window,
23850 cx: &mut App,
23851 ) -> Option<AnyElement> {
23852 let folded = self.is_line_folded(buffer_row);
23853 if let Crease::Inline { render_trailer, .. } = self
23854 .crease_snapshot
23855 .query_row(buffer_row, self.buffer_snapshot())?
23856 {
23857 let render_trailer = render_trailer.as_ref()?;
23858 Some(render_trailer(buffer_row, folded, window, cx))
23859 } else {
23860 None
23861 }
23862 }
23863}
23864
23865impl Deref for EditorSnapshot {
23866 type Target = DisplaySnapshot;
23867
23868 fn deref(&self) -> &Self::Target {
23869 &self.display_snapshot
23870 }
23871}
23872
23873#[derive(Clone, Debug, PartialEq, Eq)]
23874pub enum EditorEvent {
23875 InputIgnored {
23876 text: Arc<str>,
23877 },
23878 InputHandled {
23879 utf16_range_to_replace: Option<Range<isize>>,
23880 text: Arc<str>,
23881 },
23882 ExcerptsAdded {
23883 buffer: Entity<Buffer>,
23884 predecessor: ExcerptId,
23885 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23886 },
23887 ExcerptsRemoved {
23888 ids: Vec<ExcerptId>,
23889 removed_buffer_ids: Vec<BufferId>,
23890 },
23891 BufferFoldToggled {
23892 ids: Vec<ExcerptId>,
23893 folded: bool,
23894 },
23895 ExcerptsEdited {
23896 ids: Vec<ExcerptId>,
23897 },
23898 ExcerptsExpanded {
23899 ids: Vec<ExcerptId>,
23900 },
23901 BufferEdited,
23902 Edited {
23903 transaction_id: clock::Lamport,
23904 },
23905 Reparsed(BufferId),
23906 Focused,
23907 FocusedIn,
23908 Blurred,
23909 DirtyChanged,
23910 Saved,
23911 TitleChanged,
23912 SelectionsChanged {
23913 local: bool,
23914 },
23915 ScrollPositionChanged {
23916 local: bool,
23917 autoscroll: bool,
23918 },
23919 TransactionUndone {
23920 transaction_id: clock::Lamport,
23921 },
23922 TransactionBegun {
23923 transaction_id: clock::Lamport,
23924 },
23925 CursorShapeChanged,
23926 BreadcrumbsChanged,
23927 PushedToNavHistory {
23928 anchor: Anchor,
23929 is_deactivate: bool,
23930 },
23931}
23932
23933impl EventEmitter<EditorEvent> for Editor {}
23934
23935impl Focusable for Editor {
23936 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23937 self.focus_handle.clone()
23938 }
23939}
23940
23941impl Render for Editor {
23942 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23943 let settings = ThemeSettings::get_global(cx);
23944
23945 let mut text_style = match self.mode {
23946 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23947 color: cx.theme().colors().editor_foreground,
23948 font_family: settings.ui_font.family.clone(),
23949 font_features: settings.ui_font.features.clone(),
23950 font_fallbacks: settings.ui_font.fallbacks.clone(),
23951 font_size: rems(0.875).into(),
23952 font_weight: settings.ui_font.weight,
23953 line_height: relative(settings.buffer_line_height.value()),
23954 ..Default::default()
23955 },
23956 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23957 color: cx.theme().colors().editor_foreground,
23958 font_family: settings.buffer_font.family.clone(),
23959 font_features: settings.buffer_font.features.clone(),
23960 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23961 font_size: settings.buffer_font_size(cx).into(),
23962 font_weight: settings.buffer_font.weight,
23963 line_height: relative(settings.buffer_line_height.value()),
23964 ..Default::default()
23965 },
23966 };
23967 if let Some(text_style_refinement) = &self.text_style_refinement {
23968 text_style.refine(text_style_refinement)
23969 }
23970
23971 let background = match self.mode {
23972 EditorMode::SingleLine => cx.theme().system().transparent,
23973 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23974 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23975 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23976 };
23977
23978 EditorElement::new(
23979 &cx.entity(),
23980 EditorStyle {
23981 background,
23982 border: cx.theme().colors().border,
23983 local_player: cx.theme().players().local(),
23984 text: text_style,
23985 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23986 syntax: cx.theme().syntax().clone(),
23987 status: cx.theme().status().clone(),
23988 inlay_hints_style: make_inlay_hints_style(cx),
23989 edit_prediction_styles: make_suggestion_styles(cx),
23990 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23991 show_underlines: self.diagnostics_enabled(),
23992 },
23993 )
23994 }
23995}
23996
23997impl EntityInputHandler for Editor {
23998 fn text_for_range(
23999 &mut self,
24000 range_utf16: Range<usize>,
24001 adjusted_range: &mut Option<Range<usize>>,
24002 _: &mut Window,
24003 cx: &mut Context<Self>,
24004 ) -> Option<String> {
24005 let snapshot = self.buffer.read(cx).read(cx);
24006 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24007 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24008 if (start.0..end.0) != range_utf16 {
24009 adjusted_range.replace(start.0..end.0);
24010 }
24011 Some(snapshot.text_for_range(start..end).collect())
24012 }
24013
24014 fn selected_text_range(
24015 &mut self,
24016 ignore_disabled_input: bool,
24017 _: &mut Window,
24018 cx: &mut Context<Self>,
24019 ) -> Option<UTF16Selection> {
24020 // Prevent the IME menu from appearing when holding down an alphabetic key
24021 // while input is disabled.
24022 if !ignore_disabled_input && !self.input_enabled {
24023 return None;
24024 }
24025
24026 let selection = self
24027 .selections
24028 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24029 let range = selection.range();
24030
24031 Some(UTF16Selection {
24032 range: range.start.0..range.end.0,
24033 reversed: selection.reversed,
24034 })
24035 }
24036
24037 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24038 let snapshot = self.buffer.read(cx).read(cx);
24039 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24040 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24041 }
24042
24043 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24044 self.clear_highlights::<InputComposition>(cx);
24045 self.ime_transaction.take();
24046 }
24047
24048 fn replace_text_in_range(
24049 &mut self,
24050 range_utf16: Option<Range<usize>>,
24051 text: &str,
24052 window: &mut Window,
24053 cx: &mut Context<Self>,
24054 ) {
24055 if !self.input_enabled {
24056 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24057 return;
24058 }
24059
24060 self.transact(window, cx, |this, window, cx| {
24061 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24062 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24063 Some(this.selection_replacement_ranges(range_utf16, cx))
24064 } else {
24065 this.marked_text_ranges(cx)
24066 };
24067
24068 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24069 let newest_selection_id = this.selections.newest_anchor().id;
24070 this.selections
24071 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24072 .iter()
24073 .zip(ranges_to_replace.iter())
24074 .find_map(|(selection, range)| {
24075 if selection.id == newest_selection_id {
24076 Some(
24077 (range.start.0 as isize - selection.head().0 as isize)
24078 ..(range.end.0 as isize - selection.head().0 as isize),
24079 )
24080 } else {
24081 None
24082 }
24083 })
24084 });
24085
24086 cx.emit(EditorEvent::InputHandled {
24087 utf16_range_to_replace: range_to_replace,
24088 text: text.into(),
24089 });
24090
24091 if let Some(new_selected_ranges) = new_selected_ranges {
24092 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24093 selections.select_ranges(new_selected_ranges)
24094 });
24095 this.backspace(&Default::default(), window, cx);
24096 }
24097
24098 this.handle_input(text, window, cx);
24099 });
24100
24101 if let Some(transaction) = self.ime_transaction {
24102 self.buffer.update(cx, |buffer, cx| {
24103 buffer.group_until_transaction(transaction, cx);
24104 });
24105 }
24106
24107 self.unmark_text(window, cx);
24108 }
24109
24110 fn replace_and_mark_text_in_range(
24111 &mut self,
24112 range_utf16: Option<Range<usize>>,
24113 text: &str,
24114 new_selected_range_utf16: Option<Range<usize>>,
24115 window: &mut Window,
24116 cx: &mut Context<Self>,
24117 ) {
24118 if !self.input_enabled {
24119 return;
24120 }
24121
24122 let transaction = self.transact(window, cx, |this, window, cx| {
24123 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24124 let snapshot = this.buffer.read(cx).read(cx);
24125 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24126 for marked_range in &mut marked_ranges {
24127 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24128 marked_range.start.0 += relative_range_utf16.start;
24129 marked_range.start =
24130 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24131 marked_range.end =
24132 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24133 }
24134 }
24135 Some(marked_ranges)
24136 } else if let Some(range_utf16) = range_utf16 {
24137 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24138 Some(this.selection_replacement_ranges(range_utf16, cx))
24139 } else {
24140 None
24141 };
24142
24143 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24144 let newest_selection_id = this.selections.newest_anchor().id;
24145 this.selections
24146 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24147 .iter()
24148 .zip(ranges_to_replace.iter())
24149 .find_map(|(selection, range)| {
24150 if selection.id == newest_selection_id {
24151 Some(
24152 (range.start.0 as isize - selection.head().0 as isize)
24153 ..(range.end.0 as isize - selection.head().0 as isize),
24154 )
24155 } else {
24156 None
24157 }
24158 })
24159 });
24160
24161 cx.emit(EditorEvent::InputHandled {
24162 utf16_range_to_replace: range_to_replace,
24163 text: text.into(),
24164 });
24165
24166 if let Some(ranges) = ranges_to_replace {
24167 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24168 s.select_ranges(ranges)
24169 });
24170 }
24171
24172 let marked_ranges = {
24173 let snapshot = this.buffer.read(cx).read(cx);
24174 this.selections
24175 .disjoint_anchors_arc()
24176 .iter()
24177 .map(|selection| {
24178 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24179 })
24180 .collect::<Vec<_>>()
24181 };
24182
24183 if text.is_empty() {
24184 this.unmark_text(window, cx);
24185 } else {
24186 this.highlight_text::<InputComposition>(
24187 marked_ranges.clone(),
24188 HighlightStyle {
24189 underline: Some(UnderlineStyle {
24190 thickness: px(1.),
24191 color: None,
24192 wavy: false,
24193 }),
24194 ..Default::default()
24195 },
24196 cx,
24197 );
24198 }
24199
24200 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24201 let use_autoclose = this.use_autoclose;
24202 let use_auto_surround = this.use_auto_surround;
24203 this.set_use_autoclose(false);
24204 this.set_use_auto_surround(false);
24205 this.handle_input(text, window, cx);
24206 this.set_use_autoclose(use_autoclose);
24207 this.set_use_auto_surround(use_auto_surround);
24208
24209 if let Some(new_selected_range) = new_selected_range_utf16 {
24210 let snapshot = this.buffer.read(cx).read(cx);
24211 let new_selected_ranges = marked_ranges
24212 .into_iter()
24213 .map(|marked_range| {
24214 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24215 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24216 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24217 snapshot.clip_offset_utf16(new_start, Bias::Left)
24218 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24219 })
24220 .collect::<Vec<_>>();
24221
24222 drop(snapshot);
24223 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24224 selections.select_ranges(new_selected_ranges)
24225 });
24226 }
24227 });
24228
24229 self.ime_transaction = self.ime_transaction.or(transaction);
24230 if let Some(transaction) = self.ime_transaction {
24231 self.buffer.update(cx, |buffer, cx| {
24232 buffer.group_until_transaction(transaction, cx);
24233 });
24234 }
24235
24236 if self.text_highlights::<InputComposition>(cx).is_none() {
24237 self.ime_transaction.take();
24238 }
24239 }
24240
24241 fn bounds_for_range(
24242 &mut self,
24243 range_utf16: Range<usize>,
24244 element_bounds: gpui::Bounds<Pixels>,
24245 window: &mut Window,
24246 cx: &mut Context<Self>,
24247 ) -> Option<gpui::Bounds<Pixels>> {
24248 let text_layout_details = self.text_layout_details(window);
24249 let CharacterDimensions {
24250 em_width,
24251 em_advance,
24252 line_height,
24253 } = self.character_dimensions(window);
24254
24255 let snapshot = self.snapshot(window, cx);
24256 let scroll_position = snapshot.scroll_position();
24257 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24258
24259 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24260 let x = Pixels::from(
24261 ScrollOffset::from(
24262 snapshot.x_for_display_point(start, &text_layout_details)
24263 + self.gutter_dimensions.full_width(),
24264 ) - scroll_left,
24265 );
24266 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24267
24268 Some(Bounds {
24269 origin: element_bounds.origin + point(x, y),
24270 size: size(em_width, line_height),
24271 })
24272 }
24273
24274 fn character_index_for_point(
24275 &mut self,
24276 point: gpui::Point<Pixels>,
24277 _window: &mut Window,
24278 _cx: &mut Context<Self>,
24279 ) -> Option<usize> {
24280 let position_map = self.last_position_map.as_ref()?;
24281 if !position_map.text_hitbox.contains(&point) {
24282 return None;
24283 }
24284 let display_point = position_map.point_for_position(point).previous_valid;
24285 let anchor = position_map
24286 .snapshot
24287 .display_point_to_anchor(display_point, Bias::Left);
24288 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24289 Some(utf16_offset.0)
24290 }
24291
24292 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24293 self.input_enabled
24294 }
24295}
24296
24297trait SelectionExt {
24298 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24299 fn spanned_rows(
24300 &self,
24301 include_end_if_at_line_start: bool,
24302 map: &DisplaySnapshot,
24303 ) -> Range<MultiBufferRow>;
24304}
24305
24306impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24307 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24308 let start = self
24309 .start
24310 .to_point(map.buffer_snapshot())
24311 .to_display_point(map);
24312 let end = self
24313 .end
24314 .to_point(map.buffer_snapshot())
24315 .to_display_point(map);
24316 if self.reversed {
24317 end..start
24318 } else {
24319 start..end
24320 }
24321 }
24322
24323 fn spanned_rows(
24324 &self,
24325 include_end_if_at_line_start: bool,
24326 map: &DisplaySnapshot,
24327 ) -> Range<MultiBufferRow> {
24328 let start = self.start.to_point(map.buffer_snapshot());
24329 let mut end = self.end.to_point(map.buffer_snapshot());
24330 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24331 end.row -= 1;
24332 }
24333
24334 let buffer_start = map.prev_line_boundary(start).0;
24335 let buffer_end = map.next_line_boundary(end).0;
24336 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24337 }
24338}
24339
24340impl<T: InvalidationRegion> InvalidationStack<T> {
24341 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24342 where
24343 S: Clone + ToOffset,
24344 {
24345 while let Some(region) = self.last() {
24346 let all_selections_inside_invalidation_ranges =
24347 if selections.len() == region.ranges().len() {
24348 selections
24349 .iter()
24350 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24351 .all(|(selection, invalidation_range)| {
24352 let head = selection.head().to_offset(buffer);
24353 invalidation_range.start <= head && invalidation_range.end >= head
24354 })
24355 } else {
24356 false
24357 };
24358
24359 if all_selections_inside_invalidation_ranges {
24360 break;
24361 } else {
24362 self.pop();
24363 }
24364 }
24365 }
24366}
24367
24368impl<T> Default for InvalidationStack<T> {
24369 fn default() -> Self {
24370 Self(Default::default())
24371 }
24372}
24373
24374impl<T> Deref for InvalidationStack<T> {
24375 type Target = Vec<T>;
24376
24377 fn deref(&self) -> &Self::Target {
24378 &self.0
24379 }
24380}
24381
24382impl<T> DerefMut for InvalidationStack<T> {
24383 fn deref_mut(&mut self) -> &mut Self::Target {
24384 &mut self.0
24385 }
24386}
24387
24388impl InvalidationRegion for SnippetState {
24389 fn ranges(&self) -> &[Range<Anchor>] {
24390 &self.ranges[self.active_index]
24391 }
24392}
24393
24394fn edit_prediction_edit_text(
24395 current_snapshot: &BufferSnapshot,
24396 edits: &[(Range<Anchor>, impl AsRef<str>)],
24397 edit_preview: &EditPreview,
24398 include_deletions: bool,
24399 cx: &App,
24400) -> HighlightedText {
24401 let edits = edits
24402 .iter()
24403 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24404 .collect::<Vec<_>>();
24405
24406 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24407}
24408
24409fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24410 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24411 // Just show the raw edit text with basic styling
24412 let mut text = String::new();
24413 let mut highlights = Vec::new();
24414
24415 let insertion_highlight_style = HighlightStyle {
24416 color: Some(cx.theme().colors().text),
24417 ..Default::default()
24418 };
24419
24420 for (_, edit_text) in edits {
24421 let start_offset = text.len();
24422 text.push_str(edit_text);
24423 let end_offset = text.len();
24424
24425 if start_offset < end_offset {
24426 highlights.push((start_offset..end_offset, insertion_highlight_style));
24427 }
24428 }
24429
24430 HighlightedText {
24431 text: text.into(),
24432 highlights,
24433 }
24434}
24435
24436pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24437 match severity {
24438 lsp::DiagnosticSeverity::ERROR => colors.error,
24439 lsp::DiagnosticSeverity::WARNING => colors.warning,
24440 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24441 lsp::DiagnosticSeverity::HINT => colors.info,
24442 _ => colors.ignored,
24443 }
24444}
24445
24446pub fn styled_runs_for_code_label<'a>(
24447 label: &'a CodeLabel,
24448 syntax_theme: &'a theme::SyntaxTheme,
24449) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24450 let fade_out = HighlightStyle {
24451 fade_out: Some(0.35),
24452 ..Default::default()
24453 };
24454
24455 let mut prev_end = label.filter_range.end;
24456 label
24457 .runs
24458 .iter()
24459 .enumerate()
24460 .flat_map(move |(ix, (range, highlight_id))| {
24461 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24462 style
24463 } else {
24464 return Default::default();
24465 };
24466 let muted_style = style.highlight(fade_out);
24467
24468 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24469 if range.start >= label.filter_range.end {
24470 if range.start > prev_end {
24471 runs.push((prev_end..range.start, fade_out));
24472 }
24473 runs.push((range.clone(), muted_style));
24474 } else if range.end <= label.filter_range.end {
24475 runs.push((range.clone(), style));
24476 } else {
24477 runs.push((range.start..label.filter_range.end, style));
24478 runs.push((label.filter_range.end..range.end, muted_style));
24479 }
24480 prev_end = cmp::max(prev_end, range.end);
24481
24482 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24483 runs.push((prev_end..label.text.len(), fade_out));
24484 }
24485
24486 runs
24487 })
24488}
24489
24490pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24491 let mut prev_index = 0;
24492 let mut prev_codepoint: Option<char> = None;
24493 text.char_indices()
24494 .chain([(text.len(), '\0')])
24495 .filter_map(move |(index, codepoint)| {
24496 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24497 let is_boundary = index == text.len()
24498 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24499 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24500 if is_boundary {
24501 let chunk = &text[prev_index..index];
24502 prev_index = index;
24503 Some(chunk)
24504 } else {
24505 None
24506 }
24507 })
24508}
24509
24510pub trait RangeToAnchorExt: Sized {
24511 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24512
24513 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24514 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24515 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24516 }
24517}
24518
24519impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24520 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24521 let start_offset = self.start.to_offset(snapshot);
24522 let end_offset = self.end.to_offset(snapshot);
24523 if start_offset == end_offset {
24524 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24525 } else {
24526 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24527 }
24528 }
24529}
24530
24531pub trait RowExt {
24532 fn as_f64(&self) -> f64;
24533
24534 fn next_row(&self) -> Self;
24535
24536 fn previous_row(&self) -> Self;
24537
24538 fn minus(&self, other: Self) -> u32;
24539}
24540
24541impl RowExt for DisplayRow {
24542 fn as_f64(&self) -> f64 {
24543 self.0 as _
24544 }
24545
24546 fn next_row(&self) -> Self {
24547 Self(self.0 + 1)
24548 }
24549
24550 fn previous_row(&self) -> Self {
24551 Self(self.0.saturating_sub(1))
24552 }
24553
24554 fn minus(&self, other: Self) -> u32 {
24555 self.0 - other.0
24556 }
24557}
24558
24559impl RowExt for MultiBufferRow {
24560 fn as_f64(&self) -> f64 {
24561 self.0 as _
24562 }
24563
24564 fn next_row(&self) -> Self {
24565 Self(self.0 + 1)
24566 }
24567
24568 fn previous_row(&self) -> Self {
24569 Self(self.0.saturating_sub(1))
24570 }
24571
24572 fn minus(&self, other: Self) -> u32 {
24573 self.0 - other.0
24574 }
24575}
24576
24577trait RowRangeExt {
24578 type Row;
24579
24580 fn len(&self) -> usize;
24581
24582 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24583}
24584
24585impl RowRangeExt for Range<MultiBufferRow> {
24586 type Row = MultiBufferRow;
24587
24588 fn len(&self) -> usize {
24589 (self.end.0 - self.start.0) as usize
24590 }
24591
24592 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24593 (self.start.0..self.end.0).map(MultiBufferRow)
24594 }
24595}
24596
24597impl RowRangeExt for Range<DisplayRow> {
24598 type Row = DisplayRow;
24599
24600 fn len(&self) -> usize {
24601 (self.end.0 - self.start.0) as usize
24602 }
24603
24604 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24605 (self.start.0..self.end.0).map(DisplayRow)
24606 }
24607}
24608
24609/// If select range has more than one line, we
24610/// just point the cursor to range.start.
24611fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24612 if range.start.row == range.end.row {
24613 range
24614 } else {
24615 range.start..range.start
24616 }
24617}
24618pub struct KillRing(ClipboardItem);
24619impl Global for KillRing {}
24620
24621const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24622
24623enum BreakpointPromptEditAction {
24624 Log,
24625 Condition,
24626 HitCondition,
24627}
24628
24629struct BreakpointPromptEditor {
24630 pub(crate) prompt: Entity<Editor>,
24631 editor: WeakEntity<Editor>,
24632 breakpoint_anchor: Anchor,
24633 breakpoint: Breakpoint,
24634 edit_action: BreakpointPromptEditAction,
24635 block_ids: HashSet<CustomBlockId>,
24636 editor_margins: Arc<Mutex<EditorMargins>>,
24637 _subscriptions: Vec<Subscription>,
24638}
24639
24640impl BreakpointPromptEditor {
24641 const MAX_LINES: u8 = 4;
24642
24643 fn new(
24644 editor: WeakEntity<Editor>,
24645 breakpoint_anchor: Anchor,
24646 breakpoint: Breakpoint,
24647 edit_action: BreakpointPromptEditAction,
24648 window: &mut Window,
24649 cx: &mut Context<Self>,
24650 ) -> Self {
24651 let base_text = match edit_action {
24652 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24653 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24654 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24655 }
24656 .map(|msg| msg.to_string())
24657 .unwrap_or_default();
24658
24659 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24660 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24661
24662 let prompt = cx.new(|cx| {
24663 let mut prompt = Editor::new(
24664 EditorMode::AutoHeight {
24665 min_lines: 1,
24666 max_lines: Some(Self::MAX_LINES as usize),
24667 },
24668 buffer,
24669 None,
24670 window,
24671 cx,
24672 );
24673 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24674 prompt.set_show_cursor_when_unfocused(false, cx);
24675 prompt.set_placeholder_text(
24676 match edit_action {
24677 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24678 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24679 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24680 },
24681 window,
24682 cx,
24683 );
24684
24685 prompt
24686 });
24687
24688 Self {
24689 prompt,
24690 editor,
24691 breakpoint_anchor,
24692 breakpoint,
24693 edit_action,
24694 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24695 block_ids: Default::default(),
24696 _subscriptions: vec![],
24697 }
24698 }
24699
24700 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24701 self.block_ids.extend(block_ids)
24702 }
24703
24704 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24705 if let Some(editor) = self.editor.upgrade() {
24706 let message = self
24707 .prompt
24708 .read(cx)
24709 .buffer
24710 .read(cx)
24711 .as_singleton()
24712 .expect("A multi buffer in breakpoint prompt isn't possible")
24713 .read(cx)
24714 .as_rope()
24715 .to_string();
24716
24717 editor.update(cx, |editor, cx| {
24718 editor.edit_breakpoint_at_anchor(
24719 self.breakpoint_anchor,
24720 self.breakpoint.clone(),
24721 match self.edit_action {
24722 BreakpointPromptEditAction::Log => {
24723 BreakpointEditAction::EditLogMessage(message.into())
24724 }
24725 BreakpointPromptEditAction::Condition => {
24726 BreakpointEditAction::EditCondition(message.into())
24727 }
24728 BreakpointPromptEditAction::HitCondition => {
24729 BreakpointEditAction::EditHitCondition(message.into())
24730 }
24731 },
24732 cx,
24733 );
24734
24735 editor.remove_blocks(self.block_ids.clone(), None, cx);
24736 cx.focus_self(window);
24737 });
24738 }
24739 }
24740
24741 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24742 self.editor
24743 .update(cx, |editor, cx| {
24744 editor.remove_blocks(self.block_ids.clone(), None, cx);
24745 window.focus(&editor.focus_handle);
24746 })
24747 .log_err();
24748 }
24749
24750 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24751 let settings = ThemeSettings::get_global(cx);
24752 let text_style = TextStyle {
24753 color: if self.prompt.read(cx).read_only(cx) {
24754 cx.theme().colors().text_disabled
24755 } else {
24756 cx.theme().colors().text
24757 },
24758 font_family: settings.buffer_font.family.clone(),
24759 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24760 font_size: settings.buffer_font_size(cx).into(),
24761 font_weight: settings.buffer_font.weight,
24762 line_height: relative(settings.buffer_line_height.value()),
24763 ..Default::default()
24764 };
24765 EditorElement::new(
24766 &self.prompt,
24767 EditorStyle {
24768 background: cx.theme().colors().editor_background,
24769 local_player: cx.theme().players().local(),
24770 text: text_style,
24771 ..Default::default()
24772 },
24773 )
24774 }
24775}
24776
24777impl Render for BreakpointPromptEditor {
24778 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24779 let editor_margins = *self.editor_margins.lock();
24780 let gutter_dimensions = editor_margins.gutter;
24781 h_flex()
24782 .key_context("Editor")
24783 .bg(cx.theme().colors().editor_background)
24784 .border_y_1()
24785 .border_color(cx.theme().status().info_border)
24786 .size_full()
24787 .py(window.line_height() / 2.5)
24788 .on_action(cx.listener(Self::confirm))
24789 .on_action(cx.listener(Self::cancel))
24790 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24791 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24792 }
24793}
24794
24795impl Focusable for BreakpointPromptEditor {
24796 fn focus_handle(&self, cx: &App) -> FocusHandle {
24797 self.prompt.focus_handle(cx)
24798 }
24799}
24800
24801fn all_edits_insertions_or_deletions(
24802 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24803 snapshot: &MultiBufferSnapshot,
24804) -> bool {
24805 let mut all_insertions = true;
24806 let mut all_deletions = true;
24807
24808 for (range, new_text) in edits.iter() {
24809 let range_is_empty = range.to_offset(snapshot).is_empty();
24810 let text_is_empty = new_text.is_empty();
24811
24812 if range_is_empty != text_is_empty {
24813 if range_is_empty {
24814 all_deletions = false;
24815 } else {
24816 all_insertions = false;
24817 }
24818 } else {
24819 return false;
24820 }
24821
24822 if !all_insertions && !all_deletions {
24823 return false;
24824 }
24825 }
24826 all_insertions || all_deletions
24827}
24828
24829struct MissingEditPredictionKeybindingTooltip;
24830
24831impl Render for MissingEditPredictionKeybindingTooltip {
24832 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24833 ui::tooltip_container(cx, |container, cx| {
24834 container
24835 .flex_shrink_0()
24836 .max_w_80()
24837 .min_h(rems_from_px(124.))
24838 .justify_between()
24839 .child(
24840 v_flex()
24841 .flex_1()
24842 .text_ui_sm(cx)
24843 .child(Label::new("Conflict with Accept Keybinding"))
24844 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24845 )
24846 .child(
24847 h_flex()
24848 .pb_1()
24849 .gap_1()
24850 .items_end()
24851 .w_full()
24852 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24853 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24854 }))
24855 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24856 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24857 })),
24858 )
24859 })
24860 }
24861}
24862
24863#[derive(Debug, Clone, Copy, PartialEq)]
24864pub struct LineHighlight {
24865 pub background: Background,
24866 pub border: Option<gpui::Hsla>,
24867 pub include_gutter: bool,
24868 pub type_id: Option<TypeId>,
24869}
24870
24871struct LineManipulationResult {
24872 pub new_text: String,
24873 pub line_count_before: usize,
24874 pub line_count_after: usize,
24875}
24876
24877fn render_diff_hunk_controls(
24878 row: u32,
24879 status: &DiffHunkStatus,
24880 hunk_range: Range<Anchor>,
24881 is_created_file: bool,
24882 line_height: Pixels,
24883 editor: &Entity<Editor>,
24884 _window: &mut Window,
24885 cx: &mut App,
24886) -> AnyElement {
24887 h_flex()
24888 .h(line_height)
24889 .mr_1()
24890 .gap_1()
24891 .px_0p5()
24892 .pb_1()
24893 .border_x_1()
24894 .border_b_1()
24895 .border_color(cx.theme().colors().border_variant)
24896 .rounded_b_lg()
24897 .bg(cx.theme().colors().editor_background)
24898 .gap_1()
24899 .block_mouse_except_scroll()
24900 .shadow_md()
24901 .child(if status.has_secondary_hunk() {
24902 Button::new(("stage", row as u64), "Stage")
24903 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24904 .tooltip({
24905 let focus_handle = editor.focus_handle(cx);
24906 move |_window, cx| {
24907 Tooltip::for_action_in(
24908 "Stage Hunk",
24909 &::git::ToggleStaged,
24910 &focus_handle,
24911 cx,
24912 )
24913 }
24914 })
24915 .on_click({
24916 let editor = editor.clone();
24917 move |_event, _window, cx| {
24918 editor.update(cx, |editor, cx| {
24919 editor.stage_or_unstage_diff_hunks(
24920 true,
24921 vec![hunk_range.start..hunk_range.start],
24922 cx,
24923 );
24924 });
24925 }
24926 })
24927 } else {
24928 Button::new(("unstage", row as u64), "Unstage")
24929 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24930 .tooltip({
24931 let focus_handle = editor.focus_handle(cx);
24932 move |_window, cx| {
24933 Tooltip::for_action_in(
24934 "Unstage Hunk",
24935 &::git::ToggleStaged,
24936 &focus_handle,
24937 cx,
24938 )
24939 }
24940 })
24941 .on_click({
24942 let editor = editor.clone();
24943 move |_event, _window, cx| {
24944 editor.update(cx, |editor, cx| {
24945 editor.stage_or_unstage_diff_hunks(
24946 false,
24947 vec![hunk_range.start..hunk_range.start],
24948 cx,
24949 );
24950 });
24951 }
24952 })
24953 })
24954 .child(
24955 Button::new(("restore", row as u64), "Restore")
24956 .tooltip({
24957 let focus_handle = editor.focus_handle(cx);
24958 move |_window, cx| {
24959 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24960 }
24961 })
24962 .on_click({
24963 let editor = editor.clone();
24964 move |_event, window, cx| {
24965 editor.update(cx, |editor, cx| {
24966 let snapshot = editor.snapshot(window, cx);
24967 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24968 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24969 });
24970 }
24971 })
24972 .disabled(is_created_file),
24973 )
24974 .when(
24975 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24976 |el| {
24977 el.child(
24978 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24979 .shape(IconButtonShape::Square)
24980 .icon_size(IconSize::Small)
24981 // .disabled(!has_multiple_hunks)
24982 .tooltip({
24983 let focus_handle = editor.focus_handle(cx);
24984 move |_window, cx| {
24985 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24986 }
24987 })
24988 .on_click({
24989 let editor = editor.clone();
24990 move |_event, window, cx| {
24991 editor.update(cx, |editor, cx| {
24992 let snapshot = editor.snapshot(window, cx);
24993 let position =
24994 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24995 editor.go_to_hunk_before_or_after_position(
24996 &snapshot,
24997 position,
24998 Direction::Next,
24999 window,
25000 cx,
25001 );
25002 editor.expand_selected_diff_hunks(cx);
25003 });
25004 }
25005 }),
25006 )
25007 .child(
25008 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25009 .shape(IconButtonShape::Square)
25010 .icon_size(IconSize::Small)
25011 // .disabled(!has_multiple_hunks)
25012 .tooltip({
25013 let focus_handle = editor.focus_handle(cx);
25014 move |_window, cx| {
25015 Tooltip::for_action_in(
25016 "Previous Hunk",
25017 &GoToPreviousHunk,
25018 &focus_handle,
25019 cx,
25020 )
25021 }
25022 })
25023 .on_click({
25024 let editor = editor.clone();
25025 move |_event, window, cx| {
25026 editor.update(cx, |editor, cx| {
25027 let snapshot = editor.snapshot(window, cx);
25028 let point =
25029 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25030 editor.go_to_hunk_before_or_after_position(
25031 &snapshot,
25032 point,
25033 Direction::Prev,
25034 window,
25035 cx,
25036 );
25037 editor.expand_selected_diff_hunks(cx);
25038 });
25039 }
25040 }),
25041 )
25042 },
25043 )
25044 .into_any_element()
25045}
25046
25047pub fn multibuffer_context_lines(cx: &App) -> u32 {
25048 EditorSettings::try_get(cx)
25049 .map(|settings| settings.excerpt_context_lines)
25050 .unwrap_or(2)
25051 .min(32)
25052}