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;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79 status::FileStatus,
80};
81use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
82use anyhow::{Context as _, Result, anyhow};
83use blink_manager::BlinkManager;
84use buffer_diff::DiffHunkStatus;
85use client::{Collaborator, ParticipantIndex, parse_zed_link};
86use clock::ReplicaId;
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use collections::{BTreeMap, HashMap, HashSet, VecDeque};
92use convert_case::{Case, Casing};
93use dap::TelemetrySpawnLocation;
94use display_map::*;
95use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
111 ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
112 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
113 WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
114};
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
125 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, Peekable},
183 mem,
184 num::NonZeroU32,
185 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
193use theme::{
194 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287pub struct HunkAddedColor;
288pub struct HunkRemovedColor;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291pub enum Navigated {
292 Yes,
293 No,
294}
295
296impl Navigated {
297 pub fn from_bool(yes: bool) -> Navigated {
298 if yes { Navigated::Yes } else { Navigated::No }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303enum DisplayDiffHunk {
304 Folded {
305 display_row: DisplayRow,
306 },
307 Unfolded {
308 is_created_file: bool,
309 diff_base_byte_range: Range<usize>,
310 display_row_range: Range<DisplayRow>,
311 multi_buffer_range: Range<Anchor>,
312 status: DiffHunkStatus,
313 word_diffs: Vec<Range<MultiBufferOffset>>,
314 },
315}
316
317pub enum HideMouseCursorOrigin {
318 TypingAction,
319 MovementAction,
320}
321
322pub fn init(cx: &mut App) {
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 });
355 cx.on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 language_registry: Option<Arc<LanguageRegistry>>,
384 cx: &mut App,
385 ) -> Vec<BlockProperties<Anchor>>;
386
387 fn render_hover(
388 &self,
389 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
390 range: Range<Point>,
391 buffer_id: BufferId,
392 language_registry: Option<Arc<LanguageRegistry>>,
393 cx: &mut App,
394 ) -> Option<Entity<markdown::Markdown>>;
395
396 fn open_link(
397 &self,
398 editor: &mut Editor,
399 link: SharedString,
400 window: &mut Window,
401 cx: &mut Context<Editor>,
402 );
403}
404
405pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
406
407impl GlobalDiagnosticRenderer {
408 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
409 cx.try_global::<Self>().map(|g| g.0.clone())
410 }
411}
412
413impl gpui::Global for GlobalDiagnosticRenderer {}
414pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
415 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
416}
417
418pub struct SearchWithinRange;
419
420trait InvalidationRegion {
421 fn ranges(&self) -> &[Range<Anchor>];
422}
423
424#[derive(Clone, Debug, PartialEq)]
425pub enum SelectPhase {
426 Begin {
427 position: DisplayPoint,
428 add: bool,
429 click_count: usize,
430 },
431 BeginColumnar {
432 position: DisplayPoint,
433 reset: bool,
434 mode: ColumnarMode,
435 goal_column: u32,
436 },
437 Extend {
438 position: DisplayPoint,
439 click_count: usize,
440 },
441 Update {
442 position: DisplayPoint,
443 goal_column: u32,
444 scroll_delta: gpui::Point<f32>,
445 },
446 End,
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum ColumnarMode {
451 FromMouse,
452 FromSelection,
453}
454
455#[derive(Clone, Debug)]
456pub enum SelectMode {
457 Character,
458 Word(Range<Anchor>),
459 Line(Range<Anchor>),
460 All,
461}
462
463#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
464pub enum SizingBehavior {
465 /// The editor will layout itself using `size_full` and will include the vertical
466 /// scroll margin as requested by user settings.
467 #[default]
468 Default,
469 /// The editor will layout itself using `size_full`, but will not have any
470 /// vertical overscroll.
471 ExcludeOverscrollMargin,
472 /// The editor will request a vertical size according to its content and will be
473 /// layouted without a vertical scroll margin.
474 SizeByContent,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine,
480 AutoHeight {
481 min_lines: usize,
482 max_lines: Option<usize>,
483 },
484 Full {
485 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
486 scale_ui_elements_with_buffer_font_size: bool,
487 /// When set to `true`, the editor will render a background for the active line.
488 show_active_line_background: bool,
489 /// Determines the sizing behavior for this editor
490 sizing_behavior: SizingBehavior,
491 },
492 Minimap {
493 parent: WeakEntity<Editor>,
494 },
495}
496
497impl EditorMode {
498 pub fn full() -> Self {
499 Self::Full {
500 scale_ui_elements_with_buffer_font_size: true,
501 show_active_line_background: true,
502 sizing_behavior: SizingBehavior::Default,
503 }
504 }
505
506 #[inline]
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 #[inline]
512 pub fn is_single_line(&self) -> bool {
513 matches!(self, Self::SingleLine { .. })
514 }
515
516 #[inline]
517 fn is_minimap(&self) -> bool {
518 matches!(self, Self::Minimap { .. })
519 }
520}
521
522#[derive(Copy, Clone, Debug)]
523pub enum SoftWrap {
524 /// Prefer not to wrap at all.
525 ///
526 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
527 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
528 GitDiff,
529 /// Prefer a single line generally, unless an overly long line is encountered.
530 None,
531 /// Soft wrap lines that exceed the editor width.
532 EditorWidth,
533 /// Soft wrap lines at the preferred line length.
534 Column(u32),
535 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
536 Bounded(u32),
537}
538
539#[derive(Clone)]
540pub struct EditorStyle {
541 pub background: Hsla,
542 pub border: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub edit_prediction_styles: EditPredictionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 border: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 edit_prediction_styles: EditPredictionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 let mut style = cx.theme().syntax().get("hint");
584
585 if style.color.is_none() {
586 style.color = Some(cx.theme().status().hint);
587 }
588
589 if !show_background {
590 style.background_color = None;
591 return style;
592 }
593
594 if style.background_color.is_none() {
595 style.background_color = Some(cx.theme().status().hint_background);
596 }
597
598 style
599}
600
601pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
602 EditPredictionStyles {
603 insertion: HighlightStyle {
604 color: Some(cx.theme().status().predictive),
605 ..HighlightStyle::default()
606 },
607 whitespace: HighlightStyle {
608 background_color: Some(cx.theme().status().created_background),
609 ..HighlightStyle::default()
610 },
611 }
612}
613
614type CompletionId = usize;
615
616pub(crate) enum EditDisplayMode {
617 TabAccept,
618 DiffPopover,
619 Inline,
620}
621
622enum EditPrediction {
623 Edit {
624 edits: Vec<(Range<Anchor>, Arc<str>)>,
625 edit_preview: Option<EditPreview>,
626 display_mode: EditDisplayMode,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in the active editor
630 MoveWithin {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634 /// Move to a specific location in a different editor (not the active one)
635 MoveOutside {
636 target: language::Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Option<Range<Anchor>>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
730type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
731
732#[derive(Default)]
733struct ScrollbarMarkerState {
734 scrollbar_size: Size<Pixels>,
735 dirty: bool,
736 markers: Arc<[PaintQuad]>,
737 pending_refresh: Option<Task<Result<()>>>,
738}
739
740impl ScrollbarMarkerState {
741 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
742 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
743 }
744}
745
746#[derive(Clone, Copy, PartialEq, Eq)]
747pub enum MinimapVisibility {
748 Disabled,
749 Enabled {
750 /// The configuration currently present in the users settings.
751 setting_configuration: bool,
752 /// Whether to override the currently set visibility from the users setting.
753 toggle_override: bool,
754 },
755}
756
757impl MinimapVisibility {
758 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
759 if mode.is_full() {
760 Self::Enabled {
761 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
762 toggle_override: false,
763 }
764 } else {
765 Self::Disabled
766 }
767 }
768
769 fn hidden(&self) -> Self {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 ..
774 } => Self::Enabled {
775 setting_configuration,
776 toggle_override: setting_configuration,
777 },
778 Self::Disabled => Self::Disabled,
779 }
780 }
781
782 fn disabled(&self) -> bool {
783 matches!(*self, Self::Disabled)
784 }
785
786 fn settings_visibility(&self) -> bool {
787 match *self {
788 Self::Enabled {
789 setting_configuration,
790 ..
791 } => setting_configuration,
792 _ => false,
793 }
794 }
795
796 fn visible(&self) -> bool {
797 match *self {
798 Self::Enabled {
799 setting_configuration,
800 toggle_override,
801 } => setting_configuration ^ toggle_override,
802 _ => false,
803 }
804 }
805
806 fn toggle_visibility(&self) -> Self {
807 match *self {
808 Self::Enabled {
809 toggle_override,
810 setting_configuration,
811 } => Self::Enabled {
812 setting_configuration,
813 toggle_override: !toggle_override,
814 },
815 Self::Disabled => Self::Disabled,
816 }
817 }
818}
819
820#[derive(Debug, Clone, Copy, PartialEq, Eq)]
821pub enum BufferSerialization {
822 All,
823 NonDirtyBuffers,
824}
825
826impl BufferSerialization {
827 fn new(restore_unsaved_buffers: bool) -> Self {
828 if restore_unsaved_buffers {
829 Self::All
830 } else {
831 Self::NonDirtyBuffers
832 }
833 }
834}
835
836#[derive(Clone, Debug)]
837struct RunnableTasks {
838 templates: Vec<(TaskSourceKind, TaskTemplate)>,
839 offset: multi_buffer::Anchor,
840 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
841 column: u32,
842 // Values of all named captures, including those starting with '_'
843 extra_variables: HashMap<String, String>,
844 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
845 context_range: Range<BufferOffset>,
846}
847
848impl RunnableTasks {
849 fn resolve<'a>(
850 &'a self,
851 cx: &'a task::TaskContext,
852 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
853 self.templates.iter().filter_map(|(kind, template)| {
854 template
855 .resolve_task(&kind.to_id_base(), cx)
856 .map(|task| (kind.clone(), task))
857 })
858 }
859}
860
861#[derive(Clone)]
862pub struct ResolvedTasks {
863 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
864 position: Anchor,
865}
866
867/// Addons allow storing per-editor state in other crates (e.g. Vim)
868pub trait Addon: 'static {
869 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
870
871 fn render_buffer_header_controls(
872 &self,
873 _: &ExcerptInfo,
874 _: &Window,
875 _: &App,
876 ) -> Option<AnyElement> {
877 None
878 }
879
880 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
881 None
882 }
883
884 fn to_any(&self) -> &dyn std::any::Any;
885
886 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
887 None
888 }
889}
890
891struct ChangeLocation {
892 current: Option<Vec<Anchor>>,
893 original: Vec<Anchor>,
894}
895impl ChangeLocation {
896 fn locations(&self) -> &[Anchor] {
897 self.current.as_ref().unwrap_or(&self.original)
898 }
899}
900
901/// A set of caret positions, registered when the editor was edited.
902pub struct ChangeList {
903 changes: Vec<ChangeLocation>,
904 /// Currently "selected" change.
905 position: Option<usize>,
906}
907
908impl ChangeList {
909 pub fn new() -> Self {
910 Self {
911 changes: Vec::new(),
912 position: None,
913 }
914 }
915
916 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
917 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
918 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
919 if self.changes.is_empty() {
920 return None;
921 }
922
923 let prev = self.position.unwrap_or(self.changes.len());
924 let next = if direction == Direction::Prev {
925 prev.saturating_sub(count)
926 } else {
927 (prev + count).min(self.changes.len() - 1)
928 };
929 self.position = Some(next);
930 self.changes.get(next).map(|change| change.locations())
931 }
932
933 /// Adds a new change to the list, resetting the change list position.
934 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
935 self.position.take();
936 if let Some(last) = self.changes.last_mut()
937 && group
938 {
939 last.current = Some(new_positions)
940 } else {
941 self.changes.push(ChangeLocation {
942 original: new_positions,
943 current: None,
944 });
945 }
946 }
947
948 pub fn last(&self) -> Option<&[Anchor]> {
949 self.changes.last().map(|change| change.locations())
950 }
951
952 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
953 self.changes.last().map(|change| change.original.as_slice())
954 }
955
956 pub fn invert_last_group(&mut self) {
957 if let Some(last) = self.changes.last_mut()
958 && let Some(current) = last.current.as_mut()
959 {
960 mem::swap(&mut last.original, current);
961 }
962 }
963}
964
965#[derive(Clone)]
966struct InlineBlamePopoverState {
967 scroll_handle: ScrollHandle,
968 commit_message: Option<ParsedCommitMessage>,
969 markdown: Entity<Markdown>,
970}
971
972struct InlineBlamePopover {
973 position: gpui::Point<Pixels>,
974 hide_task: Option<Task<()>>,
975 popover_bounds: Option<Bounds<Pixels>>,
976 popover_state: InlineBlamePopoverState,
977 keyboard_grace: bool,
978}
979
980enum SelectionDragState {
981 /// State when no drag related activity is detected.
982 None,
983 /// State when the mouse is down on a selection that is about to be dragged.
984 ReadyToDrag {
985 selection: Selection<Anchor>,
986 click_position: gpui::Point<Pixels>,
987 mouse_down_time: Instant,
988 },
989 /// State when the mouse is dragging the selection in the editor.
990 Dragging {
991 selection: Selection<Anchor>,
992 drop_cursor: Selection<Anchor>,
993 hide_drop_cursor: bool,
994 },
995}
996
997enum ColumnarSelectionState {
998 FromMouse {
999 selection_tail: Anchor,
1000 display_point: Option<DisplayPoint>,
1001 },
1002 FromSelection {
1003 selection_tail: Anchor,
1004 },
1005}
1006
1007/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1008/// a breakpoint on them.
1009#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1010struct PhantomBreakpointIndicator {
1011 display_row: DisplayRow,
1012 /// There's a small debounce between hovering over the line and showing the indicator.
1013 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1014 is_active: bool,
1015 collides_with_existing_breakpoint: bool,
1016}
1017
1018/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1019///
1020/// See the [module level documentation](self) for more information.
1021pub struct Editor {
1022 focus_handle: FocusHandle,
1023 last_focused_descendant: Option<WeakFocusHandle>,
1024 /// The text buffer being edited
1025 buffer: Entity<MultiBuffer>,
1026 /// Map of how text in the buffer should be displayed.
1027 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1028 pub display_map: Entity<DisplayMap>,
1029 placeholder_display_map: Option<Entity<DisplayMap>>,
1030 pub selections: SelectionsCollection,
1031 pub scroll_manager: ScrollManager,
1032 /// When inline assist editors are linked, they all render cursors because
1033 /// typing enters text into each of them, even the ones that aren't focused.
1034 pub(crate) show_cursor_when_unfocused: bool,
1035 columnar_selection_state: Option<ColumnarSelectionState>,
1036 add_selections_state: Option<AddSelectionsState>,
1037 select_next_state: Option<SelectNextState>,
1038 select_prev_state: Option<SelectNextState>,
1039 selection_history: SelectionHistory,
1040 defer_selection_effects: bool,
1041 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1042 autoclose_regions: Vec<AutocloseRegion>,
1043 snippet_stack: InvalidationStack<SnippetState>,
1044 select_syntax_node_history: SelectSyntaxNodeHistory,
1045 ime_transaction: Option<TransactionId>,
1046 pub diagnostics_max_severity: DiagnosticSeverity,
1047 active_diagnostics: ActiveDiagnostic,
1048 show_inline_diagnostics: bool,
1049 inline_diagnostics_update: Task<()>,
1050 inline_diagnostics_enabled: bool,
1051 diagnostics_enabled: bool,
1052 word_completions_enabled: bool,
1053 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1054 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1055 hard_wrap: Option<usize>,
1056 project: Option<Entity<Project>>,
1057 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1058 completion_provider: Option<Rc<dyn CompletionProvider>>,
1059 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1060 blink_manager: Entity<BlinkManager>,
1061 show_cursor_names: bool,
1062 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1063 pub show_local_selections: bool,
1064 mode: EditorMode,
1065 show_breadcrumbs: bool,
1066 show_gutter: bool,
1067 show_scrollbars: ScrollbarAxes,
1068 minimap_visibility: MinimapVisibility,
1069 offset_content: bool,
1070 disable_expand_excerpt_buttons: bool,
1071 show_line_numbers: Option<bool>,
1072 use_relative_line_numbers: Option<bool>,
1073 show_git_diff_gutter: Option<bool>,
1074 show_code_actions: Option<bool>,
1075 show_runnables: Option<bool>,
1076 show_breakpoints: Option<bool>,
1077 show_wrap_guides: Option<bool>,
1078 show_indent_guides: Option<bool>,
1079 highlight_order: usize,
1080 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1081 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1082 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1083 scrollbar_marker_state: ScrollbarMarkerState,
1084 active_indent_guides_state: ActiveIndentGuidesState,
1085 nav_history: Option<ItemNavHistory>,
1086 context_menu: RefCell<Option<CodeContextMenu>>,
1087 context_menu_options: Option<ContextMenuOptions>,
1088 mouse_context_menu: Option<MouseContextMenu>,
1089 completion_tasks: Vec<(CompletionId, Task<()>)>,
1090 inline_blame_popover: Option<InlineBlamePopover>,
1091 inline_blame_popover_show_task: Option<Task<()>>,
1092 signature_help_state: SignatureHelpState,
1093 auto_signature_help: Option<bool>,
1094 find_all_references_task_sources: Vec<Anchor>,
1095 next_completion_id: CompletionId,
1096 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1097 code_actions_task: Option<Task<Result<()>>>,
1098 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1099 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1100 document_highlights_task: Option<Task<()>>,
1101 linked_editing_range_task: Option<Task<Option<()>>>,
1102 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1103 pending_rename: Option<RenameState>,
1104 searchable: bool,
1105 cursor_shape: CursorShape,
1106 current_line_highlight: Option<CurrentLineHighlight>,
1107 pub collapse_matches: bool,
1108 autoindent_mode: Option<AutoindentMode>,
1109 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1110 input_enabled: bool,
1111 use_modal_editing: bool,
1112 read_only: bool,
1113 leader_id: Option<CollaboratorId>,
1114 remote_id: Option<ViewId>,
1115 pub hover_state: HoverState,
1116 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1117 gutter_hovered: bool,
1118 hovered_link_state: Option<HoveredLinkState>,
1119 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1120 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1121 active_edit_prediction: Option<EditPredictionState>,
1122 /// Used to prevent flickering as the user types while the menu is open
1123 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1124 edit_prediction_settings: EditPredictionSettings,
1125 edit_predictions_hidden_for_vim_mode: bool,
1126 show_edit_predictions_override: Option<bool>,
1127 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1128 edit_prediction_preview: EditPredictionPreview,
1129 edit_prediction_indent_conflict: bool,
1130 edit_prediction_requires_modifier_in_indent_conflict: bool,
1131 next_inlay_id: usize,
1132 next_color_inlay_id: usize,
1133 _subscriptions: Vec<Subscription>,
1134 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1135 gutter_dimensions: GutterDimensions,
1136 style: Option<EditorStyle>,
1137 text_style_refinement: Option<TextStyleRefinement>,
1138 next_editor_action_id: EditorActionId,
1139 editor_actions: Rc<
1140 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1141 >,
1142 use_autoclose: bool,
1143 use_auto_surround: bool,
1144 auto_replace_emoji_shortcode: bool,
1145 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1146 show_git_blame_gutter: bool,
1147 show_git_blame_inline: bool,
1148 show_git_blame_inline_delay_task: Option<Task<()>>,
1149 git_blame_inline_enabled: bool,
1150 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1151 buffer_serialization: Option<BufferSerialization>,
1152 show_selection_menu: Option<bool>,
1153 blame: Option<Entity<GitBlame>>,
1154 blame_subscription: Option<Subscription>,
1155 custom_context_menu: Option<
1156 Box<
1157 dyn 'static
1158 + Fn(
1159 &mut Self,
1160 DisplayPoint,
1161 &mut Window,
1162 &mut Context<Self>,
1163 ) -> Option<Entity<ui::ContextMenu>>,
1164 >,
1165 >,
1166 last_bounds: Option<Bounds<Pixels>>,
1167 last_position_map: Option<Rc<PositionMap>>,
1168 expect_bounds_change: Option<Bounds<Pixels>>,
1169 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1170 tasks_update_task: Option<Task<()>>,
1171 breakpoint_store: Option<Entity<BreakpointStore>>,
1172 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1173 hovered_diff_hunk_row: Option<DisplayRow>,
1174 pull_diagnostics_task: Task<()>,
1175 in_project_search: bool,
1176 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1177 breadcrumb_header: Option<String>,
1178 focused_block: Option<FocusedBlock>,
1179 next_scroll_position: NextScrollCursorCenterTopBottom,
1180 addons: HashMap<TypeId, Box<dyn Addon>>,
1181 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1182 load_diff_task: Option<Shared<Task<()>>>,
1183 /// Whether we are temporarily displaying a diff other than git's
1184 temporary_diff_override: bool,
1185 selection_mark_mode: bool,
1186 toggle_fold_multiple_buffers: Task<()>,
1187 _scroll_cursor_center_top_bottom_task: Task<()>,
1188 serialize_selections: Task<()>,
1189 serialize_folds: Task<()>,
1190 mouse_cursor_hidden: bool,
1191 minimap: Option<Entity<Self>>,
1192 hide_mouse_mode: HideMouseMode,
1193 pub change_list: ChangeList,
1194 inline_value_cache: InlineValueCache,
1195
1196 selection_drag_state: SelectionDragState,
1197 colors: Option<LspColorData>,
1198 post_scroll_update: Task<()>,
1199 refresh_colors_task: Task<()>,
1200 inlay_hints: Option<LspInlayHintData>,
1201 folding_newlines: Task<()>,
1202 select_next_is_case_sensitive: Option<bool>,
1203 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1204 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1205 accent_overrides: Vec<SharedString>,
1206 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1207}
1208
1209fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1210 if debounce_ms > 0 {
1211 Some(Duration::from_millis(debounce_ms))
1212 } else {
1213 None
1214 }
1215}
1216
1217#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1218enum NextScrollCursorCenterTopBottom {
1219 #[default]
1220 Center,
1221 Top,
1222 Bottom,
1223}
1224
1225impl NextScrollCursorCenterTopBottom {
1226 fn next(&self) -> Self {
1227 match self {
1228 Self::Center => Self::Top,
1229 Self::Top => Self::Bottom,
1230 Self::Bottom => Self::Center,
1231 }
1232 }
1233}
1234
1235#[derive(Clone)]
1236pub struct EditorSnapshot {
1237 pub mode: EditorMode,
1238 show_gutter: bool,
1239 show_line_numbers: Option<bool>,
1240 show_git_diff_gutter: Option<bool>,
1241 show_code_actions: Option<bool>,
1242 show_runnables: Option<bool>,
1243 show_breakpoints: Option<bool>,
1244 git_blame_gutter_max_author_length: Option<usize>,
1245 pub display_snapshot: DisplaySnapshot,
1246 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1247 is_focused: bool,
1248 scroll_anchor: ScrollAnchor,
1249 ongoing_scroll: OngoingScroll,
1250 current_line_highlight: CurrentLineHighlight,
1251 gutter_hovered: bool,
1252}
1253
1254#[derive(Default, Debug, Clone, Copy)]
1255pub struct GutterDimensions {
1256 pub left_padding: Pixels,
1257 pub right_padding: Pixels,
1258 pub width: Pixels,
1259 pub margin: Pixels,
1260 pub git_blame_entries_width: Option<Pixels>,
1261}
1262
1263impl GutterDimensions {
1264 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1265 Self {
1266 margin: Self::default_gutter_margin(font_id, font_size, cx),
1267 ..Default::default()
1268 }
1269 }
1270
1271 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1272 -cx.text_system().descent(font_id, font_size)
1273 }
1274 /// The full width of the space taken up by the gutter.
1275 pub fn full_width(&self) -> Pixels {
1276 self.margin + self.width
1277 }
1278
1279 /// The width of the space reserved for the fold indicators,
1280 /// use alongside 'justify_end' and `gutter_width` to
1281 /// right align content with the line numbers
1282 pub fn fold_area_width(&self) -> Pixels {
1283 self.margin + self.right_padding
1284 }
1285}
1286
1287struct CharacterDimensions {
1288 em_width: Pixels,
1289 em_advance: Pixels,
1290 line_height: Pixels,
1291}
1292
1293#[derive(Debug)]
1294pub struct RemoteSelection {
1295 pub replica_id: ReplicaId,
1296 pub selection: Selection<Anchor>,
1297 pub cursor_shape: CursorShape,
1298 pub collaborator_id: CollaboratorId,
1299 pub line_mode: bool,
1300 pub user_name: Option<SharedString>,
1301 pub color: PlayerColor,
1302}
1303
1304#[derive(Clone, Debug)]
1305struct SelectionHistoryEntry {
1306 selections: Arc<[Selection<Anchor>]>,
1307 select_next_state: Option<SelectNextState>,
1308 select_prev_state: Option<SelectNextState>,
1309 add_selections_state: Option<AddSelectionsState>,
1310}
1311
1312#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1313enum SelectionHistoryMode {
1314 #[default]
1315 Normal,
1316 Undoing,
1317 Redoing,
1318 Skipping,
1319}
1320
1321#[derive(Clone, PartialEq, Eq, Hash)]
1322struct HoveredCursor {
1323 replica_id: ReplicaId,
1324 selection_id: usize,
1325}
1326
1327#[derive(Debug)]
1328/// SelectionEffects controls the side-effects of updating the selection.
1329///
1330/// The default behaviour does "what you mostly want":
1331/// - it pushes to the nav history if the cursor moved by >10 lines
1332/// - it re-triggers completion requests
1333/// - it scrolls to fit
1334///
1335/// You might want to modify these behaviours. For example when doing a "jump"
1336/// like go to definition, we always want to add to nav history; but when scrolling
1337/// in vim mode we never do.
1338///
1339/// Similarly, you might want to disable scrolling if you don't want the viewport to
1340/// move.
1341#[derive(Clone)]
1342pub struct SelectionEffects {
1343 nav_history: Option<bool>,
1344 completions: bool,
1345 scroll: Option<Autoscroll>,
1346}
1347
1348impl Default for SelectionEffects {
1349 fn default() -> Self {
1350 Self {
1351 nav_history: None,
1352 completions: true,
1353 scroll: Some(Autoscroll::fit()),
1354 }
1355 }
1356}
1357impl SelectionEffects {
1358 pub fn scroll(scroll: Autoscroll) -> Self {
1359 Self {
1360 scroll: Some(scroll),
1361 ..Default::default()
1362 }
1363 }
1364
1365 pub fn no_scroll() -> Self {
1366 Self {
1367 scroll: None,
1368 ..Default::default()
1369 }
1370 }
1371
1372 pub fn completions(self, completions: bool) -> Self {
1373 Self {
1374 completions,
1375 ..self
1376 }
1377 }
1378
1379 pub fn nav_history(self, nav_history: bool) -> Self {
1380 Self {
1381 nav_history: Some(nav_history),
1382 ..self
1383 }
1384 }
1385}
1386
1387struct DeferredSelectionEffectsState {
1388 changed: bool,
1389 effects: SelectionEffects,
1390 old_cursor_position: Anchor,
1391 history_entry: SelectionHistoryEntry,
1392}
1393
1394#[derive(Default)]
1395struct SelectionHistory {
1396 #[allow(clippy::type_complexity)]
1397 selections_by_transaction:
1398 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1399 mode: SelectionHistoryMode,
1400 undo_stack: VecDeque<SelectionHistoryEntry>,
1401 redo_stack: VecDeque<SelectionHistoryEntry>,
1402}
1403
1404impl SelectionHistory {
1405 #[track_caller]
1406 fn insert_transaction(
1407 &mut self,
1408 transaction_id: TransactionId,
1409 selections: Arc<[Selection<Anchor>]>,
1410 ) {
1411 if selections.is_empty() {
1412 log::error!(
1413 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1414 std::panic::Location::caller()
1415 );
1416 return;
1417 }
1418 self.selections_by_transaction
1419 .insert(transaction_id, (selections, None));
1420 }
1421
1422 #[allow(clippy::type_complexity)]
1423 fn transaction(
1424 &self,
1425 transaction_id: TransactionId,
1426 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1427 self.selections_by_transaction.get(&transaction_id)
1428 }
1429
1430 #[allow(clippy::type_complexity)]
1431 fn transaction_mut(
1432 &mut self,
1433 transaction_id: TransactionId,
1434 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1435 self.selections_by_transaction.get_mut(&transaction_id)
1436 }
1437
1438 fn push(&mut self, entry: SelectionHistoryEntry) {
1439 if !entry.selections.is_empty() {
1440 match self.mode {
1441 SelectionHistoryMode::Normal => {
1442 self.push_undo(entry);
1443 self.redo_stack.clear();
1444 }
1445 SelectionHistoryMode::Undoing => self.push_redo(entry),
1446 SelectionHistoryMode::Redoing => self.push_undo(entry),
1447 SelectionHistoryMode::Skipping => {}
1448 }
1449 }
1450 }
1451
1452 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1453 if self
1454 .undo_stack
1455 .back()
1456 .is_none_or(|e| e.selections != entry.selections)
1457 {
1458 self.undo_stack.push_back(entry);
1459 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1460 self.undo_stack.pop_front();
1461 }
1462 }
1463 }
1464
1465 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1466 if self
1467 .redo_stack
1468 .back()
1469 .is_none_or(|e| e.selections != entry.selections)
1470 {
1471 self.redo_stack.push_back(entry);
1472 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1473 self.redo_stack.pop_front();
1474 }
1475 }
1476 }
1477}
1478
1479#[derive(Clone, Copy)]
1480pub struct RowHighlightOptions {
1481 pub autoscroll: bool,
1482 pub include_gutter: bool,
1483}
1484
1485impl Default for RowHighlightOptions {
1486 fn default() -> Self {
1487 Self {
1488 autoscroll: Default::default(),
1489 include_gutter: true,
1490 }
1491 }
1492}
1493
1494struct RowHighlight {
1495 index: usize,
1496 range: Range<Anchor>,
1497 color: Hsla,
1498 options: RowHighlightOptions,
1499 type_id: TypeId,
1500}
1501
1502#[derive(Clone, Debug)]
1503struct AddSelectionsState {
1504 groups: Vec<AddSelectionsGroup>,
1505}
1506
1507#[derive(Clone, Debug)]
1508struct AddSelectionsGroup {
1509 above: bool,
1510 stack: Vec<usize>,
1511}
1512
1513#[derive(Clone)]
1514struct SelectNextState {
1515 query: AhoCorasick,
1516 wordwise: bool,
1517 done: bool,
1518}
1519
1520impl std::fmt::Debug for SelectNextState {
1521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1522 f.debug_struct(std::any::type_name::<Self>())
1523 .field("wordwise", &self.wordwise)
1524 .field("done", &self.done)
1525 .finish()
1526 }
1527}
1528
1529#[derive(Debug)]
1530struct AutocloseRegion {
1531 selection_id: usize,
1532 range: Range<Anchor>,
1533 pair: BracketPair,
1534}
1535
1536#[derive(Debug)]
1537struct SnippetState {
1538 ranges: Vec<Vec<Range<Anchor>>>,
1539 active_index: usize,
1540 choices: Vec<Option<Vec<String>>>,
1541}
1542
1543#[doc(hidden)]
1544pub struct RenameState {
1545 pub range: Range<Anchor>,
1546 pub old_name: Arc<str>,
1547 pub editor: Entity<Editor>,
1548 block_id: CustomBlockId,
1549}
1550
1551struct InvalidationStack<T>(Vec<T>);
1552
1553struct RegisteredEditPredictionProvider {
1554 provider: Arc<dyn EditPredictionProviderHandle>,
1555 _subscription: Subscription,
1556}
1557
1558#[derive(Debug, PartialEq, Eq)]
1559pub struct ActiveDiagnosticGroup {
1560 pub active_range: Range<Anchor>,
1561 pub active_message: String,
1562 pub group_id: usize,
1563 pub blocks: HashSet<CustomBlockId>,
1564}
1565
1566#[derive(Debug, PartialEq, Eq)]
1567
1568pub(crate) enum ActiveDiagnostic {
1569 None,
1570 All,
1571 Group(ActiveDiagnosticGroup),
1572}
1573
1574#[derive(Serialize, Deserialize, Clone, Debug)]
1575pub struct ClipboardSelection {
1576 /// The number of bytes in this selection.
1577 pub len: usize,
1578 /// Whether this was a full-line selection.
1579 pub is_entire_line: bool,
1580 /// The indentation of the first line when this content was originally copied.
1581 pub first_line_indent: u32,
1582}
1583
1584// selections, scroll behavior, was newest selection reversed
1585type SelectSyntaxNodeHistoryState = (
1586 Box<[Selection<MultiBufferOffset>]>,
1587 SelectSyntaxNodeScrollBehavior,
1588 bool,
1589);
1590
1591#[derive(Default)]
1592struct SelectSyntaxNodeHistory {
1593 stack: Vec<SelectSyntaxNodeHistoryState>,
1594 // disable temporarily to allow changing selections without losing the stack
1595 pub disable_clearing: bool,
1596}
1597
1598impl SelectSyntaxNodeHistory {
1599 pub fn try_clear(&mut self) {
1600 if !self.disable_clearing {
1601 self.stack.clear();
1602 }
1603 }
1604
1605 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1606 self.stack.push(selection);
1607 }
1608
1609 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1610 self.stack.pop()
1611 }
1612}
1613
1614enum SelectSyntaxNodeScrollBehavior {
1615 CursorTop,
1616 FitSelection,
1617 CursorBottom,
1618}
1619
1620#[derive(Debug)]
1621pub(crate) struct NavigationData {
1622 cursor_anchor: Anchor,
1623 cursor_position: Point,
1624 scroll_anchor: ScrollAnchor,
1625 scroll_top_row: u32,
1626}
1627
1628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1629pub enum GotoDefinitionKind {
1630 Symbol,
1631 Declaration,
1632 Type,
1633 Implementation,
1634}
1635
1636pub enum FormatTarget {
1637 Buffers(HashSet<Entity<Buffer>>),
1638 Ranges(Vec<Range<MultiBufferPoint>>),
1639}
1640
1641pub(crate) struct FocusedBlock {
1642 id: BlockId,
1643 focus_handle: WeakFocusHandle,
1644}
1645
1646#[derive(Clone, Debug)]
1647enum JumpData {
1648 MultiBufferRow {
1649 row: MultiBufferRow,
1650 line_offset_from_top: u32,
1651 },
1652 MultiBufferPoint {
1653 excerpt_id: ExcerptId,
1654 position: Point,
1655 anchor: text::Anchor,
1656 line_offset_from_top: u32,
1657 },
1658}
1659
1660pub enum MultibufferSelectionMode {
1661 First,
1662 All,
1663}
1664
1665#[derive(Clone, Copy, Debug, Default)]
1666pub struct RewrapOptions {
1667 pub override_language_settings: bool,
1668 pub preserve_existing_whitespace: bool,
1669}
1670
1671impl Editor {
1672 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1673 let buffer = cx.new(|cx| Buffer::local("", cx));
1674 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1675 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1676 }
1677
1678 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1679 let buffer = cx.new(|cx| Buffer::local("", cx));
1680 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1681 Self::new(EditorMode::full(), buffer, None, window, cx)
1682 }
1683
1684 pub fn auto_height(
1685 min_lines: usize,
1686 max_lines: usize,
1687 window: &mut Window,
1688 cx: &mut Context<Self>,
1689 ) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(
1693 EditorMode::AutoHeight {
1694 min_lines,
1695 max_lines: Some(max_lines),
1696 },
1697 buffer,
1698 None,
1699 window,
1700 cx,
1701 )
1702 }
1703
1704 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1705 /// The editor grows as tall as needed to fit its content.
1706 pub fn auto_height_unbounded(
1707 min_lines: usize,
1708 window: &mut Window,
1709 cx: &mut Context<Self>,
1710 ) -> Self {
1711 let buffer = cx.new(|cx| Buffer::local("", cx));
1712 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1713 Self::new(
1714 EditorMode::AutoHeight {
1715 min_lines,
1716 max_lines: None,
1717 },
1718 buffer,
1719 None,
1720 window,
1721 cx,
1722 )
1723 }
1724
1725 pub fn for_buffer(
1726 buffer: Entity<Buffer>,
1727 project: Option<Entity<Project>>,
1728 window: &mut Window,
1729 cx: &mut Context<Self>,
1730 ) -> Self {
1731 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1732 Self::new(EditorMode::full(), buffer, project, window, cx)
1733 }
1734
1735 pub fn for_multibuffer(
1736 buffer: Entity<MultiBuffer>,
1737 project: Option<Entity<Project>>,
1738 window: &mut Window,
1739 cx: &mut Context<Self>,
1740 ) -> Self {
1741 Self::new(EditorMode::full(), buffer, project, window, cx)
1742 }
1743
1744 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1745 let mut clone = Self::new(
1746 self.mode.clone(),
1747 self.buffer.clone(),
1748 self.project.clone(),
1749 window,
1750 cx,
1751 );
1752 self.display_map.update(cx, |display_map, cx| {
1753 let snapshot = display_map.snapshot(cx);
1754 clone.display_map.update(cx, |display_map, cx| {
1755 display_map.set_state(&snapshot, cx);
1756 });
1757 });
1758 clone.folds_did_change(cx);
1759 clone.selections.clone_state(&self.selections);
1760 clone.scroll_manager.clone_state(&self.scroll_manager);
1761 clone.searchable = self.searchable;
1762 clone.read_only = self.read_only;
1763 clone
1764 }
1765
1766 pub fn new(
1767 mode: EditorMode,
1768 buffer: Entity<MultiBuffer>,
1769 project: Option<Entity<Project>>,
1770 window: &mut Window,
1771 cx: &mut Context<Self>,
1772 ) -> Self {
1773 Editor::new_internal(mode, buffer, project, None, window, cx)
1774 }
1775
1776 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1777 let multi_buffer = self.buffer().read(cx);
1778 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1779 let multi_buffer_visible_start = self
1780 .scroll_manager
1781 .anchor()
1782 .anchor
1783 .to_point(&multi_buffer_snapshot);
1784 let max_row = multi_buffer_snapshot.max_point().row;
1785
1786 let start_row = (multi_buffer_visible_start.row).min(max_row);
1787 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1788
1789 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1790 let outline_items = buffer
1791 .outline_items_containing(
1792 Point::new(start_row, 0)..Point::new(end_row, 0),
1793 true,
1794 self.style().map(|style| style.syntax.as_ref()),
1795 )
1796 .into_iter()
1797 .map(|outline_item| OutlineItem {
1798 depth: outline_item.depth,
1799 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1800 source_range_for_text: Anchor::range_in_buffer(
1801 *excerpt_id,
1802 outline_item.source_range_for_text,
1803 ),
1804 text: outline_item.text,
1805 highlight_ranges: outline_item.highlight_ranges,
1806 name_ranges: outline_item.name_ranges,
1807 body_range: outline_item
1808 .body_range
1809 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1810 annotation_range: outline_item
1811 .annotation_range
1812 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1813 });
1814 return Some(outline_items.collect());
1815 }
1816
1817 None
1818 }
1819
1820 fn new_internal(
1821 mode: EditorMode,
1822 multi_buffer: Entity<MultiBuffer>,
1823 project: Option<Entity<Project>>,
1824 display_map: Option<Entity<DisplayMap>>,
1825 window: &mut Window,
1826 cx: &mut Context<Self>,
1827 ) -> Self {
1828 debug_assert!(
1829 display_map.is_none() || mode.is_minimap(),
1830 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1831 );
1832
1833 let full_mode = mode.is_full();
1834 let is_minimap = mode.is_minimap();
1835 let diagnostics_max_severity = if full_mode {
1836 EditorSettings::get_global(cx)
1837 .diagnostics_max_severity
1838 .unwrap_or(DiagnosticSeverity::Hint)
1839 } else {
1840 DiagnosticSeverity::Off
1841 };
1842 let style = window.text_style();
1843 let font_size = style.font_size.to_pixels(window.rem_size());
1844 let editor = cx.entity().downgrade();
1845 let fold_placeholder = FoldPlaceholder {
1846 constrain_width: false,
1847 render: Arc::new(move |fold_id, fold_range, cx| {
1848 let editor = editor.clone();
1849 div()
1850 .id(fold_id)
1851 .bg(cx.theme().colors().ghost_element_background)
1852 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1853 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1854 .rounded_xs()
1855 .size_full()
1856 .cursor_pointer()
1857 .child("⋯")
1858 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1859 .on_click(move |_, _window, cx| {
1860 editor
1861 .update(cx, |editor, cx| {
1862 editor.unfold_ranges(
1863 &[fold_range.start..fold_range.end],
1864 true,
1865 false,
1866 cx,
1867 );
1868 cx.stop_propagation();
1869 })
1870 .ok();
1871 })
1872 .into_any()
1873 }),
1874 merge_adjacent: true,
1875 ..FoldPlaceholder::default()
1876 };
1877 let display_map = display_map.unwrap_or_else(|| {
1878 cx.new(|cx| {
1879 DisplayMap::new(
1880 multi_buffer.clone(),
1881 style.font(),
1882 font_size,
1883 None,
1884 FILE_HEADER_HEIGHT,
1885 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1886 fold_placeholder,
1887 diagnostics_max_severity,
1888 cx,
1889 )
1890 })
1891 });
1892
1893 let selections = SelectionsCollection::new();
1894
1895 let blink_manager = cx.new(|cx| {
1896 let mut blink_manager = BlinkManager::new(
1897 CURSOR_BLINK_INTERVAL,
1898 |cx| EditorSettings::get_global(cx).cursor_blink,
1899 cx,
1900 );
1901 if is_minimap {
1902 blink_manager.disable(cx);
1903 }
1904 blink_manager
1905 });
1906
1907 let soft_wrap_mode_override =
1908 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1909
1910 let mut project_subscriptions = Vec::new();
1911 if full_mode && let Some(project) = project.as_ref() {
1912 project_subscriptions.push(cx.subscribe_in(
1913 project,
1914 window,
1915 |editor, _, event, window, cx| match event {
1916 project::Event::RefreshCodeLens => {
1917 // we always query lens with actions, without storing them, always refreshing them
1918 }
1919 project::Event::RefreshInlayHints {
1920 server_id,
1921 request_id,
1922 } => {
1923 editor.refresh_inlay_hints(
1924 InlayHintRefreshReason::RefreshRequested {
1925 server_id: *server_id,
1926 request_id: *request_id,
1927 },
1928 cx,
1929 );
1930 }
1931 project::Event::LanguageServerRemoved(..) => {
1932 if editor.tasks_update_task.is_none() {
1933 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1934 }
1935 editor.registered_buffers.clear();
1936 editor.register_visible_buffers(cx);
1937 }
1938 project::Event::LanguageServerAdded(..) => {
1939 if editor.tasks_update_task.is_none() {
1940 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1941 }
1942 }
1943 project::Event::SnippetEdit(id, snippet_edits) => {
1944 // todo(lw): Non singletons
1945 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1946 let snapshot = buffer.read(cx).snapshot();
1947 let focus_handle = editor.focus_handle(cx);
1948 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1949 for (range, snippet) in snippet_edits {
1950 let buffer_range =
1951 language::range_from_lsp(*range).to_offset(&snapshot);
1952 editor
1953 .insert_snippet(
1954 &[MultiBufferOffset(buffer_range.start)
1955 ..MultiBufferOffset(buffer_range.end)],
1956 snippet.clone(),
1957 window,
1958 cx,
1959 )
1960 .ok();
1961 }
1962 }
1963 }
1964 }
1965 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1966 let buffer_id = *buffer_id;
1967 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1968 editor.register_buffer(buffer_id, cx);
1969 editor.update_lsp_data(Some(buffer_id), window, cx);
1970 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1971 refresh_linked_ranges(editor, window, cx);
1972 editor.refresh_code_actions(window, cx);
1973 editor.refresh_document_highlights(cx);
1974 }
1975 }
1976
1977 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1978 let Some(workspace) = editor.workspace() else {
1979 return;
1980 };
1981 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1982 else {
1983 return;
1984 };
1985
1986 if active_editor.entity_id() == cx.entity_id() {
1987 let entity_id = cx.entity_id();
1988 workspace.update(cx, |this, cx| {
1989 this.panes_mut()
1990 .iter_mut()
1991 .filter(|pane| pane.entity_id() != entity_id)
1992 .for_each(|p| {
1993 p.update(cx, |pane, _| {
1994 pane.nav_history_mut().rename_item(
1995 entity_id,
1996 project_path.clone(),
1997 abs_path.clone().into(),
1998 );
1999 })
2000 });
2001 });
2002 let edited_buffers_already_open = {
2003 let other_editors: Vec<Entity<Editor>> = workspace
2004 .read(cx)
2005 .panes()
2006 .iter()
2007 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2008 .filter(|editor| editor.entity_id() != cx.entity_id())
2009 .collect();
2010
2011 transaction.0.keys().all(|buffer| {
2012 other_editors.iter().any(|editor| {
2013 let multi_buffer = editor.read(cx).buffer();
2014 multi_buffer.read(cx).is_singleton()
2015 && multi_buffer.read(cx).as_singleton().map_or(
2016 false,
2017 |singleton| {
2018 singleton.entity_id() == buffer.entity_id()
2019 },
2020 )
2021 })
2022 })
2023 };
2024 if !edited_buffers_already_open {
2025 let workspace = workspace.downgrade();
2026 let transaction = transaction.clone();
2027 cx.defer_in(window, move |_, window, cx| {
2028 cx.spawn_in(window, async move |editor, cx| {
2029 Self::open_project_transaction(
2030 &editor,
2031 workspace,
2032 transaction,
2033 "Rename".to_string(),
2034 cx,
2035 )
2036 .await
2037 .ok()
2038 })
2039 .detach();
2040 });
2041 }
2042 }
2043 }
2044
2045 _ => {}
2046 },
2047 ));
2048 if let Some(task_inventory) = project
2049 .read(cx)
2050 .task_store()
2051 .read(cx)
2052 .task_inventory()
2053 .cloned()
2054 {
2055 project_subscriptions.push(cx.observe_in(
2056 &task_inventory,
2057 window,
2058 |editor, _, window, cx| {
2059 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2060 },
2061 ));
2062 };
2063
2064 project_subscriptions.push(cx.subscribe_in(
2065 &project.read(cx).breakpoint_store(),
2066 window,
2067 |editor, _, event, window, cx| match event {
2068 BreakpointStoreEvent::ClearDebugLines => {
2069 editor.clear_row_highlights::<ActiveDebugLine>();
2070 editor.refresh_inline_values(cx);
2071 }
2072 BreakpointStoreEvent::SetDebugLine => {
2073 if editor.go_to_active_debug_line(window, cx) {
2074 cx.stop_propagation();
2075 }
2076
2077 editor.refresh_inline_values(cx);
2078 }
2079 _ => {}
2080 },
2081 ));
2082 let git_store = project.read(cx).git_store().clone();
2083 let project = project.clone();
2084 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2085 if let GitStoreEvent::RepositoryAdded = event {
2086 this.load_diff_task = Some(
2087 update_uncommitted_diff_for_buffer(
2088 cx.entity(),
2089 &project,
2090 this.buffer.read(cx).all_buffers(),
2091 this.buffer.clone(),
2092 cx,
2093 )
2094 .shared(),
2095 );
2096 }
2097 }));
2098 }
2099
2100 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2101
2102 let inlay_hint_settings =
2103 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2104 let focus_handle = cx.focus_handle();
2105 if !is_minimap {
2106 cx.on_focus(&focus_handle, window, Self::handle_focus)
2107 .detach();
2108 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2109 .detach();
2110 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2111 .detach();
2112 cx.on_blur(&focus_handle, window, Self::handle_blur)
2113 .detach();
2114 cx.observe_pending_input(window, Self::observe_pending_input)
2115 .detach();
2116 }
2117
2118 let show_indent_guides =
2119 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2120 Some(false)
2121 } else {
2122 None
2123 };
2124
2125 let breakpoint_store = match (&mode, project.as_ref()) {
2126 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2127 _ => None,
2128 };
2129
2130 let mut code_action_providers = Vec::new();
2131 let mut load_uncommitted_diff = None;
2132 if let Some(project) = project.clone() {
2133 load_uncommitted_diff = Some(
2134 update_uncommitted_diff_for_buffer(
2135 cx.entity(),
2136 &project,
2137 multi_buffer.read(cx).all_buffers(),
2138 multi_buffer.clone(),
2139 cx,
2140 )
2141 .shared(),
2142 );
2143 code_action_providers.push(Rc::new(project) as Rc<_>);
2144 }
2145
2146 let mut editor = Self {
2147 focus_handle,
2148 show_cursor_when_unfocused: false,
2149 last_focused_descendant: None,
2150 buffer: multi_buffer.clone(),
2151 display_map: display_map.clone(),
2152 placeholder_display_map: None,
2153 selections,
2154 scroll_manager: ScrollManager::new(cx),
2155 columnar_selection_state: None,
2156 add_selections_state: None,
2157 select_next_state: None,
2158 select_prev_state: None,
2159 selection_history: SelectionHistory::default(),
2160 defer_selection_effects: false,
2161 deferred_selection_effects_state: None,
2162 autoclose_regions: Vec::new(),
2163 snippet_stack: InvalidationStack::default(),
2164 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2165 ime_transaction: None,
2166 active_diagnostics: ActiveDiagnostic::None,
2167 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2168 inline_diagnostics_update: Task::ready(()),
2169 inline_diagnostics: Vec::new(),
2170 soft_wrap_mode_override,
2171 diagnostics_max_severity,
2172 hard_wrap: None,
2173 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2174 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2175 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2176 project,
2177 blink_manager: blink_manager.clone(),
2178 show_local_selections: true,
2179 show_scrollbars: ScrollbarAxes {
2180 horizontal: full_mode,
2181 vertical: full_mode,
2182 },
2183 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2184 offset_content: !matches!(mode, EditorMode::SingleLine),
2185 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2186 show_gutter: full_mode,
2187 show_line_numbers: (!full_mode).then_some(false),
2188 use_relative_line_numbers: None,
2189 disable_expand_excerpt_buttons: !full_mode,
2190 show_git_diff_gutter: None,
2191 show_code_actions: None,
2192 show_runnables: None,
2193 show_breakpoints: None,
2194 show_wrap_guides: None,
2195 show_indent_guides,
2196 highlight_order: 0,
2197 highlighted_rows: HashMap::default(),
2198 background_highlights: HashMap::default(),
2199 gutter_highlights: HashMap::default(),
2200 scrollbar_marker_state: ScrollbarMarkerState::default(),
2201 active_indent_guides_state: ActiveIndentGuidesState::default(),
2202 nav_history: None,
2203 context_menu: RefCell::new(None),
2204 context_menu_options: None,
2205 mouse_context_menu: None,
2206 completion_tasks: Vec::new(),
2207 inline_blame_popover: None,
2208 inline_blame_popover_show_task: None,
2209 signature_help_state: SignatureHelpState::default(),
2210 auto_signature_help: None,
2211 find_all_references_task_sources: Vec::new(),
2212 next_completion_id: 0,
2213 next_inlay_id: 0,
2214 code_action_providers,
2215 available_code_actions: None,
2216 code_actions_task: None,
2217 quick_selection_highlight_task: None,
2218 debounced_selection_highlight_task: None,
2219 document_highlights_task: None,
2220 linked_editing_range_task: None,
2221 pending_rename: None,
2222 searchable: !is_minimap,
2223 cursor_shape: EditorSettings::get_global(cx)
2224 .cursor_shape
2225 .unwrap_or_default(),
2226 current_line_highlight: None,
2227 autoindent_mode: Some(AutoindentMode::EachLine),
2228 collapse_matches: false,
2229 workspace: None,
2230 input_enabled: !is_minimap,
2231 use_modal_editing: full_mode,
2232 read_only: is_minimap,
2233 use_autoclose: true,
2234 use_auto_surround: true,
2235 auto_replace_emoji_shortcode: false,
2236 jsx_tag_auto_close_enabled_in_any_buffer: false,
2237 leader_id: None,
2238 remote_id: None,
2239 hover_state: HoverState::default(),
2240 pending_mouse_down: None,
2241 hovered_link_state: None,
2242 edit_prediction_provider: None,
2243 active_edit_prediction: None,
2244 stale_edit_prediction_in_menu: None,
2245 edit_prediction_preview: EditPredictionPreview::Inactive {
2246 released_too_fast: false,
2247 },
2248 inline_diagnostics_enabled: full_mode,
2249 diagnostics_enabled: full_mode,
2250 word_completions_enabled: full_mode,
2251 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2252 gutter_hovered: false,
2253 pixel_position_of_newest_cursor: None,
2254 last_bounds: None,
2255 last_position_map: None,
2256 expect_bounds_change: None,
2257 gutter_dimensions: GutterDimensions::default(),
2258 style: None,
2259 show_cursor_names: false,
2260 hovered_cursors: HashMap::default(),
2261 next_editor_action_id: EditorActionId::default(),
2262 editor_actions: Rc::default(),
2263 edit_predictions_hidden_for_vim_mode: false,
2264 show_edit_predictions_override: None,
2265 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2266 edit_prediction_settings: EditPredictionSettings::Disabled,
2267 edit_prediction_indent_conflict: false,
2268 edit_prediction_requires_modifier_in_indent_conflict: true,
2269 custom_context_menu: None,
2270 show_git_blame_gutter: false,
2271 show_git_blame_inline: false,
2272 show_selection_menu: None,
2273 show_git_blame_inline_delay_task: None,
2274 git_blame_inline_enabled: full_mode
2275 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2276 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2277 buffer_serialization: is_minimap.not().then(|| {
2278 BufferSerialization::new(
2279 ProjectSettings::get_global(cx)
2280 .session
2281 .restore_unsaved_buffers,
2282 )
2283 }),
2284 blame: None,
2285 blame_subscription: None,
2286 tasks: BTreeMap::default(),
2287
2288 breakpoint_store,
2289 gutter_breakpoint_indicator: (None, None),
2290 hovered_diff_hunk_row: None,
2291 _subscriptions: (!is_minimap)
2292 .then(|| {
2293 vec![
2294 cx.observe(&multi_buffer, Self::on_buffer_changed),
2295 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2296 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2297 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2298 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2299 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2300 cx.observe_window_activation(window, |editor, window, cx| {
2301 let active = window.is_window_active();
2302 editor.blink_manager.update(cx, |blink_manager, cx| {
2303 if active {
2304 blink_manager.enable(cx);
2305 } else {
2306 blink_manager.disable(cx);
2307 }
2308 });
2309 if active {
2310 editor.show_mouse_cursor(cx);
2311 }
2312 }),
2313 ]
2314 })
2315 .unwrap_or_default(),
2316 tasks_update_task: None,
2317 pull_diagnostics_task: Task::ready(()),
2318 colors: None,
2319 refresh_colors_task: Task::ready(()),
2320 inlay_hints: None,
2321 next_color_inlay_id: 0,
2322 post_scroll_update: Task::ready(()),
2323 linked_edit_ranges: Default::default(),
2324 in_project_search: false,
2325 previous_search_ranges: None,
2326 breadcrumb_header: None,
2327 focused_block: None,
2328 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2329 addons: HashMap::default(),
2330 registered_buffers: HashMap::default(),
2331 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2332 selection_mark_mode: false,
2333 toggle_fold_multiple_buffers: Task::ready(()),
2334 serialize_selections: Task::ready(()),
2335 serialize_folds: Task::ready(()),
2336 text_style_refinement: None,
2337 load_diff_task: load_uncommitted_diff,
2338 temporary_diff_override: false,
2339 mouse_cursor_hidden: false,
2340 minimap: None,
2341 hide_mouse_mode: EditorSettings::get_global(cx)
2342 .hide_mouse
2343 .unwrap_or_default(),
2344 change_list: ChangeList::new(),
2345 mode,
2346 selection_drag_state: SelectionDragState::None,
2347 folding_newlines: Task::ready(()),
2348 lookup_key: None,
2349 select_next_is_case_sensitive: None,
2350 applicable_language_settings: HashMap::default(),
2351 accent_overrides: Vec::new(),
2352 fetched_tree_sitter_chunks: HashMap::default(),
2353 };
2354
2355 if is_minimap {
2356 return editor;
2357 }
2358
2359 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2360 editor.accent_overrides = editor.fetch_accent_overrides(cx);
2361
2362 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2363 editor
2364 ._subscriptions
2365 .push(cx.observe(breakpoints, |_, _, cx| {
2366 cx.notify();
2367 }));
2368 }
2369 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2370 editor._subscriptions.extend(project_subscriptions);
2371
2372 editor._subscriptions.push(cx.subscribe_in(
2373 &cx.entity(),
2374 window,
2375 |editor, _, e: &EditorEvent, window, cx| match e {
2376 EditorEvent::ScrollPositionChanged { local, .. } => {
2377 if *local {
2378 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2379 editor.inline_blame_popover.take();
2380 let new_anchor = editor.scroll_manager.anchor();
2381 let snapshot = editor.snapshot(window, cx);
2382 editor.update_restoration_data(cx, move |data| {
2383 data.scroll_position = (
2384 new_anchor.top_row(snapshot.buffer_snapshot()),
2385 new_anchor.offset,
2386 );
2387 });
2388
2389 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2390 cx.background_executor()
2391 .timer(Duration::from_millis(50))
2392 .await;
2393 editor
2394 .update_in(cx, |editor, window, cx| {
2395 editor.register_visible_buffers(cx);
2396 editor.refresh_colors_for_visible_range(None, window, cx);
2397 editor.refresh_inlay_hints(
2398 InlayHintRefreshReason::NewLinesShown,
2399 cx,
2400 );
2401 editor.colorize_brackets(false, cx);
2402 })
2403 .ok();
2404 });
2405 }
2406 }
2407 EditorEvent::Edited { .. } => {
2408 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2409 .map(|vim_mode| vim_mode.0)
2410 .unwrap_or(false);
2411 if !vim_mode {
2412 let display_map = editor.display_snapshot(cx);
2413 let selections = editor.selections.all_adjusted_display(&display_map);
2414 let pop_state = editor
2415 .change_list
2416 .last()
2417 .map(|previous| {
2418 previous.len() == selections.len()
2419 && previous.iter().enumerate().all(|(ix, p)| {
2420 p.to_display_point(&display_map).row()
2421 == selections[ix].head().row()
2422 })
2423 })
2424 .unwrap_or(false);
2425 let new_positions = selections
2426 .into_iter()
2427 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2428 .collect();
2429 editor
2430 .change_list
2431 .push_to_change_list(pop_state, new_positions);
2432 }
2433 }
2434 _ => (),
2435 },
2436 ));
2437
2438 if let Some(dap_store) = editor
2439 .project
2440 .as_ref()
2441 .map(|project| project.read(cx).dap_store())
2442 {
2443 let weak_editor = cx.weak_entity();
2444
2445 editor
2446 ._subscriptions
2447 .push(
2448 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2449 let session_entity = cx.entity();
2450 weak_editor
2451 .update(cx, |editor, cx| {
2452 editor._subscriptions.push(
2453 cx.subscribe(&session_entity, Self::on_debug_session_event),
2454 );
2455 })
2456 .ok();
2457 }),
2458 );
2459
2460 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2461 editor
2462 ._subscriptions
2463 .push(cx.subscribe(&session, Self::on_debug_session_event));
2464 }
2465 }
2466
2467 // skip adding the initial selection to selection history
2468 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2469 editor.end_selection(window, cx);
2470 editor.selection_history.mode = SelectionHistoryMode::Normal;
2471
2472 editor.scroll_manager.show_scrollbars(window, cx);
2473 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2474
2475 if full_mode {
2476 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2477 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2478
2479 if editor.git_blame_inline_enabled {
2480 editor.start_git_blame_inline(false, window, cx);
2481 }
2482
2483 editor.go_to_active_debug_line(window, cx);
2484
2485 editor.minimap =
2486 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2487 editor.colors = Some(LspColorData::new(cx));
2488 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2489
2490 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2491 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2492 }
2493 editor.update_lsp_data(None, window, cx);
2494 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2495 }
2496
2497 editor
2498 }
2499
2500 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2501 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2502 }
2503
2504 pub fn deploy_mouse_context_menu(
2505 &mut self,
2506 position: gpui::Point<Pixels>,
2507 context_menu: Entity<ContextMenu>,
2508 window: &mut Window,
2509 cx: &mut Context<Self>,
2510 ) {
2511 self.mouse_context_menu = Some(MouseContextMenu::new(
2512 self,
2513 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2514 context_menu,
2515 window,
2516 cx,
2517 ));
2518 }
2519
2520 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2521 self.mouse_context_menu
2522 .as_ref()
2523 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2524 }
2525
2526 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2527 if self
2528 .selections
2529 .pending_anchor()
2530 .is_some_and(|pending_selection| {
2531 let snapshot = self.buffer().read(cx).snapshot(cx);
2532 pending_selection.range().includes(range, &snapshot)
2533 })
2534 {
2535 return true;
2536 }
2537
2538 self.selections
2539 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2540 .into_iter()
2541 .any(|selection| {
2542 // This is needed to cover a corner case, if we just check for an existing
2543 // selection in the fold range, having a cursor at the start of the fold
2544 // marks it as selected. Non-empty selections don't cause this.
2545 let length = selection.end - selection.start;
2546 length > 0
2547 })
2548 }
2549
2550 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2551 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2552 }
2553
2554 fn key_context_internal(
2555 &self,
2556 has_active_edit_prediction: bool,
2557 window: &mut Window,
2558 cx: &mut App,
2559 ) -> KeyContext {
2560 let mut key_context = KeyContext::new_with_defaults();
2561 key_context.add("Editor");
2562 let mode = match self.mode {
2563 EditorMode::SingleLine => "single_line",
2564 EditorMode::AutoHeight { .. } => "auto_height",
2565 EditorMode::Minimap { .. } => "minimap",
2566 EditorMode::Full { .. } => "full",
2567 };
2568
2569 if EditorSettings::jupyter_enabled(cx) {
2570 key_context.add("jupyter");
2571 }
2572
2573 key_context.set("mode", mode);
2574 if self.pending_rename.is_some() {
2575 key_context.add("renaming");
2576 }
2577
2578 if let Some(snippet_stack) = self.snippet_stack.last() {
2579 key_context.add("in_snippet");
2580
2581 if snippet_stack.active_index > 0 {
2582 key_context.add("has_previous_tabstop");
2583 }
2584
2585 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2586 key_context.add("has_next_tabstop");
2587 }
2588 }
2589
2590 match self.context_menu.borrow().as_ref() {
2591 Some(CodeContextMenu::Completions(menu)) => {
2592 if menu.visible() {
2593 key_context.add("menu");
2594 key_context.add("showing_completions");
2595 }
2596 }
2597 Some(CodeContextMenu::CodeActions(menu)) => {
2598 if menu.visible() {
2599 key_context.add("menu");
2600 key_context.add("showing_code_actions")
2601 }
2602 }
2603 None => {}
2604 }
2605
2606 if self.signature_help_state.has_multiple_signatures() {
2607 key_context.add("showing_signature_help");
2608 }
2609
2610 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2611 if !self.focus_handle(cx).contains_focused(window, cx)
2612 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2613 {
2614 for addon in self.addons.values() {
2615 addon.extend_key_context(&mut key_context, cx)
2616 }
2617 }
2618
2619 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2620 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2621 Some(
2622 file.full_path(cx)
2623 .extension()?
2624 .to_string_lossy()
2625 .into_owned(),
2626 )
2627 }) {
2628 key_context.set("extension", extension);
2629 }
2630 } else {
2631 key_context.add("multibuffer");
2632 }
2633
2634 if has_active_edit_prediction {
2635 if self.edit_prediction_in_conflict() {
2636 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2637 } else {
2638 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2639 key_context.add("copilot_suggestion");
2640 }
2641 }
2642
2643 if self.selection_mark_mode {
2644 key_context.add("selection_mode");
2645 }
2646
2647 let disjoint = self.selections.disjoint_anchors();
2648 let snapshot = self.snapshot(window, cx);
2649 let snapshot = snapshot.buffer_snapshot();
2650 if self.mode == EditorMode::SingleLine
2651 && let [selection] = disjoint
2652 && selection.start == selection.end
2653 && selection.end.to_offset(snapshot) == snapshot.len()
2654 {
2655 key_context.add("end_of_input");
2656 }
2657
2658 if self.has_any_expanded_diff_hunks(cx) {
2659 key_context.add("diffs_expanded");
2660 }
2661
2662 key_context
2663 }
2664
2665 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2666 self.last_bounds.as_ref()
2667 }
2668
2669 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2670 if self.mouse_cursor_hidden {
2671 self.mouse_cursor_hidden = false;
2672 cx.notify();
2673 }
2674 }
2675
2676 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2677 let hide_mouse_cursor = match origin {
2678 HideMouseCursorOrigin::TypingAction => {
2679 matches!(
2680 self.hide_mouse_mode,
2681 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2682 )
2683 }
2684 HideMouseCursorOrigin::MovementAction => {
2685 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2686 }
2687 };
2688 if self.mouse_cursor_hidden != hide_mouse_cursor {
2689 self.mouse_cursor_hidden = hide_mouse_cursor;
2690 cx.notify();
2691 }
2692 }
2693
2694 pub fn edit_prediction_in_conflict(&self) -> bool {
2695 if !self.show_edit_predictions_in_menu() {
2696 return false;
2697 }
2698
2699 let showing_completions = self
2700 .context_menu
2701 .borrow()
2702 .as_ref()
2703 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2704
2705 showing_completions
2706 || self.edit_prediction_requires_modifier()
2707 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2708 // bindings to insert tab characters.
2709 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2710 }
2711
2712 pub fn accept_edit_prediction_keybind(
2713 &self,
2714 accept_partial: bool,
2715 window: &mut Window,
2716 cx: &mut App,
2717 ) -> AcceptEditPredictionBinding {
2718 let key_context = self.key_context_internal(true, window, cx);
2719 let in_conflict = self.edit_prediction_in_conflict();
2720
2721 let bindings = if accept_partial {
2722 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2723 } else {
2724 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2725 };
2726
2727 // TODO: if the binding contains multiple keystrokes, display all of them, not
2728 // just the first one.
2729 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2730 !in_conflict
2731 || binding
2732 .keystrokes()
2733 .first()
2734 .is_some_and(|keystroke| keystroke.modifiers().modified())
2735 }))
2736 }
2737
2738 pub fn new_file(
2739 workspace: &mut Workspace,
2740 _: &workspace::NewFile,
2741 window: &mut Window,
2742 cx: &mut Context<Workspace>,
2743 ) {
2744 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2745 "Failed to create buffer",
2746 window,
2747 cx,
2748 |e, _, _| match e.error_code() {
2749 ErrorCode::RemoteUpgradeRequired => Some(format!(
2750 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2751 e.error_tag("required").unwrap_or("the latest version")
2752 )),
2753 _ => None,
2754 },
2755 );
2756 }
2757
2758 pub fn new_in_workspace(
2759 workspace: &mut Workspace,
2760 window: &mut Window,
2761 cx: &mut Context<Workspace>,
2762 ) -> Task<Result<Entity<Editor>>> {
2763 let project = workspace.project().clone();
2764 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2765
2766 cx.spawn_in(window, async move |workspace, cx| {
2767 let buffer = create.await?;
2768 workspace.update_in(cx, |workspace, window, cx| {
2769 let editor =
2770 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2771 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2772 editor
2773 })
2774 })
2775 }
2776
2777 fn new_file_vertical(
2778 workspace: &mut Workspace,
2779 _: &workspace::NewFileSplitVertical,
2780 window: &mut Window,
2781 cx: &mut Context<Workspace>,
2782 ) {
2783 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2784 }
2785
2786 fn new_file_horizontal(
2787 workspace: &mut Workspace,
2788 _: &workspace::NewFileSplitHorizontal,
2789 window: &mut Window,
2790 cx: &mut Context<Workspace>,
2791 ) {
2792 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2793 }
2794
2795 fn new_file_split(
2796 workspace: &mut Workspace,
2797 action: &workspace::NewFileSplit,
2798 window: &mut Window,
2799 cx: &mut Context<Workspace>,
2800 ) {
2801 Self::new_file_in_direction(workspace, action.0, window, cx)
2802 }
2803
2804 fn new_file_in_direction(
2805 workspace: &mut Workspace,
2806 direction: SplitDirection,
2807 window: &mut Window,
2808 cx: &mut Context<Workspace>,
2809 ) {
2810 let project = workspace.project().clone();
2811 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2812
2813 cx.spawn_in(window, async move |workspace, cx| {
2814 let buffer = create.await?;
2815 workspace.update_in(cx, move |workspace, window, cx| {
2816 workspace.split_item(
2817 direction,
2818 Box::new(
2819 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2820 ),
2821 window,
2822 cx,
2823 )
2824 })?;
2825 anyhow::Ok(())
2826 })
2827 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2828 match e.error_code() {
2829 ErrorCode::RemoteUpgradeRequired => Some(format!(
2830 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2831 e.error_tag("required").unwrap_or("the latest version")
2832 )),
2833 _ => None,
2834 }
2835 });
2836 }
2837
2838 pub fn leader_id(&self) -> Option<CollaboratorId> {
2839 self.leader_id
2840 }
2841
2842 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2843 &self.buffer
2844 }
2845
2846 pub fn project(&self) -> Option<&Entity<Project>> {
2847 self.project.as_ref()
2848 }
2849
2850 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2851 self.workspace.as_ref()?.0.upgrade()
2852 }
2853
2854 /// Returns the workspace serialization ID if this editor should be serialized.
2855 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2856 self.workspace
2857 .as_ref()
2858 .filter(|_| self.should_serialize_buffer())
2859 .and_then(|workspace| workspace.1)
2860 }
2861
2862 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2863 self.buffer().read(cx).title(cx)
2864 }
2865
2866 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2867 let git_blame_gutter_max_author_length = self
2868 .render_git_blame_gutter(cx)
2869 .then(|| {
2870 if let Some(blame) = self.blame.as_ref() {
2871 let max_author_length =
2872 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2873 Some(max_author_length)
2874 } else {
2875 None
2876 }
2877 })
2878 .flatten();
2879
2880 EditorSnapshot {
2881 mode: self.mode.clone(),
2882 show_gutter: self.show_gutter,
2883 show_line_numbers: self.show_line_numbers,
2884 show_git_diff_gutter: self.show_git_diff_gutter,
2885 show_code_actions: self.show_code_actions,
2886 show_runnables: self.show_runnables,
2887 show_breakpoints: self.show_breakpoints,
2888 git_blame_gutter_max_author_length,
2889 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2890 placeholder_display_snapshot: self
2891 .placeholder_display_map
2892 .as_ref()
2893 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2894 scroll_anchor: self.scroll_manager.anchor(),
2895 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2896 is_focused: self.focus_handle.is_focused(window),
2897 current_line_highlight: self
2898 .current_line_highlight
2899 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2900 gutter_hovered: self.gutter_hovered,
2901 }
2902 }
2903
2904 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2905 self.buffer.read(cx).language_at(point, cx)
2906 }
2907
2908 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2909 self.buffer.read(cx).read(cx).file_at(point).cloned()
2910 }
2911
2912 pub fn active_excerpt(
2913 &self,
2914 cx: &App,
2915 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2916 self.buffer
2917 .read(cx)
2918 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2919 }
2920
2921 pub fn mode(&self) -> &EditorMode {
2922 &self.mode
2923 }
2924
2925 pub fn set_mode(&mut self, mode: EditorMode) {
2926 self.mode = mode;
2927 }
2928
2929 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2930 self.collaboration_hub.as_deref()
2931 }
2932
2933 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2934 self.collaboration_hub = Some(hub);
2935 }
2936
2937 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2938 self.in_project_search = in_project_search;
2939 }
2940
2941 pub fn set_custom_context_menu(
2942 &mut self,
2943 f: impl 'static
2944 + Fn(
2945 &mut Self,
2946 DisplayPoint,
2947 &mut Window,
2948 &mut Context<Self>,
2949 ) -> Option<Entity<ui::ContextMenu>>,
2950 ) {
2951 self.custom_context_menu = Some(Box::new(f))
2952 }
2953
2954 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2955 self.completion_provider = provider;
2956 }
2957
2958 #[cfg(any(test, feature = "test-support"))]
2959 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2960 self.completion_provider.clone()
2961 }
2962
2963 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2964 self.semantics_provider.clone()
2965 }
2966
2967 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2968 self.semantics_provider = provider;
2969 }
2970
2971 pub fn set_edit_prediction_provider<T>(
2972 &mut self,
2973 provider: Option<Entity<T>>,
2974 window: &mut Window,
2975 cx: &mut Context<Self>,
2976 ) where
2977 T: EditPredictionProvider,
2978 {
2979 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2980 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2981 if this.focus_handle.is_focused(window) {
2982 this.update_visible_edit_prediction(window, cx);
2983 }
2984 }),
2985 provider: Arc::new(provider),
2986 });
2987 self.update_edit_prediction_settings(cx);
2988 self.refresh_edit_prediction(false, false, window, cx);
2989 }
2990
2991 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2992 self.placeholder_display_map
2993 .as_ref()
2994 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2995 }
2996
2997 pub fn set_placeholder_text(
2998 &mut self,
2999 placeholder_text: &str,
3000 window: &mut Window,
3001 cx: &mut Context<Self>,
3002 ) {
3003 let multibuffer = cx
3004 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3005
3006 let style = window.text_style();
3007
3008 self.placeholder_display_map = Some(cx.new(|cx| {
3009 DisplayMap::new(
3010 multibuffer,
3011 style.font(),
3012 style.font_size.to_pixels(window.rem_size()),
3013 None,
3014 FILE_HEADER_HEIGHT,
3015 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3016 Default::default(),
3017 DiagnosticSeverity::Off,
3018 cx,
3019 )
3020 }));
3021 cx.notify();
3022 }
3023
3024 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3025 self.cursor_shape = cursor_shape;
3026
3027 // Disrupt blink for immediate user feedback that the cursor shape has changed
3028 self.blink_manager.update(cx, BlinkManager::show_cursor);
3029
3030 cx.notify();
3031 }
3032
3033 pub fn cursor_shape(&self) -> CursorShape {
3034 self.cursor_shape
3035 }
3036
3037 pub fn set_current_line_highlight(
3038 &mut self,
3039 current_line_highlight: Option<CurrentLineHighlight>,
3040 ) {
3041 self.current_line_highlight = current_line_highlight;
3042 }
3043
3044 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3045 self.collapse_matches = collapse_matches;
3046 }
3047
3048 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3049 if self.collapse_matches {
3050 return range.start..range.start;
3051 }
3052 range.clone()
3053 }
3054
3055 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3056 self.display_map.read(cx).clip_at_line_ends
3057 }
3058
3059 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3060 if self.display_map.read(cx).clip_at_line_ends != clip {
3061 self.display_map
3062 .update(cx, |map, _| map.clip_at_line_ends = clip);
3063 }
3064 }
3065
3066 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3067 self.input_enabled = input_enabled;
3068 }
3069
3070 pub fn set_edit_predictions_hidden_for_vim_mode(
3071 &mut self,
3072 hidden: bool,
3073 window: &mut Window,
3074 cx: &mut Context<Self>,
3075 ) {
3076 if hidden != self.edit_predictions_hidden_for_vim_mode {
3077 self.edit_predictions_hidden_for_vim_mode = hidden;
3078 if hidden {
3079 self.update_visible_edit_prediction(window, cx);
3080 } else {
3081 self.refresh_edit_prediction(true, false, window, cx);
3082 }
3083 }
3084 }
3085
3086 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3087 self.menu_edit_predictions_policy = value;
3088 }
3089
3090 pub fn set_autoindent(&mut self, autoindent: bool) {
3091 if autoindent {
3092 self.autoindent_mode = Some(AutoindentMode::EachLine);
3093 } else {
3094 self.autoindent_mode = None;
3095 }
3096 }
3097
3098 pub fn read_only(&self, cx: &App) -> bool {
3099 self.read_only || self.buffer.read(cx).read_only()
3100 }
3101
3102 pub fn set_read_only(&mut self, read_only: bool) {
3103 self.read_only = read_only;
3104 }
3105
3106 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3107 self.use_autoclose = autoclose;
3108 }
3109
3110 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3111 self.use_auto_surround = auto_surround;
3112 }
3113
3114 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3115 self.auto_replace_emoji_shortcode = auto_replace;
3116 }
3117
3118 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3119 self.buffer_serialization = should_serialize.then(|| {
3120 BufferSerialization::new(
3121 ProjectSettings::get_global(cx)
3122 .session
3123 .restore_unsaved_buffers,
3124 )
3125 })
3126 }
3127
3128 fn should_serialize_buffer(&self) -> bool {
3129 self.buffer_serialization.is_some()
3130 }
3131
3132 pub fn toggle_edit_predictions(
3133 &mut self,
3134 _: &ToggleEditPrediction,
3135 window: &mut Window,
3136 cx: &mut Context<Self>,
3137 ) {
3138 if self.show_edit_predictions_override.is_some() {
3139 self.set_show_edit_predictions(None, window, cx);
3140 } else {
3141 let show_edit_predictions = !self.edit_predictions_enabled();
3142 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3143 }
3144 }
3145
3146 pub fn set_show_edit_predictions(
3147 &mut self,
3148 show_edit_predictions: Option<bool>,
3149 window: &mut Window,
3150 cx: &mut Context<Self>,
3151 ) {
3152 self.show_edit_predictions_override = show_edit_predictions;
3153 self.update_edit_prediction_settings(cx);
3154
3155 if let Some(false) = show_edit_predictions {
3156 self.discard_edit_prediction(false, cx);
3157 } else {
3158 self.refresh_edit_prediction(false, true, window, cx);
3159 }
3160 }
3161
3162 fn edit_predictions_disabled_in_scope(
3163 &self,
3164 buffer: &Entity<Buffer>,
3165 buffer_position: language::Anchor,
3166 cx: &App,
3167 ) -> bool {
3168 let snapshot = buffer.read(cx).snapshot();
3169 let settings = snapshot.settings_at(buffer_position, cx);
3170
3171 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3172 return false;
3173 };
3174
3175 scope.override_name().is_some_and(|scope_name| {
3176 settings
3177 .edit_predictions_disabled_in
3178 .iter()
3179 .any(|s| s == scope_name)
3180 })
3181 }
3182
3183 pub fn set_use_modal_editing(&mut self, to: bool) {
3184 self.use_modal_editing = to;
3185 }
3186
3187 pub fn use_modal_editing(&self) -> bool {
3188 self.use_modal_editing
3189 }
3190
3191 fn selections_did_change(
3192 &mut self,
3193 local: bool,
3194 old_cursor_position: &Anchor,
3195 effects: SelectionEffects,
3196 window: &mut Window,
3197 cx: &mut Context<Self>,
3198 ) {
3199 window.invalidate_character_coordinates();
3200
3201 // Copy selections to primary selection buffer
3202 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3203 if local {
3204 let selections = self
3205 .selections
3206 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3207 let buffer_handle = self.buffer.read(cx).read(cx);
3208
3209 let mut text = String::new();
3210 for (index, selection) in selections.iter().enumerate() {
3211 let text_for_selection = buffer_handle
3212 .text_for_range(selection.start..selection.end)
3213 .collect::<String>();
3214
3215 text.push_str(&text_for_selection);
3216 if index != selections.len() - 1 {
3217 text.push('\n');
3218 }
3219 }
3220
3221 if !text.is_empty() {
3222 cx.write_to_primary(ClipboardItem::new_string(text));
3223 }
3224 }
3225
3226 let selection_anchors = self.selections.disjoint_anchors_arc();
3227
3228 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3229 self.buffer.update(cx, |buffer, cx| {
3230 buffer.set_active_selections(
3231 &selection_anchors,
3232 self.selections.line_mode(),
3233 self.cursor_shape,
3234 cx,
3235 )
3236 });
3237 }
3238 let display_map = self
3239 .display_map
3240 .update(cx, |display_map, cx| display_map.snapshot(cx));
3241 let buffer = display_map.buffer_snapshot();
3242 if self.selections.count() == 1 {
3243 self.add_selections_state = None;
3244 }
3245 self.select_next_state = None;
3246 self.select_prev_state = None;
3247 self.select_syntax_node_history.try_clear();
3248 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3249 self.snippet_stack.invalidate(&selection_anchors, buffer);
3250 self.take_rename(false, window, cx);
3251
3252 let newest_selection = self.selections.newest_anchor();
3253 let new_cursor_position = newest_selection.head();
3254 let selection_start = newest_selection.start;
3255
3256 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3257 self.push_to_nav_history(
3258 *old_cursor_position,
3259 Some(new_cursor_position.to_point(buffer)),
3260 false,
3261 effects.nav_history == Some(true),
3262 cx,
3263 );
3264 }
3265
3266 if local {
3267 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3268 self.register_buffer(buffer_id, cx);
3269 }
3270
3271 let mut context_menu = self.context_menu.borrow_mut();
3272 let completion_menu = match context_menu.as_ref() {
3273 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3274 Some(CodeContextMenu::CodeActions(_)) => {
3275 *context_menu = None;
3276 None
3277 }
3278 None => None,
3279 };
3280 let completion_position = completion_menu.map(|menu| menu.initial_position);
3281 drop(context_menu);
3282
3283 if effects.completions
3284 && let Some(completion_position) = completion_position
3285 {
3286 let start_offset = selection_start.to_offset(buffer);
3287 let position_matches = start_offset == completion_position.to_offset(buffer);
3288 let continue_showing = if position_matches {
3289 if self.snippet_stack.is_empty() {
3290 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3291 == Some(CharKind::Word)
3292 } else {
3293 // Snippet choices can be shown even when the cursor is in whitespace.
3294 // Dismissing the menu with actions like backspace is handled by
3295 // invalidation regions.
3296 true
3297 }
3298 } else {
3299 false
3300 };
3301
3302 if continue_showing {
3303 self.open_or_update_completions_menu(None, None, false, window, cx);
3304 } else {
3305 self.hide_context_menu(window, cx);
3306 }
3307 }
3308
3309 hide_hover(self, cx);
3310
3311 if old_cursor_position.to_display_point(&display_map).row()
3312 != new_cursor_position.to_display_point(&display_map).row()
3313 {
3314 self.available_code_actions.take();
3315 }
3316 self.refresh_code_actions(window, cx);
3317 self.refresh_document_highlights(cx);
3318 refresh_linked_ranges(self, window, cx);
3319
3320 self.refresh_selected_text_highlights(false, window, cx);
3321 self.refresh_matching_bracket_highlights(window, cx);
3322 self.update_visible_edit_prediction(window, cx);
3323 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3324 self.inline_blame_popover.take();
3325 if self.git_blame_inline_enabled {
3326 self.start_inline_blame_timer(window, cx);
3327 }
3328 }
3329
3330 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3331 cx.emit(EditorEvent::SelectionsChanged { local });
3332
3333 let selections = &self.selections.disjoint_anchors_arc();
3334 if selections.len() == 1 {
3335 cx.emit(SearchEvent::ActiveMatchChanged)
3336 }
3337 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3338 let inmemory_selections = selections
3339 .iter()
3340 .map(|s| {
3341 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3342 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3343 })
3344 .collect();
3345 self.update_restoration_data(cx, |data| {
3346 data.selections = inmemory_selections;
3347 });
3348
3349 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3350 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3351 {
3352 let snapshot = self.buffer().read(cx).snapshot(cx);
3353 let selections = selections.clone();
3354 let background_executor = cx.background_executor().clone();
3355 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3356 self.serialize_selections = cx.background_spawn(async move {
3357 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3358 let db_selections = selections
3359 .iter()
3360 .map(|selection| {
3361 (
3362 selection.start.to_offset(&snapshot).0,
3363 selection.end.to_offset(&snapshot).0,
3364 )
3365 })
3366 .collect();
3367
3368 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3369 .await
3370 .with_context(|| {
3371 format!(
3372 "persisting editor selections for editor {editor_id}, \
3373 workspace {workspace_id:?}"
3374 )
3375 })
3376 .log_err();
3377 });
3378 }
3379 }
3380
3381 cx.notify();
3382 }
3383
3384 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3385 use text::ToOffset as _;
3386 use text::ToPoint as _;
3387
3388 if self.mode.is_minimap()
3389 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3390 {
3391 return;
3392 }
3393
3394 if !self.buffer().read(cx).is_singleton() {
3395 return;
3396 }
3397
3398 let display_snapshot = self
3399 .display_map
3400 .update(cx, |display_map, cx| display_map.snapshot(cx));
3401 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3402 return;
3403 };
3404 let inmemory_folds = display_snapshot
3405 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3406 .map(|fold| {
3407 fold.range.start.text_anchor.to_point(&snapshot)
3408 ..fold.range.end.text_anchor.to_point(&snapshot)
3409 })
3410 .collect();
3411 self.update_restoration_data(cx, |data| {
3412 data.folds = inmemory_folds;
3413 });
3414
3415 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3416 return;
3417 };
3418 let background_executor = cx.background_executor().clone();
3419 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3420 let db_folds = display_snapshot
3421 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3422 .map(|fold| {
3423 (
3424 fold.range.start.text_anchor.to_offset(&snapshot),
3425 fold.range.end.text_anchor.to_offset(&snapshot),
3426 )
3427 })
3428 .collect();
3429 self.serialize_folds = cx.background_spawn(async move {
3430 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3431 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3432 .await
3433 .with_context(|| {
3434 format!(
3435 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3436 )
3437 })
3438 .log_err();
3439 });
3440 }
3441
3442 pub fn sync_selections(
3443 &mut self,
3444 other: Entity<Editor>,
3445 cx: &mut Context<Self>,
3446 ) -> gpui::Subscription {
3447 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3448 if !other_selections.is_empty() {
3449 self.selections
3450 .change_with(&self.display_snapshot(cx), |selections| {
3451 selections.select_anchors(other_selections);
3452 });
3453 }
3454
3455 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3456 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3457 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3458 if other_selections.is_empty() {
3459 return;
3460 }
3461 let snapshot = this.display_snapshot(cx);
3462 this.selections.change_with(&snapshot, |selections| {
3463 selections.select_anchors(other_selections);
3464 });
3465 }
3466 });
3467
3468 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3469 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3470 let these_selections = this.selections.disjoint_anchors().to_vec();
3471 if these_selections.is_empty() {
3472 return;
3473 }
3474 other.update(cx, |other_editor, cx| {
3475 let snapshot = other_editor.display_snapshot(cx);
3476 other_editor
3477 .selections
3478 .change_with(&snapshot, |selections| {
3479 selections.select_anchors(these_selections);
3480 })
3481 });
3482 }
3483 });
3484
3485 Subscription::join(other_subscription, this_subscription)
3486 }
3487
3488 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3489 if self.buffer().read(cx).is_singleton() {
3490 return;
3491 }
3492 let snapshot = self.buffer.read(cx).snapshot(cx);
3493 let buffer_ids: HashSet<BufferId> = self
3494 .selections
3495 .disjoint_anchor_ranges()
3496 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3497 .collect();
3498 for buffer_id in buffer_ids {
3499 self.unfold_buffer(buffer_id, cx);
3500 }
3501 }
3502
3503 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3504 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3505 /// effects of selection change occur at the end of the transaction.
3506 pub fn change_selections<R>(
3507 &mut self,
3508 effects: SelectionEffects,
3509 window: &mut Window,
3510 cx: &mut Context<Self>,
3511 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3512 ) -> R {
3513 let snapshot = self.display_snapshot(cx);
3514 if let Some(state) = &mut self.deferred_selection_effects_state {
3515 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3516 state.effects.completions = effects.completions;
3517 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3518 let (changed, result) = self.selections.change_with(&snapshot, change);
3519 state.changed |= changed;
3520 return result;
3521 }
3522 let mut state = DeferredSelectionEffectsState {
3523 changed: false,
3524 effects,
3525 old_cursor_position: self.selections.newest_anchor().head(),
3526 history_entry: SelectionHistoryEntry {
3527 selections: self.selections.disjoint_anchors_arc(),
3528 select_next_state: self.select_next_state.clone(),
3529 select_prev_state: self.select_prev_state.clone(),
3530 add_selections_state: self.add_selections_state.clone(),
3531 },
3532 };
3533 let (changed, result) = self.selections.change_with(&snapshot, change);
3534 state.changed = state.changed || changed;
3535 if self.defer_selection_effects {
3536 self.deferred_selection_effects_state = Some(state);
3537 } else {
3538 self.apply_selection_effects(state, window, cx);
3539 }
3540 result
3541 }
3542
3543 /// Defers the effects of selection change, so that the effects of multiple calls to
3544 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3545 /// to selection history and the state of popovers based on selection position aren't
3546 /// erroneously updated.
3547 pub fn with_selection_effects_deferred<R>(
3548 &mut self,
3549 window: &mut Window,
3550 cx: &mut Context<Self>,
3551 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3552 ) -> R {
3553 let already_deferred = self.defer_selection_effects;
3554 self.defer_selection_effects = true;
3555 let result = update(self, window, cx);
3556 if !already_deferred {
3557 self.defer_selection_effects = false;
3558 if let Some(state) = self.deferred_selection_effects_state.take() {
3559 self.apply_selection_effects(state, window, cx);
3560 }
3561 }
3562 result
3563 }
3564
3565 fn apply_selection_effects(
3566 &mut self,
3567 state: DeferredSelectionEffectsState,
3568 window: &mut Window,
3569 cx: &mut Context<Self>,
3570 ) {
3571 if state.changed {
3572 self.selection_history.push(state.history_entry);
3573
3574 if let Some(autoscroll) = state.effects.scroll {
3575 self.request_autoscroll(autoscroll, cx);
3576 }
3577
3578 let old_cursor_position = &state.old_cursor_position;
3579
3580 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3581
3582 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3583 self.show_signature_help(&ShowSignatureHelp, window, cx);
3584 }
3585 }
3586 }
3587
3588 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3589 where
3590 I: IntoIterator<Item = (Range<S>, T)>,
3591 S: ToOffset,
3592 T: Into<Arc<str>>,
3593 {
3594 if self.read_only(cx) {
3595 return;
3596 }
3597
3598 self.buffer
3599 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3600 }
3601
3602 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3603 where
3604 I: IntoIterator<Item = (Range<S>, T)>,
3605 S: ToOffset,
3606 T: Into<Arc<str>>,
3607 {
3608 if self.read_only(cx) {
3609 return;
3610 }
3611
3612 self.buffer.update(cx, |buffer, cx| {
3613 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3614 });
3615 }
3616
3617 pub fn edit_with_block_indent<I, S, T>(
3618 &mut self,
3619 edits: I,
3620 original_indent_columns: Vec<Option<u32>>,
3621 cx: &mut Context<Self>,
3622 ) where
3623 I: IntoIterator<Item = (Range<S>, T)>,
3624 S: ToOffset,
3625 T: Into<Arc<str>>,
3626 {
3627 if self.read_only(cx) {
3628 return;
3629 }
3630
3631 self.buffer.update(cx, |buffer, cx| {
3632 buffer.edit(
3633 edits,
3634 Some(AutoindentMode::Block {
3635 original_indent_columns,
3636 }),
3637 cx,
3638 )
3639 });
3640 }
3641
3642 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3643 self.hide_context_menu(window, cx);
3644
3645 match phase {
3646 SelectPhase::Begin {
3647 position,
3648 add,
3649 click_count,
3650 } => self.begin_selection(position, add, click_count, window, cx),
3651 SelectPhase::BeginColumnar {
3652 position,
3653 goal_column,
3654 reset,
3655 mode,
3656 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3657 SelectPhase::Extend {
3658 position,
3659 click_count,
3660 } => self.extend_selection(position, click_count, window, cx),
3661 SelectPhase::Update {
3662 position,
3663 goal_column,
3664 scroll_delta,
3665 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3666 SelectPhase::End => self.end_selection(window, cx),
3667 }
3668 }
3669
3670 fn extend_selection(
3671 &mut self,
3672 position: DisplayPoint,
3673 click_count: usize,
3674 window: &mut Window,
3675 cx: &mut Context<Self>,
3676 ) {
3677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3678 let tail = self
3679 .selections
3680 .newest::<MultiBufferOffset>(&display_map)
3681 .tail();
3682 let click_count = click_count.max(match self.selections.select_mode() {
3683 SelectMode::Character => 1,
3684 SelectMode::Word(_) => 2,
3685 SelectMode::Line(_) => 3,
3686 SelectMode::All => 4,
3687 });
3688 self.begin_selection(position, false, click_count, window, cx);
3689
3690 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3691
3692 let current_selection = match self.selections.select_mode() {
3693 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3694 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3695 };
3696
3697 let mut pending_selection = self
3698 .selections
3699 .pending_anchor()
3700 .cloned()
3701 .expect("extend_selection not called with pending selection");
3702
3703 if pending_selection
3704 .start
3705 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3706 == Ordering::Greater
3707 {
3708 pending_selection.start = current_selection.start;
3709 }
3710 if pending_selection
3711 .end
3712 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3713 == Ordering::Less
3714 {
3715 pending_selection.end = current_selection.end;
3716 pending_selection.reversed = true;
3717 }
3718
3719 let mut pending_mode = self.selections.pending_mode().unwrap();
3720 match &mut pending_mode {
3721 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3722 _ => {}
3723 }
3724
3725 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3726 SelectionEffects::scroll(Autoscroll::fit())
3727 } else {
3728 SelectionEffects::no_scroll()
3729 };
3730
3731 self.change_selections(effects, window, cx, |s| {
3732 s.set_pending(pending_selection.clone(), pending_mode);
3733 s.set_is_extending(true);
3734 });
3735 }
3736
3737 fn begin_selection(
3738 &mut self,
3739 position: DisplayPoint,
3740 add: bool,
3741 click_count: usize,
3742 window: &mut Window,
3743 cx: &mut Context<Self>,
3744 ) {
3745 if !self.focus_handle.is_focused(window) {
3746 self.last_focused_descendant = None;
3747 window.focus(&self.focus_handle);
3748 }
3749
3750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3751 let buffer = display_map.buffer_snapshot();
3752 let position = display_map.clip_point(position, Bias::Left);
3753
3754 let start;
3755 let end;
3756 let mode;
3757 let mut auto_scroll;
3758 match click_count {
3759 1 => {
3760 start = buffer.anchor_before(position.to_point(&display_map));
3761 end = start;
3762 mode = SelectMode::Character;
3763 auto_scroll = true;
3764 }
3765 2 => {
3766 let position = display_map
3767 .clip_point(position, Bias::Left)
3768 .to_offset(&display_map, Bias::Left);
3769 let (range, _) = buffer.surrounding_word(position, None);
3770 start = buffer.anchor_before(range.start);
3771 end = buffer.anchor_before(range.end);
3772 mode = SelectMode::Word(start..end);
3773 auto_scroll = true;
3774 }
3775 3 => {
3776 let position = display_map
3777 .clip_point(position, Bias::Left)
3778 .to_point(&display_map);
3779 let line_start = display_map.prev_line_boundary(position).0;
3780 let next_line_start = buffer.clip_point(
3781 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3782 Bias::Left,
3783 );
3784 start = buffer.anchor_before(line_start);
3785 end = buffer.anchor_before(next_line_start);
3786 mode = SelectMode::Line(start..end);
3787 auto_scroll = true;
3788 }
3789 _ => {
3790 start = buffer.anchor_before(MultiBufferOffset(0));
3791 end = buffer.anchor_before(buffer.len());
3792 mode = SelectMode::All;
3793 auto_scroll = false;
3794 }
3795 }
3796 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3797
3798 let point_to_delete: Option<usize> = {
3799 let selected_points: Vec<Selection<Point>> =
3800 self.selections.disjoint_in_range(start..end, &display_map);
3801
3802 if !add || click_count > 1 {
3803 None
3804 } else if !selected_points.is_empty() {
3805 Some(selected_points[0].id)
3806 } else {
3807 let clicked_point_already_selected =
3808 self.selections.disjoint_anchors().iter().find(|selection| {
3809 selection.start.to_point(buffer) == start.to_point(buffer)
3810 || selection.end.to_point(buffer) == end.to_point(buffer)
3811 });
3812
3813 clicked_point_already_selected.map(|selection| selection.id)
3814 }
3815 };
3816
3817 let selections_count = self.selections.count();
3818 let effects = if auto_scroll {
3819 SelectionEffects::default()
3820 } else {
3821 SelectionEffects::no_scroll()
3822 };
3823
3824 self.change_selections(effects, window, cx, |s| {
3825 if let Some(point_to_delete) = point_to_delete {
3826 s.delete(point_to_delete);
3827
3828 if selections_count == 1 {
3829 s.set_pending_anchor_range(start..end, mode);
3830 }
3831 } else {
3832 if !add {
3833 s.clear_disjoint();
3834 }
3835
3836 s.set_pending_anchor_range(start..end, mode);
3837 }
3838 });
3839 }
3840
3841 fn begin_columnar_selection(
3842 &mut self,
3843 position: DisplayPoint,
3844 goal_column: u32,
3845 reset: bool,
3846 mode: ColumnarMode,
3847 window: &mut Window,
3848 cx: &mut Context<Self>,
3849 ) {
3850 if !self.focus_handle.is_focused(window) {
3851 self.last_focused_descendant = None;
3852 window.focus(&self.focus_handle);
3853 }
3854
3855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3856
3857 if reset {
3858 let pointer_position = display_map
3859 .buffer_snapshot()
3860 .anchor_before(position.to_point(&display_map));
3861
3862 self.change_selections(
3863 SelectionEffects::scroll(Autoscroll::newest()),
3864 window,
3865 cx,
3866 |s| {
3867 s.clear_disjoint();
3868 s.set_pending_anchor_range(
3869 pointer_position..pointer_position,
3870 SelectMode::Character,
3871 );
3872 },
3873 );
3874 };
3875
3876 let tail = self.selections.newest::<Point>(&display_map).tail();
3877 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3878 self.columnar_selection_state = match mode {
3879 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3880 selection_tail: selection_anchor,
3881 display_point: if reset {
3882 if position.column() != goal_column {
3883 Some(DisplayPoint::new(position.row(), goal_column))
3884 } else {
3885 None
3886 }
3887 } else {
3888 None
3889 },
3890 }),
3891 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3892 selection_tail: selection_anchor,
3893 }),
3894 };
3895
3896 if !reset {
3897 self.select_columns(position, goal_column, &display_map, window, cx);
3898 }
3899 }
3900
3901 fn update_selection(
3902 &mut self,
3903 position: DisplayPoint,
3904 goal_column: u32,
3905 scroll_delta: gpui::Point<f32>,
3906 window: &mut Window,
3907 cx: &mut Context<Self>,
3908 ) {
3909 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3910
3911 if self.columnar_selection_state.is_some() {
3912 self.select_columns(position, goal_column, &display_map, window, cx);
3913 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3914 let buffer = display_map.buffer_snapshot();
3915 let head;
3916 let tail;
3917 let mode = self.selections.pending_mode().unwrap();
3918 match &mode {
3919 SelectMode::Character => {
3920 head = position.to_point(&display_map);
3921 tail = pending.tail().to_point(buffer);
3922 }
3923 SelectMode::Word(original_range) => {
3924 let offset = display_map
3925 .clip_point(position, Bias::Left)
3926 .to_offset(&display_map, Bias::Left);
3927 let original_range = original_range.to_offset(buffer);
3928
3929 let head_offset = if buffer.is_inside_word(offset, None)
3930 || original_range.contains(&offset)
3931 {
3932 let (word_range, _) = buffer.surrounding_word(offset, None);
3933 if word_range.start < original_range.start {
3934 word_range.start
3935 } else {
3936 word_range.end
3937 }
3938 } else {
3939 offset
3940 };
3941
3942 head = head_offset.to_point(buffer);
3943 if head_offset <= original_range.start {
3944 tail = original_range.end.to_point(buffer);
3945 } else {
3946 tail = original_range.start.to_point(buffer);
3947 }
3948 }
3949 SelectMode::Line(original_range) => {
3950 let original_range = original_range.to_point(display_map.buffer_snapshot());
3951
3952 let position = display_map
3953 .clip_point(position, Bias::Left)
3954 .to_point(&display_map);
3955 let line_start = display_map.prev_line_boundary(position).0;
3956 let next_line_start = buffer.clip_point(
3957 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3958 Bias::Left,
3959 );
3960
3961 if line_start < original_range.start {
3962 head = line_start
3963 } else {
3964 head = next_line_start
3965 }
3966
3967 if head <= original_range.start {
3968 tail = original_range.end;
3969 } else {
3970 tail = original_range.start;
3971 }
3972 }
3973 SelectMode::All => {
3974 return;
3975 }
3976 };
3977
3978 if head < tail {
3979 pending.start = buffer.anchor_before(head);
3980 pending.end = buffer.anchor_before(tail);
3981 pending.reversed = true;
3982 } else {
3983 pending.start = buffer.anchor_before(tail);
3984 pending.end = buffer.anchor_before(head);
3985 pending.reversed = false;
3986 }
3987
3988 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3989 s.set_pending(pending.clone(), mode);
3990 });
3991 } else {
3992 log::error!("update_selection dispatched with no pending selection");
3993 return;
3994 }
3995
3996 self.apply_scroll_delta(scroll_delta, window, cx);
3997 cx.notify();
3998 }
3999
4000 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4001 self.columnar_selection_state.take();
4002 if let Some(pending_mode) = self.selections.pending_mode() {
4003 let selections = self
4004 .selections
4005 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4006 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4007 s.select(selections);
4008 s.clear_pending();
4009 if s.is_extending() {
4010 s.set_is_extending(false);
4011 } else {
4012 s.set_select_mode(pending_mode);
4013 }
4014 });
4015 }
4016 }
4017
4018 fn select_columns(
4019 &mut self,
4020 head: DisplayPoint,
4021 goal_column: u32,
4022 display_map: &DisplaySnapshot,
4023 window: &mut Window,
4024 cx: &mut Context<Self>,
4025 ) {
4026 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4027 return;
4028 };
4029
4030 let tail = match columnar_state {
4031 ColumnarSelectionState::FromMouse {
4032 selection_tail,
4033 display_point,
4034 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4035 ColumnarSelectionState::FromSelection { selection_tail } => {
4036 selection_tail.to_display_point(display_map)
4037 }
4038 };
4039
4040 let start_row = cmp::min(tail.row(), head.row());
4041 let end_row = cmp::max(tail.row(), head.row());
4042 let start_column = cmp::min(tail.column(), goal_column);
4043 let end_column = cmp::max(tail.column(), goal_column);
4044 let reversed = start_column < tail.column();
4045
4046 let selection_ranges = (start_row.0..=end_row.0)
4047 .map(DisplayRow)
4048 .filter_map(|row| {
4049 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4050 || start_column <= display_map.line_len(row))
4051 && !display_map.is_block_line(row)
4052 {
4053 let start = display_map
4054 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4055 .to_point(display_map);
4056 let end = display_map
4057 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4058 .to_point(display_map);
4059 if reversed {
4060 Some(end..start)
4061 } else {
4062 Some(start..end)
4063 }
4064 } else {
4065 None
4066 }
4067 })
4068 .collect::<Vec<_>>();
4069 if selection_ranges.is_empty() {
4070 return;
4071 }
4072
4073 let ranges = match columnar_state {
4074 ColumnarSelectionState::FromMouse { .. } => {
4075 let mut non_empty_ranges = selection_ranges
4076 .iter()
4077 .filter(|selection_range| selection_range.start != selection_range.end)
4078 .peekable();
4079 if non_empty_ranges.peek().is_some() {
4080 non_empty_ranges.cloned().collect()
4081 } else {
4082 selection_ranges
4083 }
4084 }
4085 _ => selection_ranges,
4086 };
4087
4088 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4089 s.select_ranges(ranges);
4090 });
4091 cx.notify();
4092 }
4093
4094 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4095 self.selections
4096 .all_adjusted(snapshot)
4097 .iter()
4098 .any(|selection| !selection.is_empty())
4099 }
4100
4101 pub fn has_pending_nonempty_selection(&self) -> bool {
4102 let pending_nonempty_selection = match self.selections.pending_anchor() {
4103 Some(Selection { start, end, .. }) => start != end,
4104 None => false,
4105 };
4106
4107 pending_nonempty_selection
4108 || (self.columnar_selection_state.is_some()
4109 && self.selections.disjoint_anchors().len() > 1)
4110 }
4111
4112 pub fn has_pending_selection(&self) -> bool {
4113 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4114 }
4115
4116 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4117 self.selection_mark_mode = false;
4118 self.selection_drag_state = SelectionDragState::None;
4119
4120 if self.dismiss_menus_and_popups(true, window, cx) {
4121 cx.notify();
4122 return;
4123 }
4124 if self.clear_expanded_diff_hunks(cx) {
4125 cx.notify();
4126 return;
4127 }
4128 if self.show_git_blame_gutter {
4129 self.show_git_blame_gutter = false;
4130 cx.notify();
4131 return;
4132 }
4133
4134 if self.mode.is_full()
4135 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4136 {
4137 cx.notify();
4138 return;
4139 }
4140
4141 cx.propagate();
4142 }
4143
4144 pub fn dismiss_menus_and_popups(
4145 &mut self,
4146 is_user_requested: bool,
4147 window: &mut Window,
4148 cx: &mut Context<Self>,
4149 ) -> bool {
4150 let mut dismissed = false;
4151
4152 dismissed |= self.take_rename(false, window, cx).is_some();
4153 dismissed |= self.hide_blame_popover(true, cx);
4154 dismissed |= hide_hover(self, cx);
4155 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4156 dismissed |= self.hide_context_menu(window, cx).is_some();
4157 dismissed |= self.mouse_context_menu.take().is_some();
4158 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4159 dismissed |= self.snippet_stack.pop().is_some();
4160
4161 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4162 self.dismiss_diagnostics(cx);
4163 dismissed = true;
4164 }
4165
4166 dismissed
4167 }
4168
4169 fn linked_editing_ranges_for(
4170 &self,
4171 selection: Range<text::Anchor>,
4172 cx: &App,
4173 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4174 if self.linked_edit_ranges.is_empty() {
4175 return None;
4176 }
4177 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4178 selection.end.buffer_id.and_then(|end_buffer_id| {
4179 if selection.start.buffer_id != Some(end_buffer_id) {
4180 return None;
4181 }
4182 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4183 let snapshot = buffer.read(cx).snapshot();
4184 self.linked_edit_ranges
4185 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4186 .map(|ranges| (ranges, snapshot, buffer))
4187 })?;
4188 use text::ToOffset as TO;
4189 // find offset from the start of current range to current cursor position
4190 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4191
4192 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4193 let start_difference = start_offset - start_byte_offset;
4194 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4195 let end_difference = end_offset - start_byte_offset;
4196 // Current range has associated linked ranges.
4197 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4198 for range in linked_ranges.iter() {
4199 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4200 let end_offset = start_offset + end_difference;
4201 let start_offset = start_offset + start_difference;
4202 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4203 continue;
4204 }
4205 if self.selections.disjoint_anchor_ranges().any(|s| {
4206 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4207 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4208 {
4209 return false;
4210 }
4211 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4212 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4213 }) {
4214 continue;
4215 }
4216 let start = buffer_snapshot.anchor_after(start_offset);
4217 let end = buffer_snapshot.anchor_after(end_offset);
4218 linked_edits
4219 .entry(buffer.clone())
4220 .or_default()
4221 .push(start..end);
4222 }
4223 Some(linked_edits)
4224 }
4225
4226 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4227 let text: Arc<str> = text.into();
4228
4229 if self.read_only(cx) {
4230 return;
4231 }
4232
4233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4234
4235 self.unfold_buffers_with_selections(cx);
4236
4237 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4238 let mut bracket_inserted = false;
4239 let mut edits = Vec::new();
4240 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4241 let mut new_selections = Vec::with_capacity(selections.len());
4242 let mut new_autoclose_regions = Vec::new();
4243 let snapshot = self.buffer.read(cx).read(cx);
4244 let mut clear_linked_edit_ranges = false;
4245
4246 for (selection, autoclose_region) in
4247 self.selections_with_autoclose_regions(selections, &snapshot)
4248 {
4249 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4250 // Determine if the inserted text matches the opening or closing
4251 // bracket of any of this language's bracket pairs.
4252 let mut bracket_pair = None;
4253 let mut is_bracket_pair_start = false;
4254 let mut is_bracket_pair_end = false;
4255 if !text.is_empty() {
4256 let mut bracket_pair_matching_end = None;
4257 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4258 // and they are removing the character that triggered IME popup.
4259 for (pair, enabled) in scope.brackets() {
4260 if !pair.close && !pair.surround {
4261 continue;
4262 }
4263
4264 if enabled && pair.start.ends_with(text.as_ref()) {
4265 let prefix_len = pair.start.len() - text.len();
4266 let preceding_text_matches_prefix = prefix_len == 0
4267 || (selection.start.column >= (prefix_len as u32)
4268 && snapshot.contains_str_at(
4269 Point::new(
4270 selection.start.row,
4271 selection.start.column - (prefix_len as u32),
4272 ),
4273 &pair.start[..prefix_len],
4274 ));
4275 if preceding_text_matches_prefix {
4276 bracket_pair = Some(pair.clone());
4277 is_bracket_pair_start = true;
4278 break;
4279 }
4280 }
4281 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4282 {
4283 // take first bracket pair matching end, but don't break in case a later bracket
4284 // pair matches start
4285 bracket_pair_matching_end = Some(pair.clone());
4286 }
4287 }
4288 if let Some(end) = bracket_pair_matching_end
4289 && bracket_pair.is_none()
4290 {
4291 bracket_pair = Some(end);
4292 is_bracket_pair_end = true;
4293 }
4294 }
4295
4296 if let Some(bracket_pair) = bracket_pair {
4297 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4298 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4299 let auto_surround =
4300 self.use_auto_surround && snapshot_settings.use_auto_surround;
4301 if selection.is_empty() {
4302 if is_bracket_pair_start {
4303 // If the inserted text is a suffix of an opening bracket and the
4304 // selection is preceded by the rest of the opening bracket, then
4305 // insert the closing bracket.
4306 let following_text_allows_autoclose = snapshot
4307 .chars_at(selection.start)
4308 .next()
4309 .is_none_or(|c| scope.should_autoclose_before(c));
4310
4311 let preceding_text_allows_autoclose = selection.start.column == 0
4312 || snapshot
4313 .reversed_chars_at(selection.start)
4314 .next()
4315 .is_none_or(|c| {
4316 bracket_pair.start != bracket_pair.end
4317 || !snapshot
4318 .char_classifier_at(selection.start)
4319 .is_word(c)
4320 });
4321
4322 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4323 && bracket_pair.start.len() == 1
4324 {
4325 let target = bracket_pair.start.chars().next().unwrap();
4326 let current_line_count = snapshot
4327 .reversed_chars_at(selection.start)
4328 .take_while(|&c| c != '\n')
4329 .filter(|&c| c == target)
4330 .count();
4331 current_line_count % 2 == 1
4332 } else {
4333 false
4334 };
4335
4336 if autoclose
4337 && bracket_pair.close
4338 && following_text_allows_autoclose
4339 && preceding_text_allows_autoclose
4340 && !is_closing_quote
4341 {
4342 let anchor = snapshot.anchor_before(selection.end);
4343 new_selections.push((selection.map(|_| anchor), text.len()));
4344 new_autoclose_regions.push((
4345 anchor,
4346 text.len(),
4347 selection.id,
4348 bracket_pair.clone(),
4349 ));
4350 edits.push((
4351 selection.range(),
4352 format!("{}{}", text, bracket_pair.end).into(),
4353 ));
4354 bracket_inserted = true;
4355 continue;
4356 }
4357 }
4358
4359 if let Some(region) = autoclose_region {
4360 // If the selection is followed by an auto-inserted closing bracket,
4361 // then don't insert that closing bracket again; just move the selection
4362 // past the closing bracket.
4363 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4364 && text.as_ref() == region.pair.end.as_str()
4365 && snapshot.contains_str_at(region.range.end, text.as_ref());
4366 if should_skip {
4367 let anchor = snapshot.anchor_after(selection.end);
4368 new_selections
4369 .push((selection.map(|_| anchor), region.pair.end.len()));
4370 continue;
4371 }
4372 }
4373
4374 let always_treat_brackets_as_autoclosed = snapshot
4375 .language_settings_at(selection.start, cx)
4376 .always_treat_brackets_as_autoclosed;
4377 if always_treat_brackets_as_autoclosed
4378 && is_bracket_pair_end
4379 && snapshot.contains_str_at(selection.end, text.as_ref())
4380 {
4381 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4382 // and the inserted text is a closing bracket and the selection is followed
4383 // by the closing bracket then move the selection past the closing bracket.
4384 let anchor = snapshot.anchor_after(selection.end);
4385 new_selections.push((selection.map(|_| anchor), text.len()));
4386 continue;
4387 }
4388 }
4389 // If an opening bracket is 1 character long and is typed while
4390 // text is selected, then surround that text with the bracket pair.
4391 else if auto_surround
4392 && bracket_pair.surround
4393 && is_bracket_pair_start
4394 && bracket_pair.start.chars().count() == 1
4395 {
4396 edits.push((selection.start..selection.start, text.clone()));
4397 edits.push((
4398 selection.end..selection.end,
4399 bracket_pair.end.as_str().into(),
4400 ));
4401 bracket_inserted = true;
4402 new_selections.push((
4403 Selection {
4404 id: selection.id,
4405 start: snapshot.anchor_after(selection.start),
4406 end: snapshot.anchor_before(selection.end),
4407 reversed: selection.reversed,
4408 goal: selection.goal,
4409 },
4410 0,
4411 ));
4412 continue;
4413 }
4414 }
4415 }
4416
4417 if self.auto_replace_emoji_shortcode
4418 && selection.is_empty()
4419 && text.as_ref().ends_with(':')
4420 && let Some(possible_emoji_short_code) =
4421 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4422 && !possible_emoji_short_code.is_empty()
4423 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4424 {
4425 let emoji_shortcode_start = Point::new(
4426 selection.start.row,
4427 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4428 );
4429
4430 // Remove shortcode from buffer
4431 edits.push((
4432 emoji_shortcode_start..selection.start,
4433 "".to_string().into(),
4434 ));
4435 new_selections.push((
4436 Selection {
4437 id: selection.id,
4438 start: snapshot.anchor_after(emoji_shortcode_start),
4439 end: snapshot.anchor_before(selection.start),
4440 reversed: selection.reversed,
4441 goal: selection.goal,
4442 },
4443 0,
4444 ));
4445
4446 // Insert emoji
4447 let selection_start_anchor = snapshot.anchor_after(selection.start);
4448 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4449 edits.push((selection.start..selection.end, emoji.to_string().into()));
4450
4451 continue;
4452 }
4453
4454 // If not handling any auto-close operation, then just replace the selected
4455 // text with the given input and move the selection to the end of the
4456 // newly inserted text.
4457 let anchor = snapshot.anchor_after(selection.end);
4458 if !self.linked_edit_ranges.is_empty() {
4459 let start_anchor = snapshot.anchor_before(selection.start);
4460
4461 let is_word_char = text.chars().next().is_none_or(|char| {
4462 let classifier = snapshot
4463 .char_classifier_at(start_anchor.to_offset(&snapshot))
4464 .scope_context(Some(CharScopeContext::LinkedEdit));
4465 classifier.is_word(char)
4466 });
4467
4468 if is_word_char {
4469 if let Some(ranges) = self
4470 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4471 {
4472 for (buffer, edits) in ranges {
4473 linked_edits
4474 .entry(buffer.clone())
4475 .or_default()
4476 .extend(edits.into_iter().map(|range| (range, text.clone())));
4477 }
4478 }
4479 } else {
4480 clear_linked_edit_ranges = true;
4481 }
4482 }
4483
4484 new_selections.push((selection.map(|_| anchor), 0));
4485 edits.push((selection.start..selection.end, text.clone()));
4486 }
4487
4488 drop(snapshot);
4489
4490 self.transact(window, cx, |this, window, cx| {
4491 if clear_linked_edit_ranges {
4492 this.linked_edit_ranges.clear();
4493 }
4494 let initial_buffer_versions =
4495 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4496
4497 this.buffer.update(cx, |buffer, cx| {
4498 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4499 });
4500 for (buffer, edits) in linked_edits {
4501 buffer.update(cx, |buffer, cx| {
4502 let snapshot = buffer.snapshot();
4503 let edits = edits
4504 .into_iter()
4505 .map(|(range, text)| {
4506 use text::ToPoint as TP;
4507 let end_point = TP::to_point(&range.end, &snapshot);
4508 let start_point = TP::to_point(&range.start, &snapshot);
4509 (start_point..end_point, text)
4510 })
4511 .sorted_by_key(|(range, _)| range.start);
4512 buffer.edit(edits, None, cx);
4513 })
4514 }
4515 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4516 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4517 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4518 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4519 new_anchor_selections,
4520 &map,
4521 )
4522 .zip(new_selection_deltas)
4523 .map(|(selection, delta)| Selection {
4524 id: selection.id,
4525 start: selection.start + delta,
4526 end: selection.end + delta,
4527 reversed: selection.reversed,
4528 goal: SelectionGoal::None,
4529 })
4530 .collect::<Vec<_>>();
4531
4532 let mut i = 0;
4533 for (position, delta, selection_id, pair) in new_autoclose_regions {
4534 let position = position.to_offset(map.buffer_snapshot()) + delta;
4535 let start = map.buffer_snapshot().anchor_before(position);
4536 let end = map.buffer_snapshot().anchor_after(position);
4537 while let Some(existing_state) = this.autoclose_regions.get(i) {
4538 match existing_state
4539 .range
4540 .start
4541 .cmp(&start, map.buffer_snapshot())
4542 {
4543 Ordering::Less => i += 1,
4544 Ordering::Greater => break,
4545 Ordering::Equal => {
4546 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4547 Ordering::Less => i += 1,
4548 Ordering::Equal => break,
4549 Ordering::Greater => break,
4550 }
4551 }
4552 }
4553 }
4554 this.autoclose_regions.insert(
4555 i,
4556 AutocloseRegion {
4557 selection_id,
4558 range: start..end,
4559 pair,
4560 },
4561 );
4562 }
4563
4564 let had_active_edit_prediction = this.has_active_edit_prediction();
4565 this.change_selections(
4566 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4567 window,
4568 cx,
4569 |s| s.select(new_selections),
4570 );
4571
4572 if !bracket_inserted
4573 && let Some(on_type_format_task) =
4574 this.trigger_on_type_formatting(text.to_string(), window, cx)
4575 {
4576 on_type_format_task.detach_and_log_err(cx);
4577 }
4578
4579 let editor_settings = EditorSettings::get_global(cx);
4580 if bracket_inserted
4581 && (editor_settings.auto_signature_help
4582 || editor_settings.show_signature_help_after_edits)
4583 {
4584 this.show_signature_help(&ShowSignatureHelp, window, cx);
4585 }
4586
4587 let trigger_in_words =
4588 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4589 if this.hard_wrap.is_some() {
4590 let latest: Range<Point> = this.selections.newest(&map).range();
4591 if latest.is_empty()
4592 && this
4593 .buffer()
4594 .read(cx)
4595 .snapshot(cx)
4596 .line_len(MultiBufferRow(latest.start.row))
4597 == latest.start.column
4598 {
4599 this.rewrap_impl(
4600 RewrapOptions {
4601 override_language_settings: true,
4602 preserve_existing_whitespace: true,
4603 },
4604 cx,
4605 )
4606 }
4607 }
4608 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4609 refresh_linked_ranges(this, window, cx);
4610 this.refresh_edit_prediction(true, false, window, cx);
4611 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4612 });
4613 }
4614
4615 fn find_possible_emoji_shortcode_at_position(
4616 snapshot: &MultiBufferSnapshot,
4617 position: Point,
4618 ) -> Option<String> {
4619 let mut chars = Vec::new();
4620 let mut found_colon = false;
4621 for char in snapshot.reversed_chars_at(position).take(100) {
4622 // Found a possible emoji shortcode in the middle of the buffer
4623 if found_colon {
4624 if char.is_whitespace() {
4625 chars.reverse();
4626 return Some(chars.iter().collect());
4627 }
4628 // If the previous character is not a whitespace, we are in the middle of a word
4629 // and we only want to complete the shortcode if the word is made up of other emojis
4630 let mut containing_word = String::new();
4631 for ch in snapshot
4632 .reversed_chars_at(position)
4633 .skip(chars.len() + 1)
4634 .take(100)
4635 {
4636 if ch.is_whitespace() {
4637 break;
4638 }
4639 containing_word.push(ch);
4640 }
4641 let containing_word = containing_word.chars().rev().collect::<String>();
4642 if util::word_consists_of_emojis(containing_word.as_str()) {
4643 chars.reverse();
4644 return Some(chars.iter().collect());
4645 }
4646 }
4647
4648 if char.is_whitespace() || !char.is_ascii() {
4649 return None;
4650 }
4651 if char == ':' {
4652 found_colon = true;
4653 } else {
4654 chars.push(char);
4655 }
4656 }
4657 // Found a possible emoji shortcode at the beginning of the buffer
4658 chars.reverse();
4659 Some(chars.iter().collect())
4660 }
4661
4662 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4664 self.transact(window, cx, |this, window, cx| {
4665 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4666 let selections = this
4667 .selections
4668 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4669 let multi_buffer = this.buffer.read(cx);
4670 let buffer = multi_buffer.snapshot(cx);
4671 selections
4672 .iter()
4673 .map(|selection| {
4674 let start_point = selection.start.to_point(&buffer);
4675 let mut existing_indent =
4676 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4677 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4678 let start = selection.start;
4679 let end = selection.end;
4680 let selection_is_empty = start == end;
4681 let language_scope = buffer.language_scope_at(start);
4682 let (
4683 comment_delimiter,
4684 doc_delimiter,
4685 insert_extra_newline,
4686 indent_on_newline,
4687 indent_on_extra_newline,
4688 ) = if let Some(language) = &language_scope {
4689 let mut insert_extra_newline =
4690 insert_extra_newline_brackets(&buffer, start..end, language)
4691 || insert_extra_newline_tree_sitter(&buffer, start..end);
4692
4693 // Comment extension on newline is allowed only for cursor selections
4694 let comment_delimiter = maybe!({
4695 if !selection_is_empty {
4696 return None;
4697 }
4698
4699 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4700 return None;
4701 }
4702
4703 let delimiters = language.line_comment_prefixes();
4704 let max_len_of_delimiter =
4705 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4706 let (snapshot, range) =
4707 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4708
4709 let num_of_whitespaces = snapshot
4710 .chars_for_range(range.clone())
4711 .take_while(|c| c.is_whitespace())
4712 .count();
4713 let comment_candidate = snapshot
4714 .chars_for_range(range.clone())
4715 .skip(num_of_whitespaces)
4716 .take(max_len_of_delimiter)
4717 .collect::<String>();
4718 let (delimiter, trimmed_len) = delimiters
4719 .iter()
4720 .filter_map(|delimiter| {
4721 let prefix = delimiter.trim_end();
4722 if comment_candidate.starts_with(prefix) {
4723 Some((delimiter, prefix.len()))
4724 } else {
4725 None
4726 }
4727 })
4728 .max_by_key(|(_, len)| *len)?;
4729
4730 if let Some(BlockCommentConfig {
4731 start: block_start, ..
4732 }) = language.block_comment()
4733 {
4734 let block_start_trimmed = block_start.trim_end();
4735 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4736 let line_content = snapshot
4737 .chars_for_range(range)
4738 .skip(num_of_whitespaces)
4739 .take(block_start_trimmed.len())
4740 .collect::<String>();
4741
4742 if line_content.starts_with(block_start_trimmed) {
4743 return None;
4744 }
4745 }
4746 }
4747
4748 let cursor_is_placed_after_comment_marker =
4749 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4750 if cursor_is_placed_after_comment_marker {
4751 Some(delimiter.clone())
4752 } else {
4753 None
4754 }
4755 });
4756
4757 let mut indent_on_newline = IndentSize::spaces(0);
4758 let mut indent_on_extra_newline = IndentSize::spaces(0);
4759
4760 let doc_delimiter = maybe!({
4761 if !selection_is_empty {
4762 return None;
4763 }
4764
4765 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4766 return None;
4767 }
4768
4769 let BlockCommentConfig {
4770 start: start_tag,
4771 end: end_tag,
4772 prefix: delimiter,
4773 tab_size: len,
4774 } = language.documentation_comment()?;
4775 let is_within_block_comment = buffer
4776 .language_scope_at(start_point)
4777 .is_some_and(|scope| scope.override_name() == Some("comment"));
4778 if !is_within_block_comment {
4779 return None;
4780 }
4781
4782 let (snapshot, range) =
4783 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4784
4785 let num_of_whitespaces = snapshot
4786 .chars_for_range(range.clone())
4787 .take_while(|c| c.is_whitespace())
4788 .count();
4789
4790 // 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.
4791 let column = start_point.column;
4792 let cursor_is_after_start_tag = {
4793 let start_tag_len = start_tag.len();
4794 let start_tag_line = snapshot
4795 .chars_for_range(range.clone())
4796 .skip(num_of_whitespaces)
4797 .take(start_tag_len)
4798 .collect::<String>();
4799 if start_tag_line.starts_with(start_tag.as_ref()) {
4800 num_of_whitespaces + start_tag_len <= column as usize
4801 } else {
4802 false
4803 }
4804 };
4805
4806 let cursor_is_after_delimiter = {
4807 let delimiter_trim = delimiter.trim_end();
4808 let delimiter_line = snapshot
4809 .chars_for_range(range.clone())
4810 .skip(num_of_whitespaces)
4811 .take(delimiter_trim.len())
4812 .collect::<String>();
4813 if delimiter_line.starts_with(delimiter_trim) {
4814 num_of_whitespaces + delimiter_trim.len() <= column as usize
4815 } else {
4816 false
4817 }
4818 };
4819
4820 let cursor_is_before_end_tag_if_exists = {
4821 let mut char_position = 0u32;
4822 let mut end_tag_offset = None;
4823
4824 'outer: for chunk in snapshot.text_for_range(range) {
4825 if let Some(byte_pos) = chunk.find(&**end_tag) {
4826 let chars_before_match =
4827 chunk[..byte_pos].chars().count() as u32;
4828 end_tag_offset =
4829 Some(char_position + chars_before_match);
4830 break 'outer;
4831 }
4832 char_position += chunk.chars().count() as u32;
4833 }
4834
4835 if let Some(end_tag_offset) = end_tag_offset {
4836 let cursor_is_before_end_tag = column <= end_tag_offset;
4837 if cursor_is_after_start_tag {
4838 if cursor_is_before_end_tag {
4839 insert_extra_newline = true;
4840 }
4841 let cursor_is_at_start_of_end_tag =
4842 column == end_tag_offset;
4843 if cursor_is_at_start_of_end_tag {
4844 indent_on_extra_newline.len = *len;
4845 }
4846 }
4847 cursor_is_before_end_tag
4848 } else {
4849 true
4850 }
4851 };
4852
4853 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4854 && cursor_is_before_end_tag_if_exists
4855 {
4856 if cursor_is_after_start_tag {
4857 indent_on_newline.len = *len;
4858 }
4859 Some(delimiter.clone())
4860 } else {
4861 None
4862 }
4863 });
4864
4865 (
4866 comment_delimiter,
4867 doc_delimiter,
4868 insert_extra_newline,
4869 indent_on_newline,
4870 indent_on_extra_newline,
4871 )
4872 } else {
4873 (
4874 None,
4875 None,
4876 false,
4877 IndentSize::default(),
4878 IndentSize::default(),
4879 )
4880 };
4881
4882 let prevent_auto_indent = doc_delimiter.is_some();
4883 let delimiter = comment_delimiter.or(doc_delimiter);
4884
4885 let capacity_for_delimiter =
4886 delimiter.as_deref().map(str::len).unwrap_or_default();
4887 let mut new_text = String::with_capacity(
4888 1 + capacity_for_delimiter
4889 + existing_indent.len as usize
4890 + indent_on_newline.len as usize
4891 + indent_on_extra_newline.len as usize,
4892 );
4893 new_text.push('\n');
4894 new_text.extend(existing_indent.chars());
4895 new_text.extend(indent_on_newline.chars());
4896
4897 if let Some(delimiter) = &delimiter {
4898 new_text.push_str(delimiter);
4899 }
4900
4901 if insert_extra_newline {
4902 new_text.push('\n');
4903 new_text.extend(existing_indent.chars());
4904 new_text.extend(indent_on_extra_newline.chars());
4905 }
4906
4907 let anchor = buffer.anchor_after(end);
4908 let new_selection = selection.map(|_| anchor);
4909 (
4910 ((start..end, new_text), prevent_auto_indent),
4911 (insert_extra_newline, new_selection),
4912 )
4913 })
4914 .unzip()
4915 };
4916
4917 let mut auto_indent_edits = Vec::new();
4918 let mut edits = Vec::new();
4919 for (edit, prevent_auto_indent) in edits_with_flags {
4920 if prevent_auto_indent {
4921 edits.push(edit);
4922 } else {
4923 auto_indent_edits.push(edit);
4924 }
4925 }
4926 if !edits.is_empty() {
4927 this.edit(edits, cx);
4928 }
4929 if !auto_indent_edits.is_empty() {
4930 this.edit_with_autoindent(auto_indent_edits, cx);
4931 }
4932
4933 let buffer = this.buffer.read(cx).snapshot(cx);
4934 let new_selections = selection_info
4935 .into_iter()
4936 .map(|(extra_newline_inserted, new_selection)| {
4937 let mut cursor = new_selection.end.to_point(&buffer);
4938 if extra_newline_inserted {
4939 cursor.row -= 1;
4940 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4941 }
4942 new_selection.map(|_| cursor)
4943 })
4944 .collect();
4945
4946 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4947 this.refresh_edit_prediction(true, false, window, cx);
4948 });
4949 }
4950
4951 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4953
4954 let buffer = self.buffer.read(cx);
4955 let snapshot = buffer.snapshot(cx);
4956
4957 let mut edits = Vec::new();
4958 let mut rows = Vec::new();
4959
4960 for (rows_inserted, selection) in self
4961 .selections
4962 .all_adjusted(&self.display_snapshot(cx))
4963 .into_iter()
4964 .enumerate()
4965 {
4966 let cursor = selection.head();
4967 let row = cursor.row;
4968
4969 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4970
4971 let newline = "\n".to_string();
4972 edits.push((start_of_line..start_of_line, newline));
4973
4974 rows.push(row + rows_inserted as u32);
4975 }
4976
4977 self.transact(window, cx, |editor, window, cx| {
4978 editor.edit(edits, cx);
4979
4980 editor.change_selections(Default::default(), window, cx, |s| {
4981 let mut index = 0;
4982 s.move_cursors_with(|map, _, _| {
4983 let row = rows[index];
4984 index += 1;
4985
4986 let point = Point::new(row, 0);
4987 let boundary = map.next_line_boundary(point).1;
4988 let clipped = map.clip_point(boundary, Bias::Left);
4989
4990 (clipped, SelectionGoal::None)
4991 });
4992 });
4993
4994 let mut indent_edits = Vec::new();
4995 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4996 for row in rows {
4997 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4998 for (row, indent) in indents {
4999 if indent.len == 0 {
5000 continue;
5001 }
5002
5003 let text = match indent.kind {
5004 IndentKind::Space => " ".repeat(indent.len as usize),
5005 IndentKind::Tab => "\t".repeat(indent.len as usize),
5006 };
5007 let point = Point::new(row.0, 0);
5008 indent_edits.push((point..point, text));
5009 }
5010 }
5011 editor.edit(indent_edits, cx);
5012 });
5013 }
5014
5015 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5017
5018 let buffer = self.buffer.read(cx);
5019 let snapshot = buffer.snapshot(cx);
5020
5021 let mut edits = Vec::new();
5022 let mut rows = Vec::new();
5023 let mut rows_inserted = 0;
5024
5025 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5026 let cursor = selection.head();
5027 let row = cursor.row;
5028
5029 let point = Point::new(row + 1, 0);
5030 let start_of_line = snapshot.clip_point(point, Bias::Left);
5031
5032 let newline = "\n".to_string();
5033 edits.push((start_of_line..start_of_line, newline));
5034
5035 rows_inserted += 1;
5036 rows.push(row + rows_inserted);
5037 }
5038
5039 self.transact(window, cx, |editor, window, cx| {
5040 editor.edit(edits, cx);
5041
5042 editor.change_selections(Default::default(), window, cx, |s| {
5043 let mut index = 0;
5044 s.move_cursors_with(|map, _, _| {
5045 let row = rows[index];
5046 index += 1;
5047
5048 let point = Point::new(row, 0);
5049 let boundary = map.next_line_boundary(point).1;
5050 let clipped = map.clip_point(boundary, Bias::Left);
5051
5052 (clipped, SelectionGoal::None)
5053 });
5054 });
5055
5056 let mut indent_edits = Vec::new();
5057 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5058 for row in rows {
5059 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5060 for (row, indent) in indents {
5061 if indent.len == 0 {
5062 continue;
5063 }
5064
5065 let text = match indent.kind {
5066 IndentKind::Space => " ".repeat(indent.len as usize),
5067 IndentKind::Tab => "\t".repeat(indent.len as usize),
5068 };
5069 let point = Point::new(row.0, 0);
5070 indent_edits.push((point..point, text));
5071 }
5072 }
5073 editor.edit(indent_edits, cx);
5074 });
5075 }
5076
5077 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5078 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5079 original_indent_columns: Vec::new(),
5080 });
5081 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5082 }
5083
5084 fn insert_with_autoindent_mode(
5085 &mut self,
5086 text: &str,
5087 autoindent_mode: Option<AutoindentMode>,
5088 window: &mut Window,
5089 cx: &mut Context<Self>,
5090 ) {
5091 if self.read_only(cx) {
5092 return;
5093 }
5094
5095 let text: Arc<str> = text.into();
5096 self.transact(window, cx, |this, window, cx| {
5097 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5098 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5099 let anchors = {
5100 let snapshot = buffer.read(cx);
5101 old_selections
5102 .iter()
5103 .map(|s| {
5104 let anchor = snapshot.anchor_after(s.head());
5105 s.map(|_| anchor)
5106 })
5107 .collect::<Vec<_>>()
5108 };
5109 buffer.edit(
5110 old_selections
5111 .iter()
5112 .map(|s| (s.start..s.end, text.clone())),
5113 autoindent_mode,
5114 cx,
5115 );
5116 anchors
5117 });
5118
5119 this.change_selections(Default::default(), window, cx, |s| {
5120 s.select_anchors(selection_anchors);
5121 });
5122
5123 cx.notify();
5124 });
5125 }
5126
5127 fn trigger_completion_on_input(
5128 &mut self,
5129 text: &str,
5130 trigger_in_words: bool,
5131 window: &mut Window,
5132 cx: &mut Context<Self>,
5133 ) {
5134 let completions_source = self
5135 .context_menu
5136 .borrow()
5137 .as_ref()
5138 .and_then(|menu| match menu {
5139 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5140 CodeContextMenu::CodeActions(_) => None,
5141 });
5142
5143 match completions_source {
5144 Some(CompletionsMenuSource::Words { .. }) => {
5145 self.open_or_update_completions_menu(
5146 Some(CompletionsMenuSource::Words {
5147 ignore_threshold: false,
5148 }),
5149 None,
5150 trigger_in_words,
5151 window,
5152 cx,
5153 );
5154 }
5155 _ => self.open_or_update_completions_menu(
5156 None,
5157 Some(text.to_owned()).filter(|x| !x.is_empty()),
5158 true,
5159 window,
5160 cx,
5161 ),
5162 }
5163 }
5164
5165 /// If any empty selections is touching the start of its innermost containing autoclose
5166 /// region, expand it to select the brackets.
5167 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5168 let selections = self
5169 .selections
5170 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5171 let buffer = self.buffer.read(cx).read(cx);
5172 let new_selections = self
5173 .selections_with_autoclose_regions(selections, &buffer)
5174 .map(|(mut selection, region)| {
5175 if !selection.is_empty() {
5176 return selection;
5177 }
5178
5179 if let Some(region) = region {
5180 let mut range = region.range.to_offset(&buffer);
5181 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5182 range.start -= region.pair.start.len();
5183 if buffer.contains_str_at(range.start, ®ion.pair.start)
5184 && buffer.contains_str_at(range.end, ®ion.pair.end)
5185 {
5186 range.end += region.pair.end.len();
5187 selection.start = range.start;
5188 selection.end = range.end;
5189
5190 return selection;
5191 }
5192 }
5193 }
5194
5195 let always_treat_brackets_as_autoclosed = buffer
5196 .language_settings_at(selection.start, cx)
5197 .always_treat_brackets_as_autoclosed;
5198
5199 if !always_treat_brackets_as_autoclosed {
5200 return selection;
5201 }
5202
5203 if let Some(scope) = buffer.language_scope_at(selection.start) {
5204 for (pair, enabled) in scope.brackets() {
5205 if !enabled || !pair.close {
5206 continue;
5207 }
5208
5209 if buffer.contains_str_at(selection.start, &pair.end) {
5210 let pair_start_len = pair.start.len();
5211 if buffer.contains_str_at(
5212 selection.start.saturating_sub_usize(pair_start_len),
5213 &pair.start,
5214 ) {
5215 selection.start -= pair_start_len;
5216 selection.end += pair.end.len();
5217
5218 return selection;
5219 }
5220 }
5221 }
5222 }
5223
5224 selection
5225 })
5226 .collect();
5227
5228 drop(buffer);
5229 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5230 selections.select(new_selections)
5231 });
5232 }
5233
5234 /// Iterate the given selections, and for each one, find the smallest surrounding
5235 /// autoclose region. This uses the ordering of the selections and the autoclose
5236 /// regions to avoid repeated comparisons.
5237 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5238 &'a self,
5239 selections: impl IntoIterator<Item = Selection<D>>,
5240 buffer: &'a MultiBufferSnapshot,
5241 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5242 let mut i = 0;
5243 let mut regions = self.autoclose_regions.as_slice();
5244 selections.into_iter().map(move |selection| {
5245 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5246
5247 let mut enclosing = None;
5248 while let Some(pair_state) = regions.get(i) {
5249 if pair_state.range.end.to_offset(buffer) < range.start {
5250 regions = ®ions[i + 1..];
5251 i = 0;
5252 } else if pair_state.range.start.to_offset(buffer) > range.end {
5253 break;
5254 } else {
5255 if pair_state.selection_id == selection.id {
5256 enclosing = Some(pair_state);
5257 }
5258 i += 1;
5259 }
5260 }
5261
5262 (selection, enclosing)
5263 })
5264 }
5265
5266 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5267 fn invalidate_autoclose_regions(
5268 &mut self,
5269 mut selections: &[Selection<Anchor>],
5270 buffer: &MultiBufferSnapshot,
5271 ) {
5272 self.autoclose_regions.retain(|state| {
5273 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5274 return false;
5275 }
5276
5277 let mut i = 0;
5278 while let Some(selection) = selections.get(i) {
5279 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5280 selections = &selections[1..];
5281 continue;
5282 }
5283 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5284 break;
5285 }
5286 if selection.id == state.selection_id {
5287 return true;
5288 } else {
5289 i += 1;
5290 }
5291 }
5292 false
5293 });
5294 }
5295
5296 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5297 let offset = position.to_offset(buffer);
5298 let (word_range, kind) =
5299 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5300 if offset > word_range.start && kind == Some(CharKind::Word) {
5301 Some(
5302 buffer
5303 .text_for_range(word_range.start..offset)
5304 .collect::<String>(),
5305 )
5306 } else {
5307 None
5308 }
5309 }
5310
5311 pub fn visible_excerpts(
5312 &self,
5313 lsp_related_only: bool,
5314 cx: &mut Context<Editor>,
5315 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5316 let project = self.project().cloned();
5317 let multi_buffer = self.buffer().read(cx);
5318 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5319 let multi_buffer_visible_start = self
5320 .scroll_manager
5321 .anchor()
5322 .anchor
5323 .to_point(&multi_buffer_snapshot);
5324 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5325 multi_buffer_visible_start
5326 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5327 Bias::Left,
5328 );
5329 multi_buffer_snapshot
5330 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5331 .into_iter()
5332 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5333 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5334 if !lsp_related_only {
5335 return Some((
5336 excerpt_id,
5337 (
5338 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5339 buffer.version().clone(),
5340 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5341 ),
5342 ));
5343 }
5344
5345 let project = project.as_ref()?.read(cx);
5346 let buffer_file = project::File::from_dyn(buffer.file())?;
5347 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5348 let worktree_entry = buffer_worktree
5349 .read(cx)
5350 .entry_for_id(buffer_file.project_entry_id()?)?;
5351 if worktree_entry.is_ignored {
5352 None
5353 } else {
5354 Some((
5355 excerpt_id,
5356 (
5357 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5358 buffer.version().clone(),
5359 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5360 ),
5361 ))
5362 }
5363 })
5364 .collect()
5365 }
5366
5367 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5368 TextLayoutDetails {
5369 text_system: window.text_system().clone(),
5370 editor_style: self.style.clone().unwrap(),
5371 rem_size: window.rem_size(),
5372 scroll_anchor: self.scroll_manager.anchor(),
5373 visible_rows: self.visible_line_count(),
5374 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5375 }
5376 }
5377
5378 fn trigger_on_type_formatting(
5379 &self,
5380 input: String,
5381 window: &mut Window,
5382 cx: &mut Context<Self>,
5383 ) -> Option<Task<Result<()>>> {
5384 if input.len() != 1 {
5385 return None;
5386 }
5387
5388 let project = self.project()?;
5389 let position = self.selections.newest_anchor().head();
5390 let (buffer, buffer_position) = self
5391 .buffer
5392 .read(cx)
5393 .text_anchor_for_position(position, cx)?;
5394
5395 let settings = language_settings::language_settings(
5396 buffer
5397 .read(cx)
5398 .language_at(buffer_position)
5399 .map(|l| l.name()),
5400 buffer.read(cx).file(),
5401 cx,
5402 );
5403 if !settings.use_on_type_format {
5404 return None;
5405 }
5406
5407 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5408 // hence we do LSP request & edit on host side only — add formats to host's history.
5409 let push_to_lsp_host_history = true;
5410 // If this is not the host, append its history with new edits.
5411 let push_to_client_history = project.read(cx).is_via_collab();
5412
5413 let on_type_formatting = project.update(cx, |project, cx| {
5414 project.on_type_format(
5415 buffer.clone(),
5416 buffer_position,
5417 input,
5418 push_to_lsp_host_history,
5419 cx,
5420 )
5421 });
5422 Some(cx.spawn_in(window, async move |editor, cx| {
5423 if let Some(transaction) = on_type_formatting.await? {
5424 if push_to_client_history {
5425 buffer
5426 .update(cx, |buffer, _| {
5427 buffer.push_transaction(transaction, Instant::now());
5428 buffer.finalize_last_transaction();
5429 })
5430 .ok();
5431 }
5432 editor.update(cx, |editor, cx| {
5433 editor.refresh_document_highlights(cx);
5434 })?;
5435 }
5436 Ok(())
5437 }))
5438 }
5439
5440 pub fn show_word_completions(
5441 &mut self,
5442 _: &ShowWordCompletions,
5443 window: &mut Window,
5444 cx: &mut Context<Self>,
5445 ) {
5446 self.open_or_update_completions_menu(
5447 Some(CompletionsMenuSource::Words {
5448 ignore_threshold: true,
5449 }),
5450 None,
5451 false,
5452 window,
5453 cx,
5454 );
5455 }
5456
5457 pub fn show_completions(
5458 &mut self,
5459 _: &ShowCompletions,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) {
5463 self.open_or_update_completions_menu(None, None, false, window, cx);
5464 }
5465
5466 fn open_or_update_completions_menu(
5467 &mut self,
5468 requested_source: Option<CompletionsMenuSource>,
5469 trigger: Option<String>,
5470 trigger_in_words: bool,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) {
5474 if self.pending_rename.is_some() {
5475 return;
5476 }
5477
5478 let completions_source = self
5479 .context_menu
5480 .borrow()
5481 .as_ref()
5482 .and_then(|menu| match menu {
5483 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5484 CodeContextMenu::CodeActions(_) => None,
5485 });
5486
5487 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5488
5489 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5490 // inserted and selected. To handle that case, the start of the selection is used so that
5491 // the menu starts with all choices.
5492 let position = self
5493 .selections
5494 .newest_anchor()
5495 .start
5496 .bias_right(&multibuffer_snapshot);
5497 if position.diff_base_anchor.is_some() {
5498 return;
5499 }
5500 let buffer_position = multibuffer_snapshot.anchor_before(position);
5501 let Some(buffer) = buffer_position
5502 .text_anchor
5503 .buffer_id
5504 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5505 else {
5506 return;
5507 };
5508 let buffer_snapshot = buffer.read(cx).snapshot();
5509
5510 let query: Option<Arc<String>> =
5511 Self::completion_query(&multibuffer_snapshot, buffer_position)
5512 .map(|query| query.into());
5513
5514 drop(multibuffer_snapshot);
5515
5516 // Hide the current completions menu when query is empty. Without this, cached
5517 // completions from before the trigger char may be reused (#32774).
5518 if query.is_none() {
5519 let menu_is_open = matches!(
5520 self.context_menu.borrow().as_ref(),
5521 Some(CodeContextMenu::Completions(_))
5522 );
5523 if menu_is_open {
5524 self.hide_context_menu(window, cx);
5525 }
5526 }
5527
5528 let mut ignore_word_threshold = false;
5529 let provider = match requested_source {
5530 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5531 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5532 ignore_word_threshold = ignore_threshold;
5533 None
5534 }
5535 Some(CompletionsMenuSource::SnippetChoices)
5536 | Some(CompletionsMenuSource::SnippetsOnly) => {
5537 log::error!("bug: SnippetChoices requested_source is not handled");
5538 None
5539 }
5540 };
5541
5542 let sort_completions = provider
5543 .as_ref()
5544 .is_some_and(|provider| provider.sort_completions());
5545
5546 let filter_completions = provider
5547 .as_ref()
5548 .is_none_or(|provider| provider.filter_completions());
5549
5550 let was_snippets_only = matches!(
5551 completions_source,
5552 Some(CompletionsMenuSource::SnippetsOnly)
5553 );
5554
5555 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5556 if filter_completions {
5557 menu.filter(
5558 query.clone().unwrap_or_default(),
5559 buffer_position.text_anchor,
5560 &buffer,
5561 provider.clone(),
5562 window,
5563 cx,
5564 );
5565 }
5566 // When `is_incomplete` is false, no need to re-query completions when the current query
5567 // is a suffix of the initial query.
5568 let was_complete = !menu.is_incomplete;
5569 if was_complete && !was_snippets_only {
5570 // If the new query is a suffix of the old query (typing more characters) and
5571 // the previous result was complete, the existing completions can be filtered.
5572 //
5573 // Note that snippet completions are always complete.
5574 let query_matches = match (&menu.initial_query, &query) {
5575 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5576 (None, _) => true,
5577 _ => false,
5578 };
5579 if query_matches {
5580 let position_matches = if menu.initial_position == position {
5581 true
5582 } else {
5583 let snapshot = self.buffer.read(cx).read(cx);
5584 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5585 };
5586 if position_matches {
5587 return;
5588 }
5589 }
5590 }
5591 };
5592
5593 let Anchor {
5594 excerpt_id: buffer_excerpt_id,
5595 text_anchor: buffer_position,
5596 ..
5597 } = buffer_position;
5598
5599 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5600 buffer_snapshot.surrounding_word(buffer_position, None)
5601 {
5602 let word_to_exclude = buffer_snapshot
5603 .text_for_range(word_range.clone())
5604 .collect::<String>();
5605 (
5606 buffer_snapshot.anchor_before(word_range.start)
5607 ..buffer_snapshot.anchor_after(buffer_position),
5608 Some(word_to_exclude),
5609 )
5610 } else {
5611 (buffer_position..buffer_position, None)
5612 };
5613
5614 let language = buffer_snapshot
5615 .language_at(buffer_position)
5616 .map(|language| language.name());
5617
5618 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5619 .completions
5620 .clone();
5621
5622 let show_completion_documentation = buffer_snapshot
5623 .settings_at(buffer_position, cx)
5624 .show_completion_documentation;
5625
5626 // The document can be large, so stay in reasonable bounds when searching for words,
5627 // otherwise completion pop-up might be slow to appear.
5628 const WORD_LOOKUP_ROWS: u32 = 5_000;
5629 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5630 let min_word_search = buffer_snapshot.clip_point(
5631 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5632 Bias::Left,
5633 );
5634 let max_word_search = buffer_snapshot.clip_point(
5635 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5636 Bias::Right,
5637 );
5638 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5639 ..buffer_snapshot.point_to_offset(max_word_search);
5640
5641 let skip_digits = query
5642 .as_ref()
5643 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5644
5645 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5646 trigger.as_ref().is_none_or(|trigger| {
5647 provider.is_completion_trigger(
5648 &buffer,
5649 position.text_anchor,
5650 trigger,
5651 trigger_in_words,
5652 completions_source.is_some(),
5653 cx,
5654 )
5655 })
5656 });
5657
5658 let provider_responses = if let Some(provider) = &provider
5659 && load_provider_completions
5660 {
5661 let trigger_character =
5662 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5663 let completion_context = CompletionContext {
5664 trigger_kind: match &trigger_character {
5665 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5666 None => CompletionTriggerKind::INVOKED,
5667 },
5668 trigger_character,
5669 };
5670
5671 provider.completions(
5672 buffer_excerpt_id,
5673 &buffer,
5674 buffer_position,
5675 completion_context,
5676 window,
5677 cx,
5678 )
5679 } else {
5680 Task::ready(Ok(Vec::new()))
5681 };
5682
5683 let load_word_completions = if !self.word_completions_enabled {
5684 false
5685 } else if requested_source
5686 == Some(CompletionsMenuSource::Words {
5687 ignore_threshold: true,
5688 })
5689 {
5690 true
5691 } else {
5692 load_provider_completions
5693 && completion_settings.words != WordsCompletionMode::Disabled
5694 && (ignore_word_threshold || {
5695 let words_min_length = completion_settings.words_min_length;
5696 // check whether word has at least `words_min_length` characters
5697 let query_chars = query.iter().flat_map(|q| q.chars());
5698 query_chars.take(words_min_length).count() == words_min_length
5699 })
5700 };
5701
5702 let mut words = if load_word_completions {
5703 cx.background_spawn({
5704 let buffer_snapshot = buffer_snapshot.clone();
5705 async move {
5706 buffer_snapshot.words_in_range(WordsQuery {
5707 fuzzy_contents: None,
5708 range: word_search_range,
5709 skip_digits,
5710 })
5711 }
5712 })
5713 } else {
5714 Task::ready(BTreeMap::default())
5715 };
5716
5717 let snippets = if let Some(provider) = &provider
5718 && provider.show_snippets()
5719 && let Some(project) = self.project()
5720 {
5721 let char_classifier = buffer_snapshot
5722 .char_classifier_at(buffer_position)
5723 .scope_context(Some(CharScopeContext::Completion));
5724 project.update(cx, |project, cx| {
5725 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5726 })
5727 } else {
5728 Task::ready(Ok(CompletionResponse {
5729 completions: Vec::new(),
5730 display_options: Default::default(),
5731 is_incomplete: false,
5732 }))
5733 };
5734
5735 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5736
5737 let id = post_inc(&mut self.next_completion_id);
5738 let task = cx.spawn_in(window, async move |editor, cx| {
5739 let Ok(()) = editor.update(cx, |this, _| {
5740 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5741 }) else {
5742 return;
5743 };
5744
5745 // TODO: Ideally completions from different sources would be selectively re-queried, so
5746 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5747 let mut completions = Vec::new();
5748 let mut is_incomplete = false;
5749 let mut display_options: Option<CompletionDisplayOptions> = None;
5750 if let Some(provider_responses) = provider_responses.await.log_err()
5751 && !provider_responses.is_empty()
5752 {
5753 for response in provider_responses {
5754 completions.extend(response.completions);
5755 is_incomplete = is_incomplete || response.is_incomplete;
5756 match display_options.as_mut() {
5757 None => {
5758 display_options = Some(response.display_options);
5759 }
5760 Some(options) => options.merge(&response.display_options),
5761 }
5762 }
5763 if completion_settings.words == WordsCompletionMode::Fallback {
5764 words = Task::ready(BTreeMap::default());
5765 }
5766 }
5767 let display_options = display_options.unwrap_or_default();
5768
5769 let mut words = words.await;
5770 if let Some(word_to_exclude) = &word_to_exclude {
5771 words.remove(word_to_exclude);
5772 }
5773 for lsp_completion in &completions {
5774 words.remove(&lsp_completion.new_text);
5775 }
5776 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5777 replace_range: word_replace_range.clone(),
5778 new_text: word.clone(),
5779 label: CodeLabel::plain(word, None),
5780 match_start: None,
5781 snippet_deduplication_key: None,
5782 icon_path: None,
5783 documentation: None,
5784 source: CompletionSource::BufferWord {
5785 word_range,
5786 resolved: false,
5787 },
5788 insert_text_mode: Some(InsertTextMode::AS_IS),
5789 confirm: None,
5790 }));
5791
5792 completions.extend(
5793 snippets
5794 .await
5795 .into_iter()
5796 .flat_map(|response| response.completions),
5797 );
5798
5799 let menu = if completions.is_empty() {
5800 None
5801 } else {
5802 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5803 let languages = editor
5804 .workspace
5805 .as_ref()
5806 .and_then(|(workspace, _)| workspace.upgrade())
5807 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5808 let menu = CompletionsMenu::new(
5809 id,
5810 requested_source.unwrap_or(if load_provider_completions {
5811 CompletionsMenuSource::Normal
5812 } else {
5813 CompletionsMenuSource::SnippetsOnly
5814 }),
5815 sort_completions,
5816 show_completion_documentation,
5817 position,
5818 query.clone(),
5819 is_incomplete,
5820 buffer.clone(),
5821 completions.into(),
5822 display_options,
5823 snippet_sort_order,
5824 languages,
5825 language,
5826 cx,
5827 );
5828
5829 let query = if filter_completions { query } else { None };
5830 let matches_task = menu.do_async_filtering(
5831 query.unwrap_or_default(),
5832 buffer_position,
5833 &buffer,
5834 cx,
5835 );
5836 (menu, matches_task)
5837 }) else {
5838 return;
5839 };
5840
5841 let matches = matches_task.await;
5842
5843 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5844 // Newer menu already set, so exit.
5845 if let Some(CodeContextMenu::Completions(prev_menu)) =
5846 editor.context_menu.borrow().as_ref()
5847 && prev_menu.id > id
5848 {
5849 return;
5850 };
5851
5852 // Only valid to take prev_menu because either the new menu is immediately set
5853 // below, or the menu is hidden.
5854 if let Some(CodeContextMenu::Completions(prev_menu)) =
5855 editor.context_menu.borrow_mut().take()
5856 {
5857 let position_matches =
5858 if prev_menu.initial_position == menu.initial_position {
5859 true
5860 } else {
5861 let snapshot = editor.buffer.read(cx).read(cx);
5862 prev_menu.initial_position.to_offset(&snapshot)
5863 == menu.initial_position.to_offset(&snapshot)
5864 };
5865 if position_matches {
5866 // Preserve markdown cache before `set_filter_results` because it will
5867 // try to populate the documentation cache.
5868 menu.preserve_markdown_cache(prev_menu);
5869 }
5870 };
5871
5872 menu.set_filter_results(matches, provider, window, cx);
5873 }) else {
5874 return;
5875 };
5876
5877 menu.visible().then_some(menu)
5878 };
5879
5880 editor
5881 .update_in(cx, |editor, window, cx| {
5882 if editor.focus_handle.is_focused(window)
5883 && let Some(menu) = menu
5884 {
5885 *editor.context_menu.borrow_mut() =
5886 Some(CodeContextMenu::Completions(menu));
5887
5888 crate::hover_popover::hide_hover(editor, cx);
5889 if editor.show_edit_predictions_in_menu() {
5890 editor.update_visible_edit_prediction(window, cx);
5891 } else {
5892 editor.discard_edit_prediction(false, cx);
5893 }
5894
5895 cx.notify();
5896 return;
5897 }
5898
5899 if editor.completion_tasks.len() <= 1 {
5900 // If there are no more completion tasks and the last menu was empty, we should hide it.
5901 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5902 // If it was already hidden and we don't show edit predictions in the menu,
5903 // we should also show the edit prediction when available.
5904 if was_hidden && editor.show_edit_predictions_in_menu() {
5905 editor.update_visible_edit_prediction(window, cx);
5906 }
5907 }
5908 })
5909 .ok();
5910 });
5911
5912 self.completion_tasks.push((id, task));
5913 }
5914
5915 #[cfg(feature = "test-support")]
5916 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5917 let menu = self.context_menu.borrow();
5918 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5919 let completions = menu.completions.borrow();
5920 Some(completions.to_vec())
5921 } else {
5922 None
5923 }
5924 }
5925
5926 pub fn with_completions_menu_matching_id<R>(
5927 &self,
5928 id: CompletionId,
5929 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5930 ) -> R {
5931 let mut context_menu = self.context_menu.borrow_mut();
5932 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5933 return f(None);
5934 };
5935 if completions_menu.id != id {
5936 return f(None);
5937 }
5938 f(Some(completions_menu))
5939 }
5940
5941 pub fn confirm_completion(
5942 &mut self,
5943 action: &ConfirmCompletion,
5944 window: &mut Window,
5945 cx: &mut Context<Self>,
5946 ) -> Option<Task<Result<()>>> {
5947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5948 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5949 }
5950
5951 pub fn confirm_completion_insert(
5952 &mut self,
5953 _: &ConfirmCompletionInsert,
5954 window: &mut Window,
5955 cx: &mut Context<Self>,
5956 ) -> Option<Task<Result<()>>> {
5957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5958 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5959 }
5960
5961 pub fn confirm_completion_replace(
5962 &mut self,
5963 _: &ConfirmCompletionReplace,
5964 window: &mut Window,
5965 cx: &mut Context<Self>,
5966 ) -> Option<Task<Result<()>>> {
5967 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5968 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5969 }
5970
5971 pub fn compose_completion(
5972 &mut self,
5973 action: &ComposeCompletion,
5974 window: &mut Window,
5975 cx: &mut Context<Self>,
5976 ) -> Option<Task<Result<()>>> {
5977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5978 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5979 }
5980
5981 fn do_completion(
5982 &mut self,
5983 item_ix: Option<usize>,
5984 intent: CompletionIntent,
5985 window: &mut Window,
5986 cx: &mut Context<Editor>,
5987 ) -> Option<Task<Result<()>>> {
5988 use language::ToOffset as _;
5989
5990 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5991 else {
5992 return None;
5993 };
5994
5995 let candidate_id = {
5996 let entries = completions_menu.entries.borrow();
5997 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5998 if self.show_edit_predictions_in_menu() {
5999 self.discard_edit_prediction(true, cx);
6000 }
6001 mat.candidate_id
6002 };
6003
6004 let completion = completions_menu
6005 .completions
6006 .borrow()
6007 .get(candidate_id)?
6008 .clone();
6009 cx.stop_propagation();
6010
6011 let buffer_handle = completions_menu.buffer.clone();
6012
6013 let CompletionEdit {
6014 new_text,
6015 snippet,
6016 replace_range,
6017 } = process_completion_for_edit(
6018 &completion,
6019 intent,
6020 &buffer_handle,
6021 &completions_menu.initial_position.text_anchor,
6022 cx,
6023 );
6024
6025 let buffer = buffer_handle.read(cx);
6026 let snapshot = self.buffer.read(cx).snapshot(cx);
6027 let newest_anchor = self.selections.newest_anchor();
6028 let replace_range_multibuffer = {
6029 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6030 excerpt.map_range_from_buffer(replace_range.clone())
6031 };
6032 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6033 return None;
6034 }
6035
6036 let old_text = buffer
6037 .text_for_range(replace_range.clone())
6038 .collect::<String>();
6039 let lookbehind = newest_anchor
6040 .start
6041 .text_anchor
6042 .to_offset(buffer)
6043 .saturating_sub(replace_range.start.0);
6044 let lookahead = replace_range
6045 .end
6046 .0
6047 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6048 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6049 let suffix = &old_text[lookbehind.min(old_text.len())..];
6050
6051 let selections = self
6052 .selections
6053 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6054 let mut ranges = Vec::new();
6055 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6056
6057 for selection in &selections {
6058 let range = if selection.id == newest_anchor.id {
6059 replace_range_multibuffer.clone()
6060 } else {
6061 let mut range = selection.range();
6062
6063 // if prefix is present, don't duplicate it
6064 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6065 range.start = range.start.saturating_sub_usize(lookbehind);
6066
6067 // if suffix is also present, mimic the newest cursor and replace it
6068 if selection.id != newest_anchor.id
6069 && snapshot.contains_str_at(range.end, suffix)
6070 {
6071 range.end += lookahead;
6072 }
6073 }
6074 range
6075 };
6076
6077 ranges.push(range.clone());
6078
6079 if !self.linked_edit_ranges.is_empty() {
6080 let start_anchor = snapshot.anchor_before(range.start);
6081 let end_anchor = snapshot.anchor_after(range.end);
6082 if let Some(ranges) = self
6083 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6084 {
6085 for (buffer, edits) in ranges {
6086 linked_edits
6087 .entry(buffer.clone())
6088 .or_default()
6089 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6090 }
6091 }
6092 }
6093 }
6094
6095 let common_prefix_len = old_text
6096 .chars()
6097 .zip(new_text.chars())
6098 .take_while(|(a, b)| a == b)
6099 .map(|(a, _)| a.len_utf8())
6100 .sum::<usize>();
6101
6102 cx.emit(EditorEvent::InputHandled {
6103 utf16_range_to_replace: None,
6104 text: new_text[common_prefix_len..].into(),
6105 });
6106
6107 self.transact(window, cx, |editor, window, cx| {
6108 if let Some(mut snippet) = snippet {
6109 snippet.text = new_text.to_string();
6110 editor
6111 .insert_snippet(&ranges, snippet, window, cx)
6112 .log_err();
6113 } else {
6114 editor.buffer.update(cx, |multi_buffer, cx| {
6115 let auto_indent = match completion.insert_text_mode {
6116 Some(InsertTextMode::AS_IS) => None,
6117 _ => editor.autoindent_mode.clone(),
6118 };
6119 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6120 multi_buffer.edit(edits, auto_indent, cx);
6121 });
6122 }
6123 for (buffer, edits) in linked_edits {
6124 buffer.update(cx, |buffer, cx| {
6125 let snapshot = buffer.snapshot();
6126 let edits = edits
6127 .into_iter()
6128 .map(|(range, text)| {
6129 use text::ToPoint as TP;
6130 let end_point = TP::to_point(&range.end, &snapshot);
6131 let start_point = TP::to_point(&range.start, &snapshot);
6132 (start_point..end_point, text)
6133 })
6134 .sorted_by_key(|(range, _)| range.start);
6135 buffer.edit(edits, None, cx);
6136 })
6137 }
6138
6139 editor.refresh_edit_prediction(true, false, window, cx);
6140 });
6141 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6142
6143 let show_new_completions_on_confirm = completion
6144 .confirm
6145 .as_ref()
6146 .is_some_and(|confirm| confirm(intent, window, cx));
6147 if show_new_completions_on_confirm {
6148 self.open_or_update_completions_menu(None, None, false, window, cx);
6149 }
6150
6151 let provider = self.completion_provider.as_ref()?;
6152
6153 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6154 let command = lsp_store.as_ref().and_then(|lsp_store| {
6155 let CompletionSource::Lsp {
6156 lsp_completion,
6157 server_id,
6158 ..
6159 } = &completion.source
6160 else {
6161 return None;
6162 };
6163 let lsp_command = lsp_completion.command.as_ref()?;
6164 let available_commands = lsp_store
6165 .read(cx)
6166 .lsp_server_capabilities
6167 .get(server_id)
6168 .and_then(|server_capabilities| {
6169 server_capabilities
6170 .execute_command_provider
6171 .as_ref()
6172 .map(|options| options.commands.as_slice())
6173 })?;
6174 if available_commands.contains(&lsp_command.command) {
6175 Some(CodeAction {
6176 server_id: *server_id,
6177 range: language::Anchor::MIN..language::Anchor::MIN,
6178 lsp_action: LspAction::Command(lsp_command.clone()),
6179 resolved: false,
6180 })
6181 } else {
6182 None
6183 }
6184 });
6185
6186 drop(completion);
6187 let apply_edits = provider.apply_additional_edits_for_completion(
6188 buffer_handle.clone(),
6189 completions_menu.completions.clone(),
6190 candidate_id,
6191 true,
6192 cx,
6193 );
6194
6195 let editor_settings = EditorSettings::get_global(cx);
6196 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6197 // After the code completion is finished, users often want to know what signatures are needed.
6198 // so we should automatically call signature_help
6199 self.show_signature_help(&ShowSignatureHelp, window, cx);
6200 }
6201
6202 Some(cx.spawn_in(window, async move |editor, cx| {
6203 apply_edits.await?;
6204
6205 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6206 let title = command.lsp_action.title().to_owned();
6207 let project_transaction = lsp_store
6208 .update(cx, |lsp_store, cx| {
6209 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6210 })?
6211 .await
6212 .context("applying post-completion command")?;
6213 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6214 Self::open_project_transaction(
6215 &editor,
6216 workspace.downgrade(),
6217 project_transaction,
6218 title,
6219 cx,
6220 )
6221 .await?;
6222 }
6223 }
6224
6225 Ok(())
6226 }))
6227 }
6228
6229 pub fn toggle_code_actions(
6230 &mut self,
6231 action: &ToggleCodeActions,
6232 window: &mut Window,
6233 cx: &mut Context<Self>,
6234 ) {
6235 let quick_launch = action.quick_launch;
6236 let mut context_menu = self.context_menu.borrow_mut();
6237 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6238 if code_actions.deployed_from == action.deployed_from {
6239 // Toggle if we're selecting the same one
6240 *context_menu = None;
6241 cx.notify();
6242 return;
6243 } else {
6244 // Otherwise, clear it and start a new one
6245 *context_menu = None;
6246 cx.notify();
6247 }
6248 }
6249 drop(context_menu);
6250 let snapshot = self.snapshot(window, cx);
6251 let deployed_from = action.deployed_from.clone();
6252 let action = action.clone();
6253 self.completion_tasks.clear();
6254 self.discard_edit_prediction(false, cx);
6255
6256 let multibuffer_point = match &action.deployed_from {
6257 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6258 DisplayPoint::new(*row, 0).to_point(&snapshot)
6259 }
6260 _ => self
6261 .selections
6262 .newest::<Point>(&snapshot.display_snapshot)
6263 .head(),
6264 };
6265 let Some((buffer, buffer_row)) = snapshot
6266 .buffer_snapshot()
6267 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6268 .and_then(|(buffer_snapshot, range)| {
6269 self.buffer()
6270 .read(cx)
6271 .buffer(buffer_snapshot.remote_id())
6272 .map(|buffer| (buffer, range.start.row))
6273 })
6274 else {
6275 return;
6276 };
6277 let buffer_id = buffer.read(cx).remote_id();
6278 let tasks = self
6279 .tasks
6280 .get(&(buffer_id, buffer_row))
6281 .map(|t| Arc::new(t.to_owned()));
6282
6283 if !self.focus_handle.is_focused(window) {
6284 return;
6285 }
6286 let project = self.project.clone();
6287
6288 let code_actions_task = match deployed_from {
6289 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6290 _ => self.code_actions(buffer_row, window, cx),
6291 };
6292
6293 let runnable_task = match deployed_from {
6294 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6295 _ => {
6296 let mut task_context_task = Task::ready(None);
6297 if let Some(tasks) = &tasks
6298 && let Some(project) = project
6299 {
6300 task_context_task =
6301 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6302 }
6303
6304 cx.spawn_in(window, {
6305 let buffer = buffer.clone();
6306 async move |editor, cx| {
6307 let task_context = task_context_task.await;
6308
6309 let resolved_tasks =
6310 tasks
6311 .zip(task_context.clone())
6312 .map(|(tasks, task_context)| ResolvedTasks {
6313 templates: tasks.resolve(&task_context).collect(),
6314 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6315 multibuffer_point.row,
6316 tasks.column,
6317 )),
6318 });
6319 let debug_scenarios = editor
6320 .update(cx, |editor, cx| {
6321 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6322 })?
6323 .await;
6324 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6325 }
6326 })
6327 }
6328 };
6329
6330 cx.spawn_in(window, async move |editor, cx| {
6331 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6332 let code_actions = code_actions_task.await;
6333 let spawn_straight_away = quick_launch
6334 && resolved_tasks
6335 .as_ref()
6336 .is_some_and(|tasks| tasks.templates.len() == 1)
6337 && code_actions
6338 .as_ref()
6339 .is_none_or(|actions| actions.is_empty())
6340 && debug_scenarios.is_empty();
6341
6342 editor.update_in(cx, |editor, window, cx| {
6343 crate::hover_popover::hide_hover(editor, cx);
6344 let actions = CodeActionContents::new(
6345 resolved_tasks,
6346 code_actions,
6347 debug_scenarios,
6348 task_context.unwrap_or_default(),
6349 );
6350
6351 // Don't show the menu if there are no actions available
6352 if actions.is_empty() {
6353 cx.notify();
6354 return Task::ready(Ok(()));
6355 }
6356
6357 *editor.context_menu.borrow_mut() =
6358 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6359 buffer,
6360 actions,
6361 selected_item: Default::default(),
6362 scroll_handle: UniformListScrollHandle::default(),
6363 deployed_from,
6364 }));
6365 cx.notify();
6366 if spawn_straight_away
6367 && let Some(task) = editor.confirm_code_action(
6368 &ConfirmCodeAction { item_ix: Some(0) },
6369 window,
6370 cx,
6371 )
6372 {
6373 return task;
6374 }
6375
6376 Task::ready(Ok(()))
6377 })
6378 })
6379 .detach_and_log_err(cx);
6380 }
6381
6382 fn debug_scenarios(
6383 &mut self,
6384 resolved_tasks: &Option<ResolvedTasks>,
6385 buffer: &Entity<Buffer>,
6386 cx: &mut App,
6387 ) -> Task<Vec<task::DebugScenario>> {
6388 maybe!({
6389 let project = self.project()?;
6390 let dap_store = project.read(cx).dap_store();
6391 let mut scenarios = vec![];
6392 let resolved_tasks = resolved_tasks.as_ref()?;
6393 let buffer = buffer.read(cx);
6394 let language = buffer.language()?;
6395 let file = buffer.file();
6396 let debug_adapter = language_settings(language.name().into(), file, cx)
6397 .debuggers
6398 .first()
6399 .map(SharedString::from)
6400 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6401
6402 dap_store.update(cx, |dap_store, cx| {
6403 for (_, task) in &resolved_tasks.templates {
6404 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6405 task.original_task().clone(),
6406 debug_adapter.clone().into(),
6407 task.display_label().to_owned().into(),
6408 cx,
6409 );
6410 scenarios.push(maybe_scenario);
6411 }
6412 });
6413 Some(cx.background_spawn(async move {
6414 futures::future::join_all(scenarios)
6415 .await
6416 .into_iter()
6417 .flatten()
6418 .collect::<Vec<_>>()
6419 }))
6420 })
6421 .unwrap_or_else(|| Task::ready(vec![]))
6422 }
6423
6424 fn code_actions(
6425 &mut self,
6426 buffer_row: u32,
6427 window: &mut Window,
6428 cx: &mut Context<Self>,
6429 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6430 let mut task = self.code_actions_task.take();
6431 cx.spawn_in(window, async move |editor, cx| {
6432 while let Some(prev_task) = task {
6433 prev_task.await.log_err();
6434 task = editor
6435 .update(cx, |this, _| this.code_actions_task.take())
6436 .ok()?;
6437 }
6438
6439 editor
6440 .update(cx, |editor, cx| {
6441 editor
6442 .available_code_actions
6443 .clone()
6444 .and_then(|(location, code_actions)| {
6445 let snapshot = location.buffer.read(cx).snapshot();
6446 let point_range = location.range.to_point(&snapshot);
6447 let point_range = point_range.start.row..=point_range.end.row;
6448 if point_range.contains(&buffer_row) {
6449 Some(code_actions)
6450 } else {
6451 None
6452 }
6453 })
6454 })
6455 .ok()
6456 .flatten()
6457 })
6458 }
6459
6460 pub fn confirm_code_action(
6461 &mut self,
6462 action: &ConfirmCodeAction,
6463 window: &mut Window,
6464 cx: &mut Context<Self>,
6465 ) -> Option<Task<Result<()>>> {
6466 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6467
6468 let actions_menu =
6469 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6470 menu
6471 } else {
6472 return None;
6473 };
6474
6475 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6476 let action = actions_menu.actions.get(action_ix)?;
6477 let title = action.label();
6478 let buffer = actions_menu.buffer;
6479 let workspace = self.workspace()?;
6480
6481 match action {
6482 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6483 workspace.update(cx, |workspace, cx| {
6484 workspace.schedule_resolved_task(
6485 task_source_kind,
6486 resolved_task,
6487 false,
6488 window,
6489 cx,
6490 );
6491
6492 Some(Task::ready(Ok(())))
6493 })
6494 }
6495 CodeActionsItem::CodeAction {
6496 excerpt_id,
6497 action,
6498 provider,
6499 } => {
6500 let apply_code_action =
6501 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6502 let workspace = workspace.downgrade();
6503 Some(cx.spawn_in(window, async move |editor, cx| {
6504 let project_transaction = apply_code_action.await?;
6505 Self::open_project_transaction(
6506 &editor,
6507 workspace,
6508 project_transaction,
6509 title,
6510 cx,
6511 )
6512 .await
6513 }))
6514 }
6515 CodeActionsItem::DebugScenario(scenario) => {
6516 let context = actions_menu.actions.context;
6517
6518 workspace.update(cx, |workspace, cx| {
6519 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6520 workspace.start_debug_session(
6521 scenario,
6522 context,
6523 Some(buffer),
6524 None,
6525 window,
6526 cx,
6527 );
6528 });
6529 Some(Task::ready(Ok(())))
6530 }
6531 }
6532 }
6533
6534 pub async fn open_project_transaction(
6535 editor: &WeakEntity<Editor>,
6536 workspace: WeakEntity<Workspace>,
6537 transaction: ProjectTransaction,
6538 title: String,
6539 cx: &mut AsyncWindowContext,
6540 ) -> Result<()> {
6541 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6542 cx.update(|_, cx| {
6543 entries.sort_unstable_by_key(|(buffer, _)| {
6544 buffer.read(cx).file().map(|f| f.path().clone())
6545 });
6546 })?;
6547 if entries.is_empty() {
6548 return Ok(());
6549 }
6550
6551 // If the project transaction's edits are all contained within this editor, then
6552 // avoid opening a new editor to display them.
6553
6554 if let [(buffer, transaction)] = &*entries {
6555 let excerpt = editor.update(cx, |editor, cx| {
6556 editor
6557 .buffer()
6558 .read(cx)
6559 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6560 })?;
6561 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6562 && excerpted_buffer == *buffer
6563 {
6564 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6565 let excerpt_range = excerpt_range.to_offset(buffer);
6566 buffer
6567 .edited_ranges_for_transaction::<usize>(transaction)
6568 .all(|range| {
6569 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6570 })
6571 })?;
6572
6573 if all_edits_within_excerpt {
6574 return Ok(());
6575 }
6576 }
6577 }
6578
6579 let mut ranges_to_highlight = Vec::new();
6580 let excerpt_buffer = cx.new(|cx| {
6581 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6582 for (buffer_handle, transaction) in &entries {
6583 let edited_ranges = buffer_handle
6584 .read(cx)
6585 .edited_ranges_for_transaction::<Point>(transaction)
6586 .collect::<Vec<_>>();
6587 let (ranges, _) = multibuffer.set_excerpts_for_path(
6588 PathKey::for_buffer(buffer_handle, cx),
6589 buffer_handle.clone(),
6590 edited_ranges,
6591 multibuffer_context_lines(cx),
6592 cx,
6593 );
6594
6595 ranges_to_highlight.extend(ranges);
6596 }
6597 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6598 multibuffer
6599 })?;
6600
6601 workspace.update_in(cx, |workspace, window, cx| {
6602 let project = workspace.project().clone();
6603 let editor =
6604 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6605 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6606 editor.update(cx, |editor, cx| {
6607 editor.highlight_background::<Self>(
6608 &ranges_to_highlight,
6609 |theme| theme.colors().editor_highlighted_line_background,
6610 cx,
6611 );
6612 });
6613 })?;
6614
6615 Ok(())
6616 }
6617
6618 pub fn clear_code_action_providers(&mut self) {
6619 self.code_action_providers.clear();
6620 self.available_code_actions.take();
6621 }
6622
6623 pub fn add_code_action_provider(
6624 &mut self,
6625 provider: Rc<dyn CodeActionProvider>,
6626 window: &mut Window,
6627 cx: &mut Context<Self>,
6628 ) {
6629 if self
6630 .code_action_providers
6631 .iter()
6632 .any(|existing_provider| existing_provider.id() == provider.id())
6633 {
6634 return;
6635 }
6636
6637 self.code_action_providers.push(provider);
6638 self.refresh_code_actions(window, cx);
6639 }
6640
6641 pub fn remove_code_action_provider(
6642 &mut self,
6643 id: Arc<str>,
6644 window: &mut Window,
6645 cx: &mut Context<Self>,
6646 ) {
6647 self.code_action_providers
6648 .retain(|provider| provider.id() != id);
6649 self.refresh_code_actions(window, cx);
6650 }
6651
6652 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6653 !self.code_action_providers.is_empty()
6654 && EditorSettings::get_global(cx).toolbar.code_actions
6655 }
6656
6657 pub fn has_available_code_actions(&self) -> bool {
6658 self.available_code_actions
6659 .as_ref()
6660 .is_some_and(|(_, actions)| !actions.is_empty())
6661 }
6662
6663 fn render_inline_code_actions(
6664 &self,
6665 icon_size: ui::IconSize,
6666 display_row: DisplayRow,
6667 is_active: bool,
6668 cx: &mut Context<Self>,
6669 ) -> AnyElement {
6670 let show_tooltip = !self.context_menu_visible();
6671 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6672 .icon_size(icon_size)
6673 .shape(ui::IconButtonShape::Square)
6674 .icon_color(ui::Color::Hidden)
6675 .toggle_state(is_active)
6676 .when(show_tooltip, |this| {
6677 this.tooltip({
6678 let focus_handle = self.focus_handle.clone();
6679 move |_window, cx| {
6680 Tooltip::for_action_in(
6681 "Toggle Code Actions",
6682 &ToggleCodeActions {
6683 deployed_from: None,
6684 quick_launch: false,
6685 },
6686 &focus_handle,
6687 cx,
6688 )
6689 }
6690 })
6691 })
6692 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6693 window.focus(&editor.focus_handle(cx));
6694 editor.toggle_code_actions(
6695 &crate::actions::ToggleCodeActions {
6696 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6697 display_row,
6698 )),
6699 quick_launch: false,
6700 },
6701 window,
6702 cx,
6703 );
6704 }))
6705 .into_any_element()
6706 }
6707
6708 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6709 &self.context_menu
6710 }
6711
6712 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6713 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6714 cx.background_executor()
6715 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6716 .await;
6717
6718 let (start_buffer, start, _, end, newest_selection) = this
6719 .update(cx, |this, cx| {
6720 let newest_selection = this.selections.newest_anchor().clone();
6721 if newest_selection.head().diff_base_anchor.is_some() {
6722 return None;
6723 }
6724 let display_snapshot = this.display_snapshot(cx);
6725 let newest_selection_adjusted =
6726 this.selections.newest_adjusted(&display_snapshot);
6727 let buffer = this.buffer.read(cx);
6728
6729 let (start_buffer, start) =
6730 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6731 let (end_buffer, end) =
6732 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6733
6734 Some((start_buffer, start, end_buffer, end, newest_selection))
6735 })?
6736 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6737 .context(
6738 "Expected selection to lie in a single buffer when refreshing code actions",
6739 )?;
6740 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6741 let providers = this.code_action_providers.clone();
6742 let tasks = this
6743 .code_action_providers
6744 .iter()
6745 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6746 .collect::<Vec<_>>();
6747 (providers, tasks)
6748 })?;
6749
6750 let mut actions = Vec::new();
6751 for (provider, provider_actions) in
6752 providers.into_iter().zip(future::join_all(tasks).await)
6753 {
6754 if let Some(provider_actions) = provider_actions.log_err() {
6755 actions.extend(provider_actions.into_iter().map(|action| {
6756 AvailableCodeAction {
6757 excerpt_id: newest_selection.start.excerpt_id,
6758 action,
6759 provider: provider.clone(),
6760 }
6761 }));
6762 }
6763 }
6764
6765 this.update(cx, |this, cx| {
6766 this.available_code_actions = if actions.is_empty() {
6767 None
6768 } else {
6769 Some((
6770 Location {
6771 buffer: start_buffer,
6772 range: start..end,
6773 },
6774 actions.into(),
6775 ))
6776 };
6777 cx.notify();
6778 })
6779 }));
6780 }
6781
6782 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6783 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6784 self.show_git_blame_inline = false;
6785
6786 self.show_git_blame_inline_delay_task =
6787 Some(cx.spawn_in(window, async move |this, cx| {
6788 cx.background_executor().timer(delay).await;
6789
6790 this.update(cx, |this, cx| {
6791 this.show_git_blame_inline = true;
6792 cx.notify();
6793 })
6794 .log_err();
6795 }));
6796 }
6797 }
6798
6799 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6800 let snapshot = self.snapshot(window, cx);
6801 let cursor = self
6802 .selections
6803 .newest::<Point>(&snapshot.display_snapshot)
6804 .head();
6805 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6806 else {
6807 return;
6808 };
6809
6810 let Some(blame) = self.blame.as_ref() else {
6811 return;
6812 };
6813
6814 let row_info = RowInfo {
6815 buffer_id: Some(buffer.remote_id()),
6816 buffer_row: Some(point.row),
6817 ..Default::default()
6818 };
6819 let Some((buffer, blame_entry)) = blame
6820 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6821 .flatten()
6822 else {
6823 return;
6824 };
6825
6826 let anchor = self.selections.newest_anchor().head();
6827 let position = self.to_pixel_point(anchor, &snapshot, window);
6828 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6829 self.show_blame_popover(
6830 buffer,
6831 &blame_entry,
6832 position + last_bounds.origin,
6833 true,
6834 cx,
6835 );
6836 };
6837 }
6838
6839 fn show_blame_popover(
6840 &mut self,
6841 buffer: BufferId,
6842 blame_entry: &BlameEntry,
6843 position: gpui::Point<Pixels>,
6844 ignore_timeout: bool,
6845 cx: &mut Context<Self>,
6846 ) {
6847 if let Some(state) = &mut self.inline_blame_popover {
6848 state.hide_task.take();
6849 } else {
6850 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6851 let blame_entry = blame_entry.clone();
6852 let show_task = cx.spawn(async move |editor, cx| {
6853 if !ignore_timeout {
6854 cx.background_executor()
6855 .timer(std::time::Duration::from_millis(blame_popover_delay))
6856 .await;
6857 }
6858 editor
6859 .update(cx, |editor, cx| {
6860 editor.inline_blame_popover_show_task.take();
6861 let Some(blame) = editor.blame.as_ref() else {
6862 return;
6863 };
6864 let blame = blame.read(cx);
6865 let details = blame.details_for_entry(buffer, &blame_entry);
6866 let markdown = cx.new(|cx| {
6867 Markdown::new(
6868 details
6869 .as_ref()
6870 .map(|message| message.message.clone())
6871 .unwrap_or_default(),
6872 None,
6873 None,
6874 cx,
6875 )
6876 });
6877 editor.inline_blame_popover = Some(InlineBlamePopover {
6878 position,
6879 hide_task: None,
6880 popover_bounds: None,
6881 popover_state: InlineBlamePopoverState {
6882 scroll_handle: ScrollHandle::new(),
6883 commit_message: details,
6884 markdown,
6885 },
6886 keyboard_grace: ignore_timeout,
6887 });
6888 cx.notify();
6889 })
6890 .ok();
6891 });
6892 self.inline_blame_popover_show_task = Some(show_task);
6893 }
6894 }
6895
6896 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6897 self.inline_blame_popover_show_task.take();
6898 if let Some(state) = &mut self.inline_blame_popover {
6899 let hide_task = cx.spawn(async move |editor, cx| {
6900 if !ignore_timeout {
6901 cx.background_executor()
6902 .timer(std::time::Duration::from_millis(100))
6903 .await;
6904 }
6905 editor
6906 .update(cx, |editor, cx| {
6907 editor.inline_blame_popover.take();
6908 cx.notify();
6909 })
6910 .ok();
6911 });
6912 state.hide_task = Some(hide_task);
6913 true
6914 } else {
6915 false
6916 }
6917 }
6918
6919 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6920 if self.pending_rename.is_some() {
6921 return None;
6922 }
6923
6924 let provider = self.semantics_provider.clone()?;
6925 let buffer = self.buffer.read(cx);
6926 let newest_selection = self.selections.newest_anchor().clone();
6927 let cursor_position = newest_selection.head();
6928 let (cursor_buffer, cursor_buffer_position) =
6929 buffer.text_anchor_for_position(cursor_position, cx)?;
6930 let (tail_buffer, tail_buffer_position) =
6931 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6932 if cursor_buffer != tail_buffer {
6933 return None;
6934 }
6935
6936 let snapshot = cursor_buffer.read(cx).snapshot();
6937 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6938 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6939 if start_word_range != end_word_range {
6940 self.document_highlights_task.take();
6941 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6942 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6943 return None;
6944 }
6945
6946 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6947 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6948 cx.background_executor()
6949 .timer(Duration::from_millis(debounce))
6950 .await;
6951
6952 let highlights = if let Some(highlights) = cx
6953 .update(|cx| {
6954 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6955 })
6956 .ok()
6957 .flatten()
6958 {
6959 highlights.await.log_err()
6960 } else {
6961 None
6962 };
6963
6964 if let Some(highlights) = highlights {
6965 this.update(cx, |this, cx| {
6966 if this.pending_rename.is_some() {
6967 return;
6968 }
6969
6970 let buffer = this.buffer.read(cx);
6971 if buffer
6972 .text_anchor_for_position(cursor_position, cx)
6973 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6974 {
6975 return;
6976 }
6977
6978 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6979 let mut write_ranges = Vec::new();
6980 let mut read_ranges = Vec::new();
6981 for highlight in highlights {
6982 let buffer_id = cursor_buffer.read(cx).remote_id();
6983 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6984 {
6985 let start = highlight
6986 .range
6987 .start
6988 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6989 let end = highlight
6990 .range
6991 .end
6992 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6993 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6994 continue;
6995 }
6996
6997 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
6998 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6999 write_ranges.push(range);
7000 } else {
7001 read_ranges.push(range);
7002 }
7003 }
7004 }
7005
7006 this.highlight_background::<DocumentHighlightRead>(
7007 &read_ranges,
7008 |theme| theme.colors().editor_document_highlight_read_background,
7009 cx,
7010 );
7011 this.highlight_background::<DocumentHighlightWrite>(
7012 &write_ranges,
7013 |theme| theme.colors().editor_document_highlight_write_background,
7014 cx,
7015 );
7016 cx.notify();
7017 })
7018 .log_err();
7019 }
7020 }));
7021 None
7022 }
7023
7024 fn prepare_highlight_query_from_selection(
7025 &mut self,
7026 window: &Window,
7027 cx: &mut Context<Editor>,
7028 ) -> Option<(String, Range<Anchor>)> {
7029 if matches!(self.mode, EditorMode::SingleLine) {
7030 return None;
7031 }
7032 if !EditorSettings::get_global(cx).selection_highlight {
7033 return None;
7034 }
7035 if self.selections.count() != 1 || self.selections.line_mode() {
7036 return None;
7037 }
7038 let snapshot = self.snapshot(window, cx);
7039 let selection = self.selections.newest::<Point>(&snapshot);
7040 // If the selection spans multiple rows OR it is empty
7041 if selection.start.row != selection.end.row
7042 || selection.start.column == selection.end.column
7043 {
7044 return None;
7045 }
7046 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7047 let query = snapshot
7048 .buffer_snapshot()
7049 .text_for_range(selection_anchor_range.clone())
7050 .collect::<String>();
7051 if query.trim().is_empty() {
7052 return None;
7053 }
7054 Some((query, selection_anchor_range))
7055 }
7056
7057 fn update_selection_occurrence_highlights(
7058 &mut self,
7059 query_text: String,
7060 query_range: Range<Anchor>,
7061 multi_buffer_range_to_query: Range<Point>,
7062 use_debounce: bool,
7063 window: &mut Window,
7064 cx: &mut Context<Editor>,
7065 ) -> Task<()> {
7066 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7067 cx.spawn_in(window, async move |editor, cx| {
7068 if use_debounce {
7069 cx.background_executor()
7070 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7071 .await;
7072 }
7073 let match_task = cx.background_spawn(async move {
7074 let buffer_ranges = multi_buffer_snapshot
7075 .range_to_buffer_ranges(multi_buffer_range_to_query)
7076 .into_iter()
7077 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7078 let mut match_ranges = Vec::new();
7079 let Ok(regex) = project::search::SearchQuery::text(
7080 query_text.clone(),
7081 false,
7082 false,
7083 false,
7084 Default::default(),
7085 Default::default(),
7086 false,
7087 None,
7088 ) else {
7089 return Vec::default();
7090 };
7091 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7092 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7093 match_ranges.extend(
7094 regex
7095 .search(
7096 buffer_snapshot,
7097 Some(search_range.start.0..search_range.end.0),
7098 )
7099 .await
7100 .into_iter()
7101 .filter_map(|match_range| {
7102 let match_start = buffer_snapshot
7103 .anchor_after(search_range.start + match_range.start);
7104 let match_end = buffer_snapshot
7105 .anchor_before(search_range.start + match_range.end);
7106 let match_anchor_range =
7107 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7108 (match_anchor_range != query_range).then_some(match_anchor_range)
7109 }),
7110 );
7111 }
7112 match_ranges
7113 });
7114 let match_ranges = match_task.await;
7115 editor
7116 .update_in(cx, |editor, _, cx| {
7117 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7118 if !match_ranges.is_empty() {
7119 editor.highlight_background::<SelectedTextHighlight>(
7120 &match_ranges,
7121 |theme| theme.colors().editor_document_highlight_bracket_background,
7122 cx,
7123 )
7124 }
7125 })
7126 .log_err();
7127 })
7128 }
7129
7130 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7131 struct NewlineFold;
7132 let type_id = std::any::TypeId::of::<NewlineFold>();
7133 if !self.mode.is_single_line() {
7134 return;
7135 }
7136 let snapshot = self.snapshot(window, cx);
7137 if snapshot.buffer_snapshot().max_point().row == 0 {
7138 return;
7139 }
7140 let task = cx.background_spawn(async move {
7141 let new_newlines = snapshot
7142 .buffer_chars_at(MultiBufferOffset(0))
7143 .filter_map(|(c, i)| {
7144 if c == '\n' {
7145 Some(
7146 snapshot.buffer_snapshot().anchor_after(i)
7147 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7148 )
7149 } else {
7150 None
7151 }
7152 })
7153 .collect::<Vec<_>>();
7154 let existing_newlines = snapshot
7155 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7156 .filter_map(|fold| {
7157 if fold.placeholder.type_tag == Some(type_id) {
7158 Some(fold.range.start..fold.range.end)
7159 } else {
7160 None
7161 }
7162 })
7163 .collect::<Vec<_>>();
7164
7165 (new_newlines, existing_newlines)
7166 });
7167 self.folding_newlines = cx.spawn(async move |this, cx| {
7168 let (new_newlines, existing_newlines) = task.await;
7169 if new_newlines == existing_newlines {
7170 return;
7171 }
7172 let placeholder = FoldPlaceholder {
7173 render: Arc::new(move |_, _, cx| {
7174 div()
7175 .bg(cx.theme().status().hint_background)
7176 .border_b_1()
7177 .size_full()
7178 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7179 .border_color(cx.theme().status().hint)
7180 .child("\\n")
7181 .into_any()
7182 }),
7183 constrain_width: false,
7184 merge_adjacent: false,
7185 type_tag: Some(type_id),
7186 };
7187 let creases = new_newlines
7188 .into_iter()
7189 .map(|range| Crease::simple(range, placeholder.clone()))
7190 .collect();
7191 this.update(cx, |this, cx| {
7192 this.display_map.update(cx, |display_map, cx| {
7193 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7194 display_map.fold(creases, cx);
7195 });
7196 })
7197 .ok();
7198 });
7199 }
7200
7201 fn refresh_selected_text_highlights(
7202 &mut self,
7203 on_buffer_edit: bool,
7204 window: &mut Window,
7205 cx: &mut Context<Editor>,
7206 ) {
7207 let Some((query_text, query_range)) =
7208 self.prepare_highlight_query_from_selection(window, cx)
7209 else {
7210 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7211 self.quick_selection_highlight_task.take();
7212 self.debounced_selection_highlight_task.take();
7213 return;
7214 };
7215 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7216 if on_buffer_edit
7217 || self
7218 .quick_selection_highlight_task
7219 .as_ref()
7220 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7221 {
7222 let multi_buffer_visible_start = self
7223 .scroll_manager
7224 .anchor()
7225 .anchor
7226 .to_point(&multi_buffer_snapshot);
7227 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7228 multi_buffer_visible_start
7229 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7230 Bias::Left,
7231 );
7232 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7233 self.quick_selection_highlight_task = Some((
7234 query_range.clone(),
7235 self.update_selection_occurrence_highlights(
7236 query_text.clone(),
7237 query_range.clone(),
7238 multi_buffer_visible_range,
7239 false,
7240 window,
7241 cx,
7242 ),
7243 ));
7244 }
7245 if on_buffer_edit
7246 || self
7247 .debounced_selection_highlight_task
7248 .as_ref()
7249 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7250 {
7251 let multi_buffer_start = multi_buffer_snapshot
7252 .anchor_before(MultiBufferOffset(0))
7253 .to_point(&multi_buffer_snapshot);
7254 let multi_buffer_end = multi_buffer_snapshot
7255 .anchor_after(multi_buffer_snapshot.len())
7256 .to_point(&multi_buffer_snapshot);
7257 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7258 self.debounced_selection_highlight_task = Some((
7259 query_range.clone(),
7260 self.update_selection_occurrence_highlights(
7261 query_text,
7262 query_range,
7263 multi_buffer_full_range,
7264 true,
7265 window,
7266 cx,
7267 ),
7268 ));
7269 }
7270 }
7271
7272 pub fn refresh_edit_prediction(
7273 &mut self,
7274 debounce: bool,
7275 user_requested: bool,
7276 window: &mut Window,
7277 cx: &mut Context<Self>,
7278 ) -> Option<()> {
7279 if DisableAiSettings::get_global(cx).disable_ai {
7280 return None;
7281 }
7282
7283 let provider = self.edit_prediction_provider()?;
7284 let cursor = self.selections.newest_anchor().head();
7285 let (buffer, cursor_buffer_position) =
7286 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7287
7288 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7289 self.discard_edit_prediction(false, cx);
7290 return None;
7291 }
7292
7293 self.update_visible_edit_prediction(window, cx);
7294
7295 if !user_requested
7296 && (!self.should_show_edit_predictions()
7297 || !self.is_focused(window)
7298 || buffer.read(cx).is_empty())
7299 {
7300 self.discard_edit_prediction(false, cx);
7301 return None;
7302 }
7303
7304 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7305 Some(())
7306 }
7307
7308 fn show_edit_predictions_in_menu(&self) -> bool {
7309 match self.edit_prediction_settings {
7310 EditPredictionSettings::Disabled => false,
7311 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7312 }
7313 }
7314
7315 pub fn edit_predictions_enabled(&self) -> bool {
7316 match self.edit_prediction_settings {
7317 EditPredictionSettings::Disabled => false,
7318 EditPredictionSettings::Enabled { .. } => true,
7319 }
7320 }
7321
7322 fn edit_prediction_requires_modifier(&self) -> bool {
7323 match self.edit_prediction_settings {
7324 EditPredictionSettings::Disabled => false,
7325 EditPredictionSettings::Enabled {
7326 preview_requires_modifier,
7327 ..
7328 } => preview_requires_modifier,
7329 }
7330 }
7331
7332 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7333 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7334 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7335 self.discard_edit_prediction(false, cx);
7336 } else {
7337 let selection = self.selections.newest_anchor();
7338 let cursor = selection.head();
7339
7340 if let Some((buffer, cursor_buffer_position)) =
7341 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7342 {
7343 self.edit_prediction_settings =
7344 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7345 }
7346 }
7347 }
7348
7349 fn edit_prediction_settings_at_position(
7350 &self,
7351 buffer: &Entity<Buffer>,
7352 buffer_position: language::Anchor,
7353 cx: &App,
7354 ) -> EditPredictionSettings {
7355 if !self.mode.is_full()
7356 || !self.show_edit_predictions_override.unwrap_or(true)
7357 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7358 {
7359 return EditPredictionSettings::Disabled;
7360 }
7361
7362 let buffer = buffer.read(cx);
7363
7364 let file = buffer.file();
7365
7366 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7367 return EditPredictionSettings::Disabled;
7368 };
7369
7370 let by_provider = matches!(
7371 self.menu_edit_predictions_policy,
7372 MenuEditPredictionsPolicy::ByProvider
7373 );
7374
7375 let show_in_menu = by_provider
7376 && self
7377 .edit_prediction_provider
7378 .as_ref()
7379 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7380
7381 let preview_requires_modifier =
7382 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7383
7384 EditPredictionSettings::Enabled {
7385 show_in_menu,
7386 preview_requires_modifier,
7387 }
7388 }
7389
7390 fn should_show_edit_predictions(&self) -> bool {
7391 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7392 }
7393
7394 pub fn edit_prediction_preview_is_active(&self) -> bool {
7395 matches!(
7396 self.edit_prediction_preview,
7397 EditPredictionPreview::Active { .. }
7398 )
7399 }
7400
7401 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7402 let cursor = self.selections.newest_anchor().head();
7403 if let Some((buffer, cursor_position)) =
7404 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7405 {
7406 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7407 } else {
7408 false
7409 }
7410 }
7411
7412 pub fn supports_minimap(&self, cx: &App) -> bool {
7413 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7414 }
7415
7416 fn edit_predictions_enabled_in_buffer(
7417 &self,
7418 buffer: &Entity<Buffer>,
7419 buffer_position: language::Anchor,
7420 cx: &App,
7421 ) -> bool {
7422 maybe!({
7423 if self.read_only(cx) {
7424 return Some(false);
7425 }
7426 let provider = self.edit_prediction_provider()?;
7427 if !provider.is_enabled(buffer, buffer_position, cx) {
7428 return Some(false);
7429 }
7430 let buffer = buffer.read(cx);
7431 let Some(file) = buffer.file() else {
7432 return Some(true);
7433 };
7434 let settings = all_language_settings(Some(file), cx);
7435 Some(settings.edit_predictions_enabled_for_file(file, cx))
7436 })
7437 .unwrap_or(false)
7438 }
7439
7440 fn cycle_edit_prediction(
7441 &mut self,
7442 direction: Direction,
7443 window: &mut Window,
7444 cx: &mut Context<Self>,
7445 ) -> Option<()> {
7446 let provider = self.edit_prediction_provider()?;
7447 let cursor = self.selections.newest_anchor().head();
7448 let (buffer, cursor_buffer_position) =
7449 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7450 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7451 return None;
7452 }
7453
7454 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7455 self.update_visible_edit_prediction(window, cx);
7456
7457 Some(())
7458 }
7459
7460 pub fn show_edit_prediction(
7461 &mut self,
7462 _: &ShowEditPrediction,
7463 window: &mut Window,
7464 cx: &mut Context<Self>,
7465 ) {
7466 if !self.has_active_edit_prediction() {
7467 self.refresh_edit_prediction(false, true, window, cx);
7468 return;
7469 }
7470
7471 self.update_visible_edit_prediction(window, cx);
7472 }
7473
7474 pub fn display_cursor_names(
7475 &mut self,
7476 _: &DisplayCursorNames,
7477 window: &mut Window,
7478 cx: &mut Context<Self>,
7479 ) {
7480 self.show_cursor_names(window, cx);
7481 }
7482
7483 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7484 self.show_cursor_names = true;
7485 cx.notify();
7486 cx.spawn_in(window, async move |this, cx| {
7487 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7488 this.update(cx, |this, cx| {
7489 this.show_cursor_names = false;
7490 cx.notify()
7491 })
7492 .ok()
7493 })
7494 .detach();
7495 }
7496
7497 pub fn next_edit_prediction(
7498 &mut self,
7499 _: &NextEditPrediction,
7500 window: &mut Window,
7501 cx: &mut Context<Self>,
7502 ) {
7503 if self.has_active_edit_prediction() {
7504 self.cycle_edit_prediction(Direction::Next, window, cx);
7505 } else {
7506 let is_copilot_disabled = self
7507 .refresh_edit_prediction(false, true, window, cx)
7508 .is_none();
7509 if is_copilot_disabled {
7510 cx.propagate();
7511 }
7512 }
7513 }
7514
7515 pub fn previous_edit_prediction(
7516 &mut self,
7517 _: &PreviousEditPrediction,
7518 window: &mut Window,
7519 cx: &mut Context<Self>,
7520 ) {
7521 if self.has_active_edit_prediction() {
7522 self.cycle_edit_prediction(Direction::Prev, window, cx);
7523 } else {
7524 let is_copilot_disabled = self
7525 .refresh_edit_prediction(false, true, window, cx)
7526 .is_none();
7527 if is_copilot_disabled {
7528 cx.propagate();
7529 }
7530 }
7531 }
7532
7533 pub fn accept_edit_prediction(
7534 &mut self,
7535 _: &AcceptEditPrediction,
7536 window: &mut Window,
7537 cx: &mut Context<Self>,
7538 ) {
7539 if self.show_edit_predictions_in_menu() {
7540 self.hide_context_menu(window, cx);
7541 }
7542
7543 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7544 return;
7545 };
7546
7547 match &active_edit_prediction.completion {
7548 EditPrediction::MoveWithin { target, .. } => {
7549 let target = *target;
7550
7551 if let Some(position_map) = &self.last_position_map {
7552 if position_map
7553 .visible_row_range
7554 .contains(&target.to_display_point(&position_map.snapshot).row())
7555 || !self.edit_prediction_requires_modifier()
7556 {
7557 self.unfold_ranges(&[target..target], true, false, cx);
7558 // Note that this is also done in vim's handler of the Tab action.
7559 self.change_selections(
7560 SelectionEffects::scroll(Autoscroll::newest()),
7561 window,
7562 cx,
7563 |selections| {
7564 selections.select_anchor_ranges([target..target]);
7565 },
7566 );
7567 self.clear_row_highlights::<EditPredictionPreview>();
7568
7569 self.edit_prediction_preview
7570 .set_previous_scroll_position(None);
7571 } else {
7572 self.edit_prediction_preview
7573 .set_previous_scroll_position(Some(
7574 position_map.snapshot.scroll_anchor,
7575 ));
7576
7577 self.highlight_rows::<EditPredictionPreview>(
7578 target..target,
7579 cx.theme().colors().editor_highlighted_line_background,
7580 RowHighlightOptions {
7581 autoscroll: true,
7582 ..Default::default()
7583 },
7584 cx,
7585 );
7586 self.request_autoscroll(Autoscroll::fit(), cx);
7587 }
7588 }
7589 }
7590 EditPrediction::MoveOutside { snapshot, target } => {
7591 if let Some(workspace) = self.workspace() {
7592 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7593 .detach_and_log_err(cx);
7594 }
7595 }
7596 EditPrediction::Edit { edits, .. } => {
7597 self.report_edit_prediction_event(
7598 active_edit_prediction.completion_id.clone(),
7599 true,
7600 cx,
7601 );
7602
7603 if let Some(provider) = self.edit_prediction_provider() {
7604 provider.accept(cx);
7605 }
7606
7607 // Store the transaction ID and selections before applying the edit
7608 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7609
7610 let snapshot = self.buffer.read(cx).snapshot(cx);
7611 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7612
7613 self.buffer.update(cx, |buffer, cx| {
7614 buffer.edit(edits.iter().cloned(), None, cx)
7615 });
7616
7617 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7618 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7619 });
7620
7621 let selections = self.selections.disjoint_anchors_arc();
7622 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7623 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7624 if has_new_transaction {
7625 self.selection_history
7626 .insert_transaction(transaction_id_now, selections);
7627 }
7628 }
7629
7630 self.update_visible_edit_prediction(window, cx);
7631 if self.active_edit_prediction.is_none() {
7632 self.refresh_edit_prediction(true, true, window, cx);
7633 }
7634
7635 cx.notify();
7636 }
7637 }
7638
7639 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7640 }
7641
7642 pub fn accept_partial_edit_prediction(
7643 &mut self,
7644 _: &AcceptPartialEditPrediction,
7645 window: &mut Window,
7646 cx: &mut Context<Self>,
7647 ) {
7648 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7649 return;
7650 };
7651 if self.selections.count() != 1 {
7652 return;
7653 }
7654
7655 match &active_edit_prediction.completion {
7656 EditPrediction::MoveWithin { target, .. } => {
7657 let target = *target;
7658 self.change_selections(
7659 SelectionEffects::scroll(Autoscroll::newest()),
7660 window,
7661 cx,
7662 |selections| {
7663 selections.select_anchor_ranges([target..target]);
7664 },
7665 );
7666 }
7667 EditPrediction::MoveOutside { snapshot, target } => {
7668 if let Some(workspace) = self.workspace() {
7669 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7670 .detach_and_log_err(cx);
7671 }
7672 }
7673 EditPrediction::Edit { edits, .. } => {
7674 self.report_edit_prediction_event(
7675 active_edit_prediction.completion_id.clone(),
7676 true,
7677 cx,
7678 );
7679
7680 // Find an insertion that starts at the cursor position.
7681 let snapshot = self.buffer.read(cx).snapshot(cx);
7682 let cursor_offset = self
7683 .selections
7684 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7685 .head();
7686 let insertion = edits.iter().find_map(|(range, text)| {
7687 let range = range.to_offset(&snapshot);
7688 if range.is_empty() && range.start == cursor_offset {
7689 Some(text)
7690 } else {
7691 None
7692 }
7693 });
7694
7695 if let Some(text) = insertion {
7696 let mut partial_completion = text
7697 .chars()
7698 .by_ref()
7699 .take_while(|c| c.is_alphabetic())
7700 .collect::<String>();
7701 if partial_completion.is_empty() {
7702 partial_completion = text
7703 .chars()
7704 .by_ref()
7705 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7706 .collect::<String>();
7707 }
7708
7709 cx.emit(EditorEvent::InputHandled {
7710 utf16_range_to_replace: None,
7711 text: partial_completion.clone().into(),
7712 });
7713
7714 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7715
7716 self.refresh_edit_prediction(true, true, window, cx);
7717 cx.notify();
7718 } else {
7719 self.accept_edit_prediction(&Default::default(), window, cx);
7720 }
7721 }
7722 }
7723 }
7724
7725 fn discard_edit_prediction(
7726 &mut self,
7727 should_report_edit_prediction_event: bool,
7728 cx: &mut Context<Self>,
7729 ) -> bool {
7730 if should_report_edit_prediction_event {
7731 let completion_id = self
7732 .active_edit_prediction
7733 .as_ref()
7734 .and_then(|active_completion| active_completion.completion_id.clone());
7735
7736 self.report_edit_prediction_event(completion_id, false, cx);
7737 }
7738
7739 if let Some(provider) = self.edit_prediction_provider() {
7740 provider.discard(cx);
7741 }
7742
7743 self.take_active_edit_prediction(cx)
7744 }
7745
7746 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7747 let Some(provider) = self.edit_prediction_provider() else {
7748 return;
7749 };
7750
7751 let Some((_, buffer, _)) = self
7752 .buffer
7753 .read(cx)
7754 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7755 else {
7756 return;
7757 };
7758
7759 let extension = buffer
7760 .read(cx)
7761 .file()
7762 .and_then(|file| Some(file.path().extension()?.to_string()));
7763
7764 let event_type = match accepted {
7765 true => "Edit Prediction Accepted",
7766 false => "Edit Prediction Discarded",
7767 };
7768 telemetry::event!(
7769 event_type,
7770 provider = provider.name(),
7771 prediction_id = id,
7772 suggestion_accepted = accepted,
7773 file_extension = extension,
7774 );
7775 }
7776
7777 fn open_editor_at_anchor(
7778 snapshot: &language::BufferSnapshot,
7779 target: language::Anchor,
7780 workspace: &Entity<Workspace>,
7781 window: &mut Window,
7782 cx: &mut App,
7783 ) -> Task<Result<()>> {
7784 workspace.update(cx, |workspace, cx| {
7785 let path = snapshot.file().map(|file| file.full_path(cx));
7786 let Some(path) =
7787 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7788 else {
7789 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7790 };
7791 let target = text::ToPoint::to_point(&target, snapshot);
7792 let item = workspace.open_path(path, None, true, window, cx);
7793 window.spawn(cx, async move |cx| {
7794 let Some(editor) = item.await?.downcast::<Editor>() else {
7795 return Ok(());
7796 };
7797 editor
7798 .update_in(cx, |editor, window, cx| {
7799 editor.go_to_singleton_buffer_point(target, window, cx);
7800 })
7801 .ok();
7802 anyhow::Ok(())
7803 })
7804 })
7805 }
7806
7807 pub fn has_active_edit_prediction(&self) -> bool {
7808 self.active_edit_prediction.is_some()
7809 }
7810
7811 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7812 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7813 return false;
7814 };
7815
7816 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7817 self.clear_highlights::<EditPredictionHighlight>(cx);
7818 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7819 true
7820 }
7821
7822 /// Returns true when we're displaying the edit prediction popover below the cursor
7823 /// like we are not previewing and the LSP autocomplete menu is visible
7824 /// or we are in `when_holding_modifier` mode.
7825 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7826 if self.edit_prediction_preview_is_active()
7827 || !self.show_edit_predictions_in_menu()
7828 || !self.edit_predictions_enabled()
7829 {
7830 return false;
7831 }
7832
7833 if self.has_visible_completions_menu() {
7834 return true;
7835 }
7836
7837 has_completion && self.edit_prediction_requires_modifier()
7838 }
7839
7840 fn handle_modifiers_changed(
7841 &mut self,
7842 modifiers: Modifiers,
7843 position_map: &PositionMap,
7844 window: &mut Window,
7845 cx: &mut Context<Self>,
7846 ) {
7847 // Ensure that the edit prediction preview is updated, even when not
7848 // enabled, if there's an active edit prediction preview.
7849 if self.show_edit_predictions_in_menu()
7850 || matches!(
7851 self.edit_prediction_preview,
7852 EditPredictionPreview::Active { .. }
7853 )
7854 {
7855 self.update_edit_prediction_preview(&modifiers, window, cx);
7856 }
7857
7858 self.update_selection_mode(&modifiers, position_map, window, cx);
7859
7860 let mouse_position = window.mouse_position();
7861 if !position_map.text_hitbox.is_hovered(window) {
7862 return;
7863 }
7864
7865 self.update_hovered_link(
7866 position_map.point_for_position(mouse_position),
7867 &position_map.snapshot,
7868 modifiers,
7869 window,
7870 cx,
7871 )
7872 }
7873
7874 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7875 match EditorSettings::get_global(cx).multi_cursor_modifier {
7876 MultiCursorModifier::Alt => modifiers.secondary(),
7877 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7878 }
7879 }
7880
7881 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7882 match EditorSettings::get_global(cx).multi_cursor_modifier {
7883 MultiCursorModifier::Alt => modifiers.alt,
7884 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7885 }
7886 }
7887
7888 fn columnar_selection_mode(
7889 modifiers: &Modifiers,
7890 cx: &mut Context<Self>,
7891 ) -> Option<ColumnarMode> {
7892 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7893 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7894 Some(ColumnarMode::FromMouse)
7895 } else if Self::is_alt_pressed(modifiers, cx) {
7896 Some(ColumnarMode::FromSelection)
7897 } else {
7898 None
7899 }
7900 } else {
7901 None
7902 }
7903 }
7904
7905 fn update_selection_mode(
7906 &mut self,
7907 modifiers: &Modifiers,
7908 position_map: &PositionMap,
7909 window: &mut Window,
7910 cx: &mut Context<Self>,
7911 ) {
7912 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7913 return;
7914 };
7915 if self.selections.pending_anchor().is_none() {
7916 return;
7917 }
7918
7919 let mouse_position = window.mouse_position();
7920 let point_for_position = position_map.point_for_position(mouse_position);
7921 let position = point_for_position.previous_valid;
7922
7923 self.select(
7924 SelectPhase::BeginColumnar {
7925 position,
7926 reset: false,
7927 mode,
7928 goal_column: point_for_position.exact_unclipped.column(),
7929 },
7930 window,
7931 cx,
7932 );
7933 }
7934
7935 fn update_edit_prediction_preview(
7936 &mut self,
7937 modifiers: &Modifiers,
7938 window: &mut Window,
7939 cx: &mut Context<Self>,
7940 ) {
7941 let mut modifiers_held = false;
7942 if let Some(accept_keystroke) = self
7943 .accept_edit_prediction_keybind(false, window, cx)
7944 .keystroke()
7945 {
7946 modifiers_held = modifiers_held
7947 || (accept_keystroke.modifiers() == modifiers
7948 && accept_keystroke.modifiers().modified());
7949 };
7950 if let Some(accept_partial_keystroke) = self
7951 .accept_edit_prediction_keybind(true, window, cx)
7952 .keystroke()
7953 {
7954 modifiers_held = modifiers_held
7955 || (accept_partial_keystroke.modifiers() == modifiers
7956 && accept_partial_keystroke.modifiers().modified());
7957 }
7958
7959 if modifiers_held {
7960 if matches!(
7961 self.edit_prediction_preview,
7962 EditPredictionPreview::Inactive { .. }
7963 ) {
7964 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7965 provider.provider.did_show(cx)
7966 }
7967
7968 self.edit_prediction_preview = EditPredictionPreview::Active {
7969 previous_scroll_position: None,
7970 since: Instant::now(),
7971 };
7972
7973 self.update_visible_edit_prediction(window, cx);
7974 cx.notify();
7975 }
7976 } else if let EditPredictionPreview::Active {
7977 previous_scroll_position,
7978 since,
7979 } = self.edit_prediction_preview
7980 {
7981 if let (Some(previous_scroll_position), Some(position_map)) =
7982 (previous_scroll_position, self.last_position_map.as_ref())
7983 {
7984 self.set_scroll_position(
7985 previous_scroll_position
7986 .scroll_position(&position_map.snapshot.display_snapshot),
7987 window,
7988 cx,
7989 );
7990 }
7991
7992 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7993 released_too_fast: since.elapsed() < Duration::from_millis(200),
7994 };
7995 self.clear_row_highlights::<EditPredictionPreview>();
7996 self.update_visible_edit_prediction(window, cx);
7997 cx.notify();
7998 }
7999 }
8000
8001 fn update_visible_edit_prediction(
8002 &mut self,
8003 _window: &mut Window,
8004 cx: &mut Context<Self>,
8005 ) -> Option<()> {
8006 if DisableAiSettings::get_global(cx).disable_ai {
8007 return None;
8008 }
8009
8010 if self.ime_transaction.is_some() {
8011 self.discard_edit_prediction(false, cx);
8012 return None;
8013 }
8014
8015 let selection = self.selections.newest_anchor();
8016 let cursor = selection.head();
8017 let multibuffer = self.buffer.read(cx).snapshot(cx);
8018 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8019 let excerpt_id = cursor.excerpt_id;
8020
8021 let show_in_menu = self.show_edit_predictions_in_menu();
8022 let completions_menu_has_precedence = !show_in_menu
8023 && (self.context_menu.borrow().is_some()
8024 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8025
8026 if completions_menu_has_precedence
8027 || !offset_selection.is_empty()
8028 || self
8029 .active_edit_prediction
8030 .as_ref()
8031 .is_some_and(|completion| {
8032 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8033 return false;
8034 };
8035 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8036 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8037 !invalidation_range.contains(&offset_selection.head())
8038 })
8039 {
8040 self.discard_edit_prediction(false, cx);
8041 return None;
8042 }
8043
8044 self.take_active_edit_prediction(cx);
8045 let Some(provider) = self.edit_prediction_provider() else {
8046 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8047 return None;
8048 };
8049
8050 let (buffer, cursor_buffer_position) =
8051 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8052
8053 self.edit_prediction_settings =
8054 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8055
8056 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8057
8058 if self.edit_prediction_indent_conflict {
8059 let cursor_point = cursor.to_point(&multibuffer);
8060
8061 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8062
8063 if let Some((_, indent)) = indents.iter().next()
8064 && indent.len == cursor_point.column
8065 {
8066 self.edit_prediction_indent_conflict = false;
8067 }
8068 }
8069
8070 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8071
8072 let (completion_id, edits, edit_preview) = match edit_prediction {
8073 edit_prediction::EditPrediction::Local {
8074 id,
8075 edits,
8076 edit_preview,
8077 } => (id, edits, edit_preview),
8078 edit_prediction::EditPrediction::Jump {
8079 id,
8080 snapshot,
8081 target,
8082 } => {
8083 self.stale_edit_prediction_in_menu = None;
8084 self.active_edit_prediction = Some(EditPredictionState {
8085 inlay_ids: vec![],
8086 completion: EditPrediction::MoveOutside { snapshot, target },
8087 completion_id: id,
8088 invalidation_range: None,
8089 });
8090 cx.notify();
8091 return Some(());
8092 }
8093 };
8094
8095 let edits = edits
8096 .into_iter()
8097 .flat_map(|(range, new_text)| {
8098 Some((
8099 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8100 new_text,
8101 ))
8102 })
8103 .collect::<Vec<_>>();
8104 if edits.is_empty() {
8105 return None;
8106 }
8107
8108 let first_edit_start = edits.first().unwrap().0.start;
8109 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8110 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8111
8112 let last_edit_end = edits.last().unwrap().0.end;
8113 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8114 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8115
8116 let cursor_row = cursor.to_point(&multibuffer).row;
8117
8118 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8119
8120 let mut inlay_ids = Vec::new();
8121 let invalidation_row_range;
8122 let move_invalidation_row_range = if cursor_row < edit_start_row {
8123 Some(cursor_row..edit_end_row)
8124 } else if cursor_row > edit_end_row {
8125 Some(edit_start_row..cursor_row)
8126 } else {
8127 None
8128 };
8129 let supports_jump = self
8130 .edit_prediction_provider
8131 .as_ref()
8132 .map(|provider| provider.provider.supports_jump_to_edit())
8133 .unwrap_or(true);
8134
8135 let is_move = supports_jump
8136 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8137 let completion = if is_move {
8138 invalidation_row_range =
8139 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8140 let target = first_edit_start;
8141 EditPrediction::MoveWithin { target, snapshot }
8142 } else {
8143 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8144 && !self.edit_predictions_hidden_for_vim_mode;
8145
8146 if show_completions_in_buffer {
8147 if let Some(provider) = &self.edit_prediction_provider {
8148 provider.provider.did_show(cx);
8149 }
8150 if edits
8151 .iter()
8152 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8153 {
8154 let mut inlays = Vec::new();
8155 for (range, new_text) in &edits {
8156 let inlay = Inlay::edit_prediction(
8157 post_inc(&mut self.next_inlay_id),
8158 range.start,
8159 new_text.as_ref(),
8160 );
8161 inlay_ids.push(inlay.id);
8162 inlays.push(inlay);
8163 }
8164
8165 self.splice_inlays(&[], inlays, cx);
8166 } else {
8167 let background_color = cx.theme().status().deleted_background;
8168 self.highlight_text::<EditPredictionHighlight>(
8169 edits.iter().map(|(range, _)| range.clone()).collect(),
8170 HighlightStyle {
8171 background_color: Some(background_color),
8172 ..Default::default()
8173 },
8174 cx,
8175 );
8176 }
8177 }
8178
8179 invalidation_row_range = edit_start_row..edit_end_row;
8180
8181 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8182 if provider.show_tab_accept_marker() {
8183 EditDisplayMode::TabAccept
8184 } else {
8185 EditDisplayMode::Inline
8186 }
8187 } else {
8188 EditDisplayMode::DiffPopover
8189 };
8190
8191 EditPrediction::Edit {
8192 edits,
8193 edit_preview,
8194 display_mode,
8195 snapshot,
8196 }
8197 };
8198
8199 let invalidation_range = multibuffer
8200 .anchor_before(Point::new(invalidation_row_range.start, 0))
8201 ..multibuffer.anchor_after(Point::new(
8202 invalidation_row_range.end,
8203 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8204 ));
8205
8206 self.stale_edit_prediction_in_menu = None;
8207 self.active_edit_prediction = Some(EditPredictionState {
8208 inlay_ids,
8209 completion,
8210 completion_id,
8211 invalidation_range: Some(invalidation_range),
8212 });
8213
8214 cx.notify();
8215
8216 Some(())
8217 }
8218
8219 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8220 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8221 }
8222
8223 fn clear_tasks(&mut self) {
8224 self.tasks.clear()
8225 }
8226
8227 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8228 if self.tasks.insert(key, value).is_some() {
8229 // This case should hopefully be rare, but just in case...
8230 log::error!(
8231 "multiple different run targets found on a single line, only the last target will be rendered"
8232 )
8233 }
8234 }
8235
8236 /// Get all display points of breakpoints that will be rendered within editor
8237 ///
8238 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8239 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8240 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8241 fn active_breakpoints(
8242 &self,
8243 range: Range<DisplayRow>,
8244 window: &mut Window,
8245 cx: &mut Context<Self>,
8246 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8247 let mut breakpoint_display_points = HashMap::default();
8248
8249 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8250 return breakpoint_display_points;
8251 };
8252
8253 let snapshot = self.snapshot(window, cx);
8254
8255 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8256 let Some(project) = self.project() else {
8257 return breakpoint_display_points;
8258 };
8259
8260 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8261 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8262
8263 for (buffer_snapshot, range, excerpt_id) in
8264 multi_buffer_snapshot.range_to_buffer_ranges(range)
8265 {
8266 let Some(buffer) = project
8267 .read(cx)
8268 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8269 else {
8270 continue;
8271 };
8272 let breakpoints = breakpoint_store.read(cx).breakpoints(
8273 &buffer,
8274 Some(
8275 buffer_snapshot.anchor_before(range.start)
8276 ..buffer_snapshot.anchor_after(range.end),
8277 ),
8278 buffer_snapshot,
8279 cx,
8280 );
8281 for (breakpoint, state) in breakpoints {
8282 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8283 let position = multi_buffer_anchor
8284 .to_point(&multi_buffer_snapshot)
8285 .to_display_point(&snapshot);
8286
8287 breakpoint_display_points.insert(
8288 position.row(),
8289 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8290 );
8291 }
8292 }
8293
8294 breakpoint_display_points
8295 }
8296
8297 fn breakpoint_context_menu(
8298 &self,
8299 anchor: Anchor,
8300 window: &mut Window,
8301 cx: &mut Context<Self>,
8302 ) -> Entity<ui::ContextMenu> {
8303 let weak_editor = cx.weak_entity();
8304 let focus_handle = self.focus_handle(cx);
8305
8306 let row = self
8307 .buffer
8308 .read(cx)
8309 .snapshot(cx)
8310 .summary_for_anchor::<Point>(&anchor)
8311 .row;
8312
8313 let breakpoint = self
8314 .breakpoint_at_row(row, window, cx)
8315 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8316
8317 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8318 "Edit Log Breakpoint"
8319 } else {
8320 "Set Log Breakpoint"
8321 };
8322
8323 let condition_breakpoint_msg = if breakpoint
8324 .as_ref()
8325 .is_some_and(|bp| bp.1.condition.is_some())
8326 {
8327 "Edit Condition Breakpoint"
8328 } else {
8329 "Set Condition Breakpoint"
8330 };
8331
8332 let hit_condition_breakpoint_msg = if breakpoint
8333 .as_ref()
8334 .is_some_and(|bp| bp.1.hit_condition.is_some())
8335 {
8336 "Edit Hit Condition Breakpoint"
8337 } else {
8338 "Set Hit Condition Breakpoint"
8339 };
8340
8341 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8342 "Unset Breakpoint"
8343 } else {
8344 "Set Breakpoint"
8345 };
8346
8347 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8348
8349 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8350 BreakpointState::Enabled => Some("Disable"),
8351 BreakpointState::Disabled => Some("Enable"),
8352 });
8353
8354 let (anchor, breakpoint) =
8355 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8356
8357 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8358 menu.on_blur_subscription(Subscription::new(|| {}))
8359 .context(focus_handle)
8360 .when(run_to_cursor, |this| {
8361 let weak_editor = weak_editor.clone();
8362 this.entry("Run to cursor", None, move |window, cx| {
8363 weak_editor
8364 .update(cx, |editor, cx| {
8365 editor.change_selections(
8366 SelectionEffects::no_scroll(),
8367 window,
8368 cx,
8369 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8370 );
8371 })
8372 .ok();
8373
8374 window.dispatch_action(Box::new(RunToCursor), cx);
8375 })
8376 .separator()
8377 })
8378 .when_some(toggle_state_msg, |this, msg| {
8379 this.entry(msg, None, {
8380 let weak_editor = weak_editor.clone();
8381 let breakpoint = breakpoint.clone();
8382 move |_window, cx| {
8383 weak_editor
8384 .update(cx, |this, cx| {
8385 this.edit_breakpoint_at_anchor(
8386 anchor,
8387 breakpoint.as_ref().clone(),
8388 BreakpointEditAction::InvertState,
8389 cx,
8390 );
8391 })
8392 .log_err();
8393 }
8394 })
8395 })
8396 .entry(set_breakpoint_msg, None, {
8397 let weak_editor = weak_editor.clone();
8398 let breakpoint = breakpoint.clone();
8399 move |_window, cx| {
8400 weak_editor
8401 .update(cx, |this, cx| {
8402 this.edit_breakpoint_at_anchor(
8403 anchor,
8404 breakpoint.as_ref().clone(),
8405 BreakpointEditAction::Toggle,
8406 cx,
8407 );
8408 })
8409 .log_err();
8410 }
8411 })
8412 .entry(log_breakpoint_msg, None, {
8413 let breakpoint = breakpoint.clone();
8414 let weak_editor = weak_editor.clone();
8415 move |window, cx| {
8416 weak_editor
8417 .update(cx, |this, cx| {
8418 this.add_edit_breakpoint_block(
8419 anchor,
8420 breakpoint.as_ref(),
8421 BreakpointPromptEditAction::Log,
8422 window,
8423 cx,
8424 );
8425 })
8426 .log_err();
8427 }
8428 })
8429 .entry(condition_breakpoint_msg, None, {
8430 let breakpoint = breakpoint.clone();
8431 let weak_editor = weak_editor.clone();
8432 move |window, cx| {
8433 weak_editor
8434 .update(cx, |this, cx| {
8435 this.add_edit_breakpoint_block(
8436 anchor,
8437 breakpoint.as_ref(),
8438 BreakpointPromptEditAction::Condition,
8439 window,
8440 cx,
8441 );
8442 })
8443 .log_err();
8444 }
8445 })
8446 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8447 weak_editor
8448 .update(cx, |this, cx| {
8449 this.add_edit_breakpoint_block(
8450 anchor,
8451 breakpoint.as_ref(),
8452 BreakpointPromptEditAction::HitCondition,
8453 window,
8454 cx,
8455 );
8456 })
8457 .log_err();
8458 })
8459 })
8460 }
8461
8462 fn render_breakpoint(
8463 &self,
8464 position: Anchor,
8465 row: DisplayRow,
8466 breakpoint: &Breakpoint,
8467 state: Option<BreakpointSessionState>,
8468 cx: &mut Context<Self>,
8469 ) -> IconButton {
8470 let is_rejected = state.is_some_and(|s| !s.verified);
8471 // Is it a breakpoint that shows up when hovering over gutter?
8472 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8473 (false, false),
8474 |PhantomBreakpointIndicator {
8475 is_active,
8476 display_row,
8477 collides_with_existing_breakpoint,
8478 }| {
8479 (
8480 is_active && display_row == row,
8481 collides_with_existing_breakpoint,
8482 )
8483 },
8484 );
8485
8486 let (color, icon) = {
8487 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8488 (false, false) => ui::IconName::DebugBreakpoint,
8489 (true, false) => ui::IconName::DebugLogBreakpoint,
8490 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8491 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8492 };
8493
8494 let color = cx.theme().colors();
8495
8496 let color = if is_phantom {
8497 if collides_with_existing {
8498 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8499 } else {
8500 Color::Hint
8501 }
8502 } else if is_rejected {
8503 Color::Disabled
8504 } else {
8505 Color::Debugger
8506 };
8507
8508 (color, icon)
8509 };
8510
8511 let breakpoint = Arc::from(breakpoint.clone());
8512
8513 let alt_as_text = gpui::Keystroke {
8514 modifiers: Modifiers::secondary_key(),
8515 ..Default::default()
8516 };
8517 let primary_action_text = if breakpoint.is_disabled() {
8518 "Enable breakpoint"
8519 } else if is_phantom && !collides_with_existing {
8520 "Set breakpoint"
8521 } else {
8522 "Unset breakpoint"
8523 };
8524 let focus_handle = self.focus_handle.clone();
8525
8526 let meta = if is_rejected {
8527 SharedString::from("No executable code is associated with this line.")
8528 } else if collides_with_existing && !breakpoint.is_disabled() {
8529 SharedString::from(format!(
8530 "{alt_as_text}-click to disable,\nright-click for more options."
8531 ))
8532 } else {
8533 SharedString::from("Right-click for more options.")
8534 };
8535 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8536 .icon_size(IconSize::XSmall)
8537 .size(ui::ButtonSize::None)
8538 .when(is_rejected, |this| {
8539 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8540 })
8541 .icon_color(color)
8542 .style(ButtonStyle::Transparent)
8543 .on_click(cx.listener({
8544 move |editor, event: &ClickEvent, window, cx| {
8545 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8546 BreakpointEditAction::InvertState
8547 } else {
8548 BreakpointEditAction::Toggle
8549 };
8550
8551 window.focus(&editor.focus_handle(cx));
8552 editor.edit_breakpoint_at_anchor(
8553 position,
8554 breakpoint.as_ref().clone(),
8555 edit_action,
8556 cx,
8557 );
8558 }
8559 }))
8560 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8561 editor.set_breakpoint_context_menu(
8562 row,
8563 Some(position),
8564 event.position(),
8565 window,
8566 cx,
8567 );
8568 }))
8569 .tooltip(move |_window, cx| {
8570 Tooltip::with_meta_in(
8571 primary_action_text,
8572 Some(&ToggleBreakpoint),
8573 meta.clone(),
8574 &focus_handle,
8575 cx,
8576 )
8577 })
8578 }
8579
8580 fn build_tasks_context(
8581 project: &Entity<Project>,
8582 buffer: &Entity<Buffer>,
8583 buffer_row: u32,
8584 tasks: &Arc<RunnableTasks>,
8585 cx: &mut Context<Self>,
8586 ) -> Task<Option<task::TaskContext>> {
8587 let position = Point::new(buffer_row, tasks.column);
8588 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8589 let location = Location {
8590 buffer: buffer.clone(),
8591 range: range_start..range_start,
8592 };
8593 // Fill in the environmental variables from the tree-sitter captures
8594 let mut captured_task_variables = TaskVariables::default();
8595 for (capture_name, value) in tasks.extra_variables.clone() {
8596 captured_task_variables.insert(
8597 task::VariableName::Custom(capture_name.into()),
8598 value.clone(),
8599 );
8600 }
8601 project.update(cx, |project, cx| {
8602 project.task_store().update(cx, |task_store, cx| {
8603 task_store.task_context_for_location(captured_task_variables, location, cx)
8604 })
8605 })
8606 }
8607
8608 pub fn spawn_nearest_task(
8609 &mut self,
8610 action: &SpawnNearestTask,
8611 window: &mut Window,
8612 cx: &mut Context<Self>,
8613 ) {
8614 let Some((workspace, _)) = self.workspace.clone() else {
8615 return;
8616 };
8617 let Some(project) = self.project.clone() else {
8618 return;
8619 };
8620
8621 // Try to find a closest, enclosing node using tree-sitter that has a task
8622 let Some((buffer, buffer_row, tasks)) = self
8623 .find_enclosing_node_task(cx)
8624 // Or find the task that's closest in row-distance.
8625 .or_else(|| self.find_closest_task(cx))
8626 else {
8627 return;
8628 };
8629
8630 let reveal_strategy = action.reveal;
8631 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8632 cx.spawn_in(window, async move |_, cx| {
8633 let context = task_context.await?;
8634 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8635
8636 let resolved = &mut resolved_task.resolved;
8637 resolved.reveal = reveal_strategy;
8638
8639 workspace
8640 .update_in(cx, |workspace, window, cx| {
8641 workspace.schedule_resolved_task(
8642 task_source_kind,
8643 resolved_task,
8644 false,
8645 window,
8646 cx,
8647 );
8648 })
8649 .ok()
8650 })
8651 .detach();
8652 }
8653
8654 fn find_closest_task(
8655 &mut self,
8656 cx: &mut Context<Self>,
8657 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8658 let cursor_row = self
8659 .selections
8660 .newest_adjusted(&self.display_snapshot(cx))
8661 .head()
8662 .row;
8663
8664 let ((buffer_id, row), tasks) = self
8665 .tasks
8666 .iter()
8667 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8668
8669 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8670 let tasks = Arc::new(tasks.to_owned());
8671 Some((buffer, *row, tasks))
8672 }
8673
8674 fn find_enclosing_node_task(
8675 &mut self,
8676 cx: &mut Context<Self>,
8677 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8678 let snapshot = self.buffer.read(cx).snapshot(cx);
8679 let offset = self
8680 .selections
8681 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8682 .head();
8683 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8684 let offset = excerpt.map_offset_to_buffer(offset);
8685 let buffer_id = excerpt.buffer().remote_id();
8686
8687 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8688 let mut cursor = layer.node().walk();
8689
8690 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8691 if cursor.node().end_byte() == offset.0 {
8692 cursor.goto_next_sibling();
8693 }
8694 }
8695
8696 // Ascend to the smallest ancestor that contains the range and has a task.
8697 loop {
8698 let node = cursor.node();
8699 let node_range = node.byte_range();
8700 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8701
8702 // Check if this node contains our offset
8703 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8704 // If it contains offset, check for task
8705 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8706 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8707 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8708 }
8709 }
8710
8711 if !cursor.goto_parent() {
8712 break;
8713 }
8714 }
8715 None
8716 }
8717
8718 fn render_run_indicator(
8719 &self,
8720 _style: &EditorStyle,
8721 is_active: bool,
8722 row: DisplayRow,
8723 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8724 cx: &mut Context<Self>,
8725 ) -> IconButton {
8726 let color = Color::Muted;
8727 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8728
8729 IconButton::new(
8730 ("run_indicator", row.0 as usize),
8731 ui::IconName::PlayOutlined,
8732 )
8733 .shape(ui::IconButtonShape::Square)
8734 .icon_size(IconSize::XSmall)
8735 .icon_color(color)
8736 .toggle_state(is_active)
8737 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8738 let quick_launch = match e {
8739 ClickEvent::Keyboard(_) => true,
8740 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8741 };
8742
8743 window.focus(&editor.focus_handle(cx));
8744 editor.toggle_code_actions(
8745 &ToggleCodeActions {
8746 deployed_from: Some(CodeActionSource::RunMenu(row)),
8747 quick_launch,
8748 },
8749 window,
8750 cx,
8751 );
8752 }))
8753 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8754 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8755 }))
8756 }
8757
8758 pub fn context_menu_visible(&self) -> bool {
8759 !self.edit_prediction_preview_is_active()
8760 && self
8761 .context_menu
8762 .borrow()
8763 .as_ref()
8764 .is_some_and(|menu| menu.visible())
8765 }
8766
8767 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8768 self.context_menu
8769 .borrow()
8770 .as_ref()
8771 .map(|menu| menu.origin())
8772 }
8773
8774 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8775 self.context_menu_options = Some(options);
8776 }
8777
8778 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8779 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8780
8781 fn render_edit_prediction_popover(
8782 &mut self,
8783 text_bounds: &Bounds<Pixels>,
8784 content_origin: gpui::Point<Pixels>,
8785 right_margin: Pixels,
8786 editor_snapshot: &EditorSnapshot,
8787 visible_row_range: Range<DisplayRow>,
8788 scroll_top: ScrollOffset,
8789 scroll_bottom: ScrollOffset,
8790 line_layouts: &[LineWithInvisibles],
8791 line_height: Pixels,
8792 scroll_position: gpui::Point<ScrollOffset>,
8793 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8794 newest_selection_head: Option<DisplayPoint>,
8795 editor_width: Pixels,
8796 style: &EditorStyle,
8797 window: &mut Window,
8798 cx: &mut App,
8799 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8800 if self.mode().is_minimap() {
8801 return None;
8802 }
8803 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8804
8805 if self.edit_prediction_visible_in_cursor_popover(true) {
8806 return None;
8807 }
8808
8809 match &active_edit_prediction.completion {
8810 EditPrediction::MoveWithin { target, .. } => {
8811 let target_display_point = target.to_display_point(editor_snapshot);
8812
8813 if self.edit_prediction_requires_modifier() {
8814 if !self.edit_prediction_preview_is_active() {
8815 return None;
8816 }
8817
8818 self.render_edit_prediction_modifier_jump_popover(
8819 text_bounds,
8820 content_origin,
8821 visible_row_range,
8822 line_layouts,
8823 line_height,
8824 scroll_pixel_position,
8825 newest_selection_head,
8826 target_display_point,
8827 window,
8828 cx,
8829 )
8830 } else {
8831 self.render_edit_prediction_eager_jump_popover(
8832 text_bounds,
8833 content_origin,
8834 editor_snapshot,
8835 visible_row_range,
8836 scroll_top,
8837 scroll_bottom,
8838 line_height,
8839 scroll_pixel_position,
8840 target_display_point,
8841 editor_width,
8842 window,
8843 cx,
8844 )
8845 }
8846 }
8847 EditPrediction::Edit {
8848 display_mode: EditDisplayMode::Inline,
8849 ..
8850 } => None,
8851 EditPrediction::Edit {
8852 display_mode: EditDisplayMode::TabAccept,
8853 edits,
8854 ..
8855 } => {
8856 let range = &edits.first()?.0;
8857 let target_display_point = range.end.to_display_point(editor_snapshot);
8858
8859 self.render_edit_prediction_end_of_line_popover(
8860 "Accept",
8861 editor_snapshot,
8862 visible_row_range,
8863 target_display_point,
8864 line_height,
8865 scroll_pixel_position,
8866 content_origin,
8867 editor_width,
8868 window,
8869 cx,
8870 )
8871 }
8872 EditPrediction::Edit {
8873 edits,
8874 edit_preview,
8875 display_mode: EditDisplayMode::DiffPopover,
8876 snapshot,
8877 } => self.render_edit_prediction_diff_popover(
8878 text_bounds,
8879 content_origin,
8880 right_margin,
8881 editor_snapshot,
8882 visible_row_range,
8883 line_layouts,
8884 line_height,
8885 scroll_position,
8886 scroll_pixel_position,
8887 newest_selection_head,
8888 editor_width,
8889 style,
8890 edits,
8891 edit_preview,
8892 snapshot,
8893 window,
8894 cx,
8895 ),
8896 EditPrediction::MoveOutside { snapshot, .. } => {
8897 let mut element = self
8898 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8899 .into_any();
8900
8901 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8902 let origin_x = text_bounds.size.width - size.width - px(30.);
8903 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8904 element.prepaint_at(origin, window, cx);
8905
8906 Some((element, origin))
8907 }
8908 }
8909 }
8910
8911 fn render_edit_prediction_modifier_jump_popover(
8912 &mut self,
8913 text_bounds: &Bounds<Pixels>,
8914 content_origin: gpui::Point<Pixels>,
8915 visible_row_range: Range<DisplayRow>,
8916 line_layouts: &[LineWithInvisibles],
8917 line_height: Pixels,
8918 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8919 newest_selection_head: Option<DisplayPoint>,
8920 target_display_point: DisplayPoint,
8921 window: &mut Window,
8922 cx: &mut App,
8923 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8924 let scrolled_content_origin =
8925 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8926
8927 const SCROLL_PADDING_Y: Pixels = px(12.);
8928
8929 if target_display_point.row() < visible_row_range.start {
8930 return self.render_edit_prediction_scroll_popover(
8931 |_| SCROLL_PADDING_Y,
8932 IconName::ArrowUp,
8933 visible_row_range,
8934 line_layouts,
8935 newest_selection_head,
8936 scrolled_content_origin,
8937 window,
8938 cx,
8939 );
8940 } else if target_display_point.row() >= visible_row_range.end {
8941 return self.render_edit_prediction_scroll_popover(
8942 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8943 IconName::ArrowDown,
8944 visible_row_range,
8945 line_layouts,
8946 newest_selection_head,
8947 scrolled_content_origin,
8948 window,
8949 cx,
8950 );
8951 }
8952
8953 const POLE_WIDTH: Pixels = px(2.);
8954
8955 let line_layout =
8956 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8957 let target_column = target_display_point.column() as usize;
8958
8959 let target_x = line_layout.x_for_index(target_column);
8960 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8961 - scroll_pixel_position.y;
8962
8963 let flag_on_right = target_x < text_bounds.size.width / 2.;
8964
8965 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8966 border_color.l += 0.001;
8967
8968 let mut element = v_flex()
8969 .items_end()
8970 .when(flag_on_right, |el| el.items_start())
8971 .child(if flag_on_right {
8972 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8973 .rounded_bl(px(0.))
8974 .rounded_tl(px(0.))
8975 .border_l_2()
8976 .border_color(border_color)
8977 } else {
8978 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8979 .rounded_br(px(0.))
8980 .rounded_tr(px(0.))
8981 .border_r_2()
8982 .border_color(border_color)
8983 })
8984 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8985 .into_any();
8986
8987 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8988
8989 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8990 - point(
8991 if flag_on_right {
8992 POLE_WIDTH
8993 } else {
8994 size.width - POLE_WIDTH
8995 },
8996 size.height - line_height,
8997 );
8998
8999 origin.x = origin.x.max(content_origin.x);
9000
9001 element.prepaint_at(origin, window, cx);
9002
9003 Some((element, origin))
9004 }
9005
9006 fn render_edit_prediction_scroll_popover(
9007 &mut self,
9008 to_y: impl Fn(Size<Pixels>) -> Pixels,
9009 scroll_icon: IconName,
9010 visible_row_range: Range<DisplayRow>,
9011 line_layouts: &[LineWithInvisibles],
9012 newest_selection_head: Option<DisplayPoint>,
9013 scrolled_content_origin: gpui::Point<Pixels>,
9014 window: &mut Window,
9015 cx: &mut App,
9016 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9017 let mut element = self
9018 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9019 .into_any();
9020
9021 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9022
9023 let cursor = newest_selection_head?;
9024 let cursor_row_layout =
9025 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9026 let cursor_column = cursor.column() as usize;
9027
9028 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9029
9030 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9031
9032 element.prepaint_at(origin, window, cx);
9033 Some((element, origin))
9034 }
9035
9036 fn render_edit_prediction_eager_jump_popover(
9037 &mut self,
9038 text_bounds: &Bounds<Pixels>,
9039 content_origin: gpui::Point<Pixels>,
9040 editor_snapshot: &EditorSnapshot,
9041 visible_row_range: Range<DisplayRow>,
9042 scroll_top: ScrollOffset,
9043 scroll_bottom: ScrollOffset,
9044 line_height: Pixels,
9045 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9046 target_display_point: DisplayPoint,
9047 editor_width: Pixels,
9048 window: &mut Window,
9049 cx: &mut App,
9050 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9051 if target_display_point.row().as_f64() < scroll_top {
9052 let mut element = self
9053 .render_edit_prediction_line_popover(
9054 "Jump to Edit",
9055 Some(IconName::ArrowUp),
9056 window,
9057 cx,
9058 )
9059 .into_any();
9060
9061 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9062 let offset = point(
9063 (text_bounds.size.width - size.width) / 2.,
9064 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9065 );
9066
9067 let origin = text_bounds.origin + offset;
9068 element.prepaint_at(origin, window, cx);
9069 Some((element, origin))
9070 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9071 let mut element = self
9072 .render_edit_prediction_line_popover(
9073 "Jump to Edit",
9074 Some(IconName::ArrowDown),
9075 window,
9076 cx,
9077 )
9078 .into_any();
9079
9080 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9081 let offset = point(
9082 (text_bounds.size.width - size.width) / 2.,
9083 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9084 );
9085
9086 let origin = text_bounds.origin + offset;
9087 element.prepaint_at(origin, window, cx);
9088 Some((element, origin))
9089 } else {
9090 self.render_edit_prediction_end_of_line_popover(
9091 "Jump to Edit",
9092 editor_snapshot,
9093 visible_row_range,
9094 target_display_point,
9095 line_height,
9096 scroll_pixel_position,
9097 content_origin,
9098 editor_width,
9099 window,
9100 cx,
9101 )
9102 }
9103 }
9104
9105 fn render_edit_prediction_end_of_line_popover(
9106 self: &mut Editor,
9107 label: &'static str,
9108 editor_snapshot: &EditorSnapshot,
9109 visible_row_range: Range<DisplayRow>,
9110 target_display_point: DisplayPoint,
9111 line_height: Pixels,
9112 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9113 content_origin: gpui::Point<Pixels>,
9114 editor_width: Pixels,
9115 window: &mut Window,
9116 cx: &mut App,
9117 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9118 let target_line_end = DisplayPoint::new(
9119 target_display_point.row(),
9120 editor_snapshot.line_len(target_display_point.row()),
9121 );
9122
9123 let mut element = self
9124 .render_edit_prediction_line_popover(label, None, window, cx)
9125 .into_any();
9126
9127 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9128
9129 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9130
9131 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9132 let mut origin = start_point
9133 + line_origin
9134 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9135 origin.x = origin.x.max(content_origin.x);
9136
9137 let max_x = content_origin.x + editor_width - size.width;
9138
9139 if origin.x > max_x {
9140 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9141
9142 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9143 origin.y += offset;
9144 IconName::ArrowUp
9145 } else {
9146 origin.y -= offset;
9147 IconName::ArrowDown
9148 };
9149
9150 element = self
9151 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9152 .into_any();
9153
9154 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9155
9156 origin.x = content_origin.x + editor_width - size.width - px(2.);
9157 }
9158
9159 element.prepaint_at(origin, window, cx);
9160 Some((element, origin))
9161 }
9162
9163 fn render_edit_prediction_diff_popover(
9164 self: &Editor,
9165 text_bounds: &Bounds<Pixels>,
9166 content_origin: gpui::Point<Pixels>,
9167 right_margin: Pixels,
9168 editor_snapshot: &EditorSnapshot,
9169 visible_row_range: Range<DisplayRow>,
9170 line_layouts: &[LineWithInvisibles],
9171 line_height: Pixels,
9172 scroll_position: gpui::Point<ScrollOffset>,
9173 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9174 newest_selection_head: Option<DisplayPoint>,
9175 editor_width: Pixels,
9176 style: &EditorStyle,
9177 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9178 edit_preview: &Option<language::EditPreview>,
9179 snapshot: &language::BufferSnapshot,
9180 window: &mut Window,
9181 cx: &mut App,
9182 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9183 let edit_start = edits
9184 .first()
9185 .unwrap()
9186 .0
9187 .start
9188 .to_display_point(editor_snapshot);
9189 let edit_end = edits
9190 .last()
9191 .unwrap()
9192 .0
9193 .end
9194 .to_display_point(editor_snapshot);
9195
9196 let is_visible = visible_row_range.contains(&edit_start.row())
9197 || visible_row_range.contains(&edit_end.row());
9198 if !is_visible {
9199 return None;
9200 }
9201
9202 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9203 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9204 } else {
9205 // Fallback for providers without edit_preview
9206 crate::edit_prediction_fallback_text(edits, cx)
9207 };
9208
9209 let styled_text = highlighted_edits.to_styled_text(&style.text);
9210 let line_count = highlighted_edits.text.lines().count();
9211
9212 const BORDER_WIDTH: Pixels = px(1.);
9213
9214 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9215 let has_keybind = keybind.is_some();
9216
9217 let mut element = h_flex()
9218 .items_start()
9219 .child(
9220 h_flex()
9221 .bg(cx.theme().colors().editor_background)
9222 .border(BORDER_WIDTH)
9223 .shadow_xs()
9224 .border_color(cx.theme().colors().border)
9225 .rounded_l_lg()
9226 .when(line_count > 1, |el| el.rounded_br_lg())
9227 .pr_1()
9228 .child(styled_text),
9229 )
9230 .child(
9231 h_flex()
9232 .h(line_height + BORDER_WIDTH * 2.)
9233 .px_1p5()
9234 .gap_1()
9235 // Workaround: For some reason, there's a gap if we don't do this
9236 .ml(-BORDER_WIDTH)
9237 .shadow(vec![gpui::BoxShadow {
9238 color: gpui::black().opacity(0.05),
9239 offset: point(px(1.), px(1.)),
9240 blur_radius: px(2.),
9241 spread_radius: px(0.),
9242 }])
9243 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9244 .border(BORDER_WIDTH)
9245 .border_color(cx.theme().colors().border)
9246 .rounded_r_lg()
9247 .id("edit_prediction_diff_popover_keybind")
9248 .when(!has_keybind, |el| {
9249 let status_colors = cx.theme().status();
9250
9251 el.bg(status_colors.error_background)
9252 .border_color(status_colors.error.opacity(0.6))
9253 .child(Icon::new(IconName::Info).color(Color::Error))
9254 .cursor_default()
9255 .hoverable_tooltip(move |_window, cx| {
9256 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9257 })
9258 })
9259 .children(keybind),
9260 )
9261 .into_any();
9262
9263 let longest_row =
9264 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9265 let longest_line_width = if visible_row_range.contains(&longest_row) {
9266 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9267 } else {
9268 layout_line(
9269 longest_row,
9270 editor_snapshot,
9271 style,
9272 editor_width,
9273 |_| false,
9274 window,
9275 cx,
9276 )
9277 .width
9278 };
9279
9280 let viewport_bounds =
9281 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9282 right: -right_margin,
9283 ..Default::default()
9284 });
9285
9286 let x_after_longest = Pixels::from(
9287 ScrollPixelOffset::from(
9288 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9289 ) - scroll_pixel_position.x,
9290 );
9291
9292 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9293
9294 // Fully visible if it can be displayed within the window (allow overlapping other
9295 // panes). However, this is only allowed if the popover starts within text_bounds.
9296 let can_position_to_the_right = x_after_longest < text_bounds.right()
9297 && x_after_longest + element_bounds.width < viewport_bounds.right();
9298
9299 let mut origin = if can_position_to_the_right {
9300 point(
9301 x_after_longest,
9302 text_bounds.origin.y
9303 + Pixels::from(
9304 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9305 - scroll_pixel_position.y,
9306 ),
9307 )
9308 } else {
9309 let cursor_row = newest_selection_head.map(|head| head.row());
9310 let above_edit = edit_start
9311 .row()
9312 .0
9313 .checked_sub(line_count as u32)
9314 .map(DisplayRow);
9315 let below_edit = Some(edit_end.row() + 1);
9316 let above_cursor =
9317 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9318 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9319
9320 // Place the edit popover adjacent to the edit if there is a location
9321 // available that is onscreen and does not obscure the cursor. Otherwise,
9322 // place it adjacent to the cursor.
9323 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9324 .into_iter()
9325 .flatten()
9326 .find(|&start_row| {
9327 let end_row = start_row + line_count as u32;
9328 visible_row_range.contains(&start_row)
9329 && visible_row_range.contains(&end_row)
9330 && cursor_row
9331 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9332 })?;
9333
9334 content_origin
9335 + point(
9336 Pixels::from(-scroll_pixel_position.x),
9337 Pixels::from(
9338 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9339 ),
9340 )
9341 };
9342
9343 origin.x -= BORDER_WIDTH;
9344
9345 window.defer_draw(element, origin, 1);
9346
9347 // Do not return an element, since it will already be drawn due to defer_draw.
9348 None
9349 }
9350
9351 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9352 px(30.)
9353 }
9354
9355 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9356 if self.read_only(cx) {
9357 cx.theme().players().read_only()
9358 } else {
9359 self.style.as_ref().unwrap().local_player
9360 }
9361 }
9362
9363 fn render_edit_prediction_accept_keybind(
9364 &self,
9365 window: &mut Window,
9366 cx: &mut App,
9367 ) -> Option<AnyElement> {
9368 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9369 let accept_keystroke = accept_binding.keystroke()?;
9370
9371 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9372
9373 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9374 Color::Accent
9375 } else {
9376 Color::Muted
9377 };
9378
9379 h_flex()
9380 .px_0p5()
9381 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9382 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9383 .text_size(TextSize::XSmall.rems(cx))
9384 .child(h_flex().children(ui::render_modifiers(
9385 accept_keystroke.modifiers(),
9386 PlatformStyle::platform(),
9387 Some(modifiers_color),
9388 Some(IconSize::XSmall.rems().into()),
9389 true,
9390 )))
9391 .when(is_platform_style_mac, |parent| {
9392 parent.child(accept_keystroke.key().to_string())
9393 })
9394 .when(!is_platform_style_mac, |parent| {
9395 parent.child(
9396 Key::new(
9397 util::capitalize(accept_keystroke.key()),
9398 Some(Color::Default),
9399 )
9400 .size(Some(IconSize::XSmall.rems().into())),
9401 )
9402 })
9403 .into_any()
9404 .into()
9405 }
9406
9407 fn render_edit_prediction_line_popover(
9408 &self,
9409 label: impl Into<SharedString>,
9410 icon: Option<IconName>,
9411 window: &mut Window,
9412 cx: &mut App,
9413 ) -> Stateful<Div> {
9414 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9415
9416 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9417 let has_keybind = keybind.is_some();
9418
9419 h_flex()
9420 .id("ep-line-popover")
9421 .py_0p5()
9422 .pl_1()
9423 .pr(padding_right)
9424 .gap_1()
9425 .rounded_md()
9426 .border_1()
9427 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9428 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9429 .shadow_xs()
9430 .when(!has_keybind, |el| {
9431 let status_colors = cx.theme().status();
9432
9433 el.bg(status_colors.error_background)
9434 .border_color(status_colors.error.opacity(0.6))
9435 .pl_2()
9436 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9437 .cursor_default()
9438 .hoverable_tooltip(move |_window, cx| {
9439 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9440 })
9441 })
9442 .children(keybind)
9443 .child(
9444 Label::new(label)
9445 .size(LabelSize::Small)
9446 .when(!has_keybind, |el| {
9447 el.color(cx.theme().status().error.into()).strikethrough()
9448 }),
9449 )
9450 .when(!has_keybind, |el| {
9451 el.child(
9452 h_flex().ml_1().child(
9453 Icon::new(IconName::Info)
9454 .size(IconSize::Small)
9455 .color(cx.theme().status().error.into()),
9456 ),
9457 )
9458 })
9459 .when_some(icon, |element, icon| {
9460 element.child(
9461 div()
9462 .mt(px(1.5))
9463 .child(Icon::new(icon).size(IconSize::Small)),
9464 )
9465 })
9466 }
9467
9468 fn render_edit_prediction_jump_outside_popover(
9469 &self,
9470 snapshot: &BufferSnapshot,
9471 window: &mut Window,
9472 cx: &mut App,
9473 ) -> Stateful<Div> {
9474 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9475 let has_keybind = keybind.is_some();
9476
9477 let file_name = snapshot
9478 .file()
9479 .map(|file| SharedString::new(file.file_name(cx)))
9480 .unwrap_or(SharedString::new_static("untitled"));
9481
9482 h_flex()
9483 .id("ep-jump-outside-popover")
9484 .py_1()
9485 .px_2()
9486 .gap_1()
9487 .rounded_md()
9488 .border_1()
9489 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9490 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9491 .shadow_xs()
9492 .when(!has_keybind, |el| {
9493 let status_colors = cx.theme().status();
9494
9495 el.bg(status_colors.error_background)
9496 .border_color(status_colors.error.opacity(0.6))
9497 .pl_2()
9498 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9499 .cursor_default()
9500 .hoverable_tooltip(move |_window, cx| {
9501 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9502 })
9503 })
9504 .children(keybind)
9505 .child(
9506 Label::new(file_name)
9507 .size(LabelSize::Small)
9508 .buffer_font(cx)
9509 .when(!has_keybind, |el| {
9510 el.color(cx.theme().status().error.into()).strikethrough()
9511 }),
9512 )
9513 .when(!has_keybind, |el| {
9514 el.child(
9515 h_flex().ml_1().child(
9516 Icon::new(IconName::Info)
9517 .size(IconSize::Small)
9518 .color(cx.theme().status().error.into()),
9519 ),
9520 )
9521 })
9522 .child(
9523 div()
9524 .mt(px(1.5))
9525 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9526 )
9527 }
9528
9529 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9530 let accent_color = cx.theme().colors().text_accent;
9531 let editor_bg_color = cx.theme().colors().editor_background;
9532 editor_bg_color.blend(accent_color.opacity(0.1))
9533 }
9534
9535 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9536 let accent_color = cx.theme().colors().text_accent;
9537 let editor_bg_color = cx.theme().colors().editor_background;
9538 editor_bg_color.blend(accent_color.opacity(0.6))
9539 }
9540 fn get_prediction_provider_icon_name(
9541 provider: &Option<RegisteredEditPredictionProvider>,
9542 ) -> IconName {
9543 match provider {
9544 Some(provider) => match provider.provider.name() {
9545 "copilot" => IconName::Copilot,
9546 "supermaven" => IconName::Supermaven,
9547 _ => IconName::ZedPredict,
9548 },
9549 None => IconName::ZedPredict,
9550 }
9551 }
9552
9553 fn render_edit_prediction_cursor_popover(
9554 &self,
9555 min_width: Pixels,
9556 max_width: Pixels,
9557 cursor_point: Point,
9558 style: &EditorStyle,
9559 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9560 _window: &Window,
9561 cx: &mut Context<Editor>,
9562 ) -> Option<AnyElement> {
9563 let provider = self.edit_prediction_provider.as_ref()?;
9564 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9565
9566 let is_refreshing = provider.provider.is_refreshing(cx);
9567
9568 fn pending_completion_container(icon: IconName) -> Div {
9569 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9570 }
9571
9572 let completion = match &self.active_edit_prediction {
9573 Some(prediction) => {
9574 if !self.has_visible_completions_menu() {
9575 const RADIUS: Pixels = px(6.);
9576 const BORDER_WIDTH: Pixels = px(1.);
9577
9578 return Some(
9579 h_flex()
9580 .elevation_2(cx)
9581 .border(BORDER_WIDTH)
9582 .border_color(cx.theme().colors().border)
9583 .when(accept_keystroke.is_none(), |el| {
9584 el.border_color(cx.theme().status().error)
9585 })
9586 .rounded(RADIUS)
9587 .rounded_tl(px(0.))
9588 .overflow_hidden()
9589 .child(div().px_1p5().child(match &prediction.completion {
9590 EditPrediction::MoveWithin { target, snapshot } => {
9591 use text::ToPoint as _;
9592 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9593 {
9594 Icon::new(IconName::ZedPredictDown)
9595 } else {
9596 Icon::new(IconName::ZedPredictUp)
9597 }
9598 }
9599 EditPrediction::MoveOutside { .. } => {
9600 // TODO [zeta2] custom icon for external jump?
9601 Icon::new(provider_icon)
9602 }
9603 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9604 }))
9605 .child(
9606 h_flex()
9607 .gap_1()
9608 .py_1()
9609 .px_2()
9610 .rounded_r(RADIUS - BORDER_WIDTH)
9611 .border_l_1()
9612 .border_color(cx.theme().colors().border)
9613 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9614 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9615 el.child(
9616 Label::new("Hold")
9617 .size(LabelSize::Small)
9618 .when(accept_keystroke.is_none(), |el| {
9619 el.strikethrough()
9620 })
9621 .line_height_style(LineHeightStyle::UiLabel),
9622 )
9623 })
9624 .id("edit_prediction_cursor_popover_keybind")
9625 .when(accept_keystroke.is_none(), |el| {
9626 let status_colors = cx.theme().status();
9627
9628 el.bg(status_colors.error_background)
9629 .border_color(status_colors.error.opacity(0.6))
9630 .child(Icon::new(IconName::Info).color(Color::Error))
9631 .cursor_default()
9632 .hoverable_tooltip(move |_window, cx| {
9633 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9634 .into()
9635 })
9636 })
9637 .when_some(
9638 accept_keystroke.as_ref(),
9639 |el, accept_keystroke| {
9640 el.child(h_flex().children(ui::render_modifiers(
9641 accept_keystroke.modifiers(),
9642 PlatformStyle::platform(),
9643 Some(Color::Default),
9644 Some(IconSize::XSmall.rems().into()),
9645 false,
9646 )))
9647 },
9648 ),
9649 )
9650 .into_any(),
9651 );
9652 }
9653
9654 self.render_edit_prediction_cursor_popover_preview(
9655 prediction,
9656 cursor_point,
9657 style,
9658 cx,
9659 )?
9660 }
9661
9662 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9663 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9664 stale_completion,
9665 cursor_point,
9666 style,
9667 cx,
9668 )?,
9669
9670 None => pending_completion_container(provider_icon)
9671 .child(Label::new("...").size(LabelSize::Small)),
9672 },
9673
9674 None => pending_completion_container(provider_icon)
9675 .child(Label::new("...").size(LabelSize::Small)),
9676 };
9677
9678 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9679 completion
9680 .with_animation(
9681 "loading-completion",
9682 Animation::new(Duration::from_secs(2))
9683 .repeat()
9684 .with_easing(pulsating_between(0.4, 0.8)),
9685 |label, delta| label.opacity(delta),
9686 )
9687 .into_any_element()
9688 } else {
9689 completion.into_any_element()
9690 };
9691
9692 let has_completion = self.active_edit_prediction.is_some();
9693
9694 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9695 Some(
9696 h_flex()
9697 .min_w(min_width)
9698 .max_w(max_width)
9699 .flex_1()
9700 .elevation_2(cx)
9701 .border_color(cx.theme().colors().border)
9702 .child(
9703 div()
9704 .flex_1()
9705 .py_1()
9706 .px_2()
9707 .overflow_hidden()
9708 .child(completion),
9709 )
9710 .when_some(accept_keystroke, |el, accept_keystroke| {
9711 if !accept_keystroke.modifiers().modified() {
9712 return el;
9713 }
9714
9715 el.child(
9716 h_flex()
9717 .h_full()
9718 .border_l_1()
9719 .rounded_r_lg()
9720 .border_color(cx.theme().colors().border)
9721 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9722 .gap_1()
9723 .py_1()
9724 .px_2()
9725 .child(
9726 h_flex()
9727 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9728 .when(is_platform_style_mac, |parent| parent.gap_1())
9729 .child(h_flex().children(ui::render_modifiers(
9730 accept_keystroke.modifiers(),
9731 PlatformStyle::platform(),
9732 Some(if !has_completion {
9733 Color::Muted
9734 } else {
9735 Color::Default
9736 }),
9737 None,
9738 false,
9739 ))),
9740 )
9741 .child(Label::new("Preview").into_any_element())
9742 .opacity(if has_completion { 1.0 } else { 0.4 }),
9743 )
9744 })
9745 .into_any(),
9746 )
9747 }
9748
9749 fn render_edit_prediction_cursor_popover_preview(
9750 &self,
9751 completion: &EditPredictionState,
9752 cursor_point: Point,
9753 style: &EditorStyle,
9754 cx: &mut Context<Editor>,
9755 ) -> Option<Div> {
9756 use text::ToPoint as _;
9757
9758 fn render_relative_row_jump(
9759 prefix: impl Into<String>,
9760 current_row: u32,
9761 target_row: u32,
9762 ) -> Div {
9763 let (row_diff, arrow) = if target_row < current_row {
9764 (current_row - target_row, IconName::ArrowUp)
9765 } else {
9766 (target_row - current_row, IconName::ArrowDown)
9767 };
9768
9769 h_flex()
9770 .child(
9771 Label::new(format!("{}{}", prefix.into(), row_diff))
9772 .color(Color::Muted)
9773 .size(LabelSize::Small),
9774 )
9775 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9776 }
9777
9778 let supports_jump = self
9779 .edit_prediction_provider
9780 .as_ref()
9781 .map(|provider| provider.provider.supports_jump_to_edit())
9782 .unwrap_or(true);
9783
9784 match &completion.completion {
9785 EditPrediction::MoveWithin {
9786 target, snapshot, ..
9787 } => {
9788 if !supports_jump {
9789 return None;
9790 }
9791
9792 Some(
9793 h_flex()
9794 .px_2()
9795 .gap_2()
9796 .flex_1()
9797 .child(
9798 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9799 Icon::new(IconName::ZedPredictDown)
9800 } else {
9801 Icon::new(IconName::ZedPredictUp)
9802 },
9803 )
9804 .child(Label::new("Jump to Edit")),
9805 )
9806 }
9807 EditPrediction::MoveOutside { snapshot, .. } => {
9808 let file_name = snapshot
9809 .file()
9810 .map(|file| file.file_name(cx))
9811 .unwrap_or("untitled");
9812 Some(
9813 h_flex()
9814 .px_2()
9815 .gap_2()
9816 .flex_1()
9817 .child(Icon::new(IconName::ZedPredict))
9818 .child(Label::new(format!("Jump to {file_name}"))),
9819 )
9820 }
9821 EditPrediction::Edit {
9822 edits,
9823 edit_preview,
9824 snapshot,
9825 display_mode: _,
9826 } => {
9827 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9828
9829 let (highlighted_edits, has_more_lines) =
9830 if let Some(edit_preview) = edit_preview.as_ref() {
9831 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9832 .first_line_preview()
9833 } else {
9834 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9835 };
9836
9837 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9838 .with_default_highlights(&style.text, highlighted_edits.highlights);
9839
9840 let preview = h_flex()
9841 .gap_1()
9842 .min_w_16()
9843 .child(styled_text)
9844 .when(has_more_lines, |parent| parent.child("…"));
9845
9846 let left = if supports_jump && first_edit_row != cursor_point.row {
9847 render_relative_row_jump("", cursor_point.row, first_edit_row)
9848 .into_any_element()
9849 } else {
9850 let icon_name =
9851 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9852 Icon::new(icon_name).into_any_element()
9853 };
9854
9855 Some(
9856 h_flex()
9857 .h_full()
9858 .flex_1()
9859 .gap_2()
9860 .pr_1()
9861 .overflow_x_hidden()
9862 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9863 .child(left)
9864 .child(preview),
9865 )
9866 }
9867 }
9868 }
9869
9870 pub fn render_context_menu(
9871 &self,
9872 style: &EditorStyle,
9873 max_height_in_lines: u32,
9874 window: &mut Window,
9875 cx: &mut Context<Editor>,
9876 ) -> Option<AnyElement> {
9877 let menu = self.context_menu.borrow();
9878 let menu = menu.as_ref()?;
9879 if !menu.visible() {
9880 return None;
9881 };
9882 Some(menu.render(style, max_height_in_lines, window, cx))
9883 }
9884
9885 fn render_context_menu_aside(
9886 &mut self,
9887 max_size: Size<Pixels>,
9888 window: &mut Window,
9889 cx: &mut Context<Editor>,
9890 ) -> Option<AnyElement> {
9891 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9892 if menu.visible() {
9893 menu.render_aside(max_size, window, cx)
9894 } else {
9895 None
9896 }
9897 })
9898 }
9899
9900 fn hide_context_menu(
9901 &mut self,
9902 window: &mut Window,
9903 cx: &mut Context<Self>,
9904 ) -> Option<CodeContextMenu> {
9905 cx.notify();
9906 self.completion_tasks.clear();
9907 let context_menu = self.context_menu.borrow_mut().take();
9908 self.stale_edit_prediction_in_menu.take();
9909 self.update_visible_edit_prediction(window, cx);
9910 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9911 && let Some(completion_provider) = &self.completion_provider
9912 {
9913 completion_provider.selection_changed(None, window, cx);
9914 }
9915 context_menu
9916 }
9917
9918 fn show_snippet_choices(
9919 &mut self,
9920 choices: &Vec<String>,
9921 selection: Range<Anchor>,
9922 cx: &mut Context<Self>,
9923 ) {
9924 let Some((_, buffer, _)) = self
9925 .buffer()
9926 .read(cx)
9927 .excerpt_containing(selection.start, cx)
9928 else {
9929 return;
9930 };
9931 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9932 else {
9933 return;
9934 };
9935 if buffer != end_buffer {
9936 log::error!("expected anchor range to have matching buffer IDs");
9937 return;
9938 }
9939
9940 let id = post_inc(&mut self.next_completion_id);
9941 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9942 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9943 CompletionsMenu::new_snippet_choices(
9944 id,
9945 true,
9946 choices,
9947 selection,
9948 buffer,
9949 snippet_sort_order,
9950 ),
9951 ));
9952 }
9953
9954 pub fn insert_snippet(
9955 &mut self,
9956 insertion_ranges: &[Range<MultiBufferOffset>],
9957 snippet: Snippet,
9958 window: &mut Window,
9959 cx: &mut Context<Self>,
9960 ) -> Result<()> {
9961 struct Tabstop<T> {
9962 is_end_tabstop: bool,
9963 ranges: Vec<Range<T>>,
9964 choices: Option<Vec<String>>,
9965 }
9966
9967 let tabstops = self.buffer.update(cx, |buffer, cx| {
9968 let snippet_text: Arc<str> = snippet.text.clone().into();
9969 let edits = insertion_ranges
9970 .iter()
9971 .cloned()
9972 .map(|range| (range, snippet_text.clone()));
9973 let autoindent_mode = AutoindentMode::Block {
9974 original_indent_columns: Vec::new(),
9975 };
9976 buffer.edit(edits, Some(autoindent_mode), cx);
9977
9978 let snapshot = &*buffer.read(cx);
9979 let snippet = &snippet;
9980 snippet
9981 .tabstops
9982 .iter()
9983 .map(|tabstop| {
9984 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9985 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9986 });
9987 let mut tabstop_ranges = tabstop
9988 .ranges
9989 .iter()
9990 .flat_map(|tabstop_range| {
9991 let mut delta = 0_isize;
9992 insertion_ranges.iter().map(move |insertion_range| {
9993 let insertion_start = insertion_range.start + delta;
9994 delta += snippet.text.len() as isize
9995 - (insertion_range.end - insertion_range.start) as isize;
9996
9997 let start =
9998 (insertion_start + tabstop_range.start).min(snapshot.len());
9999 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10000 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10001 })
10002 })
10003 .collect::<Vec<_>>();
10004 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10005
10006 Tabstop {
10007 is_end_tabstop,
10008 ranges: tabstop_ranges,
10009 choices: tabstop.choices.clone(),
10010 }
10011 })
10012 .collect::<Vec<_>>()
10013 });
10014 if let Some(tabstop) = tabstops.first() {
10015 self.change_selections(Default::default(), window, cx, |s| {
10016 // Reverse order so that the first range is the newest created selection.
10017 // Completions will use it and autoscroll will prioritize it.
10018 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10019 });
10020
10021 if let Some(choices) = &tabstop.choices
10022 && let Some(selection) = tabstop.ranges.first()
10023 {
10024 self.show_snippet_choices(choices, selection.clone(), cx)
10025 }
10026
10027 // If we're already at the last tabstop and it's at the end of the snippet,
10028 // we're done, we don't need to keep the state around.
10029 if !tabstop.is_end_tabstop {
10030 let choices = tabstops
10031 .iter()
10032 .map(|tabstop| tabstop.choices.clone())
10033 .collect();
10034
10035 let ranges = tabstops
10036 .into_iter()
10037 .map(|tabstop| tabstop.ranges)
10038 .collect::<Vec<_>>();
10039
10040 self.snippet_stack.push(SnippetState {
10041 active_index: 0,
10042 ranges,
10043 choices,
10044 });
10045 }
10046
10047 // Check whether the just-entered snippet ends with an auto-closable bracket.
10048 if self.autoclose_regions.is_empty() {
10049 let snapshot = self.buffer.read(cx).snapshot(cx);
10050 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10051 let selection_head = selection.head();
10052 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10053 continue;
10054 };
10055
10056 let mut bracket_pair = None;
10057 let max_lookup_length = scope
10058 .brackets()
10059 .map(|(pair, _)| {
10060 pair.start
10061 .as_str()
10062 .chars()
10063 .count()
10064 .max(pair.end.as_str().chars().count())
10065 })
10066 .max();
10067 if let Some(max_lookup_length) = max_lookup_length {
10068 let next_text = snapshot
10069 .chars_at(selection_head)
10070 .take(max_lookup_length)
10071 .collect::<String>();
10072 let prev_text = snapshot
10073 .reversed_chars_at(selection_head)
10074 .take(max_lookup_length)
10075 .collect::<String>();
10076
10077 for (pair, enabled) in scope.brackets() {
10078 if enabled
10079 && pair.close
10080 && prev_text.starts_with(pair.start.as_str())
10081 && next_text.starts_with(pair.end.as_str())
10082 {
10083 bracket_pair = Some(pair.clone());
10084 break;
10085 }
10086 }
10087 }
10088
10089 if let Some(pair) = bracket_pair {
10090 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10091 let autoclose_enabled =
10092 self.use_autoclose && snapshot_settings.use_autoclose;
10093 if autoclose_enabled {
10094 let start = snapshot.anchor_after(selection_head);
10095 let end = snapshot.anchor_after(selection_head);
10096 self.autoclose_regions.push(AutocloseRegion {
10097 selection_id: selection.id,
10098 range: start..end,
10099 pair,
10100 });
10101 }
10102 }
10103 }
10104 }
10105 }
10106 Ok(())
10107 }
10108
10109 pub fn move_to_next_snippet_tabstop(
10110 &mut self,
10111 window: &mut Window,
10112 cx: &mut Context<Self>,
10113 ) -> bool {
10114 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10115 }
10116
10117 pub fn move_to_prev_snippet_tabstop(
10118 &mut self,
10119 window: &mut Window,
10120 cx: &mut Context<Self>,
10121 ) -> bool {
10122 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10123 }
10124
10125 pub fn move_to_snippet_tabstop(
10126 &mut self,
10127 bias: Bias,
10128 window: &mut Window,
10129 cx: &mut Context<Self>,
10130 ) -> bool {
10131 if let Some(mut snippet) = self.snippet_stack.pop() {
10132 match bias {
10133 Bias::Left => {
10134 if snippet.active_index > 0 {
10135 snippet.active_index -= 1;
10136 } else {
10137 self.snippet_stack.push(snippet);
10138 return false;
10139 }
10140 }
10141 Bias::Right => {
10142 if snippet.active_index + 1 < snippet.ranges.len() {
10143 snippet.active_index += 1;
10144 } else {
10145 self.snippet_stack.push(snippet);
10146 return false;
10147 }
10148 }
10149 }
10150 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10151 self.change_selections(Default::default(), window, cx, |s| {
10152 // Reverse order so that the first range is the newest created selection.
10153 // Completions will use it and autoscroll will prioritize it.
10154 s.select_ranges(current_ranges.iter().rev().cloned())
10155 });
10156
10157 if let Some(choices) = &snippet.choices[snippet.active_index]
10158 && let Some(selection) = current_ranges.first()
10159 {
10160 self.show_snippet_choices(choices, selection.clone(), cx);
10161 }
10162
10163 // If snippet state is not at the last tabstop, push it back on the stack
10164 if snippet.active_index + 1 < snippet.ranges.len() {
10165 self.snippet_stack.push(snippet);
10166 }
10167 return true;
10168 }
10169 }
10170
10171 false
10172 }
10173
10174 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10175 self.transact(window, cx, |this, window, cx| {
10176 this.select_all(&SelectAll, window, cx);
10177 this.insert("", window, cx);
10178 });
10179 }
10180
10181 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10182 if self.read_only(cx) {
10183 return;
10184 }
10185 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10186 self.transact(window, cx, |this, window, cx| {
10187 this.select_autoclose_pair(window, cx);
10188
10189 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10190
10191 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10192 if !this.linked_edit_ranges.is_empty() {
10193 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10194 let snapshot = this.buffer.read(cx).snapshot(cx);
10195
10196 for selection in selections.iter() {
10197 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10198 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10199 if selection_start.buffer_id != selection_end.buffer_id {
10200 continue;
10201 }
10202 if let Some(ranges) =
10203 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10204 {
10205 for (buffer, entries) in ranges {
10206 linked_ranges.entry(buffer).or_default().extend(entries);
10207 }
10208 }
10209 }
10210 }
10211
10212 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10213 for selection in &mut selections {
10214 if selection.is_empty() {
10215 let old_head = selection.head();
10216 let mut new_head =
10217 movement::left(&display_map, old_head.to_display_point(&display_map))
10218 .to_point(&display_map);
10219 if let Some((buffer, line_buffer_range)) = display_map
10220 .buffer_snapshot()
10221 .buffer_line_for_row(MultiBufferRow(old_head.row))
10222 {
10223 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10224 let indent_len = match indent_size.kind {
10225 IndentKind::Space => {
10226 buffer.settings_at(line_buffer_range.start, cx).tab_size
10227 }
10228 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10229 };
10230 if old_head.column <= indent_size.len && old_head.column > 0 {
10231 let indent_len = indent_len.get();
10232 new_head = cmp::min(
10233 new_head,
10234 MultiBufferPoint::new(
10235 old_head.row,
10236 ((old_head.column - 1) / indent_len) * indent_len,
10237 ),
10238 );
10239 }
10240 }
10241
10242 selection.set_head(new_head, SelectionGoal::None);
10243 }
10244 }
10245
10246 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10247 this.insert("", window, cx);
10248 let empty_str: Arc<str> = Arc::from("");
10249 for (buffer, edits) in linked_ranges {
10250 let snapshot = buffer.read(cx).snapshot();
10251 use text::ToPoint as TP;
10252
10253 let edits = edits
10254 .into_iter()
10255 .map(|range| {
10256 let end_point = TP::to_point(&range.end, &snapshot);
10257 let mut start_point = TP::to_point(&range.start, &snapshot);
10258
10259 if end_point == start_point {
10260 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10261 .saturating_sub(1);
10262 start_point =
10263 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10264 };
10265
10266 (start_point..end_point, empty_str.clone())
10267 })
10268 .sorted_by_key(|(range, _)| range.start)
10269 .collect::<Vec<_>>();
10270 buffer.update(cx, |this, cx| {
10271 this.edit(edits, None, cx);
10272 })
10273 }
10274 this.refresh_edit_prediction(true, false, window, cx);
10275 refresh_linked_ranges(this, window, cx);
10276 });
10277 }
10278
10279 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10280 if self.read_only(cx) {
10281 return;
10282 }
10283 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10284 self.transact(window, cx, |this, window, cx| {
10285 this.change_selections(Default::default(), window, cx, |s| {
10286 s.move_with(|map, selection| {
10287 if selection.is_empty() {
10288 let cursor = movement::right(map, selection.head());
10289 selection.end = cursor;
10290 selection.reversed = true;
10291 selection.goal = SelectionGoal::None;
10292 }
10293 })
10294 });
10295 this.insert("", window, cx);
10296 this.refresh_edit_prediction(true, false, window, cx);
10297 });
10298 }
10299
10300 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10301 if self.mode.is_single_line() {
10302 cx.propagate();
10303 return;
10304 }
10305
10306 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10307 if self.move_to_prev_snippet_tabstop(window, cx) {
10308 return;
10309 }
10310 self.outdent(&Outdent, window, cx);
10311 }
10312
10313 pub fn next_snippet_tabstop(
10314 &mut self,
10315 _: &NextSnippetTabstop,
10316 window: &mut Window,
10317 cx: &mut Context<Self>,
10318 ) {
10319 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10320 cx.propagate();
10321 return;
10322 }
10323
10324 if self.move_to_next_snippet_tabstop(window, cx) {
10325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10326 return;
10327 }
10328 cx.propagate();
10329 }
10330
10331 pub fn previous_snippet_tabstop(
10332 &mut self,
10333 _: &PreviousSnippetTabstop,
10334 window: &mut Window,
10335 cx: &mut Context<Self>,
10336 ) {
10337 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10338 cx.propagate();
10339 return;
10340 }
10341
10342 if self.move_to_prev_snippet_tabstop(window, cx) {
10343 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10344 return;
10345 }
10346 cx.propagate();
10347 }
10348
10349 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10350 if self.mode.is_single_line() {
10351 cx.propagate();
10352 return;
10353 }
10354
10355 if self.move_to_next_snippet_tabstop(window, cx) {
10356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10357 return;
10358 }
10359 if self.read_only(cx) {
10360 return;
10361 }
10362 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10363 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10364 let buffer = self.buffer.read(cx);
10365 let snapshot = buffer.snapshot(cx);
10366 let rows_iter = selections.iter().map(|s| s.head().row);
10367 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10368
10369 let has_some_cursor_in_whitespace = selections
10370 .iter()
10371 .filter(|selection| selection.is_empty())
10372 .any(|selection| {
10373 let cursor = selection.head();
10374 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10375 cursor.column < current_indent.len
10376 });
10377
10378 let mut edits = Vec::new();
10379 let mut prev_edited_row = 0;
10380 let mut row_delta = 0;
10381 for selection in &mut selections {
10382 if selection.start.row != prev_edited_row {
10383 row_delta = 0;
10384 }
10385 prev_edited_row = selection.end.row;
10386
10387 // If the selection is non-empty, then increase the indentation of the selected lines.
10388 if !selection.is_empty() {
10389 row_delta =
10390 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10391 continue;
10392 }
10393
10394 let cursor = selection.head();
10395 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10396 if let Some(suggested_indent) =
10397 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10398 {
10399 // Don't do anything if already at suggested indent
10400 // and there is any other cursor which is not
10401 if has_some_cursor_in_whitespace
10402 && cursor.column == current_indent.len
10403 && current_indent.len == suggested_indent.len
10404 {
10405 continue;
10406 }
10407
10408 // Adjust line and move cursor to suggested indent
10409 // if cursor is not at suggested indent
10410 if cursor.column < suggested_indent.len
10411 && cursor.column <= current_indent.len
10412 && current_indent.len <= suggested_indent.len
10413 {
10414 selection.start = Point::new(cursor.row, suggested_indent.len);
10415 selection.end = selection.start;
10416 if row_delta == 0 {
10417 edits.extend(Buffer::edit_for_indent_size_adjustment(
10418 cursor.row,
10419 current_indent,
10420 suggested_indent,
10421 ));
10422 row_delta = suggested_indent.len - current_indent.len;
10423 }
10424 continue;
10425 }
10426
10427 // If current indent is more than suggested indent
10428 // only move cursor to current indent and skip indent
10429 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10430 selection.start = Point::new(cursor.row, current_indent.len);
10431 selection.end = selection.start;
10432 continue;
10433 }
10434 }
10435
10436 // Otherwise, insert a hard or soft tab.
10437 let settings = buffer.language_settings_at(cursor, cx);
10438 let tab_size = if settings.hard_tabs {
10439 IndentSize::tab()
10440 } else {
10441 let tab_size = settings.tab_size.get();
10442 let indent_remainder = snapshot
10443 .text_for_range(Point::new(cursor.row, 0)..cursor)
10444 .flat_map(str::chars)
10445 .fold(row_delta % tab_size, |counter: u32, c| {
10446 if c == '\t' {
10447 0
10448 } else {
10449 (counter + 1) % tab_size
10450 }
10451 });
10452
10453 let chars_to_next_tab_stop = tab_size - indent_remainder;
10454 IndentSize::spaces(chars_to_next_tab_stop)
10455 };
10456 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10457 selection.end = selection.start;
10458 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10459 row_delta += tab_size.len;
10460 }
10461
10462 self.transact(window, cx, |this, window, cx| {
10463 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10464 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10465 this.refresh_edit_prediction(true, false, window, cx);
10466 });
10467 }
10468
10469 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10470 if self.read_only(cx) {
10471 return;
10472 }
10473 if self.mode.is_single_line() {
10474 cx.propagate();
10475 return;
10476 }
10477
10478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10479 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10480 let mut prev_edited_row = 0;
10481 let mut row_delta = 0;
10482 let mut edits = Vec::new();
10483 let buffer = self.buffer.read(cx);
10484 let snapshot = buffer.snapshot(cx);
10485 for selection in &mut selections {
10486 if selection.start.row != prev_edited_row {
10487 row_delta = 0;
10488 }
10489 prev_edited_row = selection.end.row;
10490
10491 row_delta =
10492 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10493 }
10494
10495 self.transact(window, cx, |this, window, cx| {
10496 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10497 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10498 });
10499 }
10500
10501 fn indent_selection(
10502 buffer: &MultiBuffer,
10503 snapshot: &MultiBufferSnapshot,
10504 selection: &mut Selection<Point>,
10505 edits: &mut Vec<(Range<Point>, String)>,
10506 delta_for_start_row: u32,
10507 cx: &App,
10508 ) -> u32 {
10509 let settings = buffer.language_settings_at(selection.start, cx);
10510 let tab_size = settings.tab_size.get();
10511 let indent_kind = if settings.hard_tabs {
10512 IndentKind::Tab
10513 } else {
10514 IndentKind::Space
10515 };
10516 let mut start_row = selection.start.row;
10517 let mut end_row = selection.end.row + 1;
10518
10519 // If a selection ends at the beginning of a line, don't indent
10520 // that last line.
10521 if selection.end.column == 0 && selection.end.row > selection.start.row {
10522 end_row -= 1;
10523 }
10524
10525 // Avoid re-indenting a row that has already been indented by a
10526 // previous selection, but still update this selection's column
10527 // to reflect that indentation.
10528 if delta_for_start_row > 0 {
10529 start_row += 1;
10530 selection.start.column += delta_for_start_row;
10531 if selection.end.row == selection.start.row {
10532 selection.end.column += delta_for_start_row;
10533 }
10534 }
10535
10536 let mut delta_for_end_row = 0;
10537 let has_multiple_rows = start_row + 1 != end_row;
10538 for row in start_row..end_row {
10539 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10540 let indent_delta = match (current_indent.kind, indent_kind) {
10541 (IndentKind::Space, IndentKind::Space) => {
10542 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10543 IndentSize::spaces(columns_to_next_tab_stop)
10544 }
10545 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10546 (_, IndentKind::Tab) => IndentSize::tab(),
10547 };
10548
10549 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10550 0
10551 } else {
10552 selection.start.column
10553 };
10554 let row_start = Point::new(row, start);
10555 edits.push((
10556 row_start..row_start,
10557 indent_delta.chars().collect::<String>(),
10558 ));
10559
10560 // Update this selection's endpoints to reflect the indentation.
10561 if row == selection.start.row {
10562 selection.start.column += indent_delta.len;
10563 }
10564 if row == selection.end.row {
10565 selection.end.column += indent_delta.len;
10566 delta_for_end_row = indent_delta.len;
10567 }
10568 }
10569
10570 if selection.start.row == selection.end.row {
10571 delta_for_start_row + delta_for_end_row
10572 } else {
10573 delta_for_end_row
10574 }
10575 }
10576
10577 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10578 if self.read_only(cx) {
10579 return;
10580 }
10581 if self.mode.is_single_line() {
10582 cx.propagate();
10583 return;
10584 }
10585
10586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10588 let selections = self.selections.all::<Point>(&display_map);
10589 let mut deletion_ranges = Vec::new();
10590 let mut last_outdent = None;
10591 {
10592 let buffer = self.buffer.read(cx);
10593 let snapshot = buffer.snapshot(cx);
10594 for selection in &selections {
10595 let settings = buffer.language_settings_at(selection.start, cx);
10596 let tab_size = settings.tab_size.get();
10597 let mut rows = selection.spanned_rows(false, &display_map);
10598
10599 // Avoid re-outdenting a row that has already been outdented by a
10600 // previous selection.
10601 if let Some(last_row) = last_outdent
10602 && last_row == rows.start
10603 {
10604 rows.start = rows.start.next_row();
10605 }
10606 let has_multiple_rows = rows.len() > 1;
10607 for row in rows.iter_rows() {
10608 let indent_size = snapshot.indent_size_for_line(row);
10609 if indent_size.len > 0 {
10610 let deletion_len = match indent_size.kind {
10611 IndentKind::Space => {
10612 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10613 if columns_to_prev_tab_stop == 0 {
10614 tab_size
10615 } else {
10616 columns_to_prev_tab_stop
10617 }
10618 }
10619 IndentKind::Tab => 1,
10620 };
10621 let start = if has_multiple_rows
10622 || deletion_len > selection.start.column
10623 || indent_size.len < selection.start.column
10624 {
10625 0
10626 } else {
10627 selection.start.column - deletion_len
10628 };
10629 deletion_ranges.push(
10630 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10631 );
10632 last_outdent = Some(row);
10633 }
10634 }
10635 }
10636 }
10637
10638 self.transact(window, cx, |this, window, cx| {
10639 this.buffer.update(cx, |buffer, cx| {
10640 let empty_str: Arc<str> = Arc::default();
10641 buffer.edit(
10642 deletion_ranges
10643 .into_iter()
10644 .map(|range| (range, empty_str.clone())),
10645 None,
10646 cx,
10647 );
10648 });
10649 let selections = this
10650 .selections
10651 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10652 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10653 });
10654 }
10655
10656 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10657 if self.read_only(cx) {
10658 return;
10659 }
10660 if self.mode.is_single_line() {
10661 cx.propagate();
10662 return;
10663 }
10664
10665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10666 let selections = self
10667 .selections
10668 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10669 .into_iter()
10670 .map(|s| s.range());
10671
10672 self.transact(window, cx, |this, window, cx| {
10673 this.buffer.update(cx, |buffer, cx| {
10674 buffer.autoindent_ranges(selections, cx);
10675 });
10676 let selections = this
10677 .selections
10678 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10679 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10680 });
10681 }
10682
10683 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10684 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10686 let selections = self.selections.all::<Point>(&display_map);
10687
10688 let mut new_cursors = Vec::new();
10689 let mut edit_ranges = Vec::new();
10690 let mut selections = selections.iter().peekable();
10691 while let Some(selection) = selections.next() {
10692 let mut rows = selection.spanned_rows(false, &display_map);
10693
10694 // Accumulate contiguous regions of rows that we want to delete.
10695 while let Some(next_selection) = selections.peek() {
10696 let next_rows = next_selection.spanned_rows(false, &display_map);
10697 if next_rows.start <= rows.end {
10698 rows.end = next_rows.end;
10699 selections.next().unwrap();
10700 } else {
10701 break;
10702 }
10703 }
10704
10705 let buffer = display_map.buffer_snapshot();
10706 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10707 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10708 // If there's a line after the range, delete the \n from the end of the row range
10709 (
10710 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10711 rows.end,
10712 )
10713 } else {
10714 // If there isn't a line after the range, delete the \n from the line before the
10715 // start of the row range
10716 edit_start = edit_start.saturating_sub_usize(1);
10717 (buffer.len(), rows.start.previous_row())
10718 };
10719
10720 let text_layout_details = self.text_layout_details(window);
10721 let x = display_map.x_for_display_point(
10722 selection.head().to_display_point(&display_map),
10723 &text_layout_details,
10724 );
10725 let row = Point::new(target_row.0, 0)
10726 .to_display_point(&display_map)
10727 .row();
10728 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10729
10730 new_cursors.push((
10731 selection.id,
10732 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10733 SelectionGoal::None,
10734 ));
10735 edit_ranges.push(edit_start..edit_end);
10736 }
10737
10738 self.transact(window, cx, |this, window, cx| {
10739 let buffer = this.buffer.update(cx, |buffer, cx| {
10740 let empty_str: Arc<str> = Arc::default();
10741 buffer.edit(
10742 edit_ranges
10743 .into_iter()
10744 .map(|range| (range, empty_str.clone())),
10745 None,
10746 cx,
10747 );
10748 buffer.snapshot(cx)
10749 });
10750 let new_selections = new_cursors
10751 .into_iter()
10752 .map(|(id, cursor, goal)| {
10753 let cursor = cursor.to_point(&buffer);
10754 Selection {
10755 id,
10756 start: cursor,
10757 end: cursor,
10758 reversed: false,
10759 goal,
10760 }
10761 })
10762 .collect();
10763
10764 this.change_selections(Default::default(), window, cx, |s| {
10765 s.select(new_selections);
10766 });
10767 });
10768 }
10769
10770 pub fn join_lines_impl(
10771 &mut self,
10772 insert_whitespace: bool,
10773 window: &mut Window,
10774 cx: &mut Context<Self>,
10775 ) {
10776 if self.read_only(cx) {
10777 return;
10778 }
10779 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10780 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10781 let start = MultiBufferRow(selection.start.row);
10782 // Treat single line selections as if they include the next line. Otherwise this action
10783 // would do nothing for single line selections individual cursors.
10784 let end = if selection.start.row == selection.end.row {
10785 MultiBufferRow(selection.start.row + 1)
10786 } else {
10787 MultiBufferRow(selection.end.row)
10788 };
10789
10790 if let Some(last_row_range) = row_ranges.last_mut()
10791 && start <= last_row_range.end
10792 {
10793 last_row_range.end = end;
10794 continue;
10795 }
10796 row_ranges.push(start..end);
10797 }
10798
10799 let snapshot = self.buffer.read(cx).snapshot(cx);
10800 let mut cursor_positions = Vec::new();
10801 for row_range in &row_ranges {
10802 let anchor = snapshot.anchor_before(Point::new(
10803 row_range.end.previous_row().0,
10804 snapshot.line_len(row_range.end.previous_row()),
10805 ));
10806 cursor_positions.push(anchor..anchor);
10807 }
10808
10809 self.transact(window, cx, |this, window, cx| {
10810 for row_range in row_ranges.into_iter().rev() {
10811 for row in row_range.iter_rows().rev() {
10812 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10813 let next_line_row = row.next_row();
10814 let indent = snapshot.indent_size_for_line(next_line_row);
10815 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10816
10817 let replace =
10818 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10819 " "
10820 } else {
10821 ""
10822 };
10823
10824 this.buffer.update(cx, |buffer, cx| {
10825 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10826 });
10827 }
10828 }
10829
10830 this.change_selections(Default::default(), window, cx, |s| {
10831 s.select_anchor_ranges(cursor_positions)
10832 });
10833 });
10834 }
10835
10836 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10838 self.join_lines_impl(true, window, cx);
10839 }
10840
10841 pub fn sort_lines_case_sensitive(
10842 &mut self,
10843 _: &SortLinesCaseSensitive,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) {
10847 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10848 }
10849
10850 pub fn sort_lines_by_length(
10851 &mut self,
10852 _: &SortLinesByLength,
10853 window: &mut Window,
10854 cx: &mut Context<Self>,
10855 ) {
10856 self.manipulate_immutable_lines(window, cx, |lines| {
10857 lines.sort_by_key(|&line| line.chars().count())
10858 })
10859 }
10860
10861 pub fn sort_lines_case_insensitive(
10862 &mut self,
10863 _: &SortLinesCaseInsensitive,
10864 window: &mut Window,
10865 cx: &mut Context<Self>,
10866 ) {
10867 self.manipulate_immutable_lines(window, cx, |lines| {
10868 lines.sort_by_key(|line| line.to_lowercase())
10869 })
10870 }
10871
10872 pub fn unique_lines_case_insensitive(
10873 &mut self,
10874 _: &UniqueLinesCaseInsensitive,
10875 window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 self.manipulate_immutable_lines(window, cx, |lines| {
10879 let mut seen = HashSet::default();
10880 lines.retain(|line| seen.insert(line.to_lowercase()));
10881 })
10882 }
10883
10884 pub fn unique_lines_case_sensitive(
10885 &mut self,
10886 _: &UniqueLinesCaseSensitive,
10887 window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 self.manipulate_immutable_lines(window, cx, |lines| {
10891 let mut seen = HashSet::default();
10892 lines.retain(|line| seen.insert(*line));
10893 })
10894 }
10895
10896 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10897 let snapshot = self.buffer.read(cx).snapshot(cx);
10898 for selection in self.selections.disjoint_anchors_arc().iter() {
10899 if snapshot
10900 .language_at(selection.start)
10901 .and_then(|lang| lang.config().wrap_characters.as_ref())
10902 .is_some()
10903 {
10904 return true;
10905 }
10906 }
10907 false
10908 }
10909
10910 fn wrap_selections_in_tag(
10911 &mut self,
10912 _: &WrapSelectionsInTag,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10917
10918 let snapshot = self.buffer.read(cx).snapshot(cx);
10919
10920 let mut edits = Vec::new();
10921 let mut boundaries = Vec::new();
10922
10923 for selection in self
10924 .selections
10925 .all_adjusted(&self.display_snapshot(cx))
10926 .iter()
10927 {
10928 let Some(wrap_config) = snapshot
10929 .language_at(selection.start)
10930 .and_then(|lang| lang.config().wrap_characters.clone())
10931 else {
10932 continue;
10933 };
10934
10935 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10936 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10937
10938 let start_before = snapshot.anchor_before(selection.start);
10939 let end_after = snapshot.anchor_after(selection.end);
10940
10941 edits.push((start_before..start_before, open_tag));
10942 edits.push((end_after..end_after, close_tag));
10943
10944 boundaries.push((
10945 start_before,
10946 end_after,
10947 wrap_config.start_prefix.len(),
10948 wrap_config.end_suffix.len(),
10949 ));
10950 }
10951
10952 if edits.is_empty() {
10953 return;
10954 }
10955
10956 self.transact(window, cx, |this, window, cx| {
10957 let buffer = this.buffer.update(cx, |buffer, cx| {
10958 buffer.edit(edits, None, cx);
10959 buffer.snapshot(cx)
10960 });
10961
10962 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10963 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10964 boundaries.into_iter()
10965 {
10966 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10967 let close_offset = end_after
10968 .to_offset(&buffer)
10969 .saturating_sub_usize(end_suffix_len);
10970 new_selections.push(open_offset..open_offset);
10971 new_selections.push(close_offset..close_offset);
10972 }
10973
10974 this.change_selections(Default::default(), window, cx, |s| {
10975 s.select_ranges(new_selections);
10976 });
10977
10978 this.request_autoscroll(Autoscroll::fit(), cx);
10979 });
10980 }
10981
10982 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10983 let Some(project) = self.project.clone() else {
10984 return;
10985 };
10986 self.reload(project, window, cx)
10987 .detach_and_notify_err(window, cx);
10988 }
10989
10990 pub fn restore_file(
10991 &mut self,
10992 _: &::git::RestoreFile,
10993 window: &mut Window,
10994 cx: &mut Context<Self>,
10995 ) {
10996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10997 let mut buffer_ids = HashSet::default();
10998 let snapshot = self.buffer().read(cx).snapshot(cx);
10999 for selection in self
11000 .selections
11001 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11002 {
11003 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11004 }
11005
11006 let buffer = self.buffer().read(cx);
11007 let ranges = buffer_ids
11008 .into_iter()
11009 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11010 .collect::<Vec<_>>();
11011
11012 self.restore_hunks_in_ranges(ranges, window, cx);
11013 }
11014
11015 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11017 let selections = self
11018 .selections
11019 .all(&self.display_snapshot(cx))
11020 .into_iter()
11021 .map(|s| s.range())
11022 .collect();
11023 self.restore_hunks_in_ranges(selections, window, cx);
11024 }
11025
11026 pub fn restore_hunks_in_ranges(
11027 &mut self,
11028 ranges: Vec<Range<Point>>,
11029 window: &mut Window,
11030 cx: &mut Context<Editor>,
11031 ) {
11032 let mut revert_changes = HashMap::default();
11033 let chunk_by = self
11034 .snapshot(window, cx)
11035 .hunks_for_ranges(ranges)
11036 .into_iter()
11037 .chunk_by(|hunk| hunk.buffer_id);
11038 for (buffer_id, hunks) in &chunk_by {
11039 let hunks = hunks.collect::<Vec<_>>();
11040 for hunk in &hunks {
11041 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11042 }
11043 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11044 }
11045 drop(chunk_by);
11046 if !revert_changes.is_empty() {
11047 self.transact(window, cx, |editor, window, cx| {
11048 editor.restore(revert_changes, window, cx);
11049 });
11050 }
11051 }
11052
11053 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11054 if let Some(status) = self
11055 .addons
11056 .iter()
11057 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11058 {
11059 return Some(status);
11060 }
11061 self.project
11062 .as_ref()?
11063 .read(cx)
11064 .status_for_buffer_id(buffer_id, cx)
11065 }
11066
11067 pub fn open_active_item_in_terminal(
11068 &mut self,
11069 _: &OpenInTerminal,
11070 window: &mut Window,
11071 cx: &mut Context<Self>,
11072 ) {
11073 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11074 let project_path = buffer.read(cx).project_path(cx)?;
11075 let project = self.project()?.read(cx);
11076 let entry = project.entry_for_path(&project_path, cx)?;
11077 let parent = match &entry.canonical_path {
11078 Some(canonical_path) => canonical_path.to_path_buf(),
11079 None => project.absolute_path(&project_path, cx)?,
11080 }
11081 .parent()?
11082 .to_path_buf();
11083 Some(parent)
11084 }) {
11085 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11086 }
11087 }
11088
11089 fn set_breakpoint_context_menu(
11090 &mut self,
11091 display_row: DisplayRow,
11092 position: Option<Anchor>,
11093 clicked_point: gpui::Point<Pixels>,
11094 window: &mut Window,
11095 cx: &mut Context<Self>,
11096 ) {
11097 let source = self
11098 .buffer
11099 .read(cx)
11100 .snapshot(cx)
11101 .anchor_before(Point::new(display_row.0, 0u32));
11102
11103 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11104
11105 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11106 self,
11107 source,
11108 clicked_point,
11109 context_menu,
11110 window,
11111 cx,
11112 );
11113 }
11114
11115 fn add_edit_breakpoint_block(
11116 &mut self,
11117 anchor: Anchor,
11118 breakpoint: &Breakpoint,
11119 edit_action: BreakpointPromptEditAction,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 ) {
11123 let weak_editor = cx.weak_entity();
11124 let bp_prompt = cx.new(|cx| {
11125 BreakpointPromptEditor::new(
11126 weak_editor,
11127 anchor,
11128 breakpoint.clone(),
11129 edit_action,
11130 window,
11131 cx,
11132 )
11133 });
11134
11135 let height = bp_prompt.update(cx, |this, cx| {
11136 this.prompt
11137 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11138 });
11139 let cloned_prompt = bp_prompt.clone();
11140 let blocks = vec![BlockProperties {
11141 style: BlockStyle::Sticky,
11142 placement: BlockPlacement::Above(anchor),
11143 height: Some(height),
11144 render: Arc::new(move |cx| {
11145 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11146 cloned_prompt.clone().into_any_element()
11147 }),
11148 priority: 0,
11149 }];
11150
11151 let focus_handle = bp_prompt.focus_handle(cx);
11152 window.focus(&focus_handle);
11153
11154 let block_ids = self.insert_blocks(blocks, None, cx);
11155 bp_prompt.update(cx, |prompt, _| {
11156 prompt.add_block_ids(block_ids);
11157 });
11158 }
11159
11160 pub(crate) fn breakpoint_at_row(
11161 &self,
11162 row: u32,
11163 window: &mut Window,
11164 cx: &mut Context<Self>,
11165 ) -> Option<(Anchor, Breakpoint)> {
11166 let snapshot = self.snapshot(window, cx);
11167 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11168
11169 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11170 }
11171
11172 pub(crate) fn breakpoint_at_anchor(
11173 &self,
11174 breakpoint_position: Anchor,
11175 snapshot: &EditorSnapshot,
11176 cx: &mut Context<Self>,
11177 ) -> Option<(Anchor, Breakpoint)> {
11178 let buffer = self
11179 .buffer
11180 .read(cx)
11181 .buffer_for_anchor(breakpoint_position, cx)?;
11182
11183 let enclosing_excerpt = breakpoint_position.excerpt_id;
11184 let buffer_snapshot = buffer.read(cx).snapshot();
11185
11186 let row = buffer_snapshot
11187 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11188 .row;
11189
11190 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11191 let anchor_end = snapshot
11192 .buffer_snapshot()
11193 .anchor_after(Point::new(row, line_len));
11194
11195 self.breakpoint_store
11196 .as_ref()?
11197 .read_with(cx, |breakpoint_store, cx| {
11198 breakpoint_store
11199 .breakpoints(
11200 &buffer,
11201 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11202 &buffer_snapshot,
11203 cx,
11204 )
11205 .next()
11206 .and_then(|(bp, _)| {
11207 let breakpoint_row = buffer_snapshot
11208 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11209 .row;
11210
11211 if breakpoint_row == row {
11212 snapshot
11213 .buffer_snapshot()
11214 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11215 .map(|position| (position, bp.bp.clone()))
11216 } else {
11217 None
11218 }
11219 })
11220 })
11221 }
11222
11223 pub fn edit_log_breakpoint(
11224 &mut self,
11225 _: &EditLogBreakpoint,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) {
11229 if self.breakpoint_store.is_none() {
11230 return;
11231 }
11232
11233 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11234 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11235 message: None,
11236 state: BreakpointState::Enabled,
11237 condition: None,
11238 hit_condition: None,
11239 });
11240
11241 self.add_edit_breakpoint_block(
11242 anchor,
11243 &breakpoint,
11244 BreakpointPromptEditAction::Log,
11245 window,
11246 cx,
11247 );
11248 }
11249 }
11250
11251 fn breakpoints_at_cursors(
11252 &self,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11256 let snapshot = self.snapshot(window, cx);
11257 let cursors = self
11258 .selections
11259 .disjoint_anchors_arc()
11260 .iter()
11261 .map(|selection| {
11262 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11263
11264 let breakpoint_position = self
11265 .breakpoint_at_row(cursor_position.row, window, cx)
11266 .map(|bp| bp.0)
11267 .unwrap_or_else(|| {
11268 snapshot
11269 .display_snapshot
11270 .buffer_snapshot()
11271 .anchor_after(Point::new(cursor_position.row, 0))
11272 });
11273
11274 let breakpoint = self
11275 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11276 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11277
11278 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11279 })
11280 // 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.
11281 .collect::<HashMap<Anchor, _>>();
11282
11283 cursors.into_iter().collect()
11284 }
11285
11286 pub fn enable_breakpoint(
11287 &mut self,
11288 _: &crate::actions::EnableBreakpoint,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 if self.breakpoint_store.is_none() {
11293 return;
11294 }
11295
11296 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11297 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11298 continue;
11299 };
11300 self.edit_breakpoint_at_anchor(
11301 anchor,
11302 breakpoint,
11303 BreakpointEditAction::InvertState,
11304 cx,
11305 );
11306 }
11307 }
11308
11309 pub fn disable_breakpoint(
11310 &mut self,
11311 _: &crate::actions::DisableBreakpoint,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 if self.breakpoint_store.is_none() {
11316 return;
11317 }
11318
11319 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11320 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11321 continue;
11322 };
11323 self.edit_breakpoint_at_anchor(
11324 anchor,
11325 breakpoint,
11326 BreakpointEditAction::InvertState,
11327 cx,
11328 );
11329 }
11330 }
11331
11332 pub fn toggle_breakpoint(
11333 &mut self,
11334 _: &crate::actions::ToggleBreakpoint,
11335 window: &mut Window,
11336 cx: &mut Context<Self>,
11337 ) {
11338 if self.breakpoint_store.is_none() {
11339 return;
11340 }
11341
11342 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11343 if let Some(breakpoint) = breakpoint {
11344 self.edit_breakpoint_at_anchor(
11345 anchor,
11346 breakpoint,
11347 BreakpointEditAction::Toggle,
11348 cx,
11349 );
11350 } else {
11351 self.edit_breakpoint_at_anchor(
11352 anchor,
11353 Breakpoint::new_standard(),
11354 BreakpointEditAction::Toggle,
11355 cx,
11356 );
11357 }
11358 }
11359 }
11360
11361 pub fn edit_breakpoint_at_anchor(
11362 &mut self,
11363 breakpoint_position: Anchor,
11364 breakpoint: Breakpoint,
11365 edit_action: BreakpointEditAction,
11366 cx: &mut Context<Self>,
11367 ) {
11368 let Some(breakpoint_store) = &self.breakpoint_store else {
11369 return;
11370 };
11371
11372 let Some(buffer) = self
11373 .buffer
11374 .read(cx)
11375 .buffer_for_anchor(breakpoint_position, cx)
11376 else {
11377 return;
11378 };
11379
11380 breakpoint_store.update(cx, |breakpoint_store, cx| {
11381 breakpoint_store.toggle_breakpoint(
11382 buffer,
11383 BreakpointWithPosition {
11384 position: breakpoint_position.text_anchor,
11385 bp: breakpoint,
11386 },
11387 edit_action,
11388 cx,
11389 );
11390 });
11391
11392 cx.notify();
11393 }
11394
11395 #[cfg(any(test, feature = "test-support"))]
11396 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11397 self.breakpoint_store.clone()
11398 }
11399
11400 pub fn prepare_restore_change(
11401 &self,
11402 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11403 hunk: &MultiBufferDiffHunk,
11404 cx: &mut App,
11405 ) -> Option<()> {
11406 if hunk.is_created_file() {
11407 return None;
11408 }
11409 let buffer = self.buffer.read(cx);
11410 let diff = buffer.diff_for(hunk.buffer_id)?;
11411 let buffer = buffer.buffer(hunk.buffer_id)?;
11412 let buffer = buffer.read(cx);
11413 let original_text = diff
11414 .read(cx)
11415 .base_text(cx)
11416 .as_rope()
11417 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11418 let buffer_snapshot = buffer.snapshot();
11419 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11420 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11421 probe
11422 .0
11423 .start
11424 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11425 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11426 }) {
11427 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11428 Some(())
11429 } else {
11430 None
11431 }
11432 }
11433
11434 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11435 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11436 }
11437
11438 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11439 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11440 }
11441
11442 fn manipulate_lines<M>(
11443 &mut self,
11444 window: &mut Window,
11445 cx: &mut Context<Self>,
11446 mut manipulate: M,
11447 ) where
11448 M: FnMut(&str) -> LineManipulationResult,
11449 {
11450 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11451
11452 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11453 let buffer = self.buffer.read(cx).snapshot(cx);
11454
11455 let mut edits = Vec::new();
11456
11457 let selections = self.selections.all::<Point>(&display_map);
11458 let mut selections = selections.iter().peekable();
11459 let mut contiguous_row_selections = Vec::new();
11460 let mut new_selections = Vec::new();
11461 let mut added_lines = 0;
11462 let mut removed_lines = 0;
11463
11464 while let Some(selection) = selections.next() {
11465 let (start_row, end_row) = consume_contiguous_rows(
11466 &mut contiguous_row_selections,
11467 selection,
11468 &display_map,
11469 &mut selections,
11470 );
11471
11472 let start_point = Point::new(start_row.0, 0);
11473 let end_point = Point::new(
11474 end_row.previous_row().0,
11475 buffer.line_len(end_row.previous_row()),
11476 );
11477 let text = buffer
11478 .text_for_range(start_point..end_point)
11479 .collect::<String>();
11480
11481 let LineManipulationResult {
11482 new_text,
11483 line_count_before,
11484 line_count_after,
11485 } = manipulate(&text);
11486
11487 edits.push((start_point..end_point, new_text));
11488
11489 // Selections must change based on added and removed line count
11490 let start_row =
11491 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11492 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11493 new_selections.push(Selection {
11494 id: selection.id,
11495 start: start_row,
11496 end: end_row,
11497 goal: SelectionGoal::None,
11498 reversed: selection.reversed,
11499 });
11500
11501 if line_count_after > line_count_before {
11502 added_lines += line_count_after - line_count_before;
11503 } else if line_count_before > line_count_after {
11504 removed_lines += line_count_before - line_count_after;
11505 }
11506 }
11507
11508 self.transact(window, cx, |this, window, cx| {
11509 let buffer = this.buffer.update(cx, |buffer, cx| {
11510 buffer.edit(edits, None, cx);
11511 buffer.snapshot(cx)
11512 });
11513
11514 // Recalculate offsets on newly edited buffer
11515 let new_selections = new_selections
11516 .iter()
11517 .map(|s| {
11518 let start_point = Point::new(s.start.0, 0);
11519 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11520 Selection {
11521 id: s.id,
11522 start: buffer.point_to_offset(start_point),
11523 end: buffer.point_to_offset(end_point),
11524 goal: s.goal,
11525 reversed: s.reversed,
11526 }
11527 })
11528 .collect();
11529
11530 this.change_selections(Default::default(), window, cx, |s| {
11531 s.select(new_selections);
11532 });
11533
11534 this.request_autoscroll(Autoscroll::fit(), cx);
11535 });
11536 }
11537
11538 fn manipulate_immutable_lines<Fn>(
11539 &mut self,
11540 window: &mut Window,
11541 cx: &mut Context<Self>,
11542 mut callback: Fn,
11543 ) where
11544 Fn: FnMut(&mut Vec<&str>),
11545 {
11546 self.manipulate_lines(window, cx, |text| {
11547 let mut lines: Vec<&str> = text.split('\n').collect();
11548 let line_count_before = lines.len();
11549
11550 callback(&mut lines);
11551
11552 LineManipulationResult {
11553 new_text: lines.join("\n"),
11554 line_count_before,
11555 line_count_after: lines.len(),
11556 }
11557 });
11558 }
11559
11560 fn manipulate_mutable_lines<Fn>(
11561 &mut self,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 mut callback: Fn,
11565 ) where
11566 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11567 {
11568 self.manipulate_lines(window, cx, |text| {
11569 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11570 let line_count_before = lines.len();
11571
11572 callback(&mut lines);
11573
11574 LineManipulationResult {
11575 new_text: lines.join("\n"),
11576 line_count_before,
11577 line_count_after: lines.len(),
11578 }
11579 });
11580 }
11581
11582 pub fn convert_indentation_to_spaces(
11583 &mut self,
11584 _: &ConvertIndentationToSpaces,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 let settings = self.buffer.read(cx).language_settings(cx);
11589 let tab_size = settings.tab_size.get() as usize;
11590
11591 self.manipulate_mutable_lines(window, cx, |lines| {
11592 // Allocates a reasonably sized scratch buffer once for the whole loop
11593 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11594 // Avoids recomputing spaces that could be inserted many times
11595 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11596 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11597 .collect();
11598
11599 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11600 let mut chars = line.as_ref().chars();
11601 let mut col = 0;
11602 let mut changed = false;
11603
11604 for ch in chars.by_ref() {
11605 match ch {
11606 ' ' => {
11607 reindented_line.push(' ');
11608 col += 1;
11609 }
11610 '\t' => {
11611 // \t are converted to spaces depending on the current column
11612 let spaces_len = tab_size - (col % tab_size);
11613 reindented_line.extend(&space_cache[spaces_len - 1]);
11614 col += spaces_len;
11615 changed = true;
11616 }
11617 _ => {
11618 // If we dont append before break, the character is consumed
11619 reindented_line.push(ch);
11620 break;
11621 }
11622 }
11623 }
11624
11625 if !changed {
11626 reindented_line.clear();
11627 continue;
11628 }
11629 // Append the rest of the line and replace old reference with new one
11630 reindented_line.extend(chars);
11631 *line = Cow::Owned(reindented_line.clone());
11632 reindented_line.clear();
11633 }
11634 });
11635 }
11636
11637 pub fn convert_indentation_to_tabs(
11638 &mut self,
11639 _: &ConvertIndentationToTabs,
11640 window: &mut Window,
11641 cx: &mut Context<Self>,
11642 ) {
11643 let settings = self.buffer.read(cx).language_settings(cx);
11644 let tab_size = settings.tab_size.get() as usize;
11645
11646 self.manipulate_mutable_lines(window, cx, |lines| {
11647 // Allocates a reasonably sized buffer once for the whole loop
11648 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11649 // Avoids recomputing spaces that could be inserted many times
11650 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11651 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11652 .collect();
11653
11654 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11655 let mut chars = line.chars();
11656 let mut spaces_count = 0;
11657 let mut first_non_indent_char = None;
11658 let mut changed = false;
11659
11660 for ch in chars.by_ref() {
11661 match ch {
11662 ' ' => {
11663 // Keep track of spaces. Append \t when we reach tab_size
11664 spaces_count += 1;
11665 changed = true;
11666 if spaces_count == tab_size {
11667 reindented_line.push('\t');
11668 spaces_count = 0;
11669 }
11670 }
11671 '\t' => {
11672 reindented_line.push('\t');
11673 spaces_count = 0;
11674 }
11675 _ => {
11676 // Dont append it yet, we might have remaining spaces
11677 first_non_indent_char = Some(ch);
11678 break;
11679 }
11680 }
11681 }
11682
11683 if !changed {
11684 reindented_line.clear();
11685 continue;
11686 }
11687 // Remaining spaces that didn't make a full tab stop
11688 if spaces_count > 0 {
11689 reindented_line.extend(&space_cache[spaces_count - 1]);
11690 }
11691 // If we consume an extra character that was not indentation, add it back
11692 if let Some(extra_char) = first_non_indent_char {
11693 reindented_line.push(extra_char);
11694 }
11695 // Append the rest of the line and replace old reference with new one
11696 reindented_line.extend(chars);
11697 *line = Cow::Owned(reindented_line.clone());
11698 reindented_line.clear();
11699 }
11700 });
11701 }
11702
11703 pub fn convert_to_upper_case(
11704 &mut self,
11705 _: &ConvertToUpperCase,
11706 window: &mut Window,
11707 cx: &mut Context<Self>,
11708 ) {
11709 self.manipulate_text(window, cx, |text| text.to_uppercase())
11710 }
11711
11712 pub fn convert_to_lower_case(
11713 &mut self,
11714 _: &ConvertToLowerCase,
11715 window: &mut Window,
11716 cx: &mut Context<Self>,
11717 ) {
11718 self.manipulate_text(window, cx, |text| text.to_lowercase())
11719 }
11720
11721 pub fn convert_to_title_case(
11722 &mut self,
11723 _: &ConvertToTitleCase,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.manipulate_text(window, cx, |text| {
11728 text.split('\n')
11729 .map(|line| line.to_case(Case::Title))
11730 .join("\n")
11731 })
11732 }
11733
11734 pub fn convert_to_snake_case(
11735 &mut self,
11736 _: &ConvertToSnakeCase,
11737 window: &mut Window,
11738 cx: &mut Context<Self>,
11739 ) {
11740 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11741 }
11742
11743 pub fn convert_to_kebab_case(
11744 &mut self,
11745 _: &ConvertToKebabCase,
11746 window: &mut Window,
11747 cx: &mut Context<Self>,
11748 ) {
11749 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11750 }
11751
11752 pub fn convert_to_upper_camel_case(
11753 &mut self,
11754 _: &ConvertToUpperCamelCase,
11755 window: &mut Window,
11756 cx: &mut Context<Self>,
11757 ) {
11758 self.manipulate_text(window, cx, |text| {
11759 text.split('\n')
11760 .map(|line| line.to_case(Case::UpperCamel))
11761 .join("\n")
11762 })
11763 }
11764
11765 pub fn convert_to_lower_camel_case(
11766 &mut self,
11767 _: &ConvertToLowerCamelCase,
11768 window: &mut Window,
11769 cx: &mut Context<Self>,
11770 ) {
11771 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11772 }
11773
11774 pub fn convert_to_opposite_case(
11775 &mut self,
11776 _: &ConvertToOppositeCase,
11777 window: &mut Window,
11778 cx: &mut Context<Self>,
11779 ) {
11780 self.manipulate_text(window, cx, |text| {
11781 text.chars()
11782 .fold(String::with_capacity(text.len()), |mut t, c| {
11783 if c.is_uppercase() {
11784 t.extend(c.to_lowercase());
11785 } else {
11786 t.extend(c.to_uppercase());
11787 }
11788 t
11789 })
11790 })
11791 }
11792
11793 pub fn convert_to_sentence_case(
11794 &mut self,
11795 _: &ConvertToSentenceCase,
11796 window: &mut Window,
11797 cx: &mut Context<Self>,
11798 ) {
11799 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11800 }
11801
11802 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11803 self.manipulate_text(window, cx, |text| {
11804 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11805 if has_upper_case_characters {
11806 text.to_lowercase()
11807 } else {
11808 text.to_uppercase()
11809 }
11810 })
11811 }
11812
11813 pub fn convert_to_rot13(
11814 &mut self,
11815 _: &ConvertToRot13,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) {
11819 self.manipulate_text(window, cx, |text| {
11820 text.chars()
11821 .map(|c| match c {
11822 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11823 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11824 _ => c,
11825 })
11826 .collect()
11827 })
11828 }
11829
11830 pub fn convert_to_rot47(
11831 &mut self,
11832 _: &ConvertToRot47,
11833 window: &mut Window,
11834 cx: &mut Context<Self>,
11835 ) {
11836 self.manipulate_text(window, cx, |text| {
11837 text.chars()
11838 .map(|c| {
11839 let code_point = c as u32;
11840 if code_point >= 33 && code_point <= 126 {
11841 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11842 }
11843 c
11844 })
11845 .collect()
11846 })
11847 }
11848
11849 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11850 where
11851 Fn: FnMut(&str) -> String,
11852 {
11853 let buffer = self.buffer.read(cx).snapshot(cx);
11854
11855 let mut new_selections = Vec::new();
11856 let mut edits = Vec::new();
11857 let mut selection_adjustment = 0isize;
11858
11859 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11860 let selection_is_empty = selection.is_empty();
11861
11862 let (start, end) = if selection_is_empty {
11863 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11864 (word_range.start, word_range.end)
11865 } else {
11866 (
11867 buffer.point_to_offset(selection.start),
11868 buffer.point_to_offset(selection.end),
11869 )
11870 };
11871
11872 let text = buffer.text_for_range(start..end).collect::<String>();
11873 let old_length = text.len() as isize;
11874 let text = callback(&text);
11875
11876 new_selections.push(Selection {
11877 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11878 end: MultiBufferOffset(
11879 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11880 ),
11881 goal: SelectionGoal::None,
11882 id: selection.id,
11883 reversed: selection.reversed,
11884 });
11885
11886 selection_adjustment += old_length - text.len() as isize;
11887
11888 edits.push((start..end, text));
11889 }
11890
11891 self.transact(window, cx, |this, window, cx| {
11892 this.buffer.update(cx, |buffer, cx| {
11893 buffer.edit(edits, None, cx);
11894 });
11895
11896 this.change_selections(Default::default(), window, cx, |s| {
11897 s.select(new_selections);
11898 });
11899
11900 this.request_autoscroll(Autoscroll::fit(), cx);
11901 });
11902 }
11903
11904 pub fn move_selection_on_drop(
11905 &mut self,
11906 selection: &Selection<Anchor>,
11907 target: DisplayPoint,
11908 is_cut: bool,
11909 window: &mut Window,
11910 cx: &mut Context<Self>,
11911 ) {
11912 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11913 let buffer = display_map.buffer_snapshot();
11914 let mut edits = Vec::new();
11915 let insert_point = display_map
11916 .clip_point(target, Bias::Left)
11917 .to_point(&display_map);
11918 let text = buffer
11919 .text_for_range(selection.start..selection.end)
11920 .collect::<String>();
11921 if is_cut {
11922 edits.push(((selection.start..selection.end), String::new()));
11923 }
11924 let insert_anchor = buffer.anchor_before(insert_point);
11925 edits.push(((insert_anchor..insert_anchor), text));
11926 let last_edit_start = insert_anchor.bias_left(buffer);
11927 let last_edit_end = insert_anchor.bias_right(buffer);
11928 self.transact(window, cx, |this, window, cx| {
11929 this.buffer.update(cx, |buffer, cx| {
11930 buffer.edit(edits, None, cx);
11931 });
11932 this.change_selections(Default::default(), window, cx, |s| {
11933 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11934 });
11935 });
11936 }
11937
11938 pub fn clear_selection_drag_state(&mut self) {
11939 self.selection_drag_state = SelectionDragState::None;
11940 }
11941
11942 pub fn duplicate(
11943 &mut self,
11944 upwards: bool,
11945 whole_lines: bool,
11946 window: &mut Window,
11947 cx: &mut Context<Self>,
11948 ) {
11949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11950
11951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11952 let buffer = display_map.buffer_snapshot();
11953 let selections = self.selections.all::<Point>(&display_map);
11954
11955 let mut edits = Vec::new();
11956 let mut selections_iter = selections.iter().peekable();
11957 while let Some(selection) = selections_iter.next() {
11958 let mut rows = selection.spanned_rows(false, &display_map);
11959 // duplicate line-wise
11960 if whole_lines || selection.start == selection.end {
11961 // Avoid duplicating the same lines twice.
11962 while let Some(next_selection) = selections_iter.peek() {
11963 let next_rows = next_selection.spanned_rows(false, &display_map);
11964 if next_rows.start < rows.end {
11965 rows.end = next_rows.end;
11966 selections_iter.next().unwrap();
11967 } else {
11968 break;
11969 }
11970 }
11971
11972 // Copy the text from the selected row region and splice it either at the start
11973 // or end of the region.
11974 let start = Point::new(rows.start.0, 0);
11975 let end = Point::new(
11976 rows.end.previous_row().0,
11977 buffer.line_len(rows.end.previous_row()),
11978 );
11979
11980 let mut text = buffer.text_for_range(start..end).collect::<String>();
11981
11982 let insert_location = if upwards {
11983 // When duplicating upward, we need to insert before the current line.
11984 // If we're on the last line and it doesn't end with a newline,
11985 // we need to add a newline before the duplicated content.
11986 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11987 && buffer.max_point().column > 0
11988 && !text.ends_with('\n');
11989
11990 if needs_leading_newline {
11991 text.insert(0, '\n');
11992 end
11993 } else {
11994 text.push('\n');
11995 Point::new(rows.start.0, 0)
11996 }
11997 } else {
11998 text.push('\n');
11999 start
12000 };
12001 edits.push((insert_location..insert_location, text));
12002 } else {
12003 // duplicate character-wise
12004 let start = selection.start;
12005 let end = selection.end;
12006 let text = buffer.text_for_range(start..end).collect::<String>();
12007 edits.push((selection.end..selection.end, text));
12008 }
12009 }
12010
12011 self.transact(window, cx, |this, window, cx| {
12012 this.buffer.update(cx, |buffer, cx| {
12013 buffer.edit(edits, None, cx);
12014 });
12015
12016 // When duplicating upward with whole lines, move the cursor to the duplicated line
12017 if upwards && whole_lines {
12018 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12019
12020 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12021 let mut new_ranges = Vec::new();
12022 let selections = s.all::<Point>(&display_map);
12023 let mut selections_iter = selections.iter().peekable();
12024
12025 while let Some(first_selection) = selections_iter.next() {
12026 // Group contiguous selections together to find the total row span
12027 let mut group_selections = vec![first_selection];
12028 let mut rows = first_selection.spanned_rows(false, &display_map);
12029
12030 while let Some(next_selection) = selections_iter.peek() {
12031 let next_rows = next_selection.spanned_rows(false, &display_map);
12032 if next_rows.start < rows.end {
12033 rows.end = next_rows.end;
12034 group_selections.push(selections_iter.next().unwrap());
12035 } else {
12036 break;
12037 }
12038 }
12039
12040 let row_count = rows.end.0 - rows.start.0;
12041
12042 // Move all selections in this group up by the total number of duplicated rows
12043 for selection in group_selections {
12044 let new_start = Point::new(
12045 selection.start.row.saturating_sub(row_count),
12046 selection.start.column,
12047 );
12048
12049 let new_end = Point::new(
12050 selection.end.row.saturating_sub(row_count),
12051 selection.end.column,
12052 );
12053
12054 new_ranges.push(new_start..new_end);
12055 }
12056 }
12057
12058 s.select_ranges(new_ranges);
12059 });
12060 }
12061
12062 this.request_autoscroll(Autoscroll::fit(), cx);
12063 });
12064 }
12065
12066 pub fn duplicate_line_up(
12067 &mut self,
12068 _: &DuplicateLineUp,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.duplicate(true, true, window, cx);
12073 }
12074
12075 pub fn duplicate_line_down(
12076 &mut self,
12077 _: &DuplicateLineDown,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 self.duplicate(false, true, window, cx);
12082 }
12083
12084 pub fn duplicate_selection(
12085 &mut self,
12086 _: &DuplicateSelection,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.duplicate(false, false, window, cx);
12091 }
12092
12093 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12095 if self.mode.is_single_line() {
12096 cx.propagate();
12097 return;
12098 }
12099
12100 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12101 let buffer = self.buffer.read(cx).snapshot(cx);
12102
12103 let mut edits = Vec::new();
12104 let mut unfold_ranges = Vec::new();
12105 let mut refold_creases = Vec::new();
12106
12107 let selections = self.selections.all::<Point>(&display_map);
12108 let mut selections = selections.iter().peekable();
12109 let mut contiguous_row_selections = Vec::new();
12110 let mut new_selections = Vec::new();
12111
12112 while let Some(selection) = selections.next() {
12113 // Find all the selections that span a contiguous row range
12114 let (start_row, end_row) = consume_contiguous_rows(
12115 &mut contiguous_row_selections,
12116 selection,
12117 &display_map,
12118 &mut selections,
12119 );
12120
12121 // Move the text spanned by the row range to be before the line preceding the row range
12122 if start_row.0 > 0 {
12123 let range_to_move = Point::new(
12124 start_row.previous_row().0,
12125 buffer.line_len(start_row.previous_row()),
12126 )
12127 ..Point::new(
12128 end_row.previous_row().0,
12129 buffer.line_len(end_row.previous_row()),
12130 );
12131 let insertion_point = display_map
12132 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12133 .0;
12134
12135 // Don't move lines across excerpts
12136 if buffer
12137 .excerpt_containing(insertion_point..range_to_move.end)
12138 .is_some()
12139 {
12140 let text = buffer
12141 .text_for_range(range_to_move.clone())
12142 .flat_map(|s| s.chars())
12143 .skip(1)
12144 .chain(['\n'])
12145 .collect::<String>();
12146
12147 edits.push((
12148 buffer.anchor_after(range_to_move.start)
12149 ..buffer.anchor_before(range_to_move.end),
12150 String::new(),
12151 ));
12152 let insertion_anchor = buffer.anchor_after(insertion_point);
12153 edits.push((insertion_anchor..insertion_anchor, text));
12154
12155 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12156
12157 // Move selections up
12158 new_selections.extend(contiguous_row_selections.drain(..).map(
12159 |mut selection| {
12160 selection.start.row -= row_delta;
12161 selection.end.row -= row_delta;
12162 selection
12163 },
12164 ));
12165
12166 // Move folds up
12167 unfold_ranges.push(range_to_move.clone());
12168 for fold in display_map.folds_in_range(
12169 buffer.anchor_before(range_to_move.start)
12170 ..buffer.anchor_after(range_to_move.end),
12171 ) {
12172 let mut start = fold.range.start.to_point(&buffer);
12173 let mut end = fold.range.end.to_point(&buffer);
12174 start.row -= row_delta;
12175 end.row -= row_delta;
12176 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12177 }
12178 }
12179 }
12180
12181 // If we didn't move line(s), preserve the existing selections
12182 new_selections.append(&mut contiguous_row_selections);
12183 }
12184
12185 self.transact(window, cx, |this, window, cx| {
12186 this.unfold_ranges(&unfold_ranges, true, true, cx);
12187 this.buffer.update(cx, |buffer, cx| {
12188 for (range, text) in edits {
12189 buffer.edit([(range, text)], None, cx);
12190 }
12191 });
12192 this.fold_creases(refold_creases, true, window, cx);
12193 this.change_selections(Default::default(), window, cx, |s| {
12194 s.select(new_selections);
12195 })
12196 });
12197 }
12198
12199 pub fn move_line_down(
12200 &mut self,
12201 _: &MoveLineDown,
12202 window: &mut Window,
12203 cx: &mut Context<Self>,
12204 ) {
12205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12206 if self.mode.is_single_line() {
12207 cx.propagate();
12208 return;
12209 }
12210
12211 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12212 let buffer = self.buffer.read(cx).snapshot(cx);
12213
12214 let mut edits = Vec::new();
12215 let mut unfold_ranges = Vec::new();
12216 let mut refold_creases = Vec::new();
12217
12218 let selections = self.selections.all::<Point>(&display_map);
12219 let mut selections = selections.iter().peekable();
12220 let mut contiguous_row_selections = Vec::new();
12221 let mut new_selections = Vec::new();
12222
12223 while let Some(selection) = selections.next() {
12224 // Find all the selections that span a contiguous row range
12225 let (start_row, end_row) = consume_contiguous_rows(
12226 &mut contiguous_row_selections,
12227 selection,
12228 &display_map,
12229 &mut selections,
12230 );
12231
12232 // Move the text spanned by the row range to be after the last line of the row range
12233 if end_row.0 <= buffer.max_point().row {
12234 let range_to_move =
12235 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12236 let insertion_point = display_map
12237 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12238 .0;
12239
12240 // Don't move lines across excerpt boundaries
12241 if buffer
12242 .excerpt_containing(range_to_move.start..insertion_point)
12243 .is_some()
12244 {
12245 let mut text = String::from("\n");
12246 text.extend(buffer.text_for_range(range_to_move.clone()));
12247 text.pop(); // Drop trailing newline
12248 edits.push((
12249 buffer.anchor_after(range_to_move.start)
12250 ..buffer.anchor_before(range_to_move.end),
12251 String::new(),
12252 ));
12253 let insertion_anchor = buffer.anchor_after(insertion_point);
12254 edits.push((insertion_anchor..insertion_anchor, text));
12255
12256 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12257
12258 // Move selections down
12259 new_selections.extend(contiguous_row_selections.drain(..).map(
12260 |mut selection| {
12261 selection.start.row += row_delta;
12262 selection.end.row += row_delta;
12263 selection
12264 },
12265 ));
12266
12267 // Move folds down
12268 unfold_ranges.push(range_to_move.clone());
12269 for fold in display_map.folds_in_range(
12270 buffer.anchor_before(range_to_move.start)
12271 ..buffer.anchor_after(range_to_move.end),
12272 ) {
12273 let mut start = fold.range.start.to_point(&buffer);
12274 let mut end = fold.range.end.to_point(&buffer);
12275 start.row += row_delta;
12276 end.row += row_delta;
12277 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12278 }
12279 }
12280 }
12281
12282 // If we didn't move line(s), preserve the existing selections
12283 new_selections.append(&mut contiguous_row_selections);
12284 }
12285
12286 self.transact(window, cx, |this, window, cx| {
12287 this.unfold_ranges(&unfold_ranges, true, true, cx);
12288 this.buffer.update(cx, |buffer, cx| {
12289 for (range, text) in edits {
12290 buffer.edit([(range, text)], None, cx);
12291 }
12292 });
12293 this.fold_creases(refold_creases, true, window, cx);
12294 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12295 });
12296 }
12297
12298 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12300 let text_layout_details = &self.text_layout_details(window);
12301 self.transact(window, cx, |this, window, cx| {
12302 let edits = this.change_selections(Default::default(), window, cx, |s| {
12303 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12304 s.move_with(|display_map, selection| {
12305 if !selection.is_empty() {
12306 return;
12307 }
12308
12309 let mut head = selection.head();
12310 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12311 if head.column() == display_map.line_len(head.row()) {
12312 transpose_offset = display_map
12313 .buffer_snapshot()
12314 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12315 }
12316
12317 if transpose_offset == MultiBufferOffset(0) {
12318 return;
12319 }
12320
12321 *head.column_mut() += 1;
12322 head = display_map.clip_point(head, Bias::Right);
12323 let goal = SelectionGoal::HorizontalPosition(
12324 display_map
12325 .x_for_display_point(head, text_layout_details)
12326 .into(),
12327 );
12328 selection.collapse_to(head, goal);
12329
12330 let transpose_start = display_map
12331 .buffer_snapshot()
12332 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12333 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12334 let transpose_end = display_map
12335 .buffer_snapshot()
12336 .clip_offset(transpose_offset + 1usize, Bias::Right);
12337 if let Some(ch) = display_map
12338 .buffer_snapshot()
12339 .chars_at(transpose_start)
12340 .next()
12341 {
12342 edits.push((transpose_start..transpose_offset, String::new()));
12343 edits.push((transpose_end..transpose_end, ch.to_string()));
12344 }
12345 }
12346 });
12347 edits
12348 });
12349 this.buffer
12350 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12351 let selections = this
12352 .selections
12353 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12354 this.change_selections(Default::default(), window, cx, |s| {
12355 s.select(selections);
12356 });
12357 });
12358 }
12359
12360 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12361 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12362 if self.mode.is_single_line() {
12363 cx.propagate();
12364 return;
12365 }
12366
12367 self.rewrap_impl(RewrapOptions::default(), cx)
12368 }
12369
12370 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12371 let buffer = self.buffer.read(cx).snapshot(cx);
12372 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12373
12374 #[derive(Clone, Debug, PartialEq)]
12375 enum CommentFormat {
12376 /// single line comment, with prefix for line
12377 Line(String),
12378 /// single line within a block comment, with prefix for line
12379 BlockLine(String),
12380 /// a single line of a block comment that includes the initial delimiter
12381 BlockCommentWithStart(BlockCommentConfig),
12382 /// a single line of a block comment that includes the ending delimiter
12383 BlockCommentWithEnd(BlockCommentConfig),
12384 }
12385
12386 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12387 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12388 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12389 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12390 .peekable();
12391
12392 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12393 row
12394 } else {
12395 return Vec::new();
12396 };
12397
12398 let language_settings = buffer.language_settings_at(selection.head(), cx);
12399 let language_scope = buffer.language_scope_at(selection.head());
12400
12401 let indent_and_prefix_for_row =
12402 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12403 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12404 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12405 &language_scope
12406 {
12407 let indent_end = Point::new(row, indent.len);
12408 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12409 let line_text_after_indent = buffer
12410 .text_for_range(indent_end..line_end)
12411 .collect::<String>();
12412
12413 let is_within_comment_override = buffer
12414 .language_scope_at(indent_end)
12415 .is_some_and(|scope| scope.override_name() == Some("comment"));
12416 let comment_delimiters = if is_within_comment_override {
12417 // we are within a comment syntax node, but we don't
12418 // yet know what kind of comment: block, doc or line
12419 match (
12420 language_scope.documentation_comment(),
12421 language_scope.block_comment(),
12422 ) {
12423 (Some(config), _) | (_, Some(config))
12424 if buffer.contains_str_at(indent_end, &config.start) =>
12425 {
12426 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12427 }
12428 (Some(config), _) | (_, Some(config))
12429 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12430 {
12431 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12432 }
12433 (Some(config), _) | (_, Some(config))
12434 if buffer.contains_str_at(indent_end, &config.prefix) =>
12435 {
12436 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12437 }
12438 (_, _) => language_scope
12439 .line_comment_prefixes()
12440 .iter()
12441 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12442 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12443 }
12444 } else {
12445 // we not in an overridden comment node, but we may
12446 // be within a non-overridden line comment node
12447 language_scope
12448 .line_comment_prefixes()
12449 .iter()
12450 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12451 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12452 };
12453
12454 let rewrap_prefix = language_scope
12455 .rewrap_prefixes()
12456 .iter()
12457 .find_map(|prefix_regex| {
12458 prefix_regex.find(&line_text_after_indent).map(|mat| {
12459 if mat.start() == 0 {
12460 Some(mat.as_str().to_string())
12461 } else {
12462 None
12463 }
12464 })
12465 })
12466 .flatten();
12467 (comment_delimiters, rewrap_prefix)
12468 } else {
12469 (None, None)
12470 };
12471 (indent, comment_prefix, rewrap_prefix)
12472 };
12473
12474 let mut ranges = Vec::new();
12475 let from_empty_selection = selection.is_empty();
12476
12477 let mut current_range_start = first_row;
12478 let mut prev_row = first_row;
12479 let (
12480 mut current_range_indent,
12481 mut current_range_comment_delimiters,
12482 mut current_range_rewrap_prefix,
12483 ) = indent_and_prefix_for_row(first_row);
12484
12485 for row in non_blank_rows_iter.skip(1) {
12486 let has_paragraph_break = row > prev_row + 1;
12487
12488 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12489 indent_and_prefix_for_row(row);
12490
12491 let has_indent_change = row_indent != current_range_indent;
12492 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12493
12494 let has_boundary_change = has_comment_change
12495 || row_rewrap_prefix.is_some()
12496 || (has_indent_change && current_range_comment_delimiters.is_some());
12497
12498 if has_paragraph_break || has_boundary_change {
12499 ranges.push((
12500 language_settings.clone(),
12501 Point::new(current_range_start, 0)
12502 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12503 current_range_indent,
12504 current_range_comment_delimiters.clone(),
12505 current_range_rewrap_prefix.clone(),
12506 from_empty_selection,
12507 ));
12508 current_range_start = row;
12509 current_range_indent = row_indent;
12510 current_range_comment_delimiters = row_comment_delimiters;
12511 current_range_rewrap_prefix = row_rewrap_prefix;
12512 }
12513 prev_row = row;
12514 }
12515
12516 ranges.push((
12517 language_settings.clone(),
12518 Point::new(current_range_start, 0)
12519 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12520 current_range_indent,
12521 current_range_comment_delimiters,
12522 current_range_rewrap_prefix,
12523 from_empty_selection,
12524 ));
12525
12526 ranges
12527 });
12528
12529 let mut edits = Vec::new();
12530 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12531
12532 for (
12533 language_settings,
12534 wrap_range,
12535 mut indent_size,
12536 comment_prefix,
12537 rewrap_prefix,
12538 from_empty_selection,
12539 ) in wrap_ranges
12540 {
12541 let mut start_row = wrap_range.start.row;
12542 let mut end_row = wrap_range.end.row;
12543
12544 // Skip selections that overlap with a range that has already been rewrapped.
12545 let selection_range = start_row..end_row;
12546 if rewrapped_row_ranges
12547 .iter()
12548 .any(|range| range.overlaps(&selection_range))
12549 {
12550 continue;
12551 }
12552
12553 let tab_size = language_settings.tab_size;
12554
12555 let (line_prefix, inside_comment) = match &comment_prefix {
12556 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12557 (Some(prefix.as_str()), true)
12558 }
12559 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12560 (Some(prefix.as_ref()), true)
12561 }
12562 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12563 start: _,
12564 end: _,
12565 prefix,
12566 tab_size,
12567 })) => {
12568 indent_size.len += tab_size;
12569 (Some(prefix.as_ref()), true)
12570 }
12571 None => (None, false),
12572 };
12573 let indent_prefix = indent_size.chars().collect::<String>();
12574 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12575
12576 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12577 RewrapBehavior::InComments => inside_comment,
12578 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12579 RewrapBehavior::Anywhere => true,
12580 };
12581
12582 let should_rewrap = options.override_language_settings
12583 || allow_rewrap_based_on_language
12584 || self.hard_wrap.is_some();
12585 if !should_rewrap {
12586 continue;
12587 }
12588
12589 if from_empty_selection {
12590 'expand_upwards: while start_row > 0 {
12591 let prev_row = start_row - 1;
12592 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12593 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12594 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12595 {
12596 start_row = prev_row;
12597 } else {
12598 break 'expand_upwards;
12599 }
12600 }
12601
12602 'expand_downwards: while end_row < buffer.max_point().row {
12603 let next_row = end_row + 1;
12604 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12605 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12606 && !buffer.is_line_blank(MultiBufferRow(next_row))
12607 {
12608 end_row = next_row;
12609 } else {
12610 break 'expand_downwards;
12611 }
12612 }
12613 }
12614
12615 let start = Point::new(start_row, 0);
12616 let start_offset = ToOffset::to_offset(&start, &buffer);
12617 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12618 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12619 let mut first_line_delimiter = None;
12620 let mut last_line_delimiter = None;
12621 let Some(lines_without_prefixes) = selection_text
12622 .lines()
12623 .enumerate()
12624 .map(|(ix, line)| {
12625 let line_trimmed = line.trim_start();
12626 if rewrap_prefix.is_some() && ix > 0 {
12627 Ok(line_trimmed)
12628 } else if let Some(
12629 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12630 start,
12631 prefix,
12632 end,
12633 tab_size,
12634 })
12635 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12636 start,
12637 prefix,
12638 end,
12639 tab_size,
12640 }),
12641 ) = &comment_prefix
12642 {
12643 let line_trimmed = line_trimmed
12644 .strip_prefix(start.as_ref())
12645 .map(|s| {
12646 let mut indent_size = indent_size;
12647 indent_size.len -= tab_size;
12648 let indent_prefix: String = indent_size.chars().collect();
12649 first_line_delimiter = Some((indent_prefix, start));
12650 s.trim_start()
12651 })
12652 .unwrap_or(line_trimmed);
12653 let line_trimmed = line_trimmed
12654 .strip_suffix(end.as_ref())
12655 .map(|s| {
12656 last_line_delimiter = Some(end);
12657 s.trim_end()
12658 })
12659 .unwrap_or(line_trimmed);
12660 let line_trimmed = line_trimmed
12661 .strip_prefix(prefix.as_ref())
12662 .unwrap_or(line_trimmed);
12663 Ok(line_trimmed)
12664 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12665 line_trimmed.strip_prefix(prefix).with_context(|| {
12666 format!("line did not start with prefix {prefix:?}: {line:?}")
12667 })
12668 } else {
12669 line_trimmed
12670 .strip_prefix(&line_prefix.trim_start())
12671 .with_context(|| {
12672 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12673 })
12674 }
12675 })
12676 .collect::<Result<Vec<_>, _>>()
12677 .log_err()
12678 else {
12679 continue;
12680 };
12681
12682 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12683 buffer
12684 .language_settings_at(Point::new(start_row, 0), cx)
12685 .preferred_line_length as usize
12686 });
12687
12688 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12689 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12690 } else {
12691 line_prefix.clone()
12692 };
12693
12694 let wrapped_text = {
12695 let mut wrapped_text = wrap_with_prefix(
12696 line_prefix,
12697 subsequent_lines_prefix,
12698 lines_without_prefixes.join("\n"),
12699 wrap_column,
12700 tab_size,
12701 options.preserve_existing_whitespace,
12702 );
12703
12704 if let Some((indent, delimiter)) = first_line_delimiter {
12705 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12706 }
12707 if let Some(last_line) = last_line_delimiter {
12708 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12709 }
12710
12711 wrapped_text
12712 };
12713
12714 // TODO: should always use char-based diff while still supporting cursor behavior that
12715 // matches vim.
12716 let mut diff_options = DiffOptions::default();
12717 if options.override_language_settings {
12718 diff_options.max_word_diff_len = 0;
12719 diff_options.max_word_diff_line_count = 0;
12720 } else {
12721 diff_options.max_word_diff_len = usize::MAX;
12722 diff_options.max_word_diff_line_count = usize::MAX;
12723 }
12724
12725 for (old_range, new_text) in
12726 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12727 {
12728 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12729 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12730 edits.push((edit_start..edit_end, new_text));
12731 }
12732
12733 rewrapped_row_ranges.push(start_row..=end_row);
12734 }
12735
12736 self.buffer
12737 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12738 }
12739
12740 pub fn cut_common(
12741 &mut self,
12742 cut_no_selection_line: bool,
12743 window: &mut Window,
12744 cx: &mut Context<Self>,
12745 ) -> ClipboardItem {
12746 let mut text = String::new();
12747 let buffer = self.buffer.read(cx).snapshot(cx);
12748 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12749 let mut clipboard_selections = Vec::with_capacity(selections.len());
12750 {
12751 let max_point = buffer.max_point();
12752 let mut is_first = true;
12753 let mut prev_selection_was_entire_line = false;
12754 for selection in &mut selections {
12755 let is_entire_line =
12756 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12757 if is_entire_line {
12758 selection.start = Point::new(selection.start.row, 0);
12759 if !selection.is_empty() && selection.end.column == 0 {
12760 selection.end = cmp::min(max_point, selection.end);
12761 } else {
12762 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12763 }
12764 selection.goal = SelectionGoal::None;
12765 }
12766 if is_first {
12767 is_first = false;
12768 } else if !prev_selection_was_entire_line {
12769 text += "\n";
12770 }
12771 prev_selection_was_entire_line = is_entire_line;
12772 let mut len = 0;
12773 for chunk in buffer.text_for_range(selection.start..selection.end) {
12774 text.push_str(chunk);
12775 len += chunk.len();
12776 }
12777 clipboard_selections.push(ClipboardSelection {
12778 len,
12779 is_entire_line,
12780 first_line_indent: buffer
12781 .indent_size_for_line(MultiBufferRow(selection.start.row))
12782 .len,
12783 });
12784 }
12785 }
12786
12787 self.transact(window, cx, |this, window, cx| {
12788 this.change_selections(Default::default(), window, cx, |s| {
12789 s.select(selections);
12790 });
12791 this.insert("", window, cx);
12792 });
12793 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12794 }
12795
12796 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12797 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12798 let item = self.cut_common(true, window, cx);
12799 cx.write_to_clipboard(item);
12800 }
12801
12802 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12803 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12804 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12805 s.move_with(|snapshot, sel| {
12806 if sel.is_empty() {
12807 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12808 }
12809 if sel.is_empty() {
12810 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12811 }
12812 });
12813 });
12814 let item = self.cut_common(false, window, cx);
12815 cx.set_global(KillRing(item))
12816 }
12817
12818 pub fn kill_ring_yank(
12819 &mut self,
12820 _: &KillRingYank,
12821 window: &mut Window,
12822 cx: &mut Context<Self>,
12823 ) {
12824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12825 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12826 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12827 (kill_ring.text().to_string(), kill_ring.metadata_json())
12828 } else {
12829 return;
12830 }
12831 } else {
12832 return;
12833 };
12834 self.do_paste(&text, metadata, false, window, cx);
12835 }
12836
12837 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12838 self.do_copy(true, cx);
12839 }
12840
12841 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12842 self.do_copy(false, cx);
12843 }
12844
12845 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12846 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12847 let buffer = self.buffer.read(cx).read(cx);
12848 let mut text = String::new();
12849
12850 let mut clipboard_selections = Vec::with_capacity(selections.len());
12851 {
12852 let max_point = buffer.max_point();
12853 let mut is_first = true;
12854 let mut prev_selection_was_entire_line = false;
12855 for selection in &selections {
12856 let mut start = selection.start;
12857 let mut end = selection.end;
12858 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12859 let mut add_trailing_newline = false;
12860 if is_entire_line {
12861 start = Point::new(start.row, 0);
12862 let next_line_start = Point::new(end.row + 1, 0);
12863 if next_line_start <= max_point {
12864 end = next_line_start;
12865 } else {
12866 // We're on the last line without a trailing newline.
12867 // Copy to the end of the line and add a newline afterwards.
12868 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12869 add_trailing_newline = true;
12870 }
12871 }
12872
12873 let mut trimmed_selections = Vec::new();
12874 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12875 let row = MultiBufferRow(start.row);
12876 let first_indent = buffer.indent_size_for_line(row);
12877 if first_indent.len == 0 || start.column > first_indent.len {
12878 trimmed_selections.push(start..end);
12879 } else {
12880 trimmed_selections.push(
12881 Point::new(row.0, first_indent.len)
12882 ..Point::new(row.0, buffer.line_len(row)),
12883 );
12884 for row in start.row + 1..=end.row {
12885 let mut line_len = buffer.line_len(MultiBufferRow(row));
12886 if row == end.row {
12887 line_len = end.column;
12888 }
12889 if line_len == 0 {
12890 trimmed_selections
12891 .push(Point::new(row, 0)..Point::new(row, line_len));
12892 continue;
12893 }
12894 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12895 if row_indent_size.len >= first_indent.len {
12896 trimmed_selections.push(
12897 Point::new(row, first_indent.len)..Point::new(row, line_len),
12898 );
12899 } else {
12900 trimmed_selections.clear();
12901 trimmed_selections.push(start..end);
12902 break;
12903 }
12904 }
12905 }
12906 } else {
12907 trimmed_selections.push(start..end);
12908 }
12909
12910 for trimmed_range in trimmed_selections {
12911 if is_first {
12912 is_first = false;
12913 } else if !prev_selection_was_entire_line {
12914 text += "\n";
12915 }
12916 prev_selection_was_entire_line = is_entire_line;
12917 let mut len = 0;
12918 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12919 text.push_str(chunk);
12920 len += chunk.len();
12921 }
12922 if add_trailing_newline {
12923 text.push('\n');
12924 len += 1;
12925 }
12926 clipboard_selections.push(ClipboardSelection {
12927 len,
12928 is_entire_line,
12929 first_line_indent: buffer
12930 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12931 .len,
12932 });
12933 }
12934 }
12935 }
12936
12937 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12938 text,
12939 clipboard_selections,
12940 ));
12941 }
12942
12943 pub fn do_paste(
12944 &mut self,
12945 text: &String,
12946 clipboard_selections: Option<Vec<ClipboardSelection>>,
12947 handle_entire_lines: bool,
12948 window: &mut Window,
12949 cx: &mut Context<Self>,
12950 ) {
12951 if self.read_only(cx) {
12952 return;
12953 }
12954
12955 let clipboard_text = Cow::Borrowed(text.as_str());
12956
12957 self.transact(window, cx, |this, window, cx| {
12958 let had_active_edit_prediction = this.has_active_edit_prediction();
12959 let display_map = this.display_snapshot(cx);
12960 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12961 let cursor_offset = this
12962 .selections
12963 .last::<MultiBufferOffset>(&display_map)
12964 .head();
12965
12966 if let Some(mut clipboard_selections) = clipboard_selections {
12967 let all_selections_were_entire_line =
12968 clipboard_selections.iter().all(|s| s.is_entire_line);
12969 let first_selection_indent_column =
12970 clipboard_selections.first().map(|s| s.first_line_indent);
12971 if clipboard_selections.len() != old_selections.len() {
12972 clipboard_selections.drain(..);
12973 }
12974 let mut auto_indent_on_paste = true;
12975
12976 this.buffer.update(cx, |buffer, cx| {
12977 let snapshot = buffer.read(cx);
12978 auto_indent_on_paste = snapshot
12979 .language_settings_at(cursor_offset, cx)
12980 .auto_indent_on_paste;
12981
12982 let mut start_offset = 0;
12983 let mut edits = Vec::new();
12984 let mut original_indent_columns = Vec::new();
12985 for (ix, selection) in old_selections.iter().enumerate() {
12986 let to_insert;
12987 let entire_line;
12988 let original_indent_column;
12989 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12990 let end_offset = start_offset + clipboard_selection.len;
12991 to_insert = &clipboard_text[start_offset..end_offset];
12992 entire_line = clipboard_selection.is_entire_line;
12993 start_offset = if entire_line {
12994 end_offset
12995 } else {
12996 end_offset + 1
12997 };
12998 original_indent_column = Some(clipboard_selection.first_line_indent);
12999 } else {
13000 to_insert = &*clipboard_text;
13001 entire_line = all_selections_were_entire_line;
13002 original_indent_column = first_selection_indent_column
13003 }
13004
13005 let (range, to_insert) =
13006 if selection.is_empty() && handle_entire_lines && entire_line {
13007 // If the corresponding selection was empty when this slice of the
13008 // clipboard text was written, then the entire line containing the
13009 // selection was copied. If this selection is also currently empty,
13010 // then paste the line before the current line of the buffer.
13011 let column = selection.start.to_point(&snapshot).column as usize;
13012 let line_start = selection.start - column;
13013 (line_start..line_start, Cow::Borrowed(to_insert))
13014 } else {
13015 let language = snapshot.language_at(selection.head());
13016 let range = selection.range();
13017 if let Some(language) = language
13018 && language.name() == "Markdown".into()
13019 {
13020 edit_for_markdown_paste(
13021 &snapshot,
13022 range,
13023 to_insert,
13024 url::Url::parse(to_insert).ok(),
13025 )
13026 } else {
13027 (range, Cow::Borrowed(to_insert))
13028 }
13029 };
13030
13031 edits.push((range, to_insert));
13032 original_indent_columns.push(original_indent_column);
13033 }
13034 drop(snapshot);
13035
13036 buffer.edit(
13037 edits,
13038 if auto_indent_on_paste {
13039 Some(AutoindentMode::Block {
13040 original_indent_columns,
13041 })
13042 } else {
13043 None
13044 },
13045 cx,
13046 );
13047 });
13048
13049 let selections = this
13050 .selections
13051 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13052 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13053 } else {
13054 let url = url::Url::parse(&clipboard_text).ok();
13055
13056 let auto_indent_mode = if !clipboard_text.is_empty() {
13057 Some(AutoindentMode::Block {
13058 original_indent_columns: Vec::new(),
13059 })
13060 } else {
13061 None
13062 };
13063
13064 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13065 let snapshot = buffer.snapshot(cx);
13066
13067 let anchors = old_selections
13068 .iter()
13069 .map(|s| {
13070 let anchor = snapshot.anchor_after(s.head());
13071 s.map(|_| anchor)
13072 })
13073 .collect::<Vec<_>>();
13074
13075 let mut edits = Vec::new();
13076
13077 for selection in old_selections.iter() {
13078 let language = snapshot.language_at(selection.head());
13079 let range = selection.range();
13080
13081 let (edit_range, edit_text) = if let Some(language) = language
13082 && language.name() == "Markdown".into()
13083 {
13084 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13085 } else {
13086 (range, clipboard_text.clone())
13087 };
13088
13089 edits.push((edit_range, edit_text));
13090 }
13091
13092 drop(snapshot);
13093 buffer.edit(edits, auto_indent_mode, cx);
13094
13095 anchors
13096 });
13097
13098 this.change_selections(Default::default(), window, cx, |s| {
13099 s.select_anchors(selection_anchors);
13100 });
13101 }
13102
13103 // 🤔 | .. | show_in_menu |
13104 // | .. | true true
13105 // | had_edit_prediction | false true
13106
13107 let trigger_in_words =
13108 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13109
13110 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13111 });
13112 }
13113
13114 pub fn diff_clipboard_with_selection(
13115 &mut self,
13116 _: &DiffClipboardWithSelection,
13117 window: &mut Window,
13118 cx: &mut Context<Self>,
13119 ) {
13120 let selections = self
13121 .selections
13122 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13123
13124 if selections.is_empty() {
13125 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13126 return;
13127 };
13128
13129 let clipboard_text = match cx.read_from_clipboard() {
13130 Some(item) => match item.entries().first() {
13131 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13132 _ => None,
13133 },
13134 None => None,
13135 };
13136
13137 let Some(clipboard_text) = clipboard_text else {
13138 log::warn!("Clipboard doesn't contain text.");
13139 return;
13140 };
13141
13142 window.dispatch_action(
13143 Box::new(DiffClipboardWithSelectionData {
13144 clipboard_text,
13145 editor: cx.entity(),
13146 }),
13147 cx,
13148 );
13149 }
13150
13151 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13153 if let Some(item) = cx.read_from_clipboard() {
13154 let entries = item.entries();
13155
13156 match entries.first() {
13157 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13158 // of all the pasted entries.
13159 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13160 .do_paste(
13161 clipboard_string.text(),
13162 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13163 true,
13164 window,
13165 cx,
13166 ),
13167 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13168 }
13169 }
13170 }
13171
13172 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13173 if self.read_only(cx) {
13174 return;
13175 }
13176
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13178
13179 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13180 if let Some((selections, _)) =
13181 self.selection_history.transaction(transaction_id).cloned()
13182 {
13183 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13184 s.select_anchors(selections.to_vec());
13185 });
13186 } else {
13187 log::error!(
13188 "No entry in selection_history found for undo. \
13189 This may correspond to a bug where undo does not update the selection. \
13190 If this is occurring, please add details to \
13191 https://github.com/zed-industries/zed/issues/22692"
13192 );
13193 }
13194 self.request_autoscroll(Autoscroll::fit(), cx);
13195 self.unmark_text(window, cx);
13196 self.refresh_edit_prediction(true, false, window, cx);
13197 cx.emit(EditorEvent::Edited { transaction_id });
13198 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13199 }
13200 }
13201
13202 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13203 if self.read_only(cx) {
13204 return;
13205 }
13206
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208
13209 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13210 if let Some((_, Some(selections))) =
13211 self.selection_history.transaction(transaction_id).cloned()
13212 {
13213 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13214 s.select_anchors(selections.to_vec());
13215 });
13216 } else {
13217 log::error!(
13218 "No entry in selection_history found for redo. \
13219 This may correspond to a bug where undo does not update the selection. \
13220 If this is occurring, please add details to \
13221 https://github.com/zed-industries/zed/issues/22692"
13222 );
13223 }
13224 self.request_autoscroll(Autoscroll::fit(), cx);
13225 self.unmark_text(window, cx);
13226 self.refresh_edit_prediction(true, false, window, cx);
13227 cx.emit(EditorEvent::Edited { transaction_id });
13228 }
13229 }
13230
13231 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13232 self.buffer
13233 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13234 }
13235
13236 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13237 self.buffer
13238 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13239 }
13240
13241 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243 self.change_selections(Default::default(), window, cx, |s| {
13244 s.move_with(|map, selection| {
13245 let cursor = if selection.is_empty() {
13246 movement::left(map, selection.start)
13247 } else {
13248 selection.start
13249 };
13250 selection.collapse_to(cursor, SelectionGoal::None);
13251 });
13252 })
13253 }
13254
13255 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13257 self.change_selections(Default::default(), window, cx, |s| {
13258 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13259 })
13260 }
13261
13262 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264 self.change_selections(Default::default(), window, cx, |s| {
13265 s.move_with(|map, selection| {
13266 let cursor = if selection.is_empty() {
13267 movement::right(map, selection.end)
13268 } else {
13269 selection.end
13270 };
13271 selection.collapse_to(cursor, SelectionGoal::None)
13272 });
13273 })
13274 }
13275
13276 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13278 self.change_selections(Default::default(), window, cx, |s| {
13279 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13280 });
13281 }
13282
13283 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13284 if self.take_rename(true, window, cx).is_some() {
13285 return;
13286 }
13287
13288 if self.mode.is_single_line() {
13289 cx.propagate();
13290 return;
13291 }
13292
13293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13294
13295 let text_layout_details = &self.text_layout_details(window);
13296 let selection_count = self.selections.count();
13297 let first_selection = self.selections.first_anchor();
13298
13299 self.change_selections(Default::default(), window, cx, |s| {
13300 s.move_with(|map, selection| {
13301 if !selection.is_empty() {
13302 selection.goal = SelectionGoal::None;
13303 }
13304 let (cursor, goal) = movement::up(
13305 map,
13306 selection.start,
13307 selection.goal,
13308 false,
13309 text_layout_details,
13310 );
13311 selection.collapse_to(cursor, goal);
13312 });
13313 });
13314
13315 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13316 {
13317 cx.propagate();
13318 }
13319 }
13320
13321 pub fn move_up_by_lines(
13322 &mut self,
13323 action: &MoveUpByLines,
13324 window: &mut Window,
13325 cx: &mut Context<Self>,
13326 ) {
13327 if self.take_rename(true, window, cx).is_some() {
13328 return;
13329 }
13330
13331 if self.mode.is_single_line() {
13332 cx.propagate();
13333 return;
13334 }
13335
13336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13337
13338 let text_layout_details = &self.text_layout_details(window);
13339
13340 self.change_selections(Default::default(), window, cx, |s| {
13341 s.move_with(|map, selection| {
13342 if !selection.is_empty() {
13343 selection.goal = SelectionGoal::None;
13344 }
13345 let (cursor, goal) = movement::up_by_rows(
13346 map,
13347 selection.start,
13348 action.lines,
13349 selection.goal,
13350 false,
13351 text_layout_details,
13352 );
13353 selection.collapse_to(cursor, goal);
13354 });
13355 })
13356 }
13357
13358 pub fn move_down_by_lines(
13359 &mut self,
13360 action: &MoveDownByLines,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 if self.take_rename(true, window, cx).is_some() {
13365 return;
13366 }
13367
13368 if self.mode.is_single_line() {
13369 cx.propagate();
13370 return;
13371 }
13372
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374
13375 let text_layout_details = &self.text_layout_details(window);
13376
13377 self.change_selections(Default::default(), window, cx, |s| {
13378 s.move_with(|map, selection| {
13379 if !selection.is_empty() {
13380 selection.goal = SelectionGoal::None;
13381 }
13382 let (cursor, goal) = movement::down_by_rows(
13383 map,
13384 selection.start,
13385 action.lines,
13386 selection.goal,
13387 false,
13388 text_layout_details,
13389 );
13390 selection.collapse_to(cursor, goal);
13391 });
13392 })
13393 }
13394
13395 pub fn select_down_by_lines(
13396 &mut self,
13397 action: &SelectDownByLines,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13402 let text_layout_details = &self.text_layout_details(window);
13403 self.change_selections(Default::default(), window, cx, |s| {
13404 s.move_heads_with(|map, head, goal| {
13405 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13406 })
13407 })
13408 }
13409
13410 pub fn select_up_by_lines(
13411 &mut self,
13412 action: &SelectUpByLines,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) {
13416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13417 let text_layout_details = &self.text_layout_details(window);
13418 self.change_selections(Default::default(), window, cx, |s| {
13419 s.move_heads_with(|map, head, goal| {
13420 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13421 })
13422 })
13423 }
13424
13425 pub fn select_page_up(
13426 &mut self,
13427 _: &SelectPageUp,
13428 window: &mut Window,
13429 cx: &mut Context<Self>,
13430 ) {
13431 let Some(row_count) = self.visible_row_count() else {
13432 return;
13433 };
13434
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436
13437 let text_layout_details = &self.text_layout_details(window);
13438
13439 self.change_selections(Default::default(), window, cx, |s| {
13440 s.move_heads_with(|map, head, goal| {
13441 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13442 })
13443 })
13444 }
13445
13446 pub fn move_page_up(
13447 &mut self,
13448 action: &MovePageUp,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 if self.take_rename(true, window, cx).is_some() {
13453 return;
13454 }
13455
13456 if self
13457 .context_menu
13458 .borrow_mut()
13459 .as_mut()
13460 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13461 .unwrap_or(false)
13462 {
13463 return;
13464 }
13465
13466 if matches!(self.mode, EditorMode::SingleLine) {
13467 cx.propagate();
13468 return;
13469 }
13470
13471 let Some(row_count) = self.visible_row_count() else {
13472 return;
13473 };
13474
13475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13476
13477 let effects = if action.center_cursor {
13478 SelectionEffects::scroll(Autoscroll::center())
13479 } else {
13480 SelectionEffects::default()
13481 };
13482
13483 let text_layout_details = &self.text_layout_details(window);
13484
13485 self.change_selections(effects, window, cx, |s| {
13486 s.move_with(|map, selection| {
13487 if !selection.is_empty() {
13488 selection.goal = SelectionGoal::None;
13489 }
13490 let (cursor, goal) = movement::up_by_rows(
13491 map,
13492 selection.end,
13493 row_count,
13494 selection.goal,
13495 false,
13496 text_layout_details,
13497 );
13498 selection.collapse_to(cursor, goal);
13499 });
13500 });
13501 }
13502
13503 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13505 let text_layout_details = &self.text_layout_details(window);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_heads_with(|map, head, goal| {
13508 movement::up(map, head, goal, false, text_layout_details)
13509 })
13510 })
13511 }
13512
13513 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13514 self.take_rename(true, window, cx);
13515
13516 if self.mode.is_single_line() {
13517 cx.propagate();
13518 return;
13519 }
13520
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522
13523 let text_layout_details = &self.text_layout_details(window);
13524 let selection_count = self.selections.count();
13525 let first_selection = self.selections.first_anchor();
13526
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 if !selection.is_empty() {
13530 selection.goal = SelectionGoal::None;
13531 }
13532 let (cursor, goal) = movement::down(
13533 map,
13534 selection.end,
13535 selection.goal,
13536 false,
13537 text_layout_details,
13538 );
13539 selection.collapse_to(cursor, goal);
13540 });
13541 });
13542
13543 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13544 {
13545 cx.propagate();
13546 }
13547 }
13548
13549 pub fn select_page_down(
13550 &mut self,
13551 _: &SelectPageDown,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 let Some(row_count) = self.visible_row_count() else {
13556 return;
13557 };
13558
13559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13560
13561 let text_layout_details = &self.text_layout_details(window);
13562
13563 self.change_selections(Default::default(), window, cx, |s| {
13564 s.move_heads_with(|map, head, goal| {
13565 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13566 })
13567 })
13568 }
13569
13570 pub fn move_page_down(
13571 &mut self,
13572 action: &MovePageDown,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 if self.take_rename(true, window, cx).is_some() {
13577 return;
13578 }
13579
13580 if self
13581 .context_menu
13582 .borrow_mut()
13583 .as_mut()
13584 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13585 .unwrap_or(false)
13586 {
13587 return;
13588 }
13589
13590 if matches!(self.mode, EditorMode::SingleLine) {
13591 cx.propagate();
13592 return;
13593 }
13594
13595 let Some(row_count) = self.visible_row_count() else {
13596 return;
13597 };
13598
13599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13600
13601 let effects = if action.center_cursor {
13602 SelectionEffects::scroll(Autoscroll::center())
13603 } else {
13604 SelectionEffects::default()
13605 };
13606
13607 let text_layout_details = &self.text_layout_details(window);
13608 self.change_selections(effects, window, cx, |s| {
13609 s.move_with(|map, selection| {
13610 if !selection.is_empty() {
13611 selection.goal = SelectionGoal::None;
13612 }
13613 let (cursor, goal) = movement::down_by_rows(
13614 map,
13615 selection.end,
13616 row_count,
13617 selection.goal,
13618 false,
13619 text_layout_details,
13620 );
13621 selection.collapse_to(cursor, goal);
13622 });
13623 });
13624 }
13625
13626 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13628 let text_layout_details = &self.text_layout_details(window);
13629 self.change_selections(Default::default(), window, cx, |s| {
13630 s.move_heads_with(|map, head, goal| {
13631 movement::down(map, head, goal, false, text_layout_details)
13632 })
13633 });
13634 }
13635
13636 pub fn context_menu_first(
13637 &mut self,
13638 _: &ContextMenuFirst,
13639 window: &mut Window,
13640 cx: &mut Context<Self>,
13641 ) {
13642 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13643 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13644 }
13645 }
13646
13647 pub fn context_menu_prev(
13648 &mut self,
13649 _: &ContextMenuPrevious,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13654 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13655 }
13656 }
13657
13658 pub fn context_menu_next(
13659 &mut self,
13660 _: &ContextMenuNext,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13665 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13666 }
13667 }
13668
13669 pub fn context_menu_last(
13670 &mut self,
13671 _: &ContextMenuLast,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13676 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13677 }
13678 }
13679
13680 pub fn signature_help_prev(
13681 &mut self,
13682 _: &SignatureHelpPrevious,
13683 _: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 if let Some(popover) = self.signature_help_state.popover_mut() {
13687 if popover.current_signature == 0 {
13688 popover.current_signature = popover.signatures.len() - 1;
13689 } else {
13690 popover.current_signature -= 1;
13691 }
13692 cx.notify();
13693 }
13694 }
13695
13696 pub fn signature_help_next(
13697 &mut self,
13698 _: &SignatureHelpNext,
13699 _: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 if let Some(popover) = self.signature_help_state.popover_mut() {
13703 if popover.current_signature + 1 == popover.signatures.len() {
13704 popover.current_signature = 0;
13705 } else {
13706 popover.current_signature += 1;
13707 }
13708 cx.notify();
13709 }
13710 }
13711
13712 pub fn move_to_previous_word_start(
13713 &mut self,
13714 _: &MoveToPreviousWordStart,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_cursors_with(|map, head, _| {
13721 (
13722 movement::previous_word_start(map, head),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn move_to_previous_subword_start(
13730 &mut self,
13731 _: &MoveToPreviousSubwordStart,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_cursors_with(|map, head, _| {
13738 (
13739 movement::previous_subword_start(map, head),
13740 SelectionGoal::None,
13741 )
13742 });
13743 })
13744 }
13745
13746 pub fn select_to_previous_word_start(
13747 &mut self,
13748 _: &SelectToPreviousWordStart,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.move_heads_with(|map, head, _| {
13755 (
13756 movement::previous_word_start(map, head),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn select_to_previous_subword_start(
13764 &mut self,
13765 _: &SelectToPreviousSubwordStart,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13770 self.change_selections(Default::default(), window, cx, |s| {
13771 s.move_heads_with(|map, head, _| {
13772 (
13773 movement::previous_subword_start(map, head),
13774 SelectionGoal::None,
13775 )
13776 });
13777 })
13778 }
13779
13780 pub fn delete_to_previous_word_start(
13781 &mut self,
13782 action: &DeleteToPreviousWordStart,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13787 self.transact(window, cx, |this, window, cx| {
13788 this.select_autoclose_pair(window, cx);
13789 this.change_selections(Default::default(), window, cx, |s| {
13790 s.move_with(|map, selection| {
13791 if selection.is_empty() {
13792 let mut cursor = if action.ignore_newlines {
13793 movement::previous_word_start(map, selection.head())
13794 } else {
13795 movement::previous_word_start_or_newline(map, selection.head())
13796 };
13797 cursor = movement::adjust_greedy_deletion(
13798 map,
13799 selection.head(),
13800 cursor,
13801 action.ignore_brackets,
13802 );
13803 selection.set_head(cursor, SelectionGoal::None);
13804 }
13805 });
13806 });
13807 this.insert("", window, cx);
13808 });
13809 }
13810
13811 pub fn delete_to_previous_subword_start(
13812 &mut self,
13813 _: &DeleteToPreviousSubwordStart,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13818 self.transact(window, cx, |this, window, cx| {
13819 this.select_autoclose_pair(window, cx);
13820 this.change_selections(Default::default(), window, cx, |s| {
13821 s.move_with(|map, selection| {
13822 if selection.is_empty() {
13823 let mut cursor = movement::previous_subword_start(map, selection.head());
13824 cursor =
13825 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13826 selection.set_head(cursor, SelectionGoal::None);
13827 }
13828 });
13829 });
13830 this.insert("", window, cx);
13831 });
13832 }
13833
13834 pub fn move_to_next_word_end(
13835 &mut self,
13836 _: &MoveToNextWordEnd,
13837 window: &mut Window,
13838 cx: &mut Context<Self>,
13839 ) {
13840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13841 self.change_selections(Default::default(), window, cx, |s| {
13842 s.move_cursors_with(|map, head, _| {
13843 (movement::next_word_end(map, head), SelectionGoal::None)
13844 });
13845 })
13846 }
13847
13848 pub fn move_to_next_subword_end(
13849 &mut self,
13850 _: &MoveToNextSubwordEnd,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) {
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13855 self.change_selections(Default::default(), window, cx, |s| {
13856 s.move_cursors_with(|map, head, _| {
13857 (movement::next_subword_end(map, head), SelectionGoal::None)
13858 });
13859 })
13860 }
13861
13862 pub fn select_to_next_word_end(
13863 &mut self,
13864 _: &SelectToNextWordEnd,
13865 window: &mut Window,
13866 cx: &mut Context<Self>,
13867 ) {
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.move_heads_with(|map, head, _| {
13871 (movement::next_word_end(map, head), SelectionGoal::None)
13872 });
13873 })
13874 }
13875
13876 pub fn select_to_next_subword_end(
13877 &mut self,
13878 _: &SelectToNextSubwordEnd,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) {
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 self.change_selections(Default::default(), window, cx, |s| {
13884 s.move_heads_with(|map, head, _| {
13885 (movement::next_subword_end(map, head), SelectionGoal::None)
13886 });
13887 })
13888 }
13889
13890 pub fn delete_to_next_word_end(
13891 &mut self,
13892 action: &DeleteToNextWordEnd,
13893 window: &mut Window,
13894 cx: &mut Context<Self>,
13895 ) {
13896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13897 self.transact(window, cx, |this, window, cx| {
13898 this.change_selections(Default::default(), window, cx, |s| {
13899 s.move_with(|map, selection| {
13900 if selection.is_empty() {
13901 let mut cursor = if action.ignore_newlines {
13902 movement::next_word_end(map, selection.head())
13903 } else {
13904 movement::next_word_end_or_newline(map, selection.head())
13905 };
13906 cursor = movement::adjust_greedy_deletion(
13907 map,
13908 selection.head(),
13909 cursor,
13910 action.ignore_brackets,
13911 );
13912 selection.set_head(cursor, SelectionGoal::None);
13913 }
13914 });
13915 });
13916 this.insert("", window, cx);
13917 });
13918 }
13919
13920 pub fn delete_to_next_subword_end(
13921 &mut self,
13922 _: &DeleteToNextSubwordEnd,
13923 window: &mut Window,
13924 cx: &mut Context<Self>,
13925 ) {
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13927 self.transact(window, cx, |this, window, cx| {
13928 this.change_selections(Default::default(), window, cx, |s| {
13929 s.move_with(|map, selection| {
13930 if selection.is_empty() {
13931 let mut cursor = movement::next_subword_end(map, selection.head());
13932 cursor =
13933 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13934 selection.set_head(cursor, SelectionGoal::None);
13935 }
13936 });
13937 });
13938 this.insert("", window, cx);
13939 });
13940 }
13941
13942 pub fn move_to_beginning_of_line(
13943 &mut self,
13944 action: &MoveToBeginningOfLine,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13949 self.change_selections(Default::default(), window, cx, |s| {
13950 s.move_cursors_with(|map, head, _| {
13951 (
13952 movement::indented_line_beginning(
13953 map,
13954 head,
13955 action.stop_at_soft_wraps,
13956 action.stop_at_indent,
13957 ),
13958 SelectionGoal::None,
13959 )
13960 });
13961 })
13962 }
13963
13964 pub fn select_to_beginning_of_line(
13965 &mut self,
13966 action: &SelectToBeginningOfLine,
13967 window: &mut Window,
13968 cx: &mut Context<Self>,
13969 ) {
13970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13971 self.change_selections(Default::default(), window, cx, |s| {
13972 s.move_heads_with(|map, head, _| {
13973 (
13974 movement::indented_line_beginning(
13975 map,
13976 head,
13977 action.stop_at_soft_wraps,
13978 action.stop_at_indent,
13979 ),
13980 SelectionGoal::None,
13981 )
13982 });
13983 });
13984 }
13985
13986 pub fn delete_to_beginning_of_line(
13987 &mut self,
13988 action: &DeleteToBeginningOfLine,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13993 self.transact(window, cx, |this, window, cx| {
13994 this.change_selections(Default::default(), window, cx, |s| {
13995 s.move_with(|_, selection| {
13996 selection.reversed = true;
13997 });
13998 });
13999
14000 this.select_to_beginning_of_line(
14001 &SelectToBeginningOfLine {
14002 stop_at_soft_wraps: false,
14003 stop_at_indent: action.stop_at_indent,
14004 },
14005 window,
14006 cx,
14007 );
14008 this.backspace(&Backspace, window, cx);
14009 });
14010 }
14011
14012 pub fn move_to_end_of_line(
14013 &mut self,
14014 action: &MoveToEndOfLine,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.move_cursors_with(|map, head, _| {
14021 (
14022 movement::line_end(map, head, action.stop_at_soft_wraps),
14023 SelectionGoal::None,
14024 )
14025 });
14026 })
14027 }
14028
14029 pub fn select_to_end_of_line(
14030 &mut self,
14031 action: &SelectToEndOfLine,
14032 window: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14036 self.change_selections(Default::default(), window, cx, |s| {
14037 s.move_heads_with(|map, head, _| {
14038 (
14039 movement::line_end(map, head, action.stop_at_soft_wraps),
14040 SelectionGoal::None,
14041 )
14042 });
14043 })
14044 }
14045
14046 pub fn delete_to_end_of_line(
14047 &mut self,
14048 _: &DeleteToEndOfLine,
14049 window: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14053 self.transact(window, cx, |this, window, cx| {
14054 this.select_to_end_of_line(
14055 &SelectToEndOfLine {
14056 stop_at_soft_wraps: false,
14057 },
14058 window,
14059 cx,
14060 );
14061 this.delete(&Delete, window, cx);
14062 });
14063 }
14064
14065 pub fn cut_to_end_of_line(
14066 &mut self,
14067 action: &CutToEndOfLine,
14068 window: &mut Window,
14069 cx: &mut Context<Self>,
14070 ) {
14071 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14072 self.transact(window, cx, |this, window, cx| {
14073 this.select_to_end_of_line(
14074 &SelectToEndOfLine {
14075 stop_at_soft_wraps: false,
14076 },
14077 window,
14078 cx,
14079 );
14080 if !action.stop_at_newlines {
14081 this.change_selections(Default::default(), window, cx, |s| {
14082 s.move_with(|_, sel| {
14083 if sel.is_empty() {
14084 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14085 }
14086 });
14087 });
14088 }
14089 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14090 let item = this.cut_common(false, window, cx);
14091 cx.write_to_clipboard(item);
14092 });
14093 }
14094
14095 pub fn move_to_start_of_paragraph(
14096 &mut self,
14097 _: &MoveToStartOfParagraph,
14098 window: &mut Window,
14099 cx: &mut Context<Self>,
14100 ) {
14101 if matches!(self.mode, EditorMode::SingleLine) {
14102 cx.propagate();
14103 return;
14104 }
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14106 self.change_selections(Default::default(), window, cx, |s| {
14107 s.move_with(|map, selection| {
14108 selection.collapse_to(
14109 movement::start_of_paragraph(map, selection.head(), 1),
14110 SelectionGoal::None,
14111 )
14112 });
14113 })
14114 }
14115
14116 pub fn move_to_end_of_paragraph(
14117 &mut self,
14118 _: &MoveToEndOfParagraph,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) {
14122 if matches!(self.mode, EditorMode::SingleLine) {
14123 cx.propagate();
14124 return;
14125 }
14126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14127 self.change_selections(Default::default(), window, cx, |s| {
14128 s.move_with(|map, selection| {
14129 selection.collapse_to(
14130 movement::end_of_paragraph(map, selection.head(), 1),
14131 SelectionGoal::None,
14132 )
14133 });
14134 })
14135 }
14136
14137 pub fn select_to_start_of_paragraph(
14138 &mut self,
14139 _: &SelectToStartOfParagraph,
14140 window: &mut Window,
14141 cx: &mut Context<Self>,
14142 ) {
14143 if matches!(self.mode, EditorMode::SingleLine) {
14144 cx.propagate();
14145 return;
14146 }
14147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14148 self.change_selections(Default::default(), window, cx, |s| {
14149 s.move_heads_with(|map, head, _| {
14150 (
14151 movement::start_of_paragraph(map, head, 1),
14152 SelectionGoal::None,
14153 )
14154 });
14155 })
14156 }
14157
14158 pub fn select_to_end_of_paragraph(
14159 &mut self,
14160 _: &SelectToEndOfParagraph,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 if matches!(self.mode, EditorMode::SingleLine) {
14165 cx.propagate();
14166 return;
14167 }
14168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14169 self.change_selections(Default::default(), window, cx, |s| {
14170 s.move_heads_with(|map, head, _| {
14171 (
14172 movement::end_of_paragraph(map, head, 1),
14173 SelectionGoal::None,
14174 )
14175 });
14176 })
14177 }
14178
14179 pub fn move_to_start_of_excerpt(
14180 &mut self,
14181 _: &MoveToStartOfExcerpt,
14182 window: &mut Window,
14183 cx: &mut Context<Self>,
14184 ) {
14185 if matches!(self.mode, EditorMode::SingleLine) {
14186 cx.propagate();
14187 return;
14188 }
14189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14190 self.change_selections(Default::default(), window, cx, |s| {
14191 s.move_with(|map, selection| {
14192 selection.collapse_to(
14193 movement::start_of_excerpt(
14194 map,
14195 selection.head(),
14196 workspace::searchable::Direction::Prev,
14197 ),
14198 SelectionGoal::None,
14199 )
14200 });
14201 })
14202 }
14203
14204 pub fn move_to_start_of_next_excerpt(
14205 &mut self,
14206 _: &MoveToStartOfNextExcerpt,
14207 window: &mut Window,
14208 cx: &mut Context<Self>,
14209 ) {
14210 if matches!(self.mode, EditorMode::SingleLine) {
14211 cx.propagate();
14212 return;
14213 }
14214
14215 self.change_selections(Default::default(), window, cx, |s| {
14216 s.move_with(|map, selection| {
14217 selection.collapse_to(
14218 movement::start_of_excerpt(
14219 map,
14220 selection.head(),
14221 workspace::searchable::Direction::Next,
14222 ),
14223 SelectionGoal::None,
14224 )
14225 });
14226 })
14227 }
14228
14229 pub fn move_to_end_of_excerpt(
14230 &mut self,
14231 _: &MoveToEndOfExcerpt,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 if matches!(self.mode, EditorMode::SingleLine) {
14236 cx.propagate();
14237 return;
14238 }
14239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14240 self.change_selections(Default::default(), window, cx, |s| {
14241 s.move_with(|map, selection| {
14242 selection.collapse_to(
14243 movement::end_of_excerpt(
14244 map,
14245 selection.head(),
14246 workspace::searchable::Direction::Next,
14247 ),
14248 SelectionGoal::None,
14249 )
14250 });
14251 })
14252 }
14253
14254 pub fn move_to_end_of_previous_excerpt(
14255 &mut self,
14256 _: &MoveToEndOfPreviousExcerpt,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 if matches!(self.mode, EditorMode::SingleLine) {
14261 cx.propagate();
14262 return;
14263 }
14264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14265 self.change_selections(Default::default(), window, cx, |s| {
14266 s.move_with(|map, selection| {
14267 selection.collapse_to(
14268 movement::end_of_excerpt(
14269 map,
14270 selection.head(),
14271 workspace::searchable::Direction::Prev,
14272 ),
14273 SelectionGoal::None,
14274 )
14275 });
14276 })
14277 }
14278
14279 pub fn select_to_start_of_excerpt(
14280 &mut self,
14281 _: &SelectToStartOfExcerpt,
14282 window: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 if matches!(self.mode, EditorMode::SingleLine) {
14286 cx.propagate();
14287 return;
14288 }
14289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14290 self.change_selections(Default::default(), window, cx, |s| {
14291 s.move_heads_with(|map, head, _| {
14292 (
14293 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14294 SelectionGoal::None,
14295 )
14296 });
14297 })
14298 }
14299
14300 pub fn select_to_start_of_next_excerpt(
14301 &mut self,
14302 _: &SelectToStartOfNextExcerpt,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) {
14306 if matches!(self.mode, EditorMode::SingleLine) {
14307 cx.propagate();
14308 return;
14309 }
14310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14311 self.change_selections(Default::default(), window, cx, |s| {
14312 s.move_heads_with(|map, head, _| {
14313 (
14314 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14315 SelectionGoal::None,
14316 )
14317 });
14318 })
14319 }
14320
14321 pub fn select_to_end_of_excerpt(
14322 &mut self,
14323 _: &SelectToEndOfExcerpt,
14324 window: &mut Window,
14325 cx: &mut Context<Self>,
14326 ) {
14327 if matches!(self.mode, EditorMode::SingleLine) {
14328 cx.propagate();
14329 return;
14330 }
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14332 self.change_selections(Default::default(), window, cx, |s| {
14333 s.move_heads_with(|map, head, _| {
14334 (
14335 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14336 SelectionGoal::None,
14337 )
14338 });
14339 })
14340 }
14341
14342 pub fn select_to_end_of_previous_excerpt(
14343 &mut self,
14344 _: &SelectToEndOfPreviousExcerpt,
14345 window: &mut Window,
14346 cx: &mut Context<Self>,
14347 ) {
14348 if matches!(self.mode, EditorMode::SingleLine) {
14349 cx.propagate();
14350 return;
14351 }
14352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14353 self.change_selections(Default::default(), window, cx, |s| {
14354 s.move_heads_with(|map, head, _| {
14355 (
14356 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14357 SelectionGoal::None,
14358 )
14359 });
14360 })
14361 }
14362
14363 pub fn move_to_beginning(
14364 &mut self,
14365 _: &MoveToBeginning,
14366 window: &mut Window,
14367 cx: &mut Context<Self>,
14368 ) {
14369 if matches!(self.mode, EditorMode::SingleLine) {
14370 cx.propagate();
14371 return;
14372 }
14373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14374 self.change_selections(Default::default(), window, cx, |s| {
14375 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14376 });
14377 }
14378
14379 pub fn select_to_beginning(
14380 &mut self,
14381 _: &SelectToBeginning,
14382 window: &mut Window,
14383 cx: &mut Context<Self>,
14384 ) {
14385 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14386 selection.set_head(Point::zero(), SelectionGoal::None);
14387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14388 self.change_selections(Default::default(), window, cx, |s| {
14389 s.select(vec![selection]);
14390 });
14391 }
14392
14393 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14394 if matches!(self.mode, EditorMode::SingleLine) {
14395 cx.propagate();
14396 return;
14397 }
14398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14399 let cursor = self.buffer.read(cx).read(cx).len();
14400 self.change_selections(Default::default(), window, cx, |s| {
14401 s.select_ranges(vec![cursor..cursor])
14402 });
14403 }
14404
14405 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14406 self.nav_history = nav_history;
14407 }
14408
14409 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14410 self.nav_history.as_ref()
14411 }
14412
14413 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14414 self.push_to_nav_history(
14415 self.selections.newest_anchor().head(),
14416 None,
14417 false,
14418 true,
14419 cx,
14420 );
14421 }
14422
14423 fn push_to_nav_history(
14424 &mut self,
14425 cursor_anchor: Anchor,
14426 new_position: Option<Point>,
14427 is_deactivate: bool,
14428 always: bool,
14429 cx: &mut Context<Self>,
14430 ) {
14431 if let Some(nav_history) = self.nav_history.as_mut() {
14432 let buffer = self.buffer.read(cx).read(cx);
14433 let cursor_position = cursor_anchor.to_point(&buffer);
14434 let scroll_state = self.scroll_manager.anchor();
14435 let scroll_top_row = scroll_state.top_row(&buffer);
14436 drop(buffer);
14437
14438 if let Some(new_position) = new_position {
14439 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14440 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14441 return;
14442 }
14443 }
14444
14445 nav_history.push(
14446 Some(NavigationData {
14447 cursor_anchor,
14448 cursor_position,
14449 scroll_anchor: scroll_state,
14450 scroll_top_row,
14451 }),
14452 cx,
14453 );
14454 cx.emit(EditorEvent::PushedToNavHistory {
14455 anchor: cursor_anchor,
14456 is_deactivate,
14457 })
14458 }
14459 }
14460
14461 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14463 let buffer = self.buffer.read(cx).snapshot(cx);
14464 let mut selection = self
14465 .selections
14466 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14467 selection.set_head(buffer.len(), SelectionGoal::None);
14468 self.change_selections(Default::default(), window, cx, |s| {
14469 s.select(vec![selection]);
14470 });
14471 }
14472
14473 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14475 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14476 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14477 });
14478 }
14479
14480 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14483 let mut selections = self.selections.all::<Point>(&display_map);
14484 let max_point = display_map.buffer_snapshot().max_point();
14485 for selection in &mut selections {
14486 let rows = selection.spanned_rows(true, &display_map);
14487 selection.start = Point::new(rows.start.0, 0);
14488 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14489 selection.reversed = false;
14490 }
14491 self.change_selections(Default::default(), window, cx, |s| {
14492 s.select(selections);
14493 });
14494 }
14495
14496 pub fn split_selection_into_lines(
14497 &mut self,
14498 action: &SplitSelectionIntoLines,
14499 window: &mut Window,
14500 cx: &mut Context<Self>,
14501 ) {
14502 let selections = self
14503 .selections
14504 .all::<Point>(&self.display_snapshot(cx))
14505 .into_iter()
14506 .map(|selection| selection.start..selection.end)
14507 .collect::<Vec<_>>();
14508 self.unfold_ranges(&selections, true, true, cx);
14509
14510 let mut new_selection_ranges = Vec::new();
14511 {
14512 let buffer = self.buffer.read(cx).read(cx);
14513 for selection in selections {
14514 for row in selection.start.row..selection.end.row {
14515 let line_start = Point::new(row, 0);
14516 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14517
14518 if action.keep_selections {
14519 // Keep the selection range for each line
14520 let selection_start = if row == selection.start.row {
14521 selection.start
14522 } else {
14523 line_start
14524 };
14525 new_selection_ranges.push(selection_start..line_end);
14526 } else {
14527 // Collapse to cursor at end of line
14528 new_selection_ranges.push(line_end..line_end);
14529 }
14530 }
14531
14532 let is_multiline_selection = selection.start.row != selection.end.row;
14533 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14534 // so this action feels more ergonomic when paired with other selection operations
14535 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14536 if !should_skip_last {
14537 if action.keep_selections {
14538 if is_multiline_selection {
14539 let line_start = Point::new(selection.end.row, 0);
14540 new_selection_ranges.push(line_start..selection.end);
14541 } else {
14542 new_selection_ranges.push(selection.start..selection.end);
14543 }
14544 } else {
14545 new_selection_ranges.push(selection.end..selection.end);
14546 }
14547 }
14548 }
14549 }
14550 self.change_selections(Default::default(), window, cx, |s| {
14551 s.select_ranges(new_selection_ranges);
14552 });
14553 }
14554
14555 pub fn add_selection_above(
14556 &mut self,
14557 action: &AddSelectionAbove,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) {
14561 self.add_selection(true, action.skip_soft_wrap, window, cx);
14562 }
14563
14564 pub fn add_selection_below(
14565 &mut self,
14566 action: &AddSelectionBelow,
14567 window: &mut Window,
14568 cx: &mut Context<Self>,
14569 ) {
14570 self.add_selection(false, action.skip_soft_wrap, window, cx);
14571 }
14572
14573 fn add_selection(
14574 &mut self,
14575 above: bool,
14576 skip_soft_wrap: bool,
14577 window: &mut Window,
14578 cx: &mut Context<Self>,
14579 ) {
14580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14581
14582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14583 let all_selections = self.selections.all::<Point>(&display_map);
14584 let text_layout_details = self.text_layout_details(window);
14585
14586 let (mut columnar_selections, new_selections_to_columnarize) = {
14587 if let Some(state) = self.add_selections_state.as_ref() {
14588 let columnar_selection_ids: HashSet<_> = state
14589 .groups
14590 .iter()
14591 .flat_map(|group| group.stack.iter())
14592 .copied()
14593 .collect();
14594
14595 all_selections
14596 .into_iter()
14597 .partition(|s| columnar_selection_ids.contains(&s.id))
14598 } else {
14599 (Vec::new(), all_selections)
14600 }
14601 };
14602
14603 let mut state = self
14604 .add_selections_state
14605 .take()
14606 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14607
14608 for selection in new_selections_to_columnarize {
14609 let range = selection.display_range(&display_map).sorted();
14610 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14611 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14612 let positions = start_x.min(end_x)..start_x.max(end_x);
14613 let mut stack = Vec::new();
14614 for row in range.start.row().0..=range.end.row().0 {
14615 if let Some(selection) = self.selections.build_columnar_selection(
14616 &display_map,
14617 DisplayRow(row),
14618 &positions,
14619 selection.reversed,
14620 &text_layout_details,
14621 ) {
14622 stack.push(selection.id);
14623 columnar_selections.push(selection);
14624 }
14625 }
14626 if !stack.is_empty() {
14627 if above {
14628 stack.reverse();
14629 }
14630 state.groups.push(AddSelectionsGroup { above, stack });
14631 }
14632 }
14633
14634 let mut final_selections = Vec::new();
14635 let end_row = if above {
14636 DisplayRow(0)
14637 } else {
14638 display_map.max_point().row()
14639 };
14640
14641 let mut last_added_item_per_group = HashMap::default();
14642 for group in state.groups.iter_mut() {
14643 if let Some(last_id) = group.stack.last() {
14644 last_added_item_per_group.insert(*last_id, group);
14645 }
14646 }
14647
14648 for selection in columnar_selections {
14649 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14650 if above == group.above {
14651 let range = selection.display_range(&display_map).sorted();
14652 debug_assert_eq!(range.start.row(), range.end.row());
14653 let mut row = range.start.row();
14654 let positions =
14655 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14656 Pixels::from(start)..Pixels::from(end)
14657 } else {
14658 let start_x =
14659 display_map.x_for_display_point(range.start, &text_layout_details);
14660 let end_x =
14661 display_map.x_for_display_point(range.end, &text_layout_details);
14662 start_x.min(end_x)..start_x.max(end_x)
14663 };
14664
14665 let mut maybe_new_selection = None;
14666 let direction = if above { -1 } else { 1 };
14667
14668 while row != end_row {
14669 if skip_soft_wrap {
14670 row = display_map
14671 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14672 .row();
14673 } else if above {
14674 row.0 -= 1;
14675 } else {
14676 row.0 += 1;
14677 }
14678
14679 if let Some(new_selection) = self.selections.build_columnar_selection(
14680 &display_map,
14681 row,
14682 &positions,
14683 selection.reversed,
14684 &text_layout_details,
14685 ) {
14686 maybe_new_selection = Some(new_selection);
14687 break;
14688 }
14689 }
14690
14691 if let Some(new_selection) = maybe_new_selection {
14692 group.stack.push(new_selection.id);
14693 if above {
14694 final_selections.push(new_selection);
14695 final_selections.push(selection);
14696 } else {
14697 final_selections.push(selection);
14698 final_selections.push(new_selection);
14699 }
14700 } else {
14701 final_selections.push(selection);
14702 }
14703 } else {
14704 group.stack.pop();
14705 }
14706 } else {
14707 final_selections.push(selection);
14708 }
14709 }
14710
14711 self.change_selections(Default::default(), window, cx, |s| {
14712 s.select(final_selections);
14713 });
14714
14715 let final_selection_ids: HashSet<_> = self
14716 .selections
14717 .all::<Point>(&display_map)
14718 .iter()
14719 .map(|s| s.id)
14720 .collect();
14721 state.groups.retain_mut(|group| {
14722 // selections might get merged above so we remove invalid items from stacks
14723 group.stack.retain(|id| final_selection_ids.contains(id));
14724
14725 // single selection in stack can be treated as initial state
14726 group.stack.len() > 1
14727 });
14728
14729 if !state.groups.is_empty() {
14730 self.add_selections_state = Some(state);
14731 }
14732 }
14733
14734 fn select_match_ranges(
14735 &mut self,
14736 range: Range<MultiBufferOffset>,
14737 reversed: bool,
14738 replace_newest: bool,
14739 auto_scroll: Option<Autoscroll>,
14740 window: &mut Window,
14741 cx: &mut Context<Editor>,
14742 ) {
14743 self.unfold_ranges(
14744 std::slice::from_ref(&range),
14745 false,
14746 auto_scroll.is_some(),
14747 cx,
14748 );
14749 let effects = if let Some(scroll) = auto_scroll {
14750 SelectionEffects::scroll(scroll)
14751 } else {
14752 SelectionEffects::no_scroll()
14753 };
14754 self.change_selections(effects, window, cx, |s| {
14755 if replace_newest {
14756 s.delete(s.newest_anchor().id);
14757 }
14758 if reversed {
14759 s.insert_range(range.end..range.start);
14760 } else {
14761 s.insert_range(range);
14762 }
14763 });
14764 }
14765
14766 pub fn select_next_match_internal(
14767 &mut self,
14768 display_map: &DisplaySnapshot,
14769 replace_newest: bool,
14770 autoscroll: Option<Autoscroll>,
14771 window: &mut Window,
14772 cx: &mut Context<Self>,
14773 ) -> Result<()> {
14774 let buffer = display_map.buffer_snapshot();
14775 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14776 if let Some(mut select_next_state) = self.select_next_state.take() {
14777 let query = &select_next_state.query;
14778 if !select_next_state.done {
14779 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14780 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14781 let mut next_selected_range = None;
14782
14783 let bytes_after_last_selection =
14784 buffer.bytes_in_range(last_selection.end..buffer.len());
14785 let bytes_before_first_selection =
14786 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14787 let query_matches = query
14788 .stream_find_iter(bytes_after_last_selection)
14789 .map(|result| (last_selection.end, result))
14790 .chain(
14791 query
14792 .stream_find_iter(bytes_before_first_selection)
14793 .map(|result| (MultiBufferOffset(0), result)),
14794 );
14795
14796 for (start_offset, query_match) in query_matches {
14797 let query_match = query_match.unwrap(); // can only fail due to I/O
14798 let offset_range =
14799 start_offset + query_match.start()..start_offset + query_match.end();
14800
14801 if !select_next_state.wordwise
14802 || (!buffer.is_inside_word(offset_range.start, None)
14803 && !buffer.is_inside_word(offset_range.end, None))
14804 {
14805 let idx = selections
14806 .partition_point(|selection| selection.end <= offset_range.start);
14807 let overlaps = selections
14808 .get(idx)
14809 .map_or(false, |selection| selection.start < offset_range.end);
14810
14811 if !overlaps {
14812 next_selected_range = Some(offset_range);
14813 break;
14814 }
14815 }
14816 }
14817
14818 if let Some(next_selected_range) = next_selected_range {
14819 self.select_match_ranges(
14820 next_selected_range,
14821 last_selection.reversed,
14822 replace_newest,
14823 autoscroll,
14824 window,
14825 cx,
14826 );
14827 } else {
14828 select_next_state.done = true;
14829 }
14830 }
14831
14832 self.select_next_state = Some(select_next_state);
14833 } else {
14834 let mut only_carets = true;
14835 let mut same_text_selected = true;
14836 let mut selected_text = None;
14837
14838 let mut selections_iter = selections.iter().peekable();
14839 while let Some(selection) = selections_iter.next() {
14840 if selection.start != selection.end {
14841 only_carets = false;
14842 }
14843
14844 if same_text_selected {
14845 if selected_text.is_none() {
14846 selected_text =
14847 Some(buffer.text_for_range(selection.range()).collect::<String>());
14848 }
14849
14850 if let Some(next_selection) = selections_iter.peek() {
14851 if next_selection.len() == selection.len() {
14852 let next_selected_text = buffer
14853 .text_for_range(next_selection.range())
14854 .collect::<String>();
14855 if Some(next_selected_text) != selected_text {
14856 same_text_selected = false;
14857 selected_text = None;
14858 }
14859 } else {
14860 same_text_selected = false;
14861 selected_text = None;
14862 }
14863 }
14864 }
14865 }
14866
14867 if only_carets {
14868 for selection in &mut selections {
14869 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14870 selection.start = word_range.start;
14871 selection.end = word_range.end;
14872 selection.goal = SelectionGoal::None;
14873 selection.reversed = false;
14874 self.select_match_ranges(
14875 selection.start..selection.end,
14876 selection.reversed,
14877 replace_newest,
14878 autoscroll,
14879 window,
14880 cx,
14881 );
14882 }
14883
14884 if selections.len() == 1 {
14885 let selection = selections
14886 .last()
14887 .expect("ensured that there's only one selection");
14888 let query = buffer
14889 .text_for_range(selection.start..selection.end)
14890 .collect::<String>();
14891 let is_empty = query.is_empty();
14892 let select_state = SelectNextState {
14893 query: self.build_query(&[query], cx)?,
14894 wordwise: true,
14895 done: is_empty,
14896 };
14897 self.select_next_state = Some(select_state);
14898 } else {
14899 self.select_next_state = None;
14900 }
14901 } else if let Some(selected_text) = selected_text {
14902 self.select_next_state = Some(SelectNextState {
14903 query: self.build_query(&[selected_text], cx)?,
14904 wordwise: false,
14905 done: false,
14906 });
14907 self.select_next_match_internal(
14908 display_map,
14909 replace_newest,
14910 autoscroll,
14911 window,
14912 cx,
14913 )?;
14914 }
14915 }
14916 Ok(())
14917 }
14918
14919 pub fn select_all_matches(
14920 &mut self,
14921 _action: &SelectAllMatches,
14922 window: &mut Window,
14923 cx: &mut Context<Self>,
14924 ) -> Result<()> {
14925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14926
14927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14928
14929 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14930 let Some(select_next_state) = self.select_next_state.as_mut() else {
14931 return Ok(());
14932 };
14933 if select_next_state.done {
14934 return Ok(());
14935 }
14936
14937 let mut new_selections = Vec::new();
14938
14939 let reversed = self
14940 .selections
14941 .oldest::<MultiBufferOffset>(&display_map)
14942 .reversed;
14943 let buffer = display_map.buffer_snapshot();
14944 let query_matches = select_next_state
14945 .query
14946 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14947
14948 for query_match in query_matches.into_iter() {
14949 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14950 let offset_range = if reversed {
14951 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14952 } else {
14953 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14954 };
14955
14956 if !select_next_state.wordwise
14957 || (!buffer.is_inside_word(offset_range.start, None)
14958 && !buffer.is_inside_word(offset_range.end, None))
14959 {
14960 new_selections.push(offset_range.start..offset_range.end);
14961 }
14962 }
14963
14964 select_next_state.done = true;
14965
14966 if new_selections.is_empty() {
14967 log::error!("bug: new_selections is empty in select_all_matches");
14968 return Ok(());
14969 }
14970
14971 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14972 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14973 selections.select_ranges(new_selections)
14974 });
14975
14976 Ok(())
14977 }
14978
14979 pub fn select_next(
14980 &mut self,
14981 action: &SelectNext,
14982 window: &mut Window,
14983 cx: &mut Context<Self>,
14984 ) -> Result<()> {
14985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14986 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14987 self.select_next_match_internal(
14988 &display_map,
14989 action.replace_newest,
14990 Some(Autoscroll::newest()),
14991 window,
14992 cx,
14993 )?;
14994 Ok(())
14995 }
14996
14997 pub fn select_previous(
14998 &mut self,
14999 action: &SelectPrevious,
15000 window: &mut Window,
15001 cx: &mut Context<Self>,
15002 ) -> Result<()> {
15003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15004 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15005 let buffer = display_map.buffer_snapshot();
15006 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15007 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15008 let query = &select_prev_state.query;
15009 if !select_prev_state.done {
15010 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15011 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15012 let mut next_selected_range = None;
15013 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15014 let bytes_before_last_selection =
15015 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15016 let bytes_after_first_selection =
15017 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15018 let query_matches = query
15019 .stream_find_iter(bytes_before_last_selection)
15020 .map(|result| (last_selection.start, result))
15021 .chain(
15022 query
15023 .stream_find_iter(bytes_after_first_selection)
15024 .map(|result| (buffer.len(), result)),
15025 );
15026 for (end_offset, query_match) in query_matches {
15027 let query_match = query_match.unwrap(); // can only fail due to I/O
15028 let offset_range =
15029 end_offset - query_match.end()..end_offset - query_match.start();
15030
15031 if !select_prev_state.wordwise
15032 || (!buffer.is_inside_word(offset_range.start, None)
15033 && !buffer.is_inside_word(offset_range.end, None))
15034 {
15035 next_selected_range = Some(offset_range);
15036 break;
15037 }
15038 }
15039
15040 if let Some(next_selected_range) = next_selected_range {
15041 self.select_match_ranges(
15042 next_selected_range,
15043 last_selection.reversed,
15044 action.replace_newest,
15045 Some(Autoscroll::newest()),
15046 window,
15047 cx,
15048 );
15049 } else {
15050 select_prev_state.done = true;
15051 }
15052 }
15053
15054 self.select_prev_state = Some(select_prev_state);
15055 } else {
15056 let mut only_carets = true;
15057 let mut same_text_selected = true;
15058 let mut selected_text = None;
15059
15060 let mut selections_iter = selections.iter().peekable();
15061 while let Some(selection) = selections_iter.next() {
15062 if selection.start != selection.end {
15063 only_carets = false;
15064 }
15065
15066 if same_text_selected {
15067 if selected_text.is_none() {
15068 selected_text =
15069 Some(buffer.text_for_range(selection.range()).collect::<String>());
15070 }
15071
15072 if let Some(next_selection) = selections_iter.peek() {
15073 if next_selection.len() == selection.len() {
15074 let next_selected_text = buffer
15075 .text_for_range(next_selection.range())
15076 .collect::<String>();
15077 if Some(next_selected_text) != selected_text {
15078 same_text_selected = false;
15079 selected_text = None;
15080 }
15081 } else {
15082 same_text_selected = false;
15083 selected_text = None;
15084 }
15085 }
15086 }
15087 }
15088
15089 if only_carets {
15090 for selection in &mut selections {
15091 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15092 selection.start = word_range.start;
15093 selection.end = word_range.end;
15094 selection.goal = SelectionGoal::None;
15095 selection.reversed = false;
15096 self.select_match_ranges(
15097 selection.start..selection.end,
15098 selection.reversed,
15099 action.replace_newest,
15100 Some(Autoscroll::newest()),
15101 window,
15102 cx,
15103 );
15104 }
15105 if selections.len() == 1 {
15106 let selection = selections
15107 .last()
15108 .expect("ensured that there's only one selection");
15109 let query = buffer
15110 .text_for_range(selection.start..selection.end)
15111 .collect::<String>();
15112 let is_empty = query.is_empty();
15113 let select_state = SelectNextState {
15114 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15115 wordwise: true,
15116 done: is_empty,
15117 };
15118 self.select_prev_state = Some(select_state);
15119 } else {
15120 self.select_prev_state = None;
15121 }
15122 } else if let Some(selected_text) = selected_text {
15123 self.select_prev_state = Some(SelectNextState {
15124 query: self
15125 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15126 wordwise: false,
15127 done: false,
15128 });
15129 self.select_previous(action, window, cx)?;
15130 }
15131 }
15132 Ok(())
15133 }
15134
15135 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15136 /// setting the case sensitivity based on the global
15137 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15138 /// editor's settings.
15139 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15140 where
15141 I: IntoIterator<Item = P>,
15142 P: AsRef<[u8]>,
15143 {
15144 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15145 || EditorSettings::get_global(cx).search.case_sensitive,
15146 |value| value,
15147 );
15148
15149 let mut builder = AhoCorasickBuilder::new();
15150 builder.ascii_case_insensitive(!case_sensitive);
15151 builder.build(patterns)
15152 }
15153
15154 pub fn find_next_match(
15155 &mut self,
15156 _: &FindNextMatch,
15157 window: &mut Window,
15158 cx: &mut Context<Self>,
15159 ) -> Result<()> {
15160 let selections = self.selections.disjoint_anchors_arc();
15161 match selections.first() {
15162 Some(first) if selections.len() >= 2 => {
15163 self.change_selections(Default::default(), window, cx, |s| {
15164 s.select_ranges([first.range()]);
15165 });
15166 }
15167 _ => self.select_next(
15168 &SelectNext {
15169 replace_newest: true,
15170 },
15171 window,
15172 cx,
15173 )?,
15174 }
15175 Ok(())
15176 }
15177
15178 pub fn find_previous_match(
15179 &mut self,
15180 _: &FindPreviousMatch,
15181 window: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) -> Result<()> {
15184 let selections = self.selections.disjoint_anchors_arc();
15185 match selections.last() {
15186 Some(last) if selections.len() >= 2 => {
15187 self.change_selections(Default::default(), window, cx, |s| {
15188 s.select_ranges([last.range()]);
15189 });
15190 }
15191 _ => self.select_previous(
15192 &SelectPrevious {
15193 replace_newest: true,
15194 },
15195 window,
15196 cx,
15197 )?,
15198 }
15199 Ok(())
15200 }
15201
15202 pub fn toggle_comments(
15203 &mut self,
15204 action: &ToggleComments,
15205 window: &mut Window,
15206 cx: &mut Context<Self>,
15207 ) {
15208 if self.read_only(cx) {
15209 return;
15210 }
15211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15212 let text_layout_details = &self.text_layout_details(window);
15213 self.transact(window, cx, |this, window, cx| {
15214 let mut selections = this
15215 .selections
15216 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15217 let mut edits = Vec::new();
15218 let mut selection_edit_ranges = Vec::new();
15219 let mut last_toggled_row = None;
15220 let snapshot = this.buffer.read(cx).read(cx);
15221 let empty_str: Arc<str> = Arc::default();
15222 let mut suffixes_inserted = Vec::new();
15223 let ignore_indent = action.ignore_indent;
15224
15225 fn comment_prefix_range(
15226 snapshot: &MultiBufferSnapshot,
15227 row: MultiBufferRow,
15228 comment_prefix: &str,
15229 comment_prefix_whitespace: &str,
15230 ignore_indent: bool,
15231 ) -> Range<Point> {
15232 let indent_size = if ignore_indent {
15233 0
15234 } else {
15235 snapshot.indent_size_for_line(row).len
15236 };
15237
15238 let start = Point::new(row.0, indent_size);
15239
15240 let mut line_bytes = snapshot
15241 .bytes_in_range(start..snapshot.max_point())
15242 .flatten()
15243 .copied();
15244
15245 // If this line currently begins with the line comment prefix, then record
15246 // the range containing the prefix.
15247 if line_bytes
15248 .by_ref()
15249 .take(comment_prefix.len())
15250 .eq(comment_prefix.bytes())
15251 {
15252 // Include any whitespace that matches the comment prefix.
15253 let matching_whitespace_len = line_bytes
15254 .zip(comment_prefix_whitespace.bytes())
15255 .take_while(|(a, b)| a == b)
15256 .count() as u32;
15257 let end = Point::new(
15258 start.row,
15259 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15260 );
15261 start..end
15262 } else {
15263 start..start
15264 }
15265 }
15266
15267 fn comment_suffix_range(
15268 snapshot: &MultiBufferSnapshot,
15269 row: MultiBufferRow,
15270 comment_suffix: &str,
15271 comment_suffix_has_leading_space: bool,
15272 ) -> Range<Point> {
15273 let end = Point::new(row.0, snapshot.line_len(row));
15274 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15275
15276 let mut line_end_bytes = snapshot
15277 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15278 .flatten()
15279 .copied();
15280
15281 let leading_space_len = if suffix_start_column > 0
15282 && line_end_bytes.next() == Some(b' ')
15283 && comment_suffix_has_leading_space
15284 {
15285 1
15286 } else {
15287 0
15288 };
15289
15290 // If this line currently begins with the line comment prefix, then record
15291 // the range containing the prefix.
15292 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15293 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15294 start..end
15295 } else {
15296 end..end
15297 }
15298 }
15299
15300 // TODO: Handle selections that cross excerpts
15301 for selection in &mut selections {
15302 let start_column = snapshot
15303 .indent_size_for_line(MultiBufferRow(selection.start.row))
15304 .len;
15305 let language = if let Some(language) =
15306 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15307 {
15308 language
15309 } else {
15310 continue;
15311 };
15312
15313 selection_edit_ranges.clear();
15314
15315 // If multiple selections contain a given row, avoid processing that
15316 // row more than once.
15317 let mut start_row = MultiBufferRow(selection.start.row);
15318 if last_toggled_row == Some(start_row) {
15319 start_row = start_row.next_row();
15320 }
15321 let end_row =
15322 if selection.end.row > selection.start.row && selection.end.column == 0 {
15323 MultiBufferRow(selection.end.row - 1)
15324 } else {
15325 MultiBufferRow(selection.end.row)
15326 };
15327 last_toggled_row = Some(end_row);
15328
15329 if start_row > end_row {
15330 continue;
15331 }
15332
15333 // If the language has line comments, toggle those.
15334 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15335
15336 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15337 if ignore_indent {
15338 full_comment_prefixes = full_comment_prefixes
15339 .into_iter()
15340 .map(|s| Arc::from(s.trim_end()))
15341 .collect();
15342 }
15343
15344 if !full_comment_prefixes.is_empty() {
15345 let first_prefix = full_comment_prefixes
15346 .first()
15347 .expect("prefixes is non-empty");
15348 let prefix_trimmed_lengths = full_comment_prefixes
15349 .iter()
15350 .map(|p| p.trim_end_matches(' ').len())
15351 .collect::<SmallVec<[usize; 4]>>();
15352
15353 let mut all_selection_lines_are_comments = true;
15354
15355 for row in start_row.0..=end_row.0 {
15356 let row = MultiBufferRow(row);
15357 if start_row < end_row && snapshot.is_line_blank(row) {
15358 continue;
15359 }
15360
15361 let prefix_range = full_comment_prefixes
15362 .iter()
15363 .zip(prefix_trimmed_lengths.iter().copied())
15364 .map(|(prefix, trimmed_prefix_len)| {
15365 comment_prefix_range(
15366 snapshot.deref(),
15367 row,
15368 &prefix[..trimmed_prefix_len],
15369 &prefix[trimmed_prefix_len..],
15370 ignore_indent,
15371 )
15372 })
15373 .max_by_key(|range| range.end.column - range.start.column)
15374 .expect("prefixes is non-empty");
15375
15376 if prefix_range.is_empty() {
15377 all_selection_lines_are_comments = false;
15378 }
15379
15380 selection_edit_ranges.push(prefix_range);
15381 }
15382
15383 if all_selection_lines_are_comments {
15384 edits.extend(
15385 selection_edit_ranges
15386 .iter()
15387 .cloned()
15388 .map(|range| (range, empty_str.clone())),
15389 );
15390 } else {
15391 let min_column = selection_edit_ranges
15392 .iter()
15393 .map(|range| range.start.column)
15394 .min()
15395 .unwrap_or(0);
15396 edits.extend(selection_edit_ranges.iter().map(|range| {
15397 let position = Point::new(range.start.row, min_column);
15398 (position..position, first_prefix.clone())
15399 }));
15400 }
15401 } else if let Some(BlockCommentConfig {
15402 start: full_comment_prefix,
15403 end: comment_suffix,
15404 ..
15405 }) = language.block_comment()
15406 {
15407 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15408 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15409 let prefix_range = comment_prefix_range(
15410 snapshot.deref(),
15411 start_row,
15412 comment_prefix,
15413 comment_prefix_whitespace,
15414 ignore_indent,
15415 );
15416 let suffix_range = comment_suffix_range(
15417 snapshot.deref(),
15418 end_row,
15419 comment_suffix.trim_start_matches(' '),
15420 comment_suffix.starts_with(' '),
15421 );
15422
15423 if prefix_range.is_empty() || suffix_range.is_empty() {
15424 edits.push((
15425 prefix_range.start..prefix_range.start,
15426 full_comment_prefix.clone(),
15427 ));
15428 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15429 suffixes_inserted.push((end_row, comment_suffix.len()));
15430 } else {
15431 edits.push((prefix_range, empty_str.clone()));
15432 edits.push((suffix_range, empty_str.clone()));
15433 }
15434 } else {
15435 continue;
15436 }
15437 }
15438
15439 drop(snapshot);
15440 this.buffer.update(cx, |buffer, cx| {
15441 buffer.edit(edits, None, cx);
15442 });
15443
15444 // Adjust selections so that they end before any comment suffixes that
15445 // were inserted.
15446 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15447 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15448 let snapshot = this.buffer.read(cx).read(cx);
15449 for selection in &mut selections {
15450 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15451 match row.cmp(&MultiBufferRow(selection.end.row)) {
15452 Ordering::Less => {
15453 suffixes_inserted.next();
15454 continue;
15455 }
15456 Ordering::Greater => break,
15457 Ordering::Equal => {
15458 if selection.end.column == snapshot.line_len(row) {
15459 if selection.is_empty() {
15460 selection.start.column -= suffix_len as u32;
15461 }
15462 selection.end.column -= suffix_len as u32;
15463 }
15464 break;
15465 }
15466 }
15467 }
15468 }
15469
15470 drop(snapshot);
15471 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15472
15473 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15474 let selections_on_single_row = selections.windows(2).all(|selections| {
15475 selections[0].start.row == selections[1].start.row
15476 && selections[0].end.row == selections[1].end.row
15477 && selections[0].start.row == selections[0].end.row
15478 });
15479 let selections_selecting = selections
15480 .iter()
15481 .any(|selection| selection.start != selection.end);
15482 let advance_downwards = action.advance_downwards
15483 && selections_on_single_row
15484 && !selections_selecting
15485 && !matches!(this.mode, EditorMode::SingleLine);
15486
15487 if advance_downwards {
15488 let snapshot = this.buffer.read(cx).snapshot(cx);
15489
15490 this.change_selections(Default::default(), window, cx, |s| {
15491 s.move_cursors_with(|display_snapshot, display_point, _| {
15492 let mut point = display_point.to_point(display_snapshot);
15493 point.row += 1;
15494 point = snapshot.clip_point(point, Bias::Left);
15495 let display_point = point.to_display_point(display_snapshot);
15496 let goal = SelectionGoal::HorizontalPosition(
15497 display_snapshot
15498 .x_for_display_point(display_point, text_layout_details)
15499 .into(),
15500 );
15501 (display_point, goal)
15502 })
15503 });
15504 }
15505 });
15506 }
15507
15508 pub fn select_enclosing_symbol(
15509 &mut self,
15510 _: &SelectEnclosingSymbol,
15511 window: &mut Window,
15512 cx: &mut Context<Self>,
15513 ) {
15514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15515
15516 let buffer = self.buffer.read(cx).snapshot(cx);
15517 let old_selections = self
15518 .selections
15519 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15520 .into_boxed_slice();
15521
15522 fn update_selection(
15523 selection: &Selection<MultiBufferOffset>,
15524 buffer_snap: &MultiBufferSnapshot,
15525 ) -> Option<Selection<MultiBufferOffset>> {
15526 let cursor = selection.head();
15527 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15528 for symbol in symbols.iter().rev() {
15529 let start = symbol.range.start.to_offset(buffer_snap);
15530 let end = symbol.range.end.to_offset(buffer_snap);
15531 let new_range = start..end;
15532 if start < selection.start || end > selection.end {
15533 return Some(Selection {
15534 id: selection.id,
15535 start: new_range.start,
15536 end: new_range.end,
15537 goal: SelectionGoal::None,
15538 reversed: selection.reversed,
15539 });
15540 }
15541 }
15542 None
15543 }
15544
15545 let mut selected_larger_symbol = false;
15546 let new_selections = old_selections
15547 .iter()
15548 .map(|selection| match update_selection(selection, &buffer) {
15549 Some(new_selection) => {
15550 if new_selection.range() != selection.range() {
15551 selected_larger_symbol = true;
15552 }
15553 new_selection
15554 }
15555 None => selection.clone(),
15556 })
15557 .collect::<Vec<_>>();
15558
15559 if selected_larger_symbol {
15560 self.change_selections(Default::default(), window, cx, |s| {
15561 s.select(new_selections);
15562 });
15563 }
15564 }
15565
15566 pub fn select_larger_syntax_node(
15567 &mut self,
15568 _: &SelectLargerSyntaxNode,
15569 window: &mut Window,
15570 cx: &mut Context<Self>,
15571 ) {
15572 let Some(visible_row_count) = self.visible_row_count() else {
15573 return;
15574 };
15575 let old_selections: Box<[_]> = self
15576 .selections
15577 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15578 .into();
15579 if old_selections.is_empty() {
15580 return;
15581 }
15582
15583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15584
15585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15586 let buffer = self.buffer.read(cx).snapshot(cx);
15587
15588 let mut selected_larger_node = false;
15589 let mut new_selections = old_selections
15590 .iter()
15591 .map(|selection| {
15592 let old_range = selection.start..selection.end;
15593
15594 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15595 // manually select word at selection
15596 if ["string_content", "inline"].contains(&node.kind()) {
15597 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15598 // ignore if word is already selected
15599 if !word_range.is_empty() && old_range != word_range {
15600 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15601 // only select word if start and end point belongs to same word
15602 if word_range == last_word_range {
15603 selected_larger_node = true;
15604 return Selection {
15605 id: selection.id,
15606 start: word_range.start,
15607 end: word_range.end,
15608 goal: SelectionGoal::None,
15609 reversed: selection.reversed,
15610 };
15611 }
15612 }
15613 }
15614 }
15615
15616 let mut new_range = old_range.clone();
15617 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15618 new_range = range;
15619 if !node.is_named() {
15620 continue;
15621 }
15622 if !display_map.intersects_fold(new_range.start)
15623 && !display_map.intersects_fold(new_range.end)
15624 {
15625 break;
15626 }
15627 }
15628
15629 selected_larger_node |= new_range != old_range;
15630 Selection {
15631 id: selection.id,
15632 start: new_range.start,
15633 end: new_range.end,
15634 goal: SelectionGoal::None,
15635 reversed: selection.reversed,
15636 }
15637 })
15638 .collect::<Vec<_>>();
15639
15640 if !selected_larger_node {
15641 return; // don't put this call in the history
15642 }
15643
15644 // scroll based on transformation done to the last selection created by the user
15645 let (last_old, last_new) = old_selections
15646 .last()
15647 .zip(new_selections.last().cloned())
15648 .expect("old_selections isn't empty");
15649
15650 // revert selection
15651 let is_selection_reversed = {
15652 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15653 new_selections.last_mut().expect("checked above").reversed =
15654 should_newest_selection_be_reversed;
15655 should_newest_selection_be_reversed
15656 };
15657
15658 if selected_larger_node {
15659 self.select_syntax_node_history.disable_clearing = true;
15660 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15661 s.select(new_selections.clone());
15662 });
15663 self.select_syntax_node_history.disable_clearing = false;
15664 }
15665
15666 let start_row = last_new.start.to_display_point(&display_map).row().0;
15667 let end_row = last_new.end.to_display_point(&display_map).row().0;
15668 let selection_height = end_row - start_row + 1;
15669 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15670
15671 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15672 let scroll_behavior = if fits_on_the_screen {
15673 self.request_autoscroll(Autoscroll::fit(), cx);
15674 SelectSyntaxNodeScrollBehavior::FitSelection
15675 } else if is_selection_reversed {
15676 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15677 SelectSyntaxNodeScrollBehavior::CursorTop
15678 } else {
15679 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15680 SelectSyntaxNodeScrollBehavior::CursorBottom
15681 };
15682
15683 self.select_syntax_node_history.push((
15684 old_selections,
15685 scroll_behavior,
15686 is_selection_reversed,
15687 ));
15688 }
15689
15690 pub fn select_smaller_syntax_node(
15691 &mut self,
15692 _: &SelectSmallerSyntaxNode,
15693 window: &mut Window,
15694 cx: &mut Context<Self>,
15695 ) {
15696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15697
15698 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15699 self.select_syntax_node_history.pop()
15700 {
15701 if let Some(selection) = selections.last_mut() {
15702 selection.reversed = is_selection_reversed;
15703 }
15704
15705 self.select_syntax_node_history.disable_clearing = true;
15706 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15707 s.select(selections.to_vec());
15708 });
15709 self.select_syntax_node_history.disable_clearing = false;
15710
15711 match scroll_behavior {
15712 SelectSyntaxNodeScrollBehavior::CursorTop => {
15713 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15714 }
15715 SelectSyntaxNodeScrollBehavior::FitSelection => {
15716 self.request_autoscroll(Autoscroll::fit(), cx);
15717 }
15718 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15719 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15720 }
15721 }
15722 }
15723 }
15724
15725 pub fn unwrap_syntax_node(
15726 &mut self,
15727 _: &UnwrapSyntaxNode,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) {
15731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15732
15733 let buffer = self.buffer.read(cx).snapshot(cx);
15734 let selections = self
15735 .selections
15736 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15737 .into_iter()
15738 // subtracting the offset requires sorting
15739 .sorted_by_key(|i| i.start);
15740
15741 let full_edits = selections
15742 .into_iter()
15743 .filter_map(|selection| {
15744 let child = if selection.is_empty()
15745 && let Some((_, ancestor_range)) =
15746 buffer.syntax_ancestor(selection.start..selection.end)
15747 {
15748 ancestor_range
15749 } else {
15750 selection.range()
15751 };
15752
15753 let mut parent = child.clone();
15754 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15755 parent = ancestor_range;
15756 if parent.start < child.start || parent.end > child.end {
15757 break;
15758 }
15759 }
15760
15761 if parent == child {
15762 return None;
15763 }
15764 let text = buffer.text_for_range(child).collect::<String>();
15765 Some((selection.id, parent, text))
15766 })
15767 .collect::<Vec<_>>();
15768 if full_edits.is_empty() {
15769 return;
15770 }
15771
15772 self.transact(window, cx, |this, window, cx| {
15773 this.buffer.update(cx, |buffer, cx| {
15774 buffer.edit(
15775 full_edits
15776 .iter()
15777 .map(|(_, p, t)| (p.clone(), t.clone()))
15778 .collect::<Vec<_>>(),
15779 None,
15780 cx,
15781 );
15782 });
15783 this.change_selections(Default::default(), window, cx, |s| {
15784 let mut offset = 0;
15785 let mut selections = vec![];
15786 for (id, parent, text) in full_edits {
15787 let start = parent.start - offset;
15788 offset += (parent.end - parent.start) - text.len();
15789 selections.push(Selection {
15790 id,
15791 start,
15792 end: start + text.len(),
15793 reversed: false,
15794 goal: Default::default(),
15795 });
15796 }
15797 s.select(selections);
15798 });
15799 });
15800 }
15801
15802 pub fn select_next_syntax_node(
15803 &mut self,
15804 _: &SelectNextSyntaxNode,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 let old_selections: Box<[_]> = self
15809 .selections
15810 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15811 .into();
15812 if old_selections.is_empty() {
15813 return;
15814 }
15815
15816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15817
15818 let buffer = self.buffer.read(cx).snapshot(cx);
15819 let mut selected_sibling = false;
15820
15821 let new_selections = old_selections
15822 .iter()
15823 .map(|selection| {
15824 let old_range = selection.start..selection.end;
15825
15826 let old_range =
15827 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15828 let excerpt = buffer.excerpt_containing(old_range.clone());
15829
15830 if let Some(mut excerpt) = excerpt
15831 && let Some(node) = excerpt
15832 .buffer()
15833 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15834 {
15835 let new_range = excerpt.map_range_from_buffer(
15836 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15837 );
15838 selected_sibling = true;
15839 Selection {
15840 id: selection.id,
15841 start: new_range.start,
15842 end: new_range.end,
15843 goal: SelectionGoal::None,
15844 reversed: selection.reversed,
15845 }
15846 } else {
15847 selection.clone()
15848 }
15849 })
15850 .collect::<Vec<_>>();
15851
15852 if selected_sibling {
15853 self.change_selections(
15854 SelectionEffects::scroll(Autoscroll::fit()),
15855 window,
15856 cx,
15857 |s| {
15858 s.select(new_selections);
15859 },
15860 );
15861 }
15862 }
15863
15864 pub fn select_prev_syntax_node(
15865 &mut self,
15866 _: &SelectPreviousSyntaxNode,
15867 window: &mut Window,
15868 cx: &mut Context<Self>,
15869 ) {
15870 let old_selections: Box<[_]> = self
15871 .selections
15872 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15873 .into();
15874 if old_selections.is_empty() {
15875 return;
15876 }
15877
15878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15879
15880 let buffer = self.buffer.read(cx).snapshot(cx);
15881 let mut selected_sibling = false;
15882
15883 let new_selections = old_selections
15884 .iter()
15885 .map(|selection| {
15886 let old_range = selection.start..selection.end;
15887 let old_range =
15888 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15889 let excerpt = buffer.excerpt_containing(old_range.clone());
15890
15891 if let Some(mut excerpt) = excerpt
15892 && let Some(node) = excerpt
15893 .buffer()
15894 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15895 {
15896 let new_range = excerpt.map_range_from_buffer(
15897 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15898 );
15899 selected_sibling = true;
15900 Selection {
15901 id: selection.id,
15902 start: new_range.start,
15903 end: new_range.end,
15904 goal: SelectionGoal::None,
15905 reversed: selection.reversed,
15906 }
15907 } else {
15908 selection.clone()
15909 }
15910 })
15911 .collect::<Vec<_>>();
15912
15913 if selected_sibling {
15914 self.change_selections(
15915 SelectionEffects::scroll(Autoscroll::fit()),
15916 window,
15917 cx,
15918 |s| {
15919 s.select(new_selections);
15920 },
15921 );
15922 }
15923 }
15924
15925 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15926 if !EditorSettings::get_global(cx).gutter.runnables {
15927 self.clear_tasks();
15928 return Task::ready(());
15929 }
15930 let project = self.project().map(Entity::downgrade);
15931 let task_sources = self.lsp_task_sources(cx);
15932 let multi_buffer = self.buffer.downgrade();
15933 cx.spawn_in(window, async move |editor, cx| {
15934 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15935 let Some(project) = project.and_then(|p| p.upgrade()) else {
15936 return;
15937 };
15938 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15939 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15940 }) else {
15941 return;
15942 };
15943
15944 let hide_runnables = project
15945 .update(cx, |project, _| project.is_via_collab())
15946 .unwrap_or(true);
15947 if hide_runnables {
15948 return;
15949 }
15950 let new_rows =
15951 cx.background_spawn({
15952 let snapshot = display_snapshot.clone();
15953 async move {
15954 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15955 }
15956 })
15957 .await;
15958 let Ok(lsp_tasks) =
15959 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15960 else {
15961 return;
15962 };
15963 let lsp_tasks = lsp_tasks.await;
15964
15965 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15966 lsp_tasks
15967 .into_iter()
15968 .flat_map(|(kind, tasks)| {
15969 tasks.into_iter().filter_map(move |(location, task)| {
15970 Some((kind.clone(), location?, task))
15971 })
15972 })
15973 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15974 let buffer = location.target.buffer;
15975 let buffer_snapshot = buffer.read(cx).snapshot();
15976 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15977 |(excerpt_id, snapshot, _)| {
15978 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15979 display_snapshot
15980 .buffer_snapshot()
15981 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15982 } else {
15983 None
15984 }
15985 },
15986 );
15987 if let Some(offset) = offset {
15988 let task_buffer_range =
15989 location.target.range.to_point(&buffer_snapshot);
15990 let context_buffer_range =
15991 task_buffer_range.to_offset(&buffer_snapshot);
15992 let context_range = BufferOffset(context_buffer_range.start)
15993 ..BufferOffset(context_buffer_range.end);
15994
15995 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15996 .or_insert_with(|| RunnableTasks {
15997 templates: Vec::new(),
15998 offset,
15999 column: task_buffer_range.start.column,
16000 extra_variables: HashMap::default(),
16001 context_range,
16002 })
16003 .templates
16004 .push((kind, task.original_task().clone()));
16005 }
16006
16007 acc
16008 })
16009 }) else {
16010 return;
16011 };
16012
16013 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16014 buffer.language_settings(cx).tasks.prefer_lsp
16015 }) else {
16016 return;
16017 };
16018
16019 let rows = Self::runnable_rows(
16020 project,
16021 display_snapshot,
16022 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16023 new_rows,
16024 cx.clone(),
16025 )
16026 .await;
16027 editor
16028 .update(cx, |editor, _| {
16029 editor.clear_tasks();
16030 for (key, mut value) in rows {
16031 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16032 value.templates.extend(lsp_tasks.templates);
16033 }
16034
16035 editor.insert_tasks(key, value);
16036 }
16037 for (key, value) in lsp_tasks_by_rows {
16038 editor.insert_tasks(key, value);
16039 }
16040 })
16041 .ok();
16042 })
16043 }
16044 fn fetch_runnable_ranges(
16045 snapshot: &DisplaySnapshot,
16046 range: Range<Anchor>,
16047 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16048 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16049 }
16050
16051 fn runnable_rows(
16052 project: Entity<Project>,
16053 snapshot: DisplaySnapshot,
16054 prefer_lsp: bool,
16055 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16056 cx: AsyncWindowContext,
16057 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16058 cx.spawn(async move |cx| {
16059 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16060 for (run_range, mut runnable) in runnable_ranges {
16061 let Some(tasks) = cx
16062 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16063 .ok()
16064 else {
16065 continue;
16066 };
16067 let mut tasks = tasks.await;
16068
16069 if prefer_lsp {
16070 tasks.retain(|(task_kind, _)| {
16071 !matches!(task_kind, TaskSourceKind::Language { .. })
16072 });
16073 }
16074 if tasks.is_empty() {
16075 continue;
16076 }
16077
16078 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16079 let Some(row) = snapshot
16080 .buffer_snapshot()
16081 .buffer_line_for_row(MultiBufferRow(point.row))
16082 .map(|(_, range)| range.start.row)
16083 else {
16084 continue;
16085 };
16086
16087 let context_range =
16088 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16089 runnable_rows.push((
16090 (runnable.buffer_id, row),
16091 RunnableTasks {
16092 templates: tasks,
16093 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16094 context_range,
16095 column: point.column,
16096 extra_variables: runnable.extra_captures,
16097 },
16098 ));
16099 }
16100 runnable_rows
16101 })
16102 }
16103
16104 fn templates_with_tags(
16105 project: &Entity<Project>,
16106 runnable: &mut Runnable,
16107 cx: &mut App,
16108 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16109 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16110 let (worktree_id, file) = project
16111 .buffer_for_id(runnable.buffer, cx)
16112 .and_then(|buffer| buffer.read(cx).file())
16113 .map(|file| (file.worktree_id(cx), file.clone()))
16114 .unzip();
16115
16116 (
16117 project.task_store().read(cx).task_inventory().cloned(),
16118 worktree_id,
16119 file,
16120 )
16121 });
16122
16123 let tags = mem::take(&mut runnable.tags);
16124 let language = runnable.language.clone();
16125 cx.spawn(async move |cx| {
16126 let mut templates_with_tags = Vec::new();
16127 if let Some(inventory) = inventory {
16128 for RunnableTag(tag) in tags {
16129 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16130 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16131 }) else {
16132 return templates_with_tags;
16133 };
16134 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16135 move |(_, template)| {
16136 template.tags.iter().any(|source_tag| source_tag == &tag)
16137 },
16138 ));
16139 }
16140 }
16141 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16142
16143 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16144 // Strongest source wins; if we have worktree tag binding, prefer that to
16145 // global and language bindings;
16146 // if we have a global binding, prefer that to language binding.
16147 let first_mismatch = templates_with_tags
16148 .iter()
16149 .position(|(tag_source, _)| tag_source != leading_tag_source);
16150 if let Some(index) = first_mismatch {
16151 templates_with_tags.truncate(index);
16152 }
16153 }
16154
16155 templates_with_tags
16156 })
16157 }
16158
16159 pub fn move_to_enclosing_bracket(
16160 &mut self,
16161 _: &MoveToEnclosingBracket,
16162 window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) {
16165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16166 self.change_selections(Default::default(), window, cx, |s| {
16167 s.move_offsets_with(|snapshot, selection| {
16168 let Some(enclosing_bracket_ranges) =
16169 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16170 else {
16171 return;
16172 };
16173
16174 let mut best_length = usize::MAX;
16175 let mut best_inside = false;
16176 let mut best_in_bracket_range = false;
16177 let mut best_destination = None;
16178 for (open, close) in enclosing_bracket_ranges {
16179 let close = close.to_inclusive();
16180 let length = *close.end() - open.start;
16181 let inside = selection.start >= open.end && selection.end <= *close.start();
16182 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16183 || close.contains(&selection.head());
16184
16185 // If best is next to a bracket and current isn't, skip
16186 if !in_bracket_range && best_in_bracket_range {
16187 continue;
16188 }
16189
16190 // Prefer smaller lengths unless best is inside and current isn't
16191 if length > best_length && (best_inside || !inside) {
16192 continue;
16193 }
16194
16195 best_length = length;
16196 best_inside = inside;
16197 best_in_bracket_range = in_bracket_range;
16198 best_destination = Some(
16199 if close.contains(&selection.start) && close.contains(&selection.end) {
16200 if inside { open.end } else { open.start }
16201 } else if inside {
16202 *close.start()
16203 } else {
16204 *close.end()
16205 },
16206 );
16207 }
16208
16209 if let Some(destination) = best_destination {
16210 selection.collapse_to(destination, SelectionGoal::None);
16211 }
16212 })
16213 });
16214 }
16215
16216 pub fn undo_selection(
16217 &mut self,
16218 _: &UndoSelection,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16223 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16224 self.selection_history.mode = SelectionHistoryMode::Undoing;
16225 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16226 this.end_selection(window, cx);
16227 this.change_selections(
16228 SelectionEffects::scroll(Autoscroll::newest()),
16229 window,
16230 cx,
16231 |s| s.select_anchors(entry.selections.to_vec()),
16232 );
16233 });
16234 self.selection_history.mode = SelectionHistoryMode::Normal;
16235
16236 self.select_next_state = entry.select_next_state;
16237 self.select_prev_state = entry.select_prev_state;
16238 self.add_selections_state = entry.add_selections_state;
16239 }
16240 }
16241
16242 pub fn redo_selection(
16243 &mut self,
16244 _: &RedoSelection,
16245 window: &mut Window,
16246 cx: &mut Context<Self>,
16247 ) {
16248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16249 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16250 self.selection_history.mode = SelectionHistoryMode::Redoing;
16251 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16252 this.end_selection(window, cx);
16253 this.change_selections(
16254 SelectionEffects::scroll(Autoscroll::newest()),
16255 window,
16256 cx,
16257 |s| s.select_anchors(entry.selections.to_vec()),
16258 );
16259 });
16260 self.selection_history.mode = SelectionHistoryMode::Normal;
16261
16262 self.select_next_state = entry.select_next_state;
16263 self.select_prev_state = entry.select_prev_state;
16264 self.add_selections_state = entry.add_selections_state;
16265 }
16266 }
16267
16268 pub fn expand_excerpts(
16269 &mut self,
16270 action: &ExpandExcerpts,
16271 _: &mut Window,
16272 cx: &mut Context<Self>,
16273 ) {
16274 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16275 }
16276
16277 pub fn expand_excerpts_down(
16278 &mut self,
16279 action: &ExpandExcerptsDown,
16280 _: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) {
16283 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16284 }
16285
16286 pub fn expand_excerpts_up(
16287 &mut self,
16288 action: &ExpandExcerptsUp,
16289 _: &mut Window,
16290 cx: &mut Context<Self>,
16291 ) {
16292 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16293 }
16294
16295 pub fn expand_excerpts_for_direction(
16296 &mut self,
16297 lines: u32,
16298 direction: ExpandExcerptDirection,
16299
16300 cx: &mut Context<Self>,
16301 ) {
16302 let selections = self.selections.disjoint_anchors_arc();
16303
16304 let lines = if lines == 0 {
16305 EditorSettings::get_global(cx).expand_excerpt_lines
16306 } else {
16307 lines
16308 };
16309
16310 self.buffer.update(cx, |buffer, cx| {
16311 let snapshot = buffer.snapshot(cx);
16312 let mut excerpt_ids = selections
16313 .iter()
16314 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16315 .collect::<Vec<_>>();
16316 excerpt_ids.sort();
16317 excerpt_ids.dedup();
16318 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16319 })
16320 }
16321
16322 pub fn expand_excerpt(
16323 &mut self,
16324 excerpt: ExcerptId,
16325 direction: ExpandExcerptDirection,
16326 window: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) {
16329 let current_scroll_position = self.scroll_position(cx);
16330 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16331 let mut scroll = None;
16332
16333 if direction == ExpandExcerptDirection::Down {
16334 let multi_buffer = self.buffer.read(cx);
16335 let snapshot = multi_buffer.snapshot(cx);
16336 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16337 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16338 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16339 {
16340 let buffer_snapshot = buffer.read(cx).snapshot();
16341 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16342 let last_row = buffer_snapshot.max_point().row;
16343 let lines_below = last_row.saturating_sub(excerpt_end_row);
16344 if lines_below >= lines_to_expand {
16345 scroll = Some(
16346 current_scroll_position
16347 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16348 );
16349 }
16350 }
16351 }
16352 if direction == ExpandExcerptDirection::Up
16353 && self
16354 .buffer
16355 .read(cx)
16356 .snapshot(cx)
16357 .excerpt_before(excerpt)
16358 .is_none()
16359 {
16360 scroll = Some(current_scroll_position);
16361 }
16362
16363 self.buffer.update(cx, |buffer, cx| {
16364 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16365 });
16366
16367 if let Some(new_scroll_position) = scroll {
16368 self.set_scroll_position(new_scroll_position, window, cx);
16369 }
16370 }
16371
16372 pub fn go_to_singleton_buffer_point(
16373 &mut self,
16374 point: Point,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) {
16378 self.go_to_singleton_buffer_range(point..point, window, cx);
16379 }
16380
16381 pub fn go_to_singleton_buffer_range(
16382 &mut self,
16383 range: Range<Point>,
16384 window: &mut Window,
16385 cx: &mut Context<Self>,
16386 ) {
16387 let multibuffer = self.buffer().read(cx);
16388 let Some(buffer) = multibuffer.as_singleton() else {
16389 return;
16390 };
16391 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16392 return;
16393 };
16394 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16395 return;
16396 };
16397 self.change_selections(
16398 SelectionEffects::default().nav_history(true),
16399 window,
16400 cx,
16401 |s| s.select_anchor_ranges([start..end]),
16402 );
16403 }
16404
16405 pub fn go_to_diagnostic(
16406 &mut self,
16407 action: &GoToDiagnostic,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) {
16411 if !self.diagnostics_enabled() {
16412 return;
16413 }
16414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16415 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16416 }
16417
16418 pub fn go_to_prev_diagnostic(
16419 &mut self,
16420 action: &GoToPreviousDiagnostic,
16421 window: &mut Window,
16422 cx: &mut Context<Self>,
16423 ) {
16424 if !self.diagnostics_enabled() {
16425 return;
16426 }
16427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16428 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16429 }
16430
16431 pub fn go_to_diagnostic_impl(
16432 &mut self,
16433 direction: Direction,
16434 severity: GoToDiagnosticSeverityFilter,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) {
16438 let buffer = self.buffer.read(cx).snapshot(cx);
16439 let selection = self
16440 .selections
16441 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16442
16443 let mut active_group_id = None;
16444 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16445 && active_group.active_range.start.to_offset(&buffer) == selection.start
16446 {
16447 active_group_id = Some(active_group.group_id);
16448 }
16449
16450 fn filtered<'a>(
16451 severity: GoToDiagnosticSeverityFilter,
16452 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16453 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16454 diagnostics
16455 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16456 .filter(|entry| entry.range.start != entry.range.end)
16457 .filter(|entry| !entry.diagnostic.is_unnecessary)
16458 }
16459
16460 let before = filtered(
16461 severity,
16462 buffer
16463 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16464 .filter(|entry| entry.range.start <= selection.start),
16465 );
16466 let after = filtered(
16467 severity,
16468 buffer
16469 .diagnostics_in_range(selection.start..buffer.len())
16470 .filter(|entry| entry.range.start >= selection.start),
16471 );
16472
16473 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16474 if direction == Direction::Prev {
16475 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16476 {
16477 for diagnostic in prev_diagnostics.into_iter().rev() {
16478 if diagnostic.range.start != selection.start
16479 || active_group_id
16480 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16481 {
16482 found = Some(diagnostic);
16483 break 'outer;
16484 }
16485 }
16486 }
16487 } else {
16488 for diagnostic in after.chain(before) {
16489 if diagnostic.range.start != selection.start
16490 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16491 {
16492 found = Some(diagnostic);
16493 break;
16494 }
16495 }
16496 }
16497 let Some(next_diagnostic) = found else {
16498 return;
16499 };
16500
16501 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16502 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16503 return;
16504 };
16505 let snapshot = self.snapshot(window, cx);
16506 if snapshot.intersects_fold(next_diagnostic.range.start) {
16507 self.unfold_ranges(
16508 std::slice::from_ref(&next_diagnostic.range),
16509 true,
16510 false,
16511 cx,
16512 );
16513 }
16514 self.change_selections(Default::default(), window, cx, |s| {
16515 s.select_ranges(vec![
16516 next_diagnostic.range.start..next_diagnostic.range.start,
16517 ])
16518 });
16519 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16520 self.refresh_edit_prediction(false, true, window, cx);
16521 }
16522
16523 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16525 let snapshot = self.snapshot(window, cx);
16526 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16527 self.go_to_hunk_before_or_after_position(
16528 &snapshot,
16529 selection.head(),
16530 Direction::Next,
16531 window,
16532 cx,
16533 );
16534 }
16535
16536 pub fn go_to_hunk_before_or_after_position(
16537 &mut self,
16538 snapshot: &EditorSnapshot,
16539 position: Point,
16540 direction: Direction,
16541 window: &mut Window,
16542 cx: &mut Context<Editor>,
16543 ) {
16544 let row = if direction == Direction::Next {
16545 self.hunk_after_position(snapshot, position)
16546 .map(|hunk| hunk.row_range.start)
16547 } else {
16548 self.hunk_before_position(snapshot, position)
16549 };
16550
16551 if let Some(row) = row {
16552 let destination = Point::new(row.0, 0);
16553 let autoscroll = Autoscroll::center();
16554
16555 self.unfold_ranges(&[destination..destination], false, false, cx);
16556 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16557 s.select_ranges([destination..destination]);
16558 });
16559 }
16560 }
16561
16562 fn hunk_after_position(
16563 &mut self,
16564 snapshot: &EditorSnapshot,
16565 position: Point,
16566 ) -> Option<MultiBufferDiffHunk> {
16567 snapshot
16568 .buffer_snapshot()
16569 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16570 .find(|hunk| hunk.row_range.start.0 > position.row)
16571 .or_else(|| {
16572 snapshot
16573 .buffer_snapshot()
16574 .diff_hunks_in_range(Point::zero()..position)
16575 .find(|hunk| hunk.row_range.end.0 < position.row)
16576 })
16577 }
16578
16579 fn go_to_prev_hunk(
16580 &mut self,
16581 _: &GoToPreviousHunk,
16582 window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16586 let snapshot = self.snapshot(window, cx);
16587 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16588 self.go_to_hunk_before_or_after_position(
16589 &snapshot,
16590 selection.head(),
16591 Direction::Prev,
16592 window,
16593 cx,
16594 );
16595 }
16596
16597 fn hunk_before_position(
16598 &mut self,
16599 snapshot: &EditorSnapshot,
16600 position: Point,
16601 ) -> Option<MultiBufferRow> {
16602 snapshot
16603 .buffer_snapshot()
16604 .diff_hunk_before(position)
16605 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16606 }
16607
16608 fn go_to_next_change(
16609 &mut self,
16610 _: &GoToNextChange,
16611 window: &mut Window,
16612 cx: &mut Context<Self>,
16613 ) {
16614 if let Some(selections) = self
16615 .change_list
16616 .next_change(1, Direction::Next)
16617 .map(|s| s.to_vec())
16618 {
16619 self.change_selections(Default::default(), window, cx, |s| {
16620 let map = s.display_snapshot();
16621 s.select_display_ranges(selections.iter().map(|a| {
16622 let point = a.to_display_point(&map);
16623 point..point
16624 }))
16625 })
16626 }
16627 }
16628
16629 fn go_to_previous_change(
16630 &mut self,
16631 _: &GoToPreviousChange,
16632 window: &mut Window,
16633 cx: &mut Context<Self>,
16634 ) {
16635 if let Some(selections) = self
16636 .change_list
16637 .next_change(1, Direction::Prev)
16638 .map(|s| s.to_vec())
16639 {
16640 self.change_selections(Default::default(), window, cx, |s| {
16641 let map = s.display_snapshot();
16642 s.select_display_ranges(selections.iter().map(|a| {
16643 let point = a.to_display_point(&map);
16644 point..point
16645 }))
16646 })
16647 }
16648 }
16649
16650 pub fn go_to_next_document_highlight(
16651 &mut self,
16652 _: &GoToNextDocumentHighlight,
16653 window: &mut Window,
16654 cx: &mut Context<Self>,
16655 ) {
16656 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16657 }
16658
16659 pub fn go_to_prev_document_highlight(
16660 &mut self,
16661 _: &GoToPreviousDocumentHighlight,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) {
16665 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16666 }
16667
16668 pub fn go_to_document_highlight_before_or_after_position(
16669 &mut self,
16670 direction: Direction,
16671 window: &mut Window,
16672 cx: &mut Context<Editor>,
16673 ) {
16674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16675 let snapshot = self.snapshot(window, cx);
16676 let buffer = &snapshot.buffer_snapshot();
16677 let position = self
16678 .selections
16679 .newest::<Point>(&snapshot.display_snapshot)
16680 .head();
16681 let anchor_position = buffer.anchor_after(position);
16682
16683 // Get all document highlights (both read and write)
16684 let mut all_highlights = Vec::new();
16685
16686 if let Some((_, read_highlights)) = self
16687 .background_highlights
16688 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16689 {
16690 all_highlights.extend(read_highlights.iter());
16691 }
16692
16693 if let Some((_, write_highlights)) = self
16694 .background_highlights
16695 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16696 {
16697 all_highlights.extend(write_highlights.iter());
16698 }
16699
16700 if all_highlights.is_empty() {
16701 return;
16702 }
16703
16704 // Sort highlights by position
16705 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16706
16707 let target_highlight = match direction {
16708 Direction::Next => {
16709 // Find the first highlight after the current position
16710 all_highlights
16711 .iter()
16712 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16713 }
16714 Direction::Prev => {
16715 // Find the last highlight before the current position
16716 all_highlights
16717 .iter()
16718 .rev()
16719 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16720 }
16721 };
16722
16723 if let Some(highlight) = target_highlight {
16724 let destination = highlight.start.to_point(buffer);
16725 let autoscroll = Autoscroll::center();
16726
16727 self.unfold_ranges(&[destination..destination], false, false, cx);
16728 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16729 s.select_ranges([destination..destination]);
16730 });
16731 }
16732 }
16733
16734 fn go_to_line<T: 'static>(
16735 &mut self,
16736 position: Anchor,
16737 highlight_color: Option<Hsla>,
16738 window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 let snapshot = self.snapshot(window, cx).display_snapshot;
16742 let position = position.to_point(&snapshot.buffer_snapshot());
16743 let start = snapshot
16744 .buffer_snapshot()
16745 .clip_point(Point::new(position.row, 0), Bias::Left);
16746 let end = start + Point::new(1, 0);
16747 let start = snapshot.buffer_snapshot().anchor_before(start);
16748 let end = snapshot.buffer_snapshot().anchor_before(end);
16749
16750 self.highlight_rows::<T>(
16751 start..end,
16752 highlight_color
16753 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16754 Default::default(),
16755 cx,
16756 );
16757
16758 if self.buffer.read(cx).is_singleton() {
16759 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16760 }
16761 }
16762
16763 pub fn go_to_definition(
16764 &mut self,
16765 _: &GoToDefinition,
16766 window: &mut Window,
16767 cx: &mut Context<Self>,
16768 ) -> Task<Result<Navigated>> {
16769 let definition =
16770 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16771 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16772 cx.spawn_in(window, async move |editor, cx| {
16773 if definition.await? == Navigated::Yes {
16774 return Ok(Navigated::Yes);
16775 }
16776 match fallback_strategy {
16777 GoToDefinitionFallback::None => Ok(Navigated::No),
16778 GoToDefinitionFallback::FindAllReferences => {
16779 match editor.update_in(cx, |editor, window, cx| {
16780 editor.find_all_references(&FindAllReferences, window, cx)
16781 })? {
16782 Some(references) => references.await,
16783 None => Ok(Navigated::No),
16784 }
16785 }
16786 }
16787 })
16788 }
16789
16790 pub fn go_to_declaration(
16791 &mut self,
16792 _: &GoToDeclaration,
16793 window: &mut Window,
16794 cx: &mut Context<Self>,
16795 ) -> Task<Result<Navigated>> {
16796 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16797 }
16798
16799 pub fn go_to_declaration_split(
16800 &mut self,
16801 _: &GoToDeclaration,
16802 window: &mut Window,
16803 cx: &mut Context<Self>,
16804 ) -> Task<Result<Navigated>> {
16805 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16806 }
16807
16808 pub fn go_to_implementation(
16809 &mut self,
16810 _: &GoToImplementation,
16811 window: &mut Window,
16812 cx: &mut Context<Self>,
16813 ) -> Task<Result<Navigated>> {
16814 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16815 }
16816
16817 pub fn go_to_implementation_split(
16818 &mut self,
16819 _: &GoToImplementationSplit,
16820 window: &mut Window,
16821 cx: &mut Context<Self>,
16822 ) -> Task<Result<Navigated>> {
16823 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16824 }
16825
16826 pub fn go_to_type_definition(
16827 &mut self,
16828 _: &GoToTypeDefinition,
16829 window: &mut Window,
16830 cx: &mut Context<Self>,
16831 ) -> Task<Result<Navigated>> {
16832 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16833 }
16834
16835 pub fn go_to_definition_split(
16836 &mut self,
16837 _: &GoToDefinitionSplit,
16838 window: &mut Window,
16839 cx: &mut Context<Self>,
16840 ) -> Task<Result<Navigated>> {
16841 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16842 }
16843
16844 pub fn go_to_type_definition_split(
16845 &mut self,
16846 _: &GoToTypeDefinitionSplit,
16847 window: &mut Window,
16848 cx: &mut Context<Self>,
16849 ) -> Task<Result<Navigated>> {
16850 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16851 }
16852
16853 fn go_to_definition_of_kind(
16854 &mut self,
16855 kind: GotoDefinitionKind,
16856 split: bool,
16857 window: &mut Window,
16858 cx: &mut Context<Self>,
16859 ) -> Task<Result<Navigated>> {
16860 let Some(provider) = self.semantics_provider.clone() else {
16861 return Task::ready(Ok(Navigated::No));
16862 };
16863 let head = self
16864 .selections
16865 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16866 .head();
16867 let buffer = self.buffer.read(cx);
16868 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16869 return Task::ready(Ok(Navigated::No));
16870 };
16871 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16872 return Task::ready(Ok(Navigated::No));
16873 };
16874
16875 cx.spawn_in(window, async move |editor, cx| {
16876 let Some(definitions) = definitions.await? else {
16877 return Ok(Navigated::No);
16878 };
16879 let navigated = editor
16880 .update_in(cx, |editor, window, cx| {
16881 editor.navigate_to_hover_links(
16882 Some(kind),
16883 definitions
16884 .into_iter()
16885 .filter(|location| {
16886 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16887 })
16888 .map(HoverLink::Text)
16889 .collect::<Vec<_>>(),
16890 split,
16891 window,
16892 cx,
16893 )
16894 })?
16895 .await?;
16896 anyhow::Ok(navigated)
16897 })
16898 }
16899
16900 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16901 let selection = self.selections.newest_anchor();
16902 let head = selection.head();
16903 let tail = selection.tail();
16904
16905 let Some((buffer, start_position)) =
16906 self.buffer.read(cx).text_anchor_for_position(head, cx)
16907 else {
16908 return;
16909 };
16910
16911 let end_position = if head != tail {
16912 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16913 return;
16914 };
16915 Some(pos)
16916 } else {
16917 None
16918 };
16919
16920 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16921 let url = if let Some(end_pos) = end_position {
16922 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16923 } else {
16924 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16925 };
16926
16927 if let Some(url) = url {
16928 cx.update(|window, cx| {
16929 if parse_zed_link(&url, cx).is_some() {
16930 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16931 } else {
16932 cx.open_url(&url);
16933 }
16934 })?;
16935 }
16936
16937 anyhow::Ok(())
16938 });
16939
16940 url_finder.detach();
16941 }
16942
16943 pub fn open_selected_filename(
16944 &mut self,
16945 _: &OpenSelectedFilename,
16946 window: &mut Window,
16947 cx: &mut Context<Self>,
16948 ) {
16949 let Some(workspace) = self.workspace() else {
16950 return;
16951 };
16952
16953 let position = self.selections.newest_anchor().head();
16954
16955 let Some((buffer, buffer_position)) =
16956 self.buffer.read(cx).text_anchor_for_position(position, cx)
16957 else {
16958 return;
16959 };
16960
16961 let project = self.project.clone();
16962
16963 cx.spawn_in(window, async move |_, cx| {
16964 let result = find_file(&buffer, project, buffer_position, cx).await;
16965
16966 if let Some((_, path)) = result {
16967 workspace
16968 .update_in(cx, |workspace, window, cx| {
16969 workspace.open_resolved_path(path, window, cx)
16970 })?
16971 .await?;
16972 }
16973 anyhow::Ok(())
16974 })
16975 .detach();
16976 }
16977
16978 pub(crate) fn navigate_to_hover_links(
16979 &mut self,
16980 kind: Option<GotoDefinitionKind>,
16981 definitions: Vec<HoverLink>,
16982 split: bool,
16983 window: &mut Window,
16984 cx: &mut Context<Editor>,
16985 ) -> Task<Result<Navigated>> {
16986 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16987 let mut first_url_or_file = None;
16988 let definitions: Vec<_> = definitions
16989 .into_iter()
16990 .filter_map(|def| match def {
16991 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16992 HoverLink::InlayHint(lsp_location, server_id) => {
16993 let computation =
16994 self.compute_target_location(lsp_location, server_id, window, cx);
16995 Some(cx.background_spawn(computation))
16996 }
16997 HoverLink::Url(url) => {
16998 first_url_or_file = Some(Either::Left(url));
16999 None
17000 }
17001 HoverLink::File(path) => {
17002 first_url_or_file = Some(Either::Right(path));
17003 None
17004 }
17005 })
17006 .collect();
17007
17008 let workspace = self.workspace();
17009
17010 cx.spawn_in(window, async move |editor, cx| {
17011 let locations: Vec<Location> = future::join_all(definitions)
17012 .await
17013 .into_iter()
17014 .filter_map(|location| location.transpose())
17015 .collect::<Result<_>>()
17016 .context("location tasks")?;
17017 let mut locations = cx.update(|_, cx| {
17018 locations
17019 .into_iter()
17020 .map(|location| {
17021 let buffer = location.buffer.read(cx);
17022 (location.buffer, location.range.to_point(buffer))
17023 })
17024 .into_group_map()
17025 })?;
17026 let mut num_locations = 0;
17027 for ranges in locations.values_mut() {
17028 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17029 ranges.dedup();
17030 num_locations += ranges.len();
17031 }
17032
17033 if num_locations > 1 {
17034 let Some(workspace) = workspace else {
17035 return Ok(Navigated::No);
17036 };
17037
17038 let tab_kind = match kind {
17039 Some(GotoDefinitionKind::Implementation) => "Implementations",
17040 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17041 Some(GotoDefinitionKind::Declaration) => "Declarations",
17042 Some(GotoDefinitionKind::Type) => "Types",
17043 };
17044 let title = editor
17045 .update_in(cx, |_, _, cx| {
17046 let target = locations
17047 .iter()
17048 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17049 .map(|(buffer, location)| {
17050 buffer
17051 .read(cx)
17052 .text_for_range(location.clone())
17053 .collect::<String>()
17054 })
17055 .filter(|text| !text.contains('\n'))
17056 .unique()
17057 .take(3)
17058 .join(", ");
17059 if target.is_empty() {
17060 tab_kind.to_owned()
17061 } else {
17062 format!("{tab_kind} for {target}")
17063 }
17064 })
17065 .context("buffer title")?;
17066
17067 let opened = workspace
17068 .update_in(cx, |workspace, window, cx| {
17069 Self::open_locations_in_multibuffer(
17070 workspace,
17071 locations,
17072 title,
17073 split,
17074 MultibufferSelectionMode::First,
17075 window,
17076 cx,
17077 )
17078 })
17079 .is_ok();
17080
17081 anyhow::Ok(Navigated::from_bool(opened))
17082 } else if num_locations == 0 {
17083 // If there is one url or file, open it directly
17084 match first_url_or_file {
17085 Some(Either::Left(url)) => {
17086 cx.update(|_, cx| cx.open_url(&url))?;
17087 Ok(Navigated::Yes)
17088 }
17089 Some(Either::Right(path)) => {
17090 let Some(workspace) = workspace else {
17091 return Ok(Navigated::No);
17092 };
17093
17094 workspace
17095 .update_in(cx, |workspace, window, cx| {
17096 workspace.open_resolved_path(path, window, cx)
17097 })?
17098 .await?;
17099 Ok(Navigated::Yes)
17100 }
17101 None => Ok(Navigated::No),
17102 }
17103 } else {
17104 let Some(workspace) = workspace else {
17105 return Ok(Navigated::No);
17106 };
17107
17108 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17109 let target_range = target_ranges.first().unwrap().clone();
17110
17111 editor.update_in(cx, |editor, window, cx| {
17112 let range = target_range.to_point(target_buffer.read(cx));
17113 let range = editor.range_for_match(&range);
17114 let range = collapse_multiline_range(range);
17115
17116 if !split
17117 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17118 {
17119 editor.go_to_singleton_buffer_range(range, window, cx);
17120 } else {
17121 let pane = workspace.read(cx).active_pane().clone();
17122 window.defer(cx, move |window, cx| {
17123 let target_editor: Entity<Self> =
17124 workspace.update(cx, |workspace, cx| {
17125 let pane = if split {
17126 workspace.adjacent_pane(window, cx)
17127 } else {
17128 workspace.active_pane().clone()
17129 };
17130
17131 workspace.open_project_item(
17132 pane,
17133 target_buffer.clone(),
17134 true,
17135 true,
17136 window,
17137 cx,
17138 )
17139 });
17140 target_editor.update(cx, |target_editor, cx| {
17141 // When selecting a definition in a different buffer, disable the nav history
17142 // to avoid creating a history entry at the previous cursor location.
17143 pane.update(cx, |pane, _| pane.disable_history());
17144 target_editor.go_to_singleton_buffer_range(range, window, cx);
17145 pane.update(cx, |pane, _| pane.enable_history());
17146 });
17147 });
17148 }
17149 Navigated::Yes
17150 })
17151 }
17152 })
17153 }
17154
17155 fn compute_target_location(
17156 &self,
17157 lsp_location: lsp::Location,
17158 server_id: LanguageServerId,
17159 window: &mut Window,
17160 cx: &mut Context<Self>,
17161 ) -> Task<anyhow::Result<Option<Location>>> {
17162 let Some(project) = self.project.clone() else {
17163 return Task::ready(Ok(None));
17164 };
17165
17166 cx.spawn_in(window, async move |editor, cx| {
17167 let location_task = editor.update(cx, |_, cx| {
17168 project.update(cx, |project, cx| {
17169 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17170 })
17171 })?;
17172 let location = Some({
17173 let target_buffer_handle = location_task.await.context("open local buffer")?;
17174 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17175 let target_start = target_buffer
17176 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17177 let target_end = target_buffer
17178 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17179 target_buffer.anchor_after(target_start)
17180 ..target_buffer.anchor_before(target_end)
17181 })?;
17182 Location {
17183 buffer: target_buffer_handle,
17184 range,
17185 }
17186 });
17187 Ok(location)
17188 })
17189 }
17190
17191 fn go_to_next_reference(
17192 &mut self,
17193 _: &GoToNextReference,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) {
17197 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17198 if let Some(task) = task {
17199 task.detach();
17200 };
17201 }
17202
17203 fn go_to_prev_reference(
17204 &mut self,
17205 _: &GoToPreviousReference,
17206 window: &mut Window,
17207 cx: &mut Context<Self>,
17208 ) {
17209 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17210 if let Some(task) = task {
17211 task.detach();
17212 };
17213 }
17214
17215 pub fn go_to_reference_before_or_after_position(
17216 &mut self,
17217 direction: Direction,
17218 count: usize,
17219 window: &mut Window,
17220 cx: &mut Context<Self>,
17221 ) -> Option<Task<Result<()>>> {
17222 let selection = self.selections.newest_anchor();
17223 let head = selection.head();
17224
17225 let multi_buffer = self.buffer.read(cx);
17226
17227 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17228 let workspace = self.workspace()?;
17229 let project = workspace.read(cx).project().clone();
17230 let references =
17231 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17232 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17233 let Some(locations) = references.await? else {
17234 return Ok(());
17235 };
17236
17237 if locations.is_empty() {
17238 // totally normal - the cursor may be on something which is not
17239 // a symbol (e.g. a keyword)
17240 log::info!("no references found under cursor");
17241 return Ok(());
17242 }
17243
17244 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17245
17246 let (locations, current_location_index) =
17247 multi_buffer.update(cx, |multi_buffer, cx| {
17248 let mut locations = locations
17249 .into_iter()
17250 .filter_map(|loc| {
17251 let start = multi_buffer.buffer_anchor_to_anchor(
17252 &loc.buffer,
17253 loc.range.start,
17254 cx,
17255 )?;
17256 let end = multi_buffer.buffer_anchor_to_anchor(
17257 &loc.buffer,
17258 loc.range.end,
17259 cx,
17260 )?;
17261 Some(start..end)
17262 })
17263 .collect::<Vec<_>>();
17264
17265 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17266 // There is an O(n) implementation, but given this list will be
17267 // small (usually <100 items), the extra O(log(n)) factor isn't
17268 // worth the (surprisingly large amount of) extra complexity.
17269 locations
17270 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17271
17272 let head_offset = head.to_offset(&multi_buffer_snapshot);
17273
17274 let current_location_index = locations.iter().position(|loc| {
17275 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17276 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17277 });
17278
17279 (locations, current_location_index)
17280 })?;
17281
17282 let Some(current_location_index) = current_location_index else {
17283 // This indicates something has gone wrong, because we already
17284 // handle the "no references" case above
17285 log::error!(
17286 "failed to find current reference under cursor. Total references: {}",
17287 locations.len()
17288 );
17289 return Ok(());
17290 };
17291
17292 let destination_location_index = match direction {
17293 Direction::Next => (current_location_index + count) % locations.len(),
17294 Direction::Prev => {
17295 (current_location_index + locations.len() - count % locations.len())
17296 % locations.len()
17297 }
17298 };
17299
17300 // TODO(cameron): is this needed?
17301 // the thinking is to avoid "jumping to the current location" (avoid
17302 // polluting "jumplist" in vim terms)
17303 if current_location_index == destination_location_index {
17304 return Ok(());
17305 }
17306
17307 let Range { start, end } = locations[destination_location_index];
17308
17309 editor.update_in(cx, |editor, window, cx| {
17310 let effects = SelectionEffects::default();
17311
17312 editor.unfold_ranges(&[start..end], false, false, cx);
17313 editor.change_selections(effects, window, cx, |s| {
17314 s.select_ranges([start..start]);
17315 });
17316 })?;
17317
17318 Ok(())
17319 }))
17320 }
17321
17322 pub fn find_all_references(
17323 &mut self,
17324 _: &FindAllReferences,
17325 window: &mut Window,
17326 cx: &mut Context<Self>,
17327 ) -> Option<Task<Result<Navigated>>> {
17328 let selection = self
17329 .selections
17330 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17331 let multi_buffer = self.buffer.read(cx);
17332 let head = selection.head();
17333
17334 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17335 let head_anchor = multi_buffer_snapshot.anchor_at(
17336 head,
17337 if head < selection.tail() {
17338 Bias::Right
17339 } else {
17340 Bias::Left
17341 },
17342 );
17343
17344 match self
17345 .find_all_references_task_sources
17346 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17347 {
17348 Ok(_) => {
17349 log::info!(
17350 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17351 );
17352 return None;
17353 }
17354 Err(i) => {
17355 self.find_all_references_task_sources.insert(i, head_anchor);
17356 }
17357 }
17358
17359 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17360 let workspace = self.workspace()?;
17361 let project = workspace.read(cx).project().clone();
17362 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17363 Some(cx.spawn_in(window, async move |editor, cx| {
17364 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17365 if let Ok(i) = editor
17366 .find_all_references_task_sources
17367 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17368 {
17369 editor.find_all_references_task_sources.remove(i);
17370 }
17371 });
17372
17373 let Some(locations) = references.await? else {
17374 return anyhow::Ok(Navigated::No);
17375 };
17376 let mut locations = cx.update(|_, cx| {
17377 locations
17378 .into_iter()
17379 .map(|location| {
17380 let buffer = location.buffer.read(cx);
17381 (location.buffer, location.range.to_point(buffer))
17382 })
17383 .into_group_map()
17384 })?;
17385 if locations.is_empty() {
17386 return anyhow::Ok(Navigated::No);
17387 }
17388 for ranges in locations.values_mut() {
17389 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17390 ranges.dedup();
17391 }
17392
17393 workspace.update_in(cx, |workspace, window, cx| {
17394 let target = locations
17395 .iter()
17396 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17397 .map(|(buffer, location)| {
17398 buffer
17399 .read(cx)
17400 .text_for_range(location.clone())
17401 .collect::<String>()
17402 })
17403 .filter(|text| !text.contains('\n'))
17404 .unique()
17405 .take(3)
17406 .join(", ");
17407 let title = if target.is_empty() {
17408 "References".to_owned()
17409 } else {
17410 format!("References to {target}")
17411 };
17412 Self::open_locations_in_multibuffer(
17413 workspace,
17414 locations,
17415 title,
17416 false,
17417 MultibufferSelectionMode::First,
17418 window,
17419 cx,
17420 );
17421 Navigated::Yes
17422 })
17423 }))
17424 }
17425
17426 /// Opens a multibuffer with the given project locations in it
17427 pub fn open_locations_in_multibuffer(
17428 workspace: &mut Workspace,
17429 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17430 title: String,
17431 split: bool,
17432 multibuffer_selection_mode: MultibufferSelectionMode,
17433 window: &mut Window,
17434 cx: &mut Context<Workspace>,
17435 ) {
17436 if locations.is_empty() {
17437 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17438 return;
17439 }
17440
17441 let capability = workspace.project().read(cx).capability();
17442 let mut ranges = <Vec<Range<Anchor>>>::new();
17443
17444 // a key to find existing multibuffer editors with the same set of locations
17445 // to prevent us from opening more and more multibuffer tabs for searches and the like
17446 let mut key = (title.clone(), vec![]);
17447 let excerpt_buffer = cx.new(|cx| {
17448 let key = &mut key.1;
17449 let mut multibuffer = MultiBuffer::new(capability);
17450 for (buffer, mut ranges_for_buffer) in locations {
17451 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17452 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17453 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17454 PathKey::for_buffer(&buffer, cx),
17455 buffer.clone(),
17456 ranges_for_buffer,
17457 multibuffer_context_lines(cx),
17458 cx,
17459 );
17460 ranges.extend(new_ranges)
17461 }
17462
17463 multibuffer.with_title(title)
17464 });
17465 let existing = workspace.active_pane().update(cx, |pane, cx| {
17466 pane.items()
17467 .filter_map(|item| item.downcast::<Editor>())
17468 .find(|editor| {
17469 editor
17470 .read(cx)
17471 .lookup_key
17472 .as_ref()
17473 .and_then(|it| {
17474 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17475 })
17476 .is_some_and(|it| *it == key)
17477 })
17478 });
17479 let editor = existing.unwrap_or_else(|| {
17480 cx.new(|cx| {
17481 let mut editor = Editor::for_multibuffer(
17482 excerpt_buffer,
17483 Some(workspace.project().clone()),
17484 window,
17485 cx,
17486 );
17487 editor.lookup_key = Some(Box::new(key));
17488 editor
17489 })
17490 });
17491 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17492 MultibufferSelectionMode::First => {
17493 if let Some(first_range) = ranges.first() {
17494 editor.change_selections(
17495 SelectionEffects::no_scroll(),
17496 window,
17497 cx,
17498 |selections| {
17499 selections.clear_disjoint();
17500 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17501 },
17502 );
17503 }
17504 editor.highlight_background::<Self>(
17505 &ranges,
17506 |theme| theme.colors().editor_highlighted_line_background,
17507 cx,
17508 );
17509 }
17510 MultibufferSelectionMode::All => {
17511 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17512 selections.clear_disjoint();
17513 selections.select_anchor_ranges(ranges);
17514 });
17515 }
17516 });
17517
17518 let item = Box::new(editor);
17519 let item_id = item.item_id();
17520
17521 if split {
17522 let pane = workspace.adjacent_pane(window, cx);
17523 workspace.add_item(pane, item, None, true, true, window, cx);
17524 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17525 let (preview_item_id, preview_item_idx) =
17526 workspace.active_pane().read_with(cx, |pane, _| {
17527 (pane.preview_item_id(), pane.preview_item_idx())
17528 });
17529
17530 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17531
17532 if let Some(preview_item_id) = preview_item_id {
17533 workspace.active_pane().update(cx, |pane, cx| {
17534 pane.remove_item(preview_item_id, false, false, window, cx);
17535 });
17536 }
17537 } else {
17538 workspace.add_item_to_active_pane(item, None, true, window, cx);
17539 }
17540 workspace.active_pane().update(cx, |pane, cx| {
17541 pane.set_preview_item_id(Some(item_id), cx);
17542 });
17543 }
17544
17545 pub fn rename(
17546 &mut self,
17547 _: &Rename,
17548 window: &mut Window,
17549 cx: &mut Context<Self>,
17550 ) -> Option<Task<Result<()>>> {
17551 use language::ToOffset as _;
17552
17553 let provider = self.semantics_provider.clone()?;
17554 let selection = self.selections.newest_anchor().clone();
17555 let (cursor_buffer, cursor_buffer_position) = self
17556 .buffer
17557 .read(cx)
17558 .text_anchor_for_position(selection.head(), cx)?;
17559 let (tail_buffer, cursor_buffer_position_end) = self
17560 .buffer
17561 .read(cx)
17562 .text_anchor_for_position(selection.tail(), cx)?;
17563 if tail_buffer != cursor_buffer {
17564 return None;
17565 }
17566
17567 let snapshot = cursor_buffer.read(cx).snapshot();
17568 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17569 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17570 let prepare_rename = provider
17571 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17572 .unwrap_or_else(|| Task::ready(Ok(None)));
17573 drop(snapshot);
17574
17575 Some(cx.spawn_in(window, async move |this, cx| {
17576 let rename_range = if let Some(range) = prepare_rename.await? {
17577 Some(range)
17578 } else {
17579 this.update(cx, |this, cx| {
17580 let buffer = this.buffer.read(cx).snapshot(cx);
17581 let mut buffer_highlights = this
17582 .document_highlights_for_position(selection.head(), &buffer)
17583 .filter(|highlight| {
17584 highlight.start.excerpt_id == selection.head().excerpt_id
17585 && highlight.end.excerpt_id == selection.head().excerpt_id
17586 });
17587 buffer_highlights
17588 .next()
17589 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17590 })?
17591 };
17592 if let Some(rename_range) = rename_range {
17593 this.update_in(cx, |this, window, cx| {
17594 let snapshot = cursor_buffer.read(cx).snapshot();
17595 let rename_buffer_range = rename_range.to_offset(&snapshot);
17596 let cursor_offset_in_rename_range =
17597 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17598 let cursor_offset_in_rename_range_end =
17599 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17600
17601 this.take_rename(false, window, cx);
17602 let buffer = this.buffer.read(cx).read(cx);
17603 let cursor_offset = selection.head().to_offset(&buffer);
17604 let rename_start =
17605 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17606 let rename_end = rename_start + rename_buffer_range.len();
17607 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17608 let mut old_highlight_id = None;
17609 let old_name: Arc<str> = buffer
17610 .chunks(rename_start..rename_end, true)
17611 .map(|chunk| {
17612 if old_highlight_id.is_none() {
17613 old_highlight_id = chunk.syntax_highlight_id;
17614 }
17615 chunk.text
17616 })
17617 .collect::<String>()
17618 .into();
17619
17620 drop(buffer);
17621
17622 // Position the selection in the rename editor so that it matches the current selection.
17623 this.show_local_selections = false;
17624 let rename_editor = cx.new(|cx| {
17625 let mut editor = Editor::single_line(window, cx);
17626 editor.buffer.update(cx, |buffer, cx| {
17627 buffer.edit(
17628 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17629 None,
17630 cx,
17631 )
17632 });
17633 let cursor_offset_in_rename_range =
17634 MultiBufferOffset(cursor_offset_in_rename_range);
17635 let cursor_offset_in_rename_range_end =
17636 MultiBufferOffset(cursor_offset_in_rename_range_end);
17637 let rename_selection_range = match cursor_offset_in_rename_range
17638 .cmp(&cursor_offset_in_rename_range_end)
17639 {
17640 Ordering::Equal => {
17641 editor.select_all(&SelectAll, window, cx);
17642 return editor;
17643 }
17644 Ordering::Less => {
17645 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17646 }
17647 Ordering::Greater => {
17648 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17649 }
17650 };
17651 if rename_selection_range.end.0 > old_name.len() {
17652 editor.select_all(&SelectAll, window, cx);
17653 } else {
17654 editor.change_selections(Default::default(), window, cx, |s| {
17655 s.select_ranges([rename_selection_range]);
17656 });
17657 }
17658 editor
17659 });
17660 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17661 if e == &EditorEvent::Focused {
17662 cx.emit(EditorEvent::FocusedIn)
17663 }
17664 })
17665 .detach();
17666
17667 let write_highlights =
17668 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17669 let read_highlights =
17670 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17671 let ranges = write_highlights
17672 .iter()
17673 .flat_map(|(_, ranges)| ranges.iter())
17674 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17675 .cloned()
17676 .collect();
17677
17678 this.highlight_text::<Rename>(
17679 ranges,
17680 HighlightStyle {
17681 fade_out: Some(0.6),
17682 ..Default::default()
17683 },
17684 cx,
17685 );
17686 let rename_focus_handle = rename_editor.focus_handle(cx);
17687 window.focus(&rename_focus_handle);
17688 let block_id = this.insert_blocks(
17689 [BlockProperties {
17690 style: BlockStyle::Flex,
17691 placement: BlockPlacement::Below(range.start),
17692 height: Some(1),
17693 render: Arc::new({
17694 let rename_editor = rename_editor.clone();
17695 move |cx: &mut BlockContext| {
17696 let mut text_style = cx.editor_style.text.clone();
17697 if let Some(highlight_style) = old_highlight_id
17698 .and_then(|h| h.style(&cx.editor_style.syntax))
17699 {
17700 text_style = text_style.highlight(highlight_style);
17701 }
17702 div()
17703 .block_mouse_except_scroll()
17704 .pl(cx.anchor_x)
17705 .child(EditorElement::new(
17706 &rename_editor,
17707 EditorStyle {
17708 background: cx.theme().system().transparent,
17709 local_player: cx.editor_style.local_player,
17710 text: text_style,
17711 scrollbar_width: cx.editor_style.scrollbar_width,
17712 syntax: cx.editor_style.syntax.clone(),
17713 status: cx.editor_style.status.clone(),
17714 inlay_hints_style: HighlightStyle {
17715 font_weight: Some(FontWeight::BOLD),
17716 ..make_inlay_hints_style(cx.app)
17717 },
17718 edit_prediction_styles: make_suggestion_styles(
17719 cx.app,
17720 ),
17721 ..EditorStyle::default()
17722 },
17723 ))
17724 .into_any_element()
17725 }
17726 }),
17727 priority: 0,
17728 }],
17729 Some(Autoscroll::fit()),
17730 cx,
17731 )[0];
17732 this.pending_rename = Some(RenameState {
17733 range,
17734 old_name,
17735 editor: rename_editor,
17736 block_id,
17737 });
17738 })?;
17739 }
17740
17741 Ok(())
17742 }))
17743 }
17744
17745 pub fn confirm_rename(
17746 &mut self,
17747 _: &ConfirmRename,
17748 window: &mut Window,
17749 cx: &mut Context<Self>,
17750 ) -> Option<Task<Result<()>>> {
17751 let rename = self.take_rename(false, window, cx)?;
17752 let workspace = self.workspace()?.downgrade();
17753 let (buffer, start) = self
17754 .buffer
17755 .read(cx)
17756 .text_anchor_for_position(rename.range.start, cx)?;
17757 let (end_buffer, _) = self
17758 .buffer
17759 .read(cx)
17760 .text_anchor_for_position(rename.range.end, cx)?;
17761 if buffer != end_buffer {
17762 return None;
17763 }
17764
17765 let old_name = rename.old_name;
17766 let new_name = rename.editor.read(cx).text(cx);
17767
17768 let rename = self.semantics_provider.as_ref()?.perform_rename(
17769 &buffer,
17770 start,
17771 new_name.clone(),
17772 cx,
17773 )?;
17774
17775 Some(cx.spawn_in(window, async move |editor, cx| {
17776 let project_transaction = rename.await?;
17777 Self::open_project_transaction(
17778 &editor,
17779 workspace,
17780 project_transaction,
17781 format!("Rename: {} → {}", old_name, new_name),
17782 cx,
17783 )
17784 .await?;
17785
17786 editor.update(cx, |editor, cx| {
17787 editor.refresh_document_highlights(cx);
17788 })?;
17789 Ok(())
17790 }))
17791 }
17792
17793 fn take_rename(
17794 &mut self,
17795 moving_cursor: bool,
17796 window: &mut Window,
17797 cx: &mut Context<Self>,
17798 ) -> Option<RenameState> {
17799 let rename = self.pending_rename.take()?;
17800 if rename.editor.focus_handle(cx).is_focused(window) {
17801 window.focus(&self.focus_handle);
17802 }
17803
17804 self.remove_blocks(
17805 [rename.block_id].into_iter().collect(),
17806 Some(Autoscroll::fit()),
17807 cx,
17808 );
17809 self.clear_highlights::<Rename>(cx);
17810 self.show_local_selections = true;
17811
17812 if moving_cursor {
17813 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17814 editor
17815 .selections
17816 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17817 .head()
17818 });
17819
17820 // Update the selection to match the position of the selection inside
17821 // the rename editor.
17822 let snapshot = self.buffer.read(cx).read(cx);
17823 let rename_range = rename.range.to_offset(&snapshot);
17824 let cursor_in_editor = snapshot
17825 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17826 .min(rename_range.end);
17827 drop(snapshot);
17828
17829 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17830 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17831 });
17832 } else {
17833 self.refresh_document_highlights(cx);
17834 }
17835
17836 Some(rename)
17837 }
17838
17839 pub fn pending_rename(&self) -> Option<&RenameState> {
17840 self.pending_rename.as_ref()
17841 }
17842
17843 fn format(
17844 &mut self,
17845 _: &Format,
17846 window: &mut Window,
17847 cx: &mut Context<Self>,
17848 ) -> Option<Task<Result<()>>> {
17849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17850
17851 let project = match &self.project {
17852 Some(project) => project.clone(),
17853 None => return None,
17854 };
17855
17856 Some(self.perform_format(
17857 project,
17858 FormatTrigger::Manual,
17859 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17860 window,
17861 cx,
17862 ))
17863 }
17864
17865 fn format_selections(
17866 &mut self,
17867 _: &FormatSelections,
17868 window: &mut Window,
17869 cx: &mut Context<Self>,
17870 ) -> Option<Task<Result<()>>> {
17871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17872
17873 let project = match &self.project {
17874 Some(project) => project.clone(),
17875 None => return None,
17876 };
17877
17878 let ranges = self
17879 .selections
17880 .all_adjusted(&self.display_snapshot(cx))
17881 .into_iter()
17882 .map(|selection| selection.range())
17883 .collect_vec();
17884
17885 Some(self.perform_format(
17886 project,
17887 FormatTrigger::Manual,
17888 FormatTarget::Ranges(ranges),
17889 window,
17890 cx,
17891 ))
17892 }
17893
17894 fn perform_format(
17895 &mut self,
17896 project: Entity<Project>,
17897 trigger: FormatTrigger,
17898 target: FormatTarget,
17899 window: &mut Window,
17900 cx: &mut Context<Self>,
17901 ) -> Task<Result<()>> {
17902 let buffer = self.buffer.clone();
17903 let (buffers, target) = match target {
17904 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17905 FormatTarget::Ranges(selection_ranges) => {
17906 let multi_buffer = buffer.read(cx);
17907 let snapshot = multi_buffer.read(cx);
17908 let mut buffers = HashSet::default();
17909 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17910 BTreeMap::new();
17911 for selection_range in selection_ranges {
17912 for (buffer, buffer_range, _) in
17913 snapshot.range_to_buffer_ranges(selection_range)
17914 {
17915 let buffer_id = buffer.remote_id();
17916 let start = buffer.anchor_before(buffer_range.start);
17917 let end = buffer.anchor_after(buffer_range.end);
17918 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17919 buffer_id_to_ranges
17920 .entry(buffer_id)
17921 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17922 .or_insert_with(|| vec![start..end]);
17923 }
17924 }
17925 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17926 }
17927 };
17928
17929 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17930 let selections_prev = transaction_id_prev
17931 .and_then(|transaction_id_prev| {
17932 // default to selections as they were after the last edit, if we have them,
17933 // instead of how they are now.
17934 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17935 // will take you back to where you made the last edit, instead of staying where you scrolled
17936 self.selection_history
17937 .transaction(transaction_id_prev)
17938 .map(|t| t.0.clone())
17939 })
17940 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17941
17942 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17943 let format = project.update(cx, |project, cx| {
17944 project.format(buffers, target, true, trigger, cx)
17945 });
17946
17947 cx.spawn_in(window, async move |editor, cx| {
17948 let transaction = futures::select_biased! {
17949 transaction = format.log_err().fuse() => transaction,
17950 () = timeout => {
17951 log::warn!("timed out waiting for formatting");
17952 None
17953 }
17954 };
17955
17956 buffer
17957 .update(cx, |buffer, cx| {
17958 if let Some(transaction) = transaction
17959 && !buffer.is_singleton()
17960 {
17961 buffer.push_transaction(&transaction.0, cx);
17962 }
17963 cx.notify();
17964 })
17965 .ok();
17966
17967 if let Some(transaction_id_now) =
17968 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17969 {
17970 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17971 if has_new_transaction {
17972 _ = editor.update(cx, |editor, _| {
17973 editor
17974 .selection_history
17975 .insert_transaction(transaction_id_now, selections_prev);
17976 });
17977 }
17978 }
17979
17980 Ok(())
17981 })
17982 }
17983
17984 fn organize_imports(
17985 &mut self,
17986 _: &OrganizeImports,
17987 window: &mut Window,
17988 cx: &mut Context<Self>,
17989 ) -> Option<Task<Result<()>>> {
17990 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17991 let project = match &self.project {
17992 Some(project) => project.clone(),
17993 None => return None,
17994 };
17995 Some(self.perform_code_action_kind(
17996 project,
17997 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17998 window,
17999 cx,
18000 ))
18001 }
18002
18003 fn perform_code_action_kind(
18004 &mut self,
18005 project: Entity<Project>,
18006 kind: CodeActionKind,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) -> Task<Result<()>> {
18010 let buffer = self.buffer.clone();
18011 let buffers = buffer.read(cx).all_buffers();
18012 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18013 let apply_action = project.update(cx, |project, cx| {
18014 project.apply_code_action_kind(buffers, kind, true, cx)
18015 });
18016 cx.spawn_in(window, async move |_, cx| {
18017 let transaction = futures::select_biased! {
18018 () = timeout => {
18019 log::warn!("timed out waiting for executing code action");
18020 None
18021 }
18022 transaction = apply_action.log_err().fuse() => transaction,
18023 };
18024 buffer
18025 .update(cx, |buffer, cx| {
18026 // check if we need this
18027 if let Some(transaction) = transaction
18028 && !buffer.is_singleton()
18029 {
18030 buffer.push_transaction(&transaction.0, cx);
18031 }
18032 cx.notify();
18033 })
18034 .ok();
18035 Ok(())
18036 })
18037 }
18038
18039 pub fn restart_language_server(
18040 &mut self,
18041 _: &RestartLanguageServer,
18042 _: &mut Window,
18043 cx: &mut Context<Self>,
18044 ) {
18045 if let Some(project) = self.project.clone() {
18046 self.buffer.update(cx, |multi_buffer, cx| {
18047 project.update(cx, |project, cx| {
18048 project.restart_language_servers_for_buffers(
18049 multi_buffer.all_buffers().into_iter().collect(),
18050 HashSet::default(),
18051 cx,
18052 );
18053 });
18054 })
18055 }
18056 }
18057
18058 pub fn stop_language_server(
18059 &mut self,
18060 _: &StopLanguageServer,
18061 _: &mut Window,
18062 cx: &mut Context<Self>,
18063 ) {
18064 if let Some(project) = self.project.clone() {
18065 self.buffer.update(cx, |multi_buffer, cx| {
18066 project.update(cx, |project, cx| {
18067 project.stop_language_servers_for_buffers(
18068 multi_buffer.all_buffers().into_iter().collect(),
18069 HashSet::default(),
18070 cx,
18071 );
18072 });
18073 });
18074 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18075 }
18076 }
18077
18078 fn cancel_language_server_work(
18079 workspace: &mut Workspace,
18080 _: &actions::CancelLanguageServerWork,
18081 _: &mut Window,
18082 cx: &mut Context<Workspace>,
18083 ) {
18084 let project = workspace.project();
18085 let buffers = workspace
18086 .active_item(cx)
18087 .and_then(|item| item.act_as::<Editor>(cx))
18088 .map_or(HashSet::default(), |editor| {
18089 editor.read(cx).buffer.read(cx).all_buffers()
18090 });
18091 project.update(cx, |project, cx| {
18092 project.cancel_language_server_work_for_buffers(buffers, cx);
18093 });
18094 }
18095
18096 fn show_character_palette(
18097 &mut self,
18098 _: &ShowCharacterPalette,
18099 window: &mut Window,
18100 _: &mut Context<Self>,
18101 ) {
18102 window.show_character_palette();
18103 }
18104
18105 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18106 if !self.diagnostics_enabled() {
18107 return;
18108 }
18109
18110 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18111 let buffer = self.buffer.read(cx).snapshot(cx);
18112 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18113 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18114 let is_valid = buffer
18115 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18116 .any(|entry| {
18117 entry.diagnostic.is_primary
18118 && !entry.range.is_empty()
18119 && entry.range.start == primary_range_start
18120 && entry.diagnostic.message == active_diagnostics.active_message
18121 });
18122
18123 if !is_valid {
18124 self.dismiss_diagnostics(cx);
18125 }
18126 }
18127 }
18128
18129 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18130 match &self.active_diagnostics {
18131 ActiveDiagnostic::Group(group) => Some(group),
18132 _ => None,
18133 }
18134 }
18135
18136 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18137 if !self.diagnostics_enabled() {
18138 return;
18139 }
18140 self.dismiss_diagnostics(cx);
18141 self.active_diagnostics = ActiveDiagnostic::All;
18142 }
18143
18144 fn activate_diagnostics(
18145 &mut self,
18146 buffer_id: BufferId,
18147 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18148 window: &mut Window,
18149 cx: &mut Context<Self>,
18150 ) {
18151 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18152 return;
18153 }
18154 self.dismiss_diagnostics(cx);
18155 let snapshot = self.snapshot(window, cx);
18156 let buffer = self.buffer.read(cx).snapshot(cx);
18157 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18158 return;
18159 };
18160
18161 let diagnostic_group = buffer
18162 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18163 .collect::<Vec<_>>();
18164
18165 let language_registry = self
18166 .project()
18167 .map(|project| project.read(cx).languages().clone());
18168
18169 let blocks = renderer.render_group(
18170 diagnostic_group,
18171 buffer_id,
18172 snapshot,
18173 cx.weak_entity(),
18174 language_registry,
18175 cx,
18176 );
18177
18178 let blocks = self.display_map.update(cx, |display_map, cx| {
18179 display_map.insert_blocks(blocks, cx).into_iter().collect()
18180 });
18181 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18182 active_range: buffer.anchor_before(diagnostic.range.start)
18183 ..buffer.anchor_after(diagnostic.range.end),
18184 active_message: diagnostic.diagnostic.message.clone(),
18185 group_id: diagnostic.diagnostic.group_id,
18186 blocks,
18187 });
18188 cx.notify();
18189 }
18190
18191 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18192 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18193 return;
18194 };
18195
18196 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18197 if let ActiveDiagnostic::Group(group) = prev {
18198 self.display_map.update(cx, |display_map, cx| {
18199 display_map.remove_blocks(group.blocks, cx);
18200 });
18201 cx.notify();
18202 }
18203 }
18204
18205 /// Disable inline diagnostics rendering for this editor.
18206 pub fn disable_inline_diagnostics(&mut self) {
18207 self.inline_diagnostics_enabled = false;
18208 self.inline_diagnostics_update = Task::ready(());
18209 self.inline_diagnostics.clear();
18210 }
18211
18212 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18213 self.diagnostics_enabled = false;
18214 self.dismiss_diagnostics(cx);
18215 self.inline_diagnostics_update = Task::ready(());
18216 self.inline_diagnostics.clear();
18217 }
18218
18219 pub fn disable_word_completions(&mut self) {
18220 self.word_completions_enabled = false;
18221 }
18222
18223 pub fn diagnostics_enabled(&self) -> bool {
18224 self.diagnostics_enabled && self.mode.is_full()
18225 }
18226
18227 pub fn inline_diagnostics_enabled(&self) -> bool {
18228 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18229 }
18230
18231 pub fn show_inline_diagnostics(&self) -> bool {
18232 self.show_inline_diagnostics
18233 }
18234
18235 pub fn toggle_inline_diagnostics(
18236 &mut self,
18237 _: &ToggleInlineDiagnostics,
18238 window: &mut Window,
18239 cx: &mut Context<Editor>,
18240 ) {
18241 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18242 self.refresh_inline_diagnostics(false, window, cx);
18243 }
18244
18245 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18246 self.diagnostics_max_severity = severity;
18247 self.display_map.update(cx, |display_map, _| {
18248 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18249 });
18250 }
18251
18252 pub fn toggle_diagnostics(
18253 &mut self,
18254 _: &ToggleDiagnostics,
18255 window: &mut Window,
18256 cx: &mut Context<Editor>,
18257 ) {
18258 if !self.diagnostics_enabled() {
18259 return;
18260 }
18261
18262 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18263 EditorSettings::get_global(cx)
18264 .diagnostics_max_severity
18265 .filter(|severity| severity != &DiagnosticSeverity::Off)
18266 .unwrap_or(DiagnosticSeverity::Hint)
18267 } else {
18268 DiagnosticSeverity::Off
18269 };
18270 self.set_max_diagnostics_severity(new_severity, cx);
18271 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18272 self.active_diagnostics = ActiveDiagnostic::None;
18273 self.inline_diagnostics_update = Task::ready(());
18274 self.inline_diagnostics.clear();
18275 } else {
18276 self.refresh_inline_diagnostics(false, window, cx);
18277 }
18278
18279 cx.notify();
18280 }
18281
18282 pub fn toggle_minimap(
18283 &mut self,
18284 _: &ToggleMinimap,
18285 window: &mut Window,
18286 cx: &mut Context<Editor>,
18287 ) {
18288 if self.supports_minimap(cx) {
18289 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18290 }
18291 }
18292
18293 fn refresh_inline_diagnostics(
18294 &mut self,
18295 debounce: bool,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 let max_severity = ProjectSettings::get_global(cx)
18300 .diagnostics
18301 .inline
18302 .max_severity
18303 .unwrap_or(self.diagnostics_max_severity);
18304
18305 if !self.inline_diagnostics_enabled()
18306 || !self.diagnostics_enabled()
18307 || !self.show_inline_diagnostics
18308 || max_severity == DiagnosticSeverity::Off
18309 {
18310 self.inline_diagnostics_update = Task::ready(());
18311 self.inline_diagnostics.clear();
18312 return;
18313 }
18314
18315 let debounce_ms = ProjectSettings::get_global(cx)
18316 .diagnostics
18317 .inline
18318 .update_debounce_ms;
18319 let debounce = if debounce && debounce_ms > 0 {
18320 Some(Duration::from_millis(debounce_ms))
18321 } else {
18322 None
18323 };
18324 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18325 if let Some(debounce) = debounce {
18326 cx.background_executor().timer(debounce).await;
18327 }
18328 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18329 editor
18330 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18331 .ok()
18332 }) else {
18333 return;
18334 };
18335
18336 let new_inline_diagnostics = cx
18337 .background_spawn(async move {
18338 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18339 for diagnostic_entry in
18340 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18341 {
18342 let message = diagnostic_entry
18343 .diagnostic
18344 .message
18345 .split_once('\n')
18346 .map(|(line, _)| line)
18347 .map(SharedString::new)
18348 .unwrap_or_else(|| {
18349 SharedString::new(&*diagnostic_entry.diagnostic.message)
18350 });
18351 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18352 let (Ok(i) | Err(i)) = inline_diagnostics
18353 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18354 inline_diagnostics.insert(
18355 i,
18356 (
18357 start_anchor,
18358 InlineDiagnostic {
18359 message,
18360 group_id: diagnostic_entry.diagnostic.group_id,
18361 start: diagnostic_entry.range.start.to_point(&snapshot),
18362 is_primary: diagnostic_entry.diagnostic.is_primary,
18363 severity: diagnostic_entry.diagnostic.severity,
18364 },
18365 ),
18366 );
18367 }
18368 inline_diagnostics
18369 })
18370 .await;
18371
18372 editor
18373 .update(cx, |editor, cx| {
18374 editor.inline_diagnostics = new_inline_diagnostics;
18375 cx.notify();
18376 })
18377 .ok();
18378 });
18379 }
18380
18381 fn pull_diagnostics(
18382 &mut self,
18383 buffer_id: Option<BufferId>,
18384 window: &Window,
18385 cx: &mut Context<Self>,
18386 ) -> Option<()> {
18387 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18388 return None;
18389 }
18390 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18391 .diagnostics
18392 .lsp_pull_diagnostics;
18393 if !pull_diagnostics_settings.enabled {
18394 return None;
18395 }
18396 let project = self.project()?.downgrade();
18397 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18398 let mut buffers = self.buffer.read(cx).all_buffers();
18399 buffers.retain(|buffer| {
18400 let buffer_id_to_retain = buffer.read(cx).remote_id();
18401 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18402 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18403 });
18404 if buffers.is_empty() {
18405 self.pull_diagnostics_task = Task::ready(());
18406 return None;
18407 }
18408
18409 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18410 cx.background_executor().timer(debounce).await;
18411
18412 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18413 buffers
18414 .into_iter()
18415 .filter_map(|buffer| {
18416 project
18417 .update(cx, |project, cx| {
18418 project.lsp_store().update(cx, |lsp_store, cx| {
18419 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18420 })
18421 })
18422 .ok()
18423 })
18424 .collect::<FuturesUnordered<_>>()
18425 }) else {
18426 return;
18427 };
18428
18429 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18430 match pull_task {
18431 Ok(()) => {
18432 if editor
18433 .update_in(cx, |editor, window, cx| {
18434 editor.update_diagnostics_state(window, cx);
18435 })
18436 .is_err()
18437 {
18438 return;
18439 }
18440 }
18441 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18442 }
18443 }
18444 });
18445
18446 Some(())
18447 }
18448
18449 pub fn set_selections_from_remote(
18450 &mut self,
18451 selections: Vec<Selection<Anchor>>,
18452 pending_selection: Option<Selection<Anchor>>,
18453 window: &mut Window,
18454 cx: &mut Context<Self>,
18455 ) {
18456 let old_cursor_position = self.selections.newest_anchor().head();
18457 self.selections
18458 .change_with(&self.display_snapshot(cx), |s| {
18459 s.select_anchors(selections);
18460 if let Some(pending_selection) = pending_selection {
18461 s.set_pending(pending_selection, SelectMode::Character);
18462 } else {
18463 s.clear_pending();
18464 }
18465 });
18466 self.selections_did_change(
18467 false,
18468 &old_cursor_position,
18469 SelectionEffects::default(),
18470 window,
18471 cx,
18472 );
18473 }
18474
18475 pub fn transact(
18476 &mut self,
18477 window: &mut Window,
18478 cx: &mut Context<Self>,
18479 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18480 ) -> Option<TransactionId> {
18481 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18482 this.start_transaction_at(Instant::now(), window, cx);
18483 update(this, window, cx);
18484 this.end_transaction_at(Instant::now(), cx)
18485 })
18486 }
18487
18488 pub fn start_transaction_at(
18489 &mut self,
18490 now: Instant,
18491 window: &mut Window,
18492 cx: &mut Context<Self>,
18493 ) -> Option<TransactionId> {
18494 self.end_selection(window, cx);
18495 if let Some(tx_id) = self
18496 .buffer
18497 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18498 {
18499 self.selection_history
18500 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18501 cx.emit(EditorEvent::TransactionBegun {
18502 transaction_id: tx_id,
18503 });
18504 Some(tx_id)
18505 } else {
18506 None
18507 }
18508 }
18509
18510 pub fn end_transaction_at(
18511 &mut self,
18512 now: Instant,
18513 cx: &mut Context<Self>,
18514 ) -> Option<TransactionId> {
18515 if let Some(transaction_id) = self
18516 .buffer
18517 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18518 {
18519 if let Some((_, end_selections)) =
18520 self.selection_history.transaction_mut(transaction_id)
18521 {
18522 *end_selections = Some(self.selections.disjoint_anchors_arc());
18523 } else {
18524 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18525 }
18526
18527 cx.emit(EditorEvent::Edited { transaction_id });
18528 Some(transaction_id)
18529 } else {
18530 None
18531 }
18532 }
18533
18534 pub fn modify_transaction_selection_history(
18535 &mut self,
18536 transaction_id: TransactionId,
18537 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18538 ) -> bool {
18539 self.selection_history
18540 .transaction_mut(transaction_id)
18541 .map(modify)
18542 .is_some()
18543 }
18544
18545 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18546 if self.selection_mark_mode {
18547 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18548 s.move_with(|_, sel| {
18549 sel.collapse_to(sel.head(), SelectionGoal::None);
18550 });
18551 })
18552 }
18553 self.selection_mark_mode = true;
18554 cx.notify();
18555 }
18556
18557 pub fn swap_selection_ends(
18558 &mut self,
18559 _: &actions::SwapSelectionEnds,
18560 window: &mut Window,
18561 cx: &mut Context<Self>,
18562 ) {
18563 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18564 s.move_with(|_, sel| {
18565 if sel.start != sel.end {
18566 sel.reversed = !sel.reversed
18567 }
18568 });
18569 });
18570 self.request_autoscroll(Autoscroll::newest(), cx);
18571 cx.notify();
18572 }
18573
18574 pub fn toggle_focus(
18575 workspace: &mut Workspace,
18576 _: &actions::ToggleFocus,
18577 window: &mut Window,
18578 cx: &mut Context<Workspace>,
18579 ) {
18580 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18581 return;
18582 };
18583 workspace.activate_item(&item, true, true, window, cx);
18584 }
18585
18586 pub fn toggle_fold(
18587 &mut self,
18588 _: &actions::ToggleFold,
18589 window: &mut Window,
18590 cx: &mut Context<Self>,
18591 ) {
18592 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18594 let selection = self.selections.newest::<Point>(&display_map);
18595
18596 let range = if selection.is_empty() {
18597 let point = selection.head().to_display_point(&display_map);
18598 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18599 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18600 .to_point(&display_map);
18601 start..end
18602 } else {
18603 selection.range()
18604 };
18605 if display_map.folds_in_range(range).next().is_some() {
18606 self.unfold_lines(&Default::default(), window, cx)
18607 } else {
18608 self.fold(&Default::default(), window, cx)
18609 }
18610 } else {
18611 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18612 let buffer_ids: HashSet<_> = self
18613 .selections
18614 .disjoint_anchor_ranges()
18615 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18616 .collect();
18617
18618 let should_unfold = buffer_ids
18619 .iter()
18620 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18621
18622 for buffer_id in buffer_ids {
18623 if should_unfold {
18624 self.unfold_buffer(buffer_id, cx);
18625 } else {
18626 self.fold_buffer(buffer_id, cx);
18627 }
18628 }
18629 }
18630 }
18631
18632 pub fn toggle_fold_recursive(
18633 &mut self,
18634 _: &actions::ToggleFoldRecursive,
18635 window: &mut Window,
18636 cx: &mut Context<Self>,
18637 ) {
18638 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18639
18640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18641 let range = if selection.is_empty() {
18642 let point = selection.head().to_display_point(&display_map);
18643 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18644 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18645 .to_point(&display_map);
18646 start..end
18647 } else {
18648 selection.range()
18649 };
18650 if display_map.folds_in_range(range).next().is_some() {
18651 self.unfold_recursive(&Default::default(), window, cx)
18652 } else {
18653 self.fold_recursive(&Default::default(), window, cx)
18654 }
18655 }
18656
18657 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18658 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18659 let mut to_fold = Vec::new();
18660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18661 let selections = self.selections.all_adjusted(&display_map);
18662
18663 for selection in selections {
18664 let range = selection.range().sorted();
18665 let buffer_start_row = range.start.row;
18666
18667 if range.start.row != range.end.row {
18668 let mut found = false;
18669 let mut row = range.start.row;
18670 while row <= range.end.row {
18671 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18672 {
18673 found = true;
18674 row = crease.range().end.row + 1;
18675 to_fold.push(crease);
18676 } else {
18677 row += 1
18678 }
18679 }
18680 if found {
18681 continue;
18682 }
18683 }
18684
18685 for row in (0..=range.start.row).rev() {
18686 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18687 && crease.range().end.row >= buffer_start_row
18688 {
18689 to_fold.push(crease);
18690 if row <= range.start.row {
18691 break;
18692 }
18693 }
18694 }
18695 }
18696
18697 self.fold_creases(to_fold, true, window, cx);
18698 } else {
18699 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18700 let buffer_ids = self
18701 .selections
18702 .disjoint_anchor_ranges()
18703 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18704 .collect::<HashSet<_>>();
18705 for buffer_id in buffer_ids {
18706 self.fold_buffer(buffer_id, cx);
18707 }
18708 }
18709 }
18710
18711 pub fn toggle_fold_all(
18712 &mut self,
18713 _: &actions::ToggleFoldAll,
18714 window: &mut Window,
18715 cx: &mut Context<Self>,
18716 ) {
18717 if self.buffer.read(cx).is_singleton() {
18718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18719 let has_folds = display_map
18720 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18721 .next()
18722 .is_some();
18723
18724 if has_folds {
18725 self.unfold_all(&actions::UnfoldAll, window, cx);
18726 } else {
18727 self.fold_all(&actions::FoldAll, window, cx);
18728 }
18729 } else {
18730 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18731 let should_unfold = buffer_ids
18732 .iter()
18733 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18734
18735 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18736 editor
18737 .update_in(cx, |editor, _, cx| {
18738 for buffer_id in buffer_ids {
18739 if should_unfold {
18740 editor.unfold_buffer(buffer_id, cx);
18741 } else {
18742 editor.fold_buffer(buffer_id, cx);
18743 }
18744 }
18745 })
18746 .ok();
18747 });
18748 }
18749 }
18750
18751 fn fold_at_level(
18752 &mut self,
18753 fold_at: &FoldAtLevel,
18754 window: &mut Window,
18755 cx: &mut Context<Self>,
18756 ) {
18757 if !self.buffer.read(cx).is_singleton() {
18758 return;
18759 }
18760
18761 let fold_at_level = fold_at.0;
18762 let snapshot = self.buffer.read(cx).snapshot(cx);
18763 let mut to_fold = Vec::new();
18764 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18765
18766 let row_ranges_to_keep: Vec<Range<u32>> = self
18767 .selections
18768 .all::<Point>(&self.display_snapshot(cx))
18769 .into_iter()
18770 .map(|sel| sel.start.row..sel.end.row)
18771 .collect();
18772
18773 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18774 while start_row < end_row {
18775 match self
18776 .snapshot(window, cx)
18777 .crease_for_buffer_row(MultiBufferRow(start_row))
18778 {
18779 Some(crease) => {
18780 let nested_start_row = crease.range().start.row + 1;
18781 let nested_end_row = crease.range().end.row;
18782
18783 if current_level < fold_at_level {
18784 stack.push((nested_start_row, nested_end_row, current_level + 1));
18785 } else if current_level == fold_at_level {
18786 // Fold iff there is no selection completely contained within the fold region
18787 if !row_ranges_to_keep.iter().any(|selection| {
18788 selection.end >= nested_start_row
18789 && selection.start <= nested_end_row
18790 }) {
18791 to_fold.push(crease);
18792 }
18793 }
18794
18795 start_row = nested_end_row + 1;
18796 }
18797 None => start_row += 1,
18798 }
18799 }
18800 }
18801
18802 self.fold_creases(to_fold, true, window, cx);
18803 }
18804
18805 pub fn fold_at_level_1(
18806 &mut self,
18807 _: &actions::FoldAtLevel1,
18808 window: &mut Window,
18809 cx: &mut Context<Self>,
18810 ) {
18811 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18812 }
18813
18814 pub fn fold_at_level_2(
18815 &mut self,
18816 _: &actions::FoldAtLevel2,
18817 window: &mut Window,
18818 cx: &mut Context<Self>,
18819 ) {
18820 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18821 }
18822
18823 pub fn fold_at_level_3(
18824 &mut self,
18825 _: &actions::FoldAtLevel3,
18826 window: &mut Window,
18827 cx: &mut Context<Self>,
18828 ) {
18829 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18830 }
18831
18832 pub fn fold_at_level_4(
18833 &mut self,
18834 _: &actions::FoldAtLevel4,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18839 }
18840
18841 pub fn fold_at_level_5(
18842 &mut self,
18843 _: &actions::FoldAtLevel5,
18844 window: &mut Window,
18845 cx: &mut Context<Self>,
18846 ) {
18847 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18848 }
18849
18850 pub fn fold_at_level_6(
18851 &mut self,
18852 _: &actions::FoldAtLevel6,
18853 window: &mut Window,
18854 cx: &mut Context<Self>,
18855 ) {
18856 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18857 }
18858
18859 pub fn fold_at_level_7(
18860 &mut self,
18861 _: &actions::FoldAtLevel7,
18862 window: &mut Window,
18863 cx: &mut Context<Self>,
18864 ) {
18865 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18866 }
18867
18868 pub fn fold_at_level_8(
18869 &mut self,
18870 _: &actions::FoldAtLevel8,
18871 window: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18875 }
18876
18877 pub fn fold_at_level_9(
18878 &mut self,
18879 _: &actions::FoldAtLevel9,
18880 window: &mut Window,
18881 cx: &mut Context<Self>,
18882 ) {
18883 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18884 }
18885
18886 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18887 if self.buffer.read(cx).is_singleton() {
18888 let mut fold_ranges = Vec::new();
18889 let snapshot = self.buffer.read(cx).snapshot(cx);
18890
18891 for row in 0..snapshot.max_row().0 {
18892 if let Some(foldable_range) = self
18893 .snapshot(window, cx)
18894 .crease_for_buffer_row(MultiBufferRow(row))
18895 {
18896 fold_ranges.push(foldable_range);
18897 }
18898 }
18899
18900 self.fold_creases(fold_ranges, true, window, cx);
18901 } else {
18902 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18903 editor
18904 .update_in(cx, |editor, _, cx| {
18905 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18906 editor.fold_buffer(buffer_id, cx);
18907 }
18908 })
18909 .ok();
18910 });
18911 }
18912 }
18913
18914 pub fn fold_function_bodies(
18915 &mut self,
18916 _: &actions::FoldFunctionBodies,
18917 window: &mut Window,
18918 cx: &mut Context<Self>,
18919 ) {
18920 let snapshot = self.buffer.read(cx).snapshot(cx);
18921
18922 let ranges = snapshot
18923 .text_object_ranges(
18924 MultiBufferOffset(0)..snapshot.len(),
18925 TreeSitterOptions::default(),
18926 )
18927 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18928 .collect::<Vec<_>>();
18929
18930 let creases = ranges
18931 .into_iter()
18932 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18933 .collect();
18934
18935 self.fold_creases(creases, true, window, cx);
18936 }
18937
18938 pub fn fold_recursive(
18939 &mut self,
18940 _: &actions::FoldRecursive,
18941 window: &mut Window,
18942 cx: &mut Context<Self>,
18943 ) {
18944 let mut to_fold = Vec::new();
18945 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18946 let selections = self.selections.all_adjusted(&display_map);
18947
18948 for selection in selections {
18949 let range = selection.range().sorted();
18950 let buffer_start_row = range.start.row;
18951
18952 if range.start.row != range.end.row {
18953 let mut found = false;
18954 for row in range.start.row..=range.end.row {
18955 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18956 found = true;
18957 to_fold.push(crease);
18958 }
18959 }
18960 if found {
18961 continue;
18962 }
18963 }
18964
18965 for row in (0..=range.start.row).rev() {
18966 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18967 if crease.range().end.row >= buffer_start_row {
18968 to_fold.push(crease);
18969 } else {
18970 break;
18971 }
18972 }
18973 }
18974 }
18975
18976 self.fold_creases(to_fold, true, window, cx);
18977 }
18978
18979 pub fn fold_at(
18980 &mut self,
18981 buffer_row: MultiBufferRow,
18982 window: &mut Window,
18983 cx: &mut Context<Self>,
18984 ) {
18985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18986
18987 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18988 let autoscroll = self
18989 .selections
18990 .all::<Point>(&display_map)
18991 .iter()
18992 .any(|selection| crease.range().overlaps(&selection.range()));
18993
18994 self.fold_creases(vec![crease], autoscroll, window, cx);
18995 }
18996 }
18997
18998 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18999 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19001 let buffer = display_map.buffer_snapshot();
19002 let selections = self.selections.all::<Point>(&display_map);
19003 let ranges = selections
19004 .iter()
19005 .map(|s| {
19006 let range = s.display_range(&display_map).sorted();
19007 let mut start = range.start.to_point(&display_map);
19008 let mut end = range.end.to_point(&display_map);
19009 start.column = 0;
19010 end.column = buffer.line_len(MultiBufferRow(end.row));
19011 start..end
19012 })
19013 .collect::<Vec<_>>();
19014
19015 self.unfold_ranges(&ranges, true, true, cx);
19016 } else {
19017 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19018 let buffer_ids = self
19019 .selections
19020 .disjoint_anchor_ranges()
19021 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19022 .collect::<HashSet<_>>();
19023 for buffer_id in buffer_ids {
19024 self.unfold_buffer(buffer_id, cx);
19025 }
19026 }
19027 }
19028
19029 pub fn unfold_recursive(
19030 &mut self,
19031 _: &UnfoldRecursive,
19032 _window: &mut Window,
19033 cx: &mut Context<Self>,
19034 ) {
19035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19036 let selections = self.selections.all::<Point>(&display_map);
19037 let ranges = selections
19038 .iter()
19039 .map(|s| {
19040 let mut range = s.display_range(&display_map).sorted();
19041 *range.start.column_mut() = 0;
19042 *range.end.column_mut() = display_map.line_len(range.end.row());
19043 let start = range.start.to_point(&display_map);
19044 let end = range.end.to_point(&display_map);
19045 start..end
19046 })
19047 .collect::<Vec<_>>();
19048
19049 self.unfold_ranges(&ranges, true, true, cx);
19050 }
19051
19052 pub fn unfold_at(
19053 &mut self,
19054 buffer_row: MultiBufferRow,
19055 _window: &mut Window,
19056 cx: &mut Context<Self>,
19057 ) {
19058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19059
19060 let intersection_range = Point::new(buffer_row.0, 0)
19061 ..Point::new(
19062 buffer_row.0,
19063 display_map.buffer_snapshot().line_len(buffer_row),
19064 );
19065
19066 let autoscroll = self
19067 .selections
19068 .all::<Point>(&display_map)
19069 .iter()
19070 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19071
19072 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19073 }
19074
19075 pub fn unfold_all(
19076 &mut self,
19077 _: &actions::UnfoldAll,
19078 _window: &mut Window,
19079 cx: &mut Context<Self>,
19080 ) {
19081 if self.buffer.read(cx).is_singleton() {
19082 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19083 self.unfold_ranges(
19084 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19085 true,
19086 true,
19087 cx,
19088 );
19089 } else {
19090 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19091 editor
19092 .update(cx, |editor, cx| {
19093 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19094 editor.unfold_buffer(buffer_id, cx);
19095 }
19096 })
19097 .ok();
19098 });
19099 }
19100 }
19101
19102 pub fn fold_selected_ranges(
19103 &mut self,
19104 _: &FoldSelectedRanges,
19105 window: &mut Window,
19106 cx: &mut Context<Self>,
19107 ) {
19108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19109 let selections = self.selections.all_adjusted(&display_map);
19110 let ranges = selections
19111 .into_iter()
19112 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19113 .collect::<Vec<_>>();
19114 self.fold_creases(ranges, true, window, cx);
19115 }
19116
19117 pub fn fold_ranges<T: ToOffset + Clone>(
19118 &mut self,
19119 ranges: Vec<Range<T>>,
19120 auto_scroll: bool,
19121 window: &mut Window,
19122 cx: &mut Context<Self>,
19123 ) {
19124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19125 let ranges = ranges
19126 .into_iter()
19127 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19128 .collect::<Vec<_>>();
19129 self.fold_creases(ranges, auto_scroll, window, cx);
19130 }
19131
19132 pub fn fold_creases<T: ToOffset + Clone>(
19133 &mut self,
19134 creases: Vec<Crease<T>>,
19135 auto_scroll: bool,
19136 _window: &mut Window,
19137 cx: &mut Context<Self>,
19138 ) {
19139 if creases.is_empty() {
19140 return;
19141 }
19142
19143 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19144
19145 if auto_scroll {
19146 self.request_autoscroll(Autoscroll::fit(), cx);
19147 }
19148
19149 cx.notify();
19150
19151 self.scrollbar_marker_state.dirty = true;
19152 self.folds_did_change(cx);
19153 }
19154
19155 /// Removes any folds whose ranges intersect any of the given ranges.
19156 pub fn unfold_ranges<T: ToOffset + Clone>(
19157 &mut self,
19158 ranges: &[Range<T>],
19159 inclusive: bool,
19160 auto_scroll: bool,
19161 cx: &mut Context<Self>,
19162 ) {
19163 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19164 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19165 });
19166 self.folds_did_change(cx);
19167 }
19168
19169 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19170 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19171 return;
19172 }
19173
19174 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19175 self.display_map.update(cx, |display_map, cx| {
19176 display_map.fold_buffers([buffer_id], cx)
19177 });
19178
19179 let snapshot = self.display_snapshot(cx);
19180 self.selections.change_with(&snapshot, |selections| {
19181 selections.remove_selections_from_buffer(buffer_id);
19182 });
19183
19184 cx.emit(EditorEvent::BufferFoldToggled {
19185 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19186 folded: true,
19187 });
19188 cx.notify();
19189 }
19190
19191 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19192 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19193 return;
19194 }
19195 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19196 self.display_map.update(cx, |display_map, cx| {
19197 display_map.unfold_buffers([buffer_id], cx);
19198 });
19199 cx.emit(EditorEvent::BufferFoldToggled {
19200 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19201 folded: false,
19202 });
19203 cx.notify();
19204 }
19205
19206 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19207 self.display_map.read(cx).is_buffer_folded(buffer)
19208 }
19209
19210 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19211 self.display_map.read(cx).folded_buffers()
19212 }
19213
19214 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19215 self.display_map.update(cx, |display_map, cx| {
19216 display_map.disable_header_for_buffer(buffer_id, cx);
19217 });
19218 cx.notify();
19219 }
19220
19221 /// Removes any folds with the given ranges.
19222 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19223 &mut self,
19224 ranges: &[Range<T>],
19225 type_id: TypeId,
19226 auto_scroll: bool,
19227 cx: &mut Context<Self>,
19228 ) {
19229 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19230 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19231 });
19232 self.folds_did_change(cx);
19233 }
19234
19235 fn remove_folds_with<T: ToOffset + Clone>(
19236 &mut self,
19237 ranges: &[Range<T>],
19238 auto_scroll: bool,
19239 cx: &mut Context<Self>,
19240 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19241 ) {
19242 if ranges.is_empty() {
19243 return;
19244 }
19245
19246 let mut buffers_affected = HashSet::default();
19247 let multi_buffer = self.buffer().read(cx);
19248 for range in ranges {
19249 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19250 buffers_affected.insert(buffer.read(cx).remote_id());
19251 };
19252 }
19253
19254 self.display_map.update(cx, update);
19255
19256 if auto_scroll {
19257 self.request_autoscroll(Autoscroll::fit(), cx);
19258 }
19259
19260 cx.notify();
19261 self.scrollbar_marker_state.dirty = true;
19262 self.active_indent_guides_state.dirty = true;
19263 }
19264
19265 pub fn update_renderer_widths(
19266 &mut self,
19267 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19268 cx: &mut Context<Self>,
19269 ) -> bool {
19270 self.display_map
19271 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19272 }
19273
19274 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19275 self.display_map.read(cx).fold_placeholder.clone()
19276 }
19277
19278 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19279 self.buffer.update(cx, |buffer, cx| {
19280 buffer.set_all_diff_hunks_expanded(cx);
19281 });
19282 }
19283
19284 pub fn expand_all_diff_hunks(
19285 &mut self,
19286 _: &ExpandAllDiffHunks,
19287 _window: &mut Window,
19288 cx: &mut Context<Self>,
19289 ) {
19290 self.buffer.update(cx, |buffer, cx| {
19291 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19292 });
19293 }
19294
19295 pub fn collapse_all_diff_hunks(
19296 &mut self,
19297 _: &CollapseAllDiffHunks,
19298 _window: &mut Window,
19299 cx: &mut Context<Self>,
19300 ) {
19301 self.buffer.update(cx, |buffer, cx| {
19302 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19303 });
19304 }
19305
19306 pub fn toggle_selected_diff_hunks(
19307 &mut self,
19308 _: &ToggleSelectedDiffHunks,
19309 _window: &mut Window,
19310 cx: &mut Context<Self>,
19311 ) {
19312 let ranges: Vec<_> = self
19313 .selections
19314 .disjoint_anchors()
19315 .iter()
19316 .map(|s| s.range())
19317 .collect();
19318 self.toggle_diff_hunks_in_ranges(ranges, cx);
19319 }
19320
19321 pub fn diff_hunks_in_ranges<'a>(
19322 &'a self,
19323 ranges: &'a [Range<Anchor>],
19324 buffer: &'a MultiBufferSnapshot,
19325 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19326 ranges.iter().flat_map(move |range| {
19327 let end_excerpt_id = range.end.excerpt_id;
19328 let range = range.to_point(buffer);
19329 let mut peek_end = range.end;
19330 if range.end.row < buffer.max_row().0 {
19331 peek_end = Point::new(range.end.row + 1, 0);
19332 }
19333 buffer
19334 .diff_hunks_in_range(range.start..peek_end)
19335 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19336 })
19337 }
19338
19339 pub fn has_stageable_diff_hunks_in_ranges(
19340 &self,
19341 ranges: &[Range<Anchor>],
19342 snapshot: &MultiBufferSnapshot,
19343 ) -> bool {
19344 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19345 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19346 }
19347
19348 pub fn toggle_staged_selected_diff_hunks(
19349 &mut self,
19350 _: &::git::ToggleStaged,
19351 _: &mut Window,
19352 cx: &mut Context<Self>,
19353 ) {
19354 let snapshot = self.buffer.read(cx).snapshot(cx);
19355 let ranges: Vec<_> = self
19356 .selections
19357 .disjoint_anchors()
19358 .iter()
19359 .map(|s| s.range())
19360 .collect();
19361 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19362 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19363 }
19364
19365 pub fn set_render_diff_hunk_controls(
19366 &mut self,
19367 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19368 cx: &mut Context<Self>,
19369 ) {
19370 self.render_diff_hunk_controls = render_diff_hunk_controls;
19371 cx.notify();
19372 }
19373
19374 pub fn stage_and_next(
19375 &mut self,
19376 _: &::git::StageAndNext,
19377 window: &mut Window,
19378 cx: &mut Context<Self>,
19379 ) {
19380 self.do_stage_or_unstage_and_next(true, window, cx);
19381 }
19382
19383 pub fn unstage_and_next(
19384 &mut self,
19385 _: &::git::UnstageAndNext,
19386 window: &mut Window,
19387 cx: &mut Context<Self>,
19388 ) {
19389 self.do_stage_or_unstage_and_next(false, window, cx);
19390 }
19391
19392 pub fn stage_or_unstage_diff_hunks(
19393 &mut self,
19394 stage: bool,
19395 ranges: Vec<Range<Anchor>>,
19396 cx: &mut Context<Self>,
19397 ) {
19398 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19399 cx.spawn(async move |this, cx| {
19400 task.await?;
19401 this.update(cx, |this, cx| {
19402 let snapshot = this.buffer.read(cx).snapshot(cx);
19403 let chunk_by = this
19404 .diff_hunks_in_ranges(&ranges, &snapshot)
19405 .chunk_by(|hunk| hunk.buffer_id);
19406 for (buffer_id, hunks) in &chunk_by {
19407 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19408 }
19409 })
19410 })
19411 .detach_and_log_err(cx);
19412 }
19413
19414 fn save_buffers_for_ranges_if_needed(
19415 &mut self,
19416 ranges: &[Range<Anchor>],
19417 cx: &mut Context<Editor>,
19418 ) -> Task<Result<()>> {
19419 let multibuffer = self.buffer.read(cx);
19420 let snapshot = multibuffer.read(cx);
19421 let buffer_ids: HashSet<_> = ranges
19422 .iter()
19423 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19424 .collect();
19425 drop(snapshot);
19426
19427 let mut buffers = HashSet::default();
19428 for buffer_id in buffer_ids {
19429 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19430 let buffer = buffer_entity.read(cx);
19431 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19432 {
19433 buffers.insert(buffer_entity);
19434 }
19435 }
19436 }
19437
19438 if let Some(project) = &self.project {
19439 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19440 } else {
19441 Task::ready(Ok(()))
19442 }
19443 }
19444
19445 fn do_stage_or_unstage_and_next(
19446 &mut self,
19447 stage: bool,
19448 window: &mut Window,
19449 cx: &mut Context<Self>,
19450 ) {
19451 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19452
19453 if ranges.iter().any(|range| range.start != range.end) {
19454 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19455 return;
19456 }
19457
19458 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19459 let snapshot = self.snapshot(window, cx);
19460 let position = self
19461 .selections
19462 .newest::<Point>(&snapshot.display_snapshot)
19463 .head();
19464 let mut row = snapshot
19465 .buffer_snapshot()
19466 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19467 .find(|hunk| hunk.row_range.start.0 > position.row)
19468 .map(|hunk| hunk.row_range.start);
19469
19470 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19471 // Outside of the project diff editor, wrap around to the beginning.
19472 if !all_diff_hunks_expanded {
19473 row = row.or_else(|| {
19474 snapshot
19475 .buffer_snapshot()
19476 .diff_hunks_in_range(Point::zero()..position)
19477 .find(|hunk| hunk.row_range.end.0 < position.row)
19478 .map(|hunk| hunk.row_range.start)
19479 });
19480 }
19481
19482 if let Some(row) = row {
19483 let destination = Point::new(row.0, 0);
19484 let autoscroll = Autoscroll::center();
19485
19486 self.unfold_ranges(&[destination..destination], false, false, cx);
19487 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19488 s.select_ranges([destination..destination]);
19489 });
19490 }
19491 }
19492
19493 fn do_stage_or_unstage(
19494 &self,
19495 stage: bool,
19496 buffer_id: BufferId,
19497 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19498 cx: &mut App,
19499 ) -> Option<()> {
19500 let project = self.project()?;
19501 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19502 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19503 let buffer_snapshot = buffer.read(cx).snapshot();
19504 let file_exists = buffer_snapshot
19505 .file()
19506 .is_some_and(|file| file.disk_state().exists());
19507 diff.update(cx, |diff, cx| {
19508 diff.stage_or_unstage_hunks(
19509 stage,
19510 &hunks
19511 .map(|hunk| buffer_diff::DiffHunk {
19512 buffer_range: hunk.buffer_range,
19513 // We don't need to pass in word diffs here because they're only used for rendering and
19514 // this function changes internal state
19515 base_word_diffs: Vec::default(),
19516 buffer_word_diffs: Vec::default(),
19517 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19518 ..hunk.diff_base_byte_range.end.0,
19519 secondary_status: hunk.secondary_status,
19520 range: Point::zero()..Point::zero(), // unused
19521 })
19522 .collect::<Vec<_>>(),
19523 &buffer_snapshot,
19524 file_exists,
19525 cx,
19526 )
19527 });
19528 None
19529 }
19530
19531 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19532 let ranges: Vec<_> = self
19533 .selections
19534 .disjoint_anchors()
19535 .iter()
19536 .map(|s| s.range())
19537 .collect();
19538 self.buffer
19539 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19540 }
19541
19542 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19543 self.buffer.update(cx, |buffer, cx| {
19544 let ranges = vec![Anchor::min()..Anchor::max()];
19545 if !buffer.all_diff_hunks_expanded()
19546 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19547 {
19548 buffer.collapse_diff_hunks(ranges, cx);
19549 true
19550 } else {
19551 false
19552 }
19553 })
19554 }
19555
19556 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19557 if self.buffer.read(cx).all_diff_hunks_expanded() {
19558 return true;
19559 }
19560 let ranges = vec![Anchor::min()..Anchor::max()];
19561 self.buffer
19562 .read(cx)
19563 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19564 }
19565
19566 fn toggle_diff_hunks_in_ranges(
19567 &mut self,
19568 ranges: Vec<Range<Anchor>>,
19569 cx: &mut Context<Editor>,
19570 ) {
19571 self.buffer.update(cx, |buffer, cx| {
19572 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19573 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19574 })
19575 }
19576
19577 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19578 self.buffer.update(cx, |buffer, cx| {
19579 let snapshot = buffer.snapshot(cx);
19580 let excerpt_id = range.end.excerpt_id;
19581 let point_range = range.to_point(&snapshot);
19582 let expand = !buffer.single_hunk_is_expanded(range, cx);
19583 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19584 })
19585 }
19586
19587 pub(crate) fn apply_all_diff_hunks(
19588 &mut self,
19589 _: &ApplyAllDiffHunks,
19590 window: &mut Window,
19591 cx: &mut Context<Self>,
19592 ) {
19593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19594
19595 let buffers = self.buffer.read(cx).all_buffers();
19596 for branch_buffer in buffers {
19597 branch_buffer.update(cx, |branch_buffer, cx| {
19598 branch_buffer.merge_into_base(Vec::new(), cx);
19599 });
19600 }
19601
19602 if let Some(project) = self.project.clone() {
19603 self.save(
19604 SaveOptions {
19605 format: true,
19606 autosave: false,
19607 },
19608 project,
19609 window,
19610 cx,
19611 )
19612 .detach_and_log_err(cx);
19613 }
19614 }
19615
19616 pub(crate) fn apply_selected_diff_hunks(
19617 &mut self,
19618 _: &ApplyDiffHunk,
19619 window: &mut Window,
19620 cx: &mut Context<Self>,
19621 ) {
19622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19623 let snapshot = self.snapshot(window, cx);
19624 let hunks = snapshot.hunks_for_ranges(
19625 self.selections
19626 .all(&snapshot.display_snapshot)
19627 .into_iter()
19628 .map(|selection| selection.range()),
19629 );
19630 let mut ranges_by_buffer = HashMap::default();
19631 self.transact(window, cx, |editor, _window, cx| {
19632 for hunk in hunks {
19633 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19634 ranges_by_buffer
19635 .entry(buffer.clone())
19636 .or_insert_with(Vec::new)
19637 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19638 }
19639 }
19640
19641 for (buffer, ranges) in ranges_by_buffer {
19642 buffer.update(cx, |buffer, cx| {
19643 buffer.merge_into_base(ranges, cx);
19644 });
19645 }
19646 });
19647
19648 if let Some(project) = self.project.clone() {
19649 self.save(
19650 SaveOptions {
19651 format: true,
19652 autosave: false,
19653 },
19654 project,
19655 window,
19656 cx,
19657 )
19658 .detach_and_log_err(cx);
19659 }
19660 }
19661
19662 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19663 if hovered != self.gutter_hovered {
19664 self.gutter_hovered = hovered;
19665 cx.notify();
19666 }
19667 }
19668
19669 pub fn insert_blocks(
19670 &mut self,
19671 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19672 autoscroll: Option<Autoscroll>,
19673 cx: &mut Context<Self>,
19674 ) -> Vec<CustomBlockId> {
19675 let blocks = self
19676 .display_map
19677 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19678 if let Some(autoscroll) = autoscroll {
19679 self.request_autoscroll(autoscroll, cx);
19680 }
19681 cx.notify();
19682 blocks
19683 }
19684
19685 pub fn resize_blocks(
19686 &mut self,
19687 heights: HashMap<CustomBlockId, u32>,
19688 autoscroll: Option<Autoscroll>,
19689 cx: &mut Context<Self>,
19690 ) {
19691 self.display_map
19692 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19693 if let Some(autoscroll) = autoscroll {
19694 self.request_autoscroll(autoscroll, cx);
19695 }
19696 cx.notify();
19697 }
19698
19699 pub fn replace_blocks(
19700 &mut self,
19701 renderers: HashMap<CustomBlockId, RenderBlock>,
19702 autoscroll: Option<Autoscroll>,
19703 cx: &mut Context<Self>,
19704 ) {
19705 self.display_map
19706 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19707 if let Some(autoscroll) = autoscroll {
19708 self.request_autoscroll(autoscroll, cx);
19709 }
19710 cx.notify();
19711 }
19712
19713 pub fn remove_blocks(
19714 &mut self,
19715 block_ids: HashSet<CustomBlockId>,
19716 autoscroll: Option<Autoscroll>,
19717 cx: &mut Context<Self>,
19718 ) {
19719 self.display_map.update(cx, |display_map, cx| {
19720 display_map.remove_blocks(block_ids, cx)
19721 });
19722 if let Some(autoscroll) = autoscroll {
19723 self.request_autoscroll(autoscroll, cx);
19724 }
19725 cx.notify();
19726 }
19727
19728 pub fn row_for_block(
19729 &self,
19730 block_id: CustomBlockId,
19731 cx: &mut Context<Self>,
19732 ) -> Option<DisplayRow> {
19733 self.display_map
19734 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19735 }
19736
19737 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19738 self.focused_block = Some(focused_block);
19739 }
19740
19741 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19742 self.focused_block.take()
19743 }
19744
19745 pub fn insert_creases(
19746 &mut self,
19747 creases: impl IntoIterator<Item = Crease<Anchor>>,
19748 cx: &mut Context<Self>,
19749 ) -> Vec<CreaseId> {
19750 self.display_map
19751 .update(cx, |map, cx| map.insert_creases(creases, cx))
19752 }
19753
19754 pub fn remove_creases(
19755 &mut self,
19756 ids: impl IntoIterator<Item = CreaseId>,
19757 cx: &mut Context<Self>,
19758 ) -> Vec<(CreaseId, Range<Anchor>)> {
19759 self.display_map
19760 .update(cx, |map, cx| map.remove_creases(ids, cx))
19761 }
19762
19763 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19764 self.display_map
19765 .update(cx, |map, cx| map.snapshot(cx))
19766 .longest_row()
19767 }
19768
19769 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19770 self.display_map
19771 .update(cx, |map, cx| map.snapshot(cx))
19772 .max_point()
19773 }
19774
19775 pub fn text(&self, cx: &App) -> String {
19776 self.buffer.read(cx).read(cx).text()
19777 }
19778
19779 pub fn is_empty(&self, cx: &App) -> bool {
19780 self.buffer.read(cx).read(cx).is_empty()
19781 }
19782
19783 pub fn text_option(&self, cx: &App) -> Option<String> {
19784 let text = self.text(cx);
19785 let text = text.trim();
19786
19787 if text.is_empty() {
19788 return None;
19789 }
19790
19791 Some(text.to_string())
19792 }
19793
19794 pub fn set_text(
19795 &mut self,
19796 text: impl Into<Arc<str>>,
19797 window: &mut Window,
19798 cx: &mut Context<Self>,
19799 ) {
19800 self.transact(window, cx, |this, _, cx| {
19801 this.buffer
19802 .read(cx)
19803 .as_singleton()
19804 .expect("you can only call set_text on editors for singleton buffers")
19805 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19806 });
19807 }
19808
19809 pub fn display_text(&self, cx: &mut App) -> String {
19810 self.display_map
19811 .update(cx, |map, cx| map.snapshot(cx))
19812 .text()
19813 }
19814
19815 fn create_minimap(
19816 &self,
19817 minimap_settings: MinimapSettings,
19818 window: &mut Window,
19819 cx: &mut Context<Self>,
19820 ) -> Option<Entity<Self>> {
19821 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19822 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19823 }
19824
19825 fn initialize_new_minimap(
19826 &self,
19827 minimap_settings: MinimapSettings,
19828 window: &mut Window,
19829 cx: &mut Context<Self>,
19830 ) -> Entity<Self> {
19831 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19832
19833 let mut minimap = Editor::new_internal(
19834 EditorMode::Minimap {
19835 parent: cx.weak_entity(),
19836 },
19837 self.buffer.clone(),
19838 None,
19839 Some(self.display_map.clone()),
19840 window,
19841 cx,
19842 );
19843 minimap.scroll_manager.clone_state(&self.scroll_manager);
19844 minimap.set_text_style_refinement(TextStyleRefinement {
19845 font_size: Some(MINIMAP_FONT_SIZE),
19846 font_weight: Some(MINIMAP_FONT_WEIGHT),
19847 ..Default::default()
19848 });
19849 minimap.update_minimap_configuration(minimap_settings, cx);
19850 cx.new(|_| minimap)
19851 }
19852
19853 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19854 let current_line_highlight = minimap_settings
19855 .current_line_highlight
19856 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19857 self.set_current_line_highlight(Some(current_line_highlight));
19858 }
19859
19860 pub fn minimap(&self) -> Option<&Entity<Self>> {
19861 self.minimap
19862 .as_ref()
19863 .filter(|_| self.minimap_visibility.visible())
19864 }
19865
19866 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19867 let mut wrap_guides = smallvec![];
19868
19869 if self.show_wrap_guides == Some(false) {
19870 return wrap_guides;
19871 }
19872
19873 let settings = self.buffer.read(cx).language_settings(cx);
19874 if settings.show_wrap_guides {
19875 match self.soft_wrap_mode(cx) {
19876 SoftWrap::Column(soft_wrap) => {
19877 wrap_guides.push((soft_wrap as usize, true));
19878 }
19879 SoftWrap::Bounded(soft_wrap) => {
19880 wrap_guides.push((soft_wrap as usize, true));
19881 }
19882 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19883 }
19884 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19885 }
19886
19887 wrap_guides
19888 }
19889
19890 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19891 let settings = self.buffer.read(cx).language_settings(cx);
19892 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19893 match mode {
19894 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19895 SoftWrap::None
19896 }
19897 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19898 language_settings::SoftWrap::PreferredLineLength => {
19899 SoftWrap::Column(settings.preferred_line_length)
19900 }
19901 language_settings::SoftWrap::Bounded => {
19902 SoftWrap::Bounded(settings.preferred_line_length)
19903 }
19904 }
19905 }
19906
19907 pub fn set_soft_wrap_mode(
19908 &mut self,
19909 mode: language_settings::SoftWrap,
19910
19911 cx: &mut Context<Self>,
19912 ) {
19913 self.soft_wrap_mode_override = Some(mode);
19914 cx.notify();
19915 }
19916
19917 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19918 self.hard_wrap = hard_wrap;
19919 cx.notify();
19920 }
19921
19922 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19923 self.text_style_refinement = Some(style);
19924 }
19925
19926 /// called by the Element so we know what style we were most recently rendered with.
19927 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19928 // We intentionally do not inform the display map about the minimap style
19929 // so that wrapping is not recalculated and stays consistent for the editor
19930 // and its linked minimap.
19931 if !self.mode.is_minimap() {
19932 let font = style.text.font();
19933 let font_size = style.text.font_size.to_pixels(window.rem_size());
19934 let display_map = self
19935 .placeholder_display_map
19936 .as_ref()
19937 .filter(|_| self.is_empty(cx))
19938 .unwrap_or(&self.display_map);
19939
19940 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19941 }
19942 self.style = Some(style);
19943 }
19944
19945 pub fn style(&self) -> Option<&EditorStyle> {
19946 self.style.as_ref()
19947 }
19948
19949 // Called by the element. This method is not designed to be called outside of the editor
19950 // element's layout code because it does not notify when rewrapping is computed synchronously.
19951 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19952 if self.is_empty(cx) {
19953 self.placeholder_display_map
19954 .as_ref()
19955 .map_or(false, |display_map| {
19956 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19957 })
19958 } else {
19959 self.display_map
19960 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19961 }
19962 }
19963
19964 pub fn set_soft_wrap(&mut self) {
19965 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19966 }
19967
19968 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19969 if self.soft_wrap_mode_override.is_some() {
19970 self.soft_wrap_mode_override.take();
19971 } else {
19972 let soft_wrap = match self.soft_wrap_mode(cx) {
19973 SoftWrap::GitDiff => return,
19974 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19975 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19976 language_settings::SoftWrap::None
19977 }
19978 };
19979 self.soft_wrap_mode_override = Some(soft_wrap);
19980 }
19981 cx.notify();
19982 }
19983
19984 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19985 let Some(workspace) = self.workspace() else {
19986 return;
19987 };
19988 let fs = workspace.read(cx).app_state().fs.clone();
19989 let current_show = TabBarSettings::get_global(cx).show;
19990 update_settings_file(fs, cx, move |setting, _| {
19991 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19992 });
19993 }
19994
19995 pub fn toggle_indent_guides(
19996 &mut self,
19997 _: &ToggleIndentGuides,
19998 _: &mut Window,
19999 cx: &mut Context<Self>,
20000 ) {
20001 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20002 self.buffer
20003 .read(cx)
20004 .language_settings(cx)
20005 .indent_guides
20006 .enabled
20007 });
20008 self.show_indent_guides = Some(!currently_enabled);
20009 cx.notify();
20010 }
20011
20012 fn should_show_indent_guides(&self) -> Option<bool> {
20013 self.show_indent_guides
20014 }
20015
20016 pub fn toggle_line_numbers(
20017 &mut self,
20018 _: &ToggleLineNumbers,
20019 _: &mut Window,
20020 cx: &mut Context<Self>,
20021 ) {
20022 let mut editor_settings = EditorSettings::get_global(cx).clone();
20023 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20024 EditorSettings::override_global(editor_settings, cx);
20025 }
20026
20027 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20028 if let Some(show_line_numbers) = self.show_line_numbers {
20029 return show_line_numbers;
20030 }
20031 EditorSettings::get_global(cx).gutter.line_numbers
20032 }
20033
20034 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20035 match (
20036 self.use_relative_line_numbers,
20037 EditorSettings::get_global(cx).relative_line_numbers,
20038 ) {
20039 (None, setting) => setting,
20040 (Some(false), _) => RelativeLineNumbers::Disabled,
20041 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20042 (Some(true), _) => RelativeLineNumbers::Enabled,
20043 }
20044 }
20045
20046 pub fn toggle_relative_line_numbers(
20047 &mut self,
20048 _: &ToggleRelativeLineNumbers,
20049 _: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 let is_relative = self.relative_line_numbers(cx);
20053 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20054 }
20055
20056 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20057 self.use_relative_line_numbers = is_relative;
20058 cx.notify();
20059 }
20060
20061 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20062 self.show_gutter = show_gutter;
20063 cx.notify();
20064 }
20065
20066 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20067 self.show_scrollbars = ScrollbarAxes {
20068 horizontal: show,
20069 vertical: show,
20070 };
20071 cx.notify();
20072 }
20073
20074 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20075 self.show_scrollbars.vertical = show;
20076 cx.notify();
20077 }
20078
20079 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20080 self.show_scrollbars.horizontal = show;
20081 cx.notify();
20082 }
20083
20084 pub fn set_minimap_visibility(
20085 &mut self,
20086 minimap_visibility: MinimapVisibility,
20087 window: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 if self.minimap_visibility != minimap_visibility {
20091 if minimap_visibility.visible() && self.minimap.is_none() {
20092 let minimap_settings = EditorSettings::get_global(cx).minimap;
20093 self.minimap =
20094 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20095 }
20096 self.minimap_visibility = minimap_visibility;
20097 cx.notify();
20098 }
20099 }
20100
20101 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20102 self.set_show_scrollbars(false, cx);
20103 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20104 }
20105
20106 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20107 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20108 }
20109
20110 /// Normally the text in full mode and auto height editors is padded on the
20111 /// left side by roughly half a character width for improved hit testing.
20112 ///
20113 /// Use this method to disable this for cases where this is not wanted (e.g.
20114 /// if you want to align the editor text with some other text above or below)
20115 /// or if you want to add this padding to single-line editors.
20116 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20117 self.offset_content = offset_content;
20118 cx.notify();
20119 }
20120
20121 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20122 self.show_line_numbers = Some(show_line_numbers);
20123 cx.notify();
20124 }
20125
20126 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20127 self.disable_expand_excerpt_buttons = true;
20128 cx.notify();
20129 }
20130
20131 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20132 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20133 cx.notify();
20134 }
20135
20136 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20137 self.show_code_actions = Some(show_code_actions);
20138 cx.notify();
20139 }
20140
20141 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20142 self.show_runnables = Some(show_runnables);
20143 cx.notify();
20144 }
20145
20146 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20147 self.show_breakpoints = Some(show_breakpoints);
20148 cx.notify();
20149 }
20150
20151 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20152 if self.display_map.read(cx).masked != masked {
20153 self.display_map.update(cx, |map, _| map.masked = masked);
20154 }
20155 cx.notify()
20156 }
20157
20158 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20159 self.show_wrap_guides = Some(show_wrap_guides);
20160 cx.notify();
20161 }
20162
20163 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20164 self.show_indent_guides = Some(show_indent_guides);
20165 cx.notify();
20166 }
20167
20168 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20169 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20170 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20171 && let Some(dir) = file.abs_path(cx).parent()
20172 {
20173 return Some(dir.to_owned());
20174 }
20175 }
20176
20177 None
20178 }
20179
20180 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20181 self.active_excerpt(cx)?
20182 .1
20183 .read(cx)
20184 .file()
20185 .and_then(|f| f.as_local())
20186 }
20187
20188 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20189 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20190 let buffer = buffer.read(cx);
20191 if let Some(project_path) = buffer.project_path(cx) {
20192 let project = self.project()?.read(cx);
20193 project.absolute_path(&project_path, cx)
20194 } else {
20195 buffer
20196 .file()
20197 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20198 }
20199 })
20200 }
20201
20202 pub fn reveal_in_finder(
20203 &mut self,
20204 _: &RevealInFileManager,
20205 _window: &mut Window,
20206 cx: &mut Context<Self>,
20207 ) {
20208 if let Some(target) = self.target_file(cx) {
20209 cx.reveal_path(&target.abs_path(cx));
20210 }
20211 }
20212
20213 pub fn copy_path(
20214 &mut self,
20215 _: &zed_actions::workspace::CopyPath,
20216 _window: &mut Window,
20217 cx: &mut Context<Self>,
20218 ) {
20219 if let Some(path) = self.target_file_abs_path(cx)
20220 && let Some(path) = path.to_str()
20221 {
20222 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20223 } else {
20224 cx.propagate();
20225 }
20226 }
20227
20228 pub fn copy_relative_path(
20229 &mut self,
20230 _: &zed_actions::workspace::CopyRelativePath,
20231 _window: &mut Window,
20232 cx: &mut Context<Self>,
20233 ) {
20234 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20235 let project = self.project()?.read(cx);
20236 let path = buffer.read(cx).file()?.path();
20237 let path = path.display(project.path_style(cx));
20238 Some(path)
20239 }) {
20240 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20241 } else {
20242 cx.propagate();
20243 }
20244 }
20245
20246 /// Returns the project path for the editor's buffer, if any buffer is
20247 /// opened in the editor.
20248 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20249 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20250 buffer.read(cx).project_path(cx)
20251 } else {
20252 None
20253 }
20254 }
20255
20256 // Returns true if the editor handled a go-to-line request
20257 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20258 maybe!({
20259 let breakpoint_store = self.breakpoint_store.as_ref()?;
20260
20261 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20262 else {
20263 self.clear_row_highlights::<ActiveDebugLine>();
20264 return None;
20265 };
20266
20267 let position = active_stack_frame.position;
20268 let buffer_id = position.buffer_id?;
20269 let snapshot = self
20270 .project
20271 .as_ref()?
20272 .read(cx)
20273 .buffer_for_id(buffer_id, cx)?
20274 .read(cx)
20275 .snapshot();
20276
20277 let mut handled = false;
20278 for (id, ExcerptRange { context, .. }) in
20279 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20280 {
20281 if context.start.cmp(&position, &snapshot).is_ge()
20282 || context.end.cmp(&position, &snapshot).is_lt()
20283 {
20284 continue;
20285 }
20286 let snapshot = self.buffer.read(cx).snapshot(cx);
20287 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20288
20289 handled = true;
20290 self.clear_row_highlights::<ActiveDebugLine>();
20291
20292 self.go_to_line::<ActiveDebugLine>(
20293 multibuffer_anchor,
20294 Some(cx.theme().colors().editor_debugger_active_line_background),
20295 window,
20296 cx,
20297 );
20298
20299 cx.notify();
20300 }
20301
20302 handled.then_some(())
20303 })
20304 .is_some()
20305 }
20306
20307 pub fn copy_file_name_without_extension(
20308 &mut self,
20309 _: &CopyFileNameWithoutExtension,
20310 _: &mut Window,
20311 cx: &mut Context<Self>,
20312 ) {
20313 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20314 let file = buffer.read(cx).file()?;
20315 file.path().file_stem()
20316 }) {
20317 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20318 }
20319 }
20320
20321 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20322 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20323 let file = buffer.read(cx).file()?;
20324 Some(file.file_name(cx))
20325 }) {
20326 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20327 }
20328 }
20329
20330 pub fn toggle_git_blame(
20331 &mut self,
20332 _: &::git::Blame,
20333 window: &mut Window,
20334 cx: &mut Context<Self>,
20335 ) {
20336 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20337
20338 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20339 self.start_git_blame(true, window, cx);
20340 }
20341
20342 cx.notify();
20343 }
20344
20345 pub fn toggle_git_blame_inline(
20346 &mut self,
20347 _: &ToggleGitBlameInline,
20348 window: &mut Window,
20349 cx: &mut Context<Self>,
20350 ) {
20351 self.toggle_git_blame_inline_internal(true, window, cx);
20352 cx.notify();
20353 }
20354
20355 pub fn open_git_blame_commit(
20356 &mut self,
20357 _: &OpenGitBlameCommit,
20358 window: &mut Window,
20359 cx: &mut Context<Self>,
20360 ) {
20361 self.open_git_blame_commit_internal(window, cx);
20362 }
20363
20364 fn open_git_blame_commit_internal(
20365 &mut self,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) -> Option<()> {
20369 let blame = self.blame.as_ref()?;
20370 let snapshot = self.snapshot(window, cx);
20371 let cursor = self
20372 .selections
20373 .newest::<Point>(&snapshot.display_snapshot)
20374 .head();
20375 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20376 let (_, blame_entry) = blame
20377 .update(cx, |blame, cx| {
20378 blame
20379 .blame_for_rows(
20380 &[RowInfo {
20381 buffer_id: Some(buffer.remote_id()),
20382 buffer_row: Some(point.row),
20383 ..Default::default()
20384 }],
20385 cx,
20386 )
20387 .next()
20388 })
20389 .flatten()?;
20390 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20391 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20392 let workspace = self.workspace()?.downgrade();
20393 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20394 None
20395 }
20396
20397 pub fn git_blame_inline_enabled(&self) -> bool {
20398 self.git_blame_inline_enabled
20399 }
20400
20401 pub fn toggle_selection_menu(
20402 &mut self,
20403 _: &ToggleSelectionMenu,
20404 _: &mut Window,
20405 cx: &mut Context<Self>,
20406 ) {
20407 self.show_selection_menu = self
20408 .show_selection_menu
20409 .map(|show_selections_menu| !show_selections_menu)
20410 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20411
20412 cx.notify();
20413 }
20414
20415 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20416 self.show_selection_menu
20417 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20418 }
20419
20420 fn start_git_blame(
20421 &mut self,
20422 user_triggered: bool,
20423 window: &mut Window,
20424 cx: &mut Context<Self>,
20425 ) {
20426 if let Some(project) = self.project() {
20427 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20428 && buffer.read(cx).file().is_none()
20429 {
20430 return;
20431 }
20432
20433 let focused = self.focus_handle(cx).contains_focused(window, cx);
20434
20435 let project = project.clone();
20436 let blame = cx
20437 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20438 self.blame_subscription =
20439 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20440 self.blame = Some(blame);
20441 }
20442 }
20443
20444 fn toggle_git_blame_inline_internal(
20445 &mut self,
20446 user_triggered: bool,
20447 window: &mut Window,
20448 cx: &mut Context<Self>,
20449 ) {
20450 if self.git_blame_inline_enabled {
20451 self.git_blame_inline_enabled = false;
20452 self.show_git_blame_inline = false;
20453 self.show_git_blame_inline_delay_task.take();
20454 } else {
20455 self.git_blame_inline_enabled = true;
20456 self.start_git_blame_inline(user_triggered, window, cx);
20457 }
20458
20459 cx.notify();
20460 }
20461
20462 fn start_git_blame_inline(
20463 &mut self,
20464 user_triggered: bool,
20465 window: &mut Window,
20466 cx: &mut Context<Self>,
20467 ) {
20468 self.start_git_blame(user_triggered, window, cx);
20469
20470 if ProjectSettings::get_global(cx)
20471 .git
20472 .inline_blame_delay()
20473 .is_some()
20474 {
20475 self.start_inline_blame_timer(window, cx);
20476 } else {
20477 self.show_git_blame_inline = true
20478 }
20479 }
20480
20481 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20482 self.blame.as_ref()
20483 }
20484
20485 pub fn show_git_blame_gutter(&self) -> bool {
20486 self.show_git_blame_gutter
20487 }
20488
20489 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20490 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20491 }
20492
20493 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20494 self.show_git_blame_inline
20495 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20496 && !self.newest_selection_head_on_empty_line(cx)
20497 && self.has_blame_entries(cx)
20498 }
20499
20500 fn has_blame_entries(&self, cx: &App) -> bool {
20501 self.blame()
20502 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20503 }
20504
20505 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20506 let cursor_anchor = self.selections.newest_anchor().head();
20507
20508 let snapshot = self.buffer.read(cx).snapshot(cx);
20509 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20510
20511 snapshot.line_len(buffer_row) == 0
20512 }
20513
20514 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20515 let buffer_and_selection = maybe!({
20516 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20517 let selection_range = selection.range();
20518
20519 let multi_buffer = self.buffer().read(cx);
20520 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20521 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20522
20523 let (buffer, range, _) = if selection.reversed {
20524 buffer_ranges.first()
20525 } else {
20526 buffer_ranges.last()
20527 }?;
20528
20529 let selection = text::ToPoint::to_point(&range.start, buffer).row
20530 ..text::ToPoint::to_point(&range.end, buffer).row;
20531 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20532 });
20533
20534 let Some((buffer, selection)) = buffer_and_selection else {
20535 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20536 };
20537
20538 let Some(project) = self.project() else {
20539 return Task::ready(Err(anyhow!("editor does not have project")));
20540 };
20541
20542 project.update(cx, |project, cx| {
20543 project.get_permalink_to_line(&buffer, selection, cx)
20544 })
20545 }
20546
20547 pub fn copy_permalink_to_line(
20548 &mut self,
20549 _: &CopyPermalinkToLine,
20550 window: &mut Window,
20551 cx: &mut Context<Self>,
20552 ) {
20553 let permalink_task = self.get_permalink_to_line(cx);
20554 let workspace = self.workspace();
20555
20556 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20557 Ok(permalink) => {
20558 cx.update(|_, cx| {
20559 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20560 })
20561 .ok();
20562 }
20563 Err(err) => {
20564 let message = format!("Failed to copy permalink: {err}");
20565
20566 anyhow::Result::<()>::Err(err).log_err();
20567
20568 if let Some(workspace) = workspace {
20569 workspace
20570 .update_in(cx, |workspace, _, cx| {
20571 struct CopyPermalinkToLine;
20572
20573 workspace.show_toast(
20574 Toast::new(
20575 NotificationId::unique::<CopyPermalinkToLine>(),
20576 message,
20577 ),
20578 cx,
20579 )
20580 })
20581 .ok();
20582 }
20583 }
20584 })
20585 .detach();
20586 }
20587
20588 pub fn copy_file_location(
20589 &mut self,
20590 _: &CopyFileLocation,
20591 _: &mut Window,
20592 cx: &mut Context<Self>,
20593 ) {
20594 let selection = self
20595 .selections
20596 .newest::<Point>(&self.display_snapshot(cx))
20597 .start
20598 .row
20599 + 1;
20600 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20601 let project = self.project()?.read(cx);
20602 let file = buffer.read(cx).file()?;
20603 let path = file.path().display(project.path_style(cx));
20604
20605 Some(format!("{path}:{selection}"))
20606 }) {
20607 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20608 }
20609 }
20610
20611 pub fn open_permalink_to_line(
20612 &mut self,
20613 _: &OpenPermalinkToLine,
20614 window: &mut Window,
20615 cx: &mut Context<Self>,
20616 ) {
20617 let permalink_task = self.get_permalink_to_line(cx);
20618 let workspace = self.workspace();
20619
20620 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20621 Ok(permalink) => {
20622 cx.update(|_, cx| {
20623 cx.open_url(permalink.as_ref());
20624 })
20625 .ok();
20626 }
20627 Err(err) => {
20628 let message = format!("Failed to open permalink: {err}");
20629
20630 anyhow::Result::<()>::Err(err).log_err();
20631
20632 if let Some(workspace) = workspace {
20633 workspace
20634 .update(cx, |workspace, cx| {
20635 struct OpenPermalinkToLine;
20636
20637 workspace.show_toast(
20638 Toast::new(
20639 NotificationId::unique::<OpenPermalinkToLine>(),
20640 message,
20641 ),
20642 cx,
20643 )
20644 })
20645 .ok();
20646 }
20647 }
20648 })
20649 .detach();
20650 }
20651
20652 pub fn insert_uuid_v4(
20653 &mut self,
20654 _: &InsertUuidV4,
20655 window: &mut Window,
20656 cx: &mut Context<Self>,
20657 ) {
20658 self.insert_uuid(UuidVersion::V4, window, cx);
20659 }
20660
20661 pub fn insert_uuid_v7(
20662 &mut self,
20663 _: &InsertUuidV7,
20664 window: &mut Window,
20665 cx: &mut Context<Self>,
20666 ) {
20667 self.insert_uuid(UuidVersion::V7, window, cx);
20668 }
20669
20670 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20671 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20672 self.transact(window, cx, |this, window, cx| {
20673 let edits = this
20674 .selections
20675 .all::<Point>(&this.display_snapshot(cx))
20676 .into_iter()
20677 .map(|selection| {
20678 let uuid = match version {
20679 UuidVersion::V4 => uuid::Uuid::new_v4(),
20680 UuidVersion::V7 => uuid::Uuid::now_v7(),
20681 };
20682
20683 (selection.range(), uuid.to_string())
20684 });
20685 this.edit(edits, cx);
20686 this.refresh_edit_prediction(true, false, window, cx);
20687 });
20688 }
20689
20690 pub fn open_selections_in_multibuffer(
20691 &mut self,
20692 _: &OpenSelectionsInMultibuffer,
20693 window: &mut Window,
20694 cx: &mut Context<Self>,
20695 ) {
20696 let multibuffer = self.buffer.read(cx);
20697
20698 let Some(buffer) = multibuffer.as_singleton() else {
20699 return;
20700 };
20701
20702 let Some(workspace) = self.workspace() else {
20703 return;
20704 };
20705
20706 let title = multibuffer.title(cx).to_string();
20707
20708 let locations = self
20709 .selections
20710 .all_anchors(&self.display_snapshot(cx))
20711 .iter()
20712 .map(|selection| {
20713 (
20714 buffer.clone(),
20715 (selection.start.text_anchor..selection.end.text_anchor)
20716 .to_point(buffer.read(cx)),
20717 )
20718 })
20719 .into_group_map();
20720
20721 cx.spawn_in(window, async move |_, cx| {
20722 workspace.update_in(cx, |workspace, window, cx| {
20723 Self::open_locations_in_multibuffer(
20724 workspace,
20725 locations,
20726 format!("Selections for '{title}'"),
20727 false,
20728 MultibufferSelectionMode::All,
20729 window,
20730 cx,
20731 );
20732 })
20733 })
20734 .detach();
20735 }
20736
20737 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20738 /// last highlight added will be used.
20739 ///
20740 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20741 pub fn highlight_rows<T: 'static>(
20742 &mut self,
20743 range: Range<Anchor>,
20744 color: Hsla,
20745 options: RowHighlightOptions,
20746 cx: &mut Context<Self>,
20747 ) {
20748 let snapshot = self.buffer().read(cx).snapshot(cx);
20749 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20750 let ix = row_highlights.binary_search_by(|highlight| {
20751 Ordering::Equal
20752 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20753 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20754 });
20755
20756 if let Err(mut ix) = ix {
20757 let index = post_inc(&mut self.highlight_order);
20758
20759 // If this range intersects with the preceding highlight, then merge it with
20760 // the preceding highlight. Otherwise insert a new highlight.
20761 let mut merged = false;
20762 if ix > 0 {
20763 let prev_highlight = &mut row_highlights[ix - 1];
20764 if prev_highlight
20765 .range
20766 .end
20767 .cmp(&range.start, &snapshot)
20768 .is_ge()
20769 {
20770 ix -= 1;
20771 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20772 prev_highlight.range.end = range.end;
20773 }
20774 merged = true;
20775 prev_highlight.index = index;
20776 prev_highlight.color = color;
20777 prev_highlight.options = options;
20778 }
20779 }
20780
20781 if !merged {
20782 row_highlights.insert(
20783 ix,
20784 RowHighlight {
20785 range,
20786 index,
20787 color,
20788 options,
20789 type_id: TypeId::of::<T>(),
20790 },
20791 );
20792 }
20793
20794 // If any of the following highlights intersect with this one, merge them.
20795 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20796 let highlight = &row_highlights[ix];
20797 if next_highlight
20798 .range
20799 .start
20800 .cmp(&highlight.range.end, &snapshot)
20801 .is_le()
20802 {
20803 if next_highlight
20804 .range
20805 .end
20806 .cmp(&highlight.range.end, &snapshot)
20807 .is_gt()
20808 {
20809 row_highlights[ix].range.end = next_highlight.range.end;
20810 }
20811 row_highlights.remove(ix + 1);
20812 } else {
20813 break;
20814 }
20815 }
20816 }
20817 }
20818
20819 /// Remove any highlighted row ranges of the given type that intersect the
20820 /// given ranges.
20821 pub fn remove_highlighted_rows<T: 'static>(
20822 &mut self,
20823 ranges_to_remove: Vec<Range<Anchor>>,
20824 cx: &mut Context<Self>,
20825 ) {
20826 let snapshot = self.buffer().read(cx).snapshot(cx);
20827 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20828 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20829 row_highlights.retain(|highlight| {
20830 while let Some(range_to_remove) = ranges_to_remove.peek() {
20831 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20832 Ordering::Less | Ordering::Equal => {
20833 ranges_to_remove.next();
20834 }
20835 Ordering::Greater => {
20836 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20837 Ordering::Less | Ordering::Equal => {
20838 return false;
20839 }
20840 Ordering::Greater => break,
20841 }
20842 }
20843 }
20844 }
20845
20846 true
20847 })
20848 }
20849
20850 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20851 pub fn clear_row_highlights<T: 'static>(&mut self) {
20852 self.highlighted_rows.remove(&TypeId::of::<T>());
20853 }
20854
20855 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20856 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20857 self.highlighted_rows
20858 .get(&TypeId::of::<T>())
20859 .map_or(&[] as &[_], |vec| vec.as_slice())
20860 .iter()
20861 .map(|highlight| (highlight.range.clone(), highlight.color))
20862 }
20863
20864 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20865 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20866 /// Allows to ignore certain kinds of highlights.
20867 pub fn highlighted_display_rows(
20868 &self,
20869 window: &mut Window,
20870 cx: &mut App,
20871 ) -> BTreeMap<DisplayRow, LineHighlight> {
20872 let snapshot = self.snapshot(window, cx);
20873 let mut used_highlight_orders = HashMap::default();
20874 self.highlighted_rows
20875 .iter()
20876 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20877 .fold(
20878 BTreeMap::<DisplayRow, LineHighlight>::new(),
20879 |mut unique_rows, highlight| {
20880 let start = highlight.range.start.to_display_point(&snapshot);
20881 let end = highlight.range.end.to_display_point(&snapshot);
20882 let start_row = start.row().0;
20883 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20884 {
20885 end.row().0.saturating_sub(1)
20886 } else {
20887 end.row().0
20888 };
20889 for row in start_row..=end_row {
20890 let used_index =
20891 used_highlight_orders.entry(row).or_insert(highlight.index);
20892 if highlight.index >= *used_index {
20893 *used_index = highlight.index;
20894 unique_rows.insert(
20895 DisplayRow(row),
20896 LineHighlight {
20897 include_gutter: highlight.options.include_gutter,
20898 border: None,
20899 background: highlight.color.into(),
20900 type_id: Some(highlight.type_id),
20901 },
20902 );
20903 }
20904 }
20905 unique_rows
20906 },
20907 )
20908 }
20909
20910 pub fn highlighted_display_row_for_autoscroll(
20911 &self,
20912 snapshot: &DisplaySnapshot,
20913 ) -> Option<DisplayRow> {
20914 self.highlighted_rows
20915 .values()
20916 .flat_map(|highlighted_rows| highlighted_rows.iter())
20917 .filter_map(|highlight| {
20918 if highlight.options.autoscroll {
20919 Some(highlight.range.start.to_display_point(snapshot).row())
20920 } else {
20921 None
20922 }
20923 })
20924 .min()
20925 }
20926
20927 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20928 self.highlight_background::<SearchWithinRange>(
20929 ranges,
20930 |colors| colors.colors().editor_document_highlight_read_background,
20931 cx,
20932 )
20933 }
20934
20935 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20936 self.breadcrumb_header = Some(new_header);
20937 }
20938
20939 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20940 self.clear_background_highlights::<SearchWithinRange>(cx);
20941 }
20942
20943 pub fn highlight_background<T: 'static>(
20944 &mut self,
20945 ranges: &[Range<Anchor>],
20946 color_fetcher: fn(&Theme) -> Hsla,
20947 cx: &mut Context<Self>,
20948 ) {
20949 self.background_highlights.insert(
20950 HighlightKey::Type(TypeId::of::<T>()),
20951 (color_fetcher, Arc::from(ranges)),
20952 );
20953 self.scrollbar_marker_state.dirty = true;
20954 cx.notify();
20955 }
20956
20957 pub fn highlight_background_key<T: 'static>(
20958 &mut self,
20959 key: usize,
20960 ranges: &[Range<Anchor>],
20961 color_fetcher: fn(&Theme) -> Hsla,
20962 cx: &mut Context<Self>,
20963 ) {
20964 self.background_highlights.insert(
20965 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20966 (color_fetcher, Arc::from(ranges)),
20967 );
20968 self.scrollbar_marker_state.dirty = true;
20969 cx.notify();
20970 }
20971
20972 pub fn clear_background_highlights<T: 'static>(
20973 &mut self,
20974 cx: &mut Context<Self>,
20975 ) -> Option<BackgroundHighlight> {
20976 let text_highlights = self
20977 .background_highlights
20978 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20979 if !text_highlights.1.is_empty() {
20980 self.scrollbar_marker_state.dirty = true;
20981 cx.notify();
20982 }
20983 Some(text_highlights)
20984 }
20985
20986 pub fn highlight_gutter<T: 'static>(
20987 &mut self,
20988 ranges: impl Into<Vec<Range<Anchor>>>,
20989 color_fetcher: fn(&App) -> Hsla,
20990 cx: &mut Context<Self>,
20991 ) {
20992 self.gutter_highlights
20993 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20994 cx.notify();
20995 }
20996
20997 pub fn clear_gutter_highlights<T: 'static>(
20998 &mut self,
20999 cx: &mut Context<Self>,
21000 ) -> Option<GutterHighlight> {
21001 cx.notify();
21002 self.gutter_highlights.remove(&TypeId::of::<T>())
21003 }
21004
21005 pub fn insert_gutter_highlight<T: 'static>(
21006 &mut self,
21007 range: Range<Anchor>,
21008 color_fetcher: fn(&App) -> Hsla,
21009 cx: &mut Context<Self>,
21010 ) {
21011 let snapshot = self.buffer().read(cx).snapshot(cx);
21012 let mut highlights = self
21013 .gutter_highlights
21014 .remove(&TypeId::of::<T>())
21015 .map(|(_, highlights)| highlights)
21016 .unwrap_or_default();
21017 let ix = highlights.binary_search_by(|highlight| {
21018 Ordering::Equal
21019 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21020 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21021 });
21022 if let Err(ix) = ix {
21023 highlights.insert(ix, range);
21024 }
21025 self.gutter_highlights
21026 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21027 }
21028
21029 pub fn remove_gutter_highlights<T: 'static>(
21030 &mut self,
21031 ranges_to_remove: Vec<Range<Anchor>>,
21032 cx: &mut Context<Self>,
21033 ) {
21034 let snapshot = self.buffer().read(cx).snapshot(cx);
21035 let Some((color_fetcher, mut gutter_highlights)) =
21036 self.gutter_highlights.remove(&TypeId::of::<T>())
21037 else {
21038 return;
21039 };
21040 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21041 gutter_highlights.retain(|highlight| {
21042 while let Some(range_to_remove) = ranges_to_remove.peek() {
21043 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21044 Ordering::Less | Ordering::Equal => {
21045 ranges_to_remove.next();
21046 }
21047 Ordering::Greater => {
21048 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21049 Ordering::Less | Ordering::Equal => {
21050 return false;
21051 }
21052 Ordering::Greater => break,
21053 }
21054 }
21055 }
21056 }
21057
21058 true
21059 });
21060 self.gutter_highlights
21061 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21062 }
21063
21064 #[cfg(feature = "test-support")]
21065 pub fn all_text_highlights(
21066 &self,
21067 window: &mut Window,
21068 cx: &mut Context<Self>,
21069 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21070 let snapshot = self.snapshot(window, cx);
21071 self.display_map.update(cx, |display_map, _| {
21072 display_map
21073 .all_text_highlights()
21074 .map(|highlight| {
21075 let (style, ranges) = highlight.as_ref();
21076 (
21077 *style,
21078 ranges
21079 .iter()
21080 .map(|range| range.clone().to_display_points(&snapshot))
21081 .collect(),
21082 )
21083 })
21084 .collect()
21085 })
21086 }
21087
21088 #[cfg(feature = "test-support")]
21089 pub fn all_text_background_highlights(
21090 &self,
21091 window: &mut Window,
21092 cx: &mut Context<Self>,
21093 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21094 let snapshot = self.snapshot(window, cx);
21095 let buffer = &snapshot.buffer_snapshot();
21096 let start = buffer.anchor_before(MultiBufferOffset(0));
21097 let end = buffer.anchor_after(buffer.len());
21098 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21099 }
21100
21101 #[cfg(any(test, feature = "test-support"))]
21102 pub fn sorted_background_highlights_in_range(
21103 &self,
21104 search_range: Range<Anchor>,
21105 display_snapshot: &DisplaySnapshot,
21106 theme: &Theme,
21107 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21108 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21109 res.sort_by(|a, b| {
21110 a.0.start
21111 .cmp(&b.0.start)
21112 .then_with(|| a.0.end.cmp(&b.0.end))
21113 .then_with(|| a.1.cmp(&b.1))
21114 });
21115 res
21116 }
21117
21118 #[cfg(feature = "test-support")]
21119 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21120 let snapshot = self.buffer().read(cx).snapshot(cx);
21121
21122 let highlights = self
21123 .background_highlights
21124 .get(&HighlightKey::Type(TypeId::of::<
21125 items::BufferSearchHighlights,
21126 >()));
21127
21128 if let Some((_color, ranges)) = highlights {
21129 ranges
21130 .iter()
21131 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21132 .collect_vec()
21133 } else {
21134 vec![]
21135 }
21136 }
21137
21138 fn document_highlights_for_position<'a>(
21139 &'a self,
21140 position: Anchor,
21141 buffer: &'a MultiBufferSnapshot,
21142 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21143 let read_highlights = self
21144 .background_highlights
21145 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21146 .map(|h| &h.1);
21147 let write_highlights = self
21148 .background_highlights
21149 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21150 .map(|h| &h.1);
21151 let left_position = position.bias_left(buffer);
21152 let right_position = position.bias_right(buffer);
21153 read_highlights
21154 .into_iter()
21155 .chain(write_highlights)
21156 .flat_map(move |ranges| {
21157 let start_ix = match ranges.binary_search_by(|probe| {
21158 let cmp = probe.end.cmp(&left_position, buffer);
21159 if cmp.is_ge() {
21160 Ordering::Greater
21161 } else {
21162 Ordering::Less
21163 }
21164 }) {
21165 Ok(i) | Err(i) => i,
21166 };
21167
21168 ranges[start_ix..]
21169 .iter()
21170 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21171 })
21172 }
21173
21174 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21175 self.background_highlights
21176 .get(&HighlightKey::Type(TypeId::of::<T>()))
21177 .is_some_and(|(_, highlights)| !highlights.is_empty())
21178 }
21179
21180 /// Returns all background highlights for a given range.
21181 ///
21182 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21183 pub fn background_highlights_in_range(
21184 &self,
21185 search_range: Range<Anchor>,
21186 display_snapshot: &DisplaySnapshot,
21187 theme: &Theme,
21188 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21189 let mut results = Vec::new();
21190 for (color_fetcher, ranges) in self.background_highlights.values() {
21191 let color = color_fetcher(theme);
21192 let start_ix = match ranges.binary_search_by(|probe| {
21193 let cmp = probe
21194 .end
21195 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21196 if cmp.is_gt() {
21197 Ordering::Greater
21198 } else {
21199 Ordering::Less
21200 }
21201 }) {
21202 Ok(i) | Err(i) => i,
21203 };
21204 for range in &ranges[start_ix..] {
21205 if range
21206 .start
21207 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21208 .is_ge()
21209 {
21210 break;
21211 }
21212
21213 let start = range.start.to_display_point(display_snapshot);
21214 let end = range.end.to_display_point(display_snapshot);
21215 results.push((start..end, color))
21216 }
21217 }
21218 results
21219 }
21220
21221 pub fn gutter_highlights_in_range(
21222 &self,
21223 search_range: Range<Anchor>,
21224 display_snapshot: &DisplaySnapshot,
21225 cx: &App,
21226 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21227 let mut results = Vec::new();
21228 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21229 let color = color_fetcher(cx);
21230 let start_ix = match ranges.binary_search_by(|probe| {
21231 let cmp = probe
21232 .end
21233 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21234 if cmp.is_gt() {
21235 Ordering::Greater
21236 } else {
21237 Ordering::Less
21238 }
21239 }) {
21240 Ok(i) | Err(i) => i,
21241 };
21242 for range in &ranges[start_ix..] {
21243 if range
21244 .start
21245 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21246 .is_ge()
21247 {
21248 break;
21249 }
21250
21251 let start = range.start.to_display_point(display_snapshot);
21252 let end = range.end.to_display_point(display_snapshot);
21253 results.push((start..end, color))
21254 }
21255 }
21256 results
21257 }
21258
21259 /// Get the text ranges corresponding to the redaction query
21260 pub fn redacted_ranges(
21261 &self,
21262 search_range: Range<Anchor>,
21263 display_snapshot: &DisplaySnapshot,
21264 cx: &App,
21265 ) -> Vec<Range<DisplayPoint>> {
21266 display_snapshot
21267 .buffer_snapshot()
21268 .redacted_ranges(search_range, |file| {
21269 if let Some(file) = file {
21270 file.is_private()
21271 && EditorSettings::get(
21272 Some(SettingsLocation {
21273 worktree_id: file.worktree_id(cx),
21274 path: file.path().as_ref(),
21275 }),
21276 cx,
21277 )
21278 .redact_private_values
21279 } else {
21280 false
21281 }
21282 })
21283 .map(|range| {
21284 range.start.to_display_point(display_snapshot)
21285 ..range.end.to_display_point(display_snapshot)
21286 })
21287 .collect()
21288 }
21289
21290 pub fn highlight_text_key<T: 'static>(
21291 &mut self,
21292 key: usize,
21293 ranges: Vec<Range<Anchor>>,
21294 style: HighlightStyle,
21295 merge: bool,
21296 cx: &mut Context<Self>,
21297 ) {
21298 self.display_map.update(cx, |map, cx| {
21299 map.highlight_text(
21300 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21301 ranges,
21302 style,
21303 merge,
21304 cx,
21305 );
21306 });
21307 cx.notify();
21308 }
21309
21310 pub fn highlight_text<T: 'static>(
21311 &mut self,
21312 ranges: Vec<Range<Anchor>>,
21313 style: HighlightStyle,
21314 cx: &mut Context<Self>,
21315 ) {
21316 self.display_map.update(cx, |map, cx| {
21317 map.highlight_text(
21318 HighlightKey::Type(TypeId::of::<T>()),
21319 ranges,
21320 style,
21321 false,
21322 cx,
21323 )
21324 });
21325 cx.notify();
21326 }
21327
21328 pub fn text_highlights<'a, T: 'static>(
21329 &'a self,
21330 cx: &'a App,
21331 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21332 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21333 }
21334
21335 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21336 let cleared = self
21337 .display_map
21338 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21339 if cleared {
21340 cx.notify();
21341 }
21342 }
21343
21344 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21345 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21346 && self.focus_handle.is_focused(window)
21347 }
21348
21349 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21350 self.show_cursor_when_unfocused = is_enabled;
21351 cx.notify();
21352 }
21353
21354 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21355 cx.notify();
21356 }
21357
21358 fn on_debug_session_event(
21359 &mut self,
21360 _session: Entity<Session>,
21361 event: &SessionEvent,
21362 cx: &mut Context<Self>,
21363 ) {
21364 if let SessionEvent::InvalidateInlineValue = event {
21365 self.refresh_inline_values(cx);
21366 }
21367 }
21368
21369 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21370 let Some(project) = self.project.clone() else {
21371 return;
21372 };
21373
21374 if !self.inline_value_cache.enabled {
21375 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21376 self.splice_inlays(&inlays, Vec::new(), cx);
21377 return;
21378 }
21379
21380 let current_execution_position = self
21381 .highlighted_rows
21382 .get(&TypeId::of::<ActiveDebugLine>())
21383 .and_then(|lines| lines.last().map(|line| line.range.end));
21384
21385 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21386 let inline_values = editor
21387 .update(cx, |editor, cx| {
21388 let Some(current_execution_position) = current_execution_position else {
21389 return Some(Task::ready(Ok(Vec::new())));
21390 };
21391
21392 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21393 let snapshot = buffer.snapshot(cx);
21394
21395 let excerpt = snapshot.excerpt_containing(
21396 current_execution_position..current_execution_position,
21397 )?;
21398
21399 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21400 })?;
21401
21402 let range =
21403 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21404
21405 project.inline_values(buffer, range, cx)
21406 })
21407 .ok()
21408 .flatten()?
21409 .await
21410 .context("refreshing debugger inlays")
21411 .log_err()?;
21412
21413 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21414
21415 for (buffer_id, inline_value) in inline_values
21416 .into_iter()
21417 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21418 {
21419 buffer_inline_values
21420 .entry(buffer_id)
21421 .or_default()
21422 .push(inline_value);
21423 }
21424
21425 editor
21426 .update(cx, |editor, cx| {
21427 let snapshot = editor.buffer.read(cx).snapshot(cx);
21428 let mut new_inlays = Vec::default();
21429
21430 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21431 let buffer_id = buffer_snapshot.remote_id();
21432 buffer_inline_values
21433 .get(&buffer_id)
21434 .into_iter()
21435 .flatten()
21436 .for_each(|hint| {
21437 let inlay = Inlay::debugger(
21438 post_inc(&mut editor.next_inlay_id),
21439 Anchor::in_buffer(excerpt_id, hint.position),
21440 hint.text(),
21441 );
21442 if !inlay.text().chars().contains(&'\n') {
21443 new_inlays.push(inlay);
21444 }
21445 });
21446 }
21447
21448 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21449 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21450
21451 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21452 })
21453 .ok()?;
21454 Some(())
21455 });
21456 }
21457
21458 fn on_buffer_event(
21459 &mut self,
21460 multibuffer: &Entity<MultiBuffer>,
21461 event: &multi_buffer::Event,
21462 window: &mut Window,
21463 cx: &mut Context<Self>,
21464 ) {
21465 match event {
21466 multi_buffer::Event::Edited { edited_buffer } => {
21467 self.scrollbar_marker_state.dirty = true;
21468 self.active_indent_guides_state.dirty = true;
21469 self.refresh_active_diagnostics(cx);
21470 self.refresh_code_actions(window, cx);
21471 self.refresh_single_line_folds(window, cx);
21472 self.refresh_matching_bracket_highlights(window, cx);
21473 if self.has_active_edit_prediction() {
21474 self.update_visible_edit_prediction(window, cx);
21475 }
21476
21477 if let Some(buffer) = edited_buffer {
21478 if buffer.read(cx).file().is_none() {
21479 cx.emit(EditorEvent::TitleChanged);
21480 }
21481
21482 if self.project.is_some() {
21483 let buffer_id = buffer.read(cx).remote_id();
21484 self.register_buffer(buffer_id, cx);
21485 self.update_lsp_data(Some(buffer_id), window, cx);
21486 self.refresh_inlay_hints(
21487 InlayHintRefreshReason::BufferEdited(buffer_id),
21488 cx,
21489 );
21490 }
21491 }
21492
21493 cx.emit(EditorEvent::BufferEdited);
21494 cx.emit(SearchEvent::MatchesInvalidated);
21495
21496 let Some(project) = &self.project else { return };
21497 let (telemetry, is_via_ssh) = {
21498 let project = project.read(cx);
21499 let telemetry = project.client().telemetry().clone();
21500 let is_via_ssh = project.is_via_remote_server();
21501 (telemetry, is_via_ssh)
21502 };
21503 telemetry.log_edit_event("editor", is_via_ssh);
21504 }
21505 multi_buffer::Event::ExcerptsAdded {
21506 buffer,
21507 predecessor,
21508 excerpts,
21509 } => {
21510 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21511 let buffer_id = buffer.read(cx).remote_id();
21512 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21513 && let Some(project) = &self.project
21514 {
21515 update_uncommitted_diff_for_buffer(
21516 cx.entity(),
21517 project,
21518 [buffer.clone()],
21519 self.buffer.clone(),
21520 cx,
21521 )
21522 .detach();
21523 }
21524 self.update_lsp_data(Some(buffer_id), window, cx);
21525 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21526 self.colorize_brackets(false, cx);
21527 cx.emit(EditorEvent::ExcerptsAdded {
21528 buffer: buffer.clone(),
21529 predecessor: *predecessor,
21530 excerpts: excerpts.clone(),
21531 });
21532 }
21533 multi_buffer::Event::ExcerptsRemoved {
21534 ids,
21535 removed_buffer_ids,
21536 } => {
21537 if let Some(inlay_hints) = &mut self.inlay_hints {
21538 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21539 }
21540 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21541 for buffer_id in removed_buffer_ids {
21542 self.registered_buffers.remove(buffer_id);
21543 }
21544 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21545 cx.emit(EditorEvent::ExcerptsRemoved {
21546 ids: ids.clone(),
21547 removed_buffer_ids: removed_buffer_ids.clone(),
21548 });
21549 }
21550 multi_buffer::Event::ExcerptsEdited {
21551 excerpt_ids,
21552 buffer_ids,
21553 } => {
21554 self.display_map.update(cx, |map, cx| {
21555 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21556 });
21557 cx.emit(EditorEvent::ExcerptsEdited {
21558 ids: excerpt_ids.clone(),
21559 });
21560 }
21561 multi_buffer::Event::ExcerptsExpanded { ids } => {
21562 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21563 self.refresh_document_highlights(cx);
21564 for id in ids {
21565 self.fetched_tree_sitter_chunks.remove(id);
21566 }
21567 self.colorize_brackets(false, cx);
21568 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21569 }
21570 multi_buffer::Event::Reparsed(buffer_id) => {
21571 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21572 self.refresh_selected_text_highlights(true, window, cx);
21573 self.colorize_brackets(true, cx);
21574 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21575
21576 cx.emit(EditorEvent::Reparsed(*buffer_id));
21577 }
21578 multi_buffer::Event::DiffHunksToggled => {
21579 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21580 }
21581 multi_buffer::Event::LanguageChanged(buffer_id) => {
21582 self.registered_buffers.remove(&buffer_id);
21583 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21584 cx.emit(EditorEvent::Reparsed(*buffer_id));
21585 cx.notify();
21586 }
21587 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21588 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21589 multi_buffer::Event::FileHandleChanged
21590 | multi_buffer::Event::Reloaded
21591 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21592 multi_buffer::Event::DiagnosticsUpdated => {
21593 self.update_diagnostics_state(window, cx);
21594 }
21595 _ => {}
21596 };
21597 }
21598
21599 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21600 if !self.diagnostics_enabled() {
21601 return;
21602 }
21603 self.refresh_active_diagnostics(cx);
21604 self.refresh_inline_diagnostics(true, window, cx);
21605 self.scrollbar_marker_state.dirty = true;
21606 cx.notify();
21607 }
21608
21609 pub fn start_temporary_diff_override(&mut self) {
21610 self.load_diff_task.take();
21611 self.temporary_diff_override = true;
21612 }
21613
21614 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21615 self.temporary_diff_override = false;
21616 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21617 self.buffer.update(cx, |buffer, cx| {
21618 buffer.set_all_diff_hunks_collapsed(cx);
21619 });
21620
21621 if let Some(project) = self.project.clone() {
21622 self.load_diff_task = Some(
21623 update_uncommitted_diff_for_buffer(
21624 cx.entity(),
21625 &project,
21626 self.buffer.read(cx).all_buffers(),
21627 self.buffer.clone(),
21628 cx,
21629 )
21630 .shared(),
21631 );
21632 }
21633 }
21634
21635 fn on_display_map_changed(
21636 &mut self,
21637 _: Entity<DisplayMap>,
21638 _: &mut Window,
21639 cx: &mut Context<Self>,
21640 ) {
21641 cx.notify();
21642 }
21643
21644 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21645 if !self.mode.is_full() {
21646 return Vec::new();
21647 }
21648
21649 let theme_settings = theme::ThemeSettings::get_global(cx);
21650
21651 theme_settings
21652 .theme_overrides
21653 .get(cx.theme().name.as_ref())
21654 .map(|theme_style| &theme_style.accents)
21655 .into_iter()
21656 .flatten()
21657 .chain(
21658 theme_settings
21659 .experimental_theme_overrides
21660 .as_ref()
21661 .map(|overrides| &overrides.accents)
21662 .into_iter()
21663 .flatten(),
21664 )
21665 .flat_map(|accent| accent.0.clone())
21666 .collect()
21667 }
21668
21669 fn fetch_applicable_language_settings(
21670 &self,
21671 cx: &App,
21672 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21673 if !self.mode.is_full() {
21674 return HashMap::default();
21675 }
21676
21677 self.buffer().read(cx).all_buffers().into_iter().fold(
21678 HashMap::default(),
21679 |mut acc, buffer| {
21680 let buffer = buffer.read(cx);
21681 let language = buffer.language().map(|language| language.name());
21682 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21683 let file = buffer.file();
21684 v.insert(language_settings(language, file, cx).into_owned());
21685 }
21686 acc
21687 },
21688 )
21689 }
21690
21691 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21692 let new_language_settings = self.fetch_applicable_language_settings(cx);
21693 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21694 self.applicable_language_settings = new_language_settings;
21695
21696 let new_accent_overrides = self.fetch_accent_overrides(cx);
21697 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21698 self.accent_overrides = new_accent_overrides;
21699
21700 if self.diagnostics_enabled() {
21701 let new_severity = EditorSettings::get_global(cx)
21702 .diagnostics_max_severity
21703 .unwrap_or(DiagnosticSeverity::Hint);
21704 self.set_max_diagnostics_severity(new_severity, cx);
21705 }
21706 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21707 self.update_edit_prediction_settings(cx);
21708 self.refresh_edit_prediction(true, false, window, cx);
21709 self.refresh_inline_values(cx);
21710 self.refresh_inlay_hints(
21711 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21712 self.selections.newest_anchor().head(),
21713 &self.buffer.read(cx).snapshot(cx),
21714 cx,
21715 )),
21716 cx,
21717 );
21718
21719 let old_cursor_shape = self.cursor_shape;
21720 let old_show_breadcrumbs = self.show_breadcrumbs;
21721
21722 {
21723 let editor_settings = EditorSettings::get_global(cx);
21724 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21725 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21726 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21727 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21728 }
21729
21730 if old_cursor_shape != self.cursor_shape {
21731 cx.emit(EditorEvent::CursorShapeChanged);
21732 }
21733
21734 if old_show_breadcrumbs != self.show_breadcrumbs {
21735 cx.emit(EditorEvent::BreadcrumbsChanged);
21736 }
21737
21738 let project_settings = ProjectSettings::get_global(cx);
21739 self.buffer_serialization = self
21740 .should_serialize_buffer()
21741 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21742
21743 if self.mode.is_full() {
21744 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21745 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21746 if self.show_inline_diagnostics != show_inline_diagnostics {
21747 self.show_inline_diagnostics = show_inline_diagnostics;
21748 self.refresh_inline_diagnostics(false, window, cx);
21749 }
21750
21751 if self.git_blame_inline_enabled != inline_blame_enabled {
21752 self.toggle_git_blame_inline_internal(false, window, cx);
21753 }
21754
21755 let minimap_settings = EditorSettings::get_global(cx).minimap;
21756 if self.minimap_visibility != MinimapVisibility::Disabled {
21757 if self.minimap_visibility.settings_visibility()
21758 != minimap_settings.minimap_enabled()
21759 {
21760 self.set_minimap_visibility(
21761 MinimapVisibility::for_mode(self.mode(), cx),
21762 window,
21763 cx,
21764 );
21765 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21766 minimap_entity.update(cx, |minimap_editor, cx| {
21767 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21768 })
21769 }
21770 }
21771
21772 if language_settings_changed || accent_overrides_changed {
21773 self.colorize_brackets(true, cx);
21774 }
21775
21776 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21777 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21778 }) {
21779 if !inlay_splice.is_empty() {
21780 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21781 }
21782 self.refresh_colors_for_visible_range(None, window, cx);
21783 }
21784 }
21785
21786 cx.notify();
21787 }
21788
21789 pub fn set_searchable(&mut self, searchable: bool) {
21790 self.searchable = searchable;
21791 }
21792
21793 pub fn searchable(&self) -> bool {
21794 self.searchable
21795 }
21796
21797 pub fn open_excerpts_in_split(
21798 &mut self,
21799 _: &OpenExcerptsSplit,
21800 window: &mut Window,
21801 cx: &mut Context<Self>,
21802 ) {
21803 self.open_excerpts_common(None, true, window, cx)
21804 }
21805
21806 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21807 self.open_excerpts_common(None, false, window, cx)
21808 }
21809
21810 fn open_excerpts_common(
21811 &mut self,
21812 jump_data: Option<JumpData>,
21813 split: bool,
21814 window: &mut Window,
21815 cx: &mut Context<Self>,
21816 ) {
21817 let Some(workspace) = self.workspace() else {
21818 cx.propagate();
21819 return;
21820 };
21821
21822 if self.buffer.read(cx).is_singleton() {
21823 cx.propagate();
21824 return;
21825 }
21826
21827 let mut new_selections_by_buffer = HashMap::default();
21828 match &jump_data {
21829 Some(JumpData::MultiBufferPoint {
21830 excerpt_id,
21831 position,
21832 anchor,
21833 line_offset_from_top,
21834 }) => {
21835 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21836 if let Some(buffer) = multi_buffer_snapshot
21837 .buffer_id_for_excerpt(*excerpt_id)
21838 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21839 {
21840 let buffer_snapshot = buffer.read(cx).snapshot();
21841 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21842 language::ToPoint::to_point(anchor, &buffer_snapshot)
21843 } else {
21844 buffer_snapshot.clip_point(*position, Bias::Left)
21845 };
21846 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21847 new_selections_by_buffer.insert(
21848 buffer,
21849 (
21850 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21851 Some(*line_offset_from_top),
21852 ),
21853 );
21854 }
21855 }
21856 Some(JumpData::MultiBufferRow {
21857 row,
21858 line_offset_from_top,
21859 }) => {
21860 let point = MultiBufferPoint::new(row.0, 0);
21861 if let Some((buffer, buffer_point, _)) =
21862 self.buffer.read(cx).point_to_buffer_point(point, cx)
21863 {
21864 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21865 new_selections_by_buffer
21866 .entry(buffer)
21867 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21868 .0
21869 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21870 }
21871 }
21872 None => {
21873 let selections = self
21874 .selections
21875 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21876 let multi_buffer = self.buffer.read(cx);
21877 for selection in selections {
21878 for (snapshot, range, _, anchor) in multi_buffer
21879 .snapshot(cx)
21880 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21881 {
21882 if let Some(anchor) = anchor {
21883 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21884 else {
21885 continue;
21886 };
21887 let offset = text::ToOffset::to_offset(
21888 &anchor.text_anchor,
21889 &buffer_handle.read(cx).snapshot(),
21890 );
21891 let range = BufferOffset(offset)..BufferOffset(offset);
21892 new_selections_by_buffer
21893 .entry(buffer_handle)
21894 .or_insert((Vec::new(), None))
21895 .0
21896 .push(range)
21897 } else {
21898 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21899 else {
21900 continue;
21901 };
21902 new_selections_by_buffer
21903 .entry(buffer_handle)
21904 .or_insert((Vec::new(), None))
21905 .0
21906 .push(range)
21907 }
21908 }
21909 }
21910 }
21911 }
21912
21913 new_selections_by_buffer
21914 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21915
21916 if new_selections_by_buffer.is_empty() {
21917 return;
21918 }
21919
21920 // We defer the pane interaction because we ourselves are a workspace item
21921 // and activating a new item causes the pane to call a method on us reentrantly,
21922 // which panics if we're on the stack.
21923 window.defer(cx, move |window, cx| {
21924 workspace.update(cx, |workspace, cx| {
21925 let pane = if split {
21926 workspace.adjacent_pane(window, cx)
21927 } else {
21928 workspace.active_pane().clone()
21929 };
21930
21931 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21932 let editor = buffer
21933 .read(cx)
21934 .file()
21935 .is_none()
21936 .then(|| {
21937 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21938 // so `workspace.open_project_item` will never find them, always opening a new editor.
21939 // Instead, we try to activate the existing editor in the pane first.
21940 let (editor, pane_item_index) =
21941 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21942 let editor = item.downcast::<Editor>()?;
21943 let singleton_buffer =
21944 editor.read(cx).buffer().read(cx).as_singleton()?;
21945 if singleton_buffer == buffer {
21946 Some((editor, i))
21947 } else {
21948 None
21949 }
21950 })?;
21951 pane.update(cx, |pane, cx| {
21952 pane.activate_item(pane_item_index, true, true, window, cx)
21953 });
21954 Some(editor)
21955 })
21956 .flatten()
21957 .unwrap_or_else(|| {
21958 workspace.open_project_item::<Self>(
21959 pane.clone(),
21960 buffer,
21961 true,
21962 true,
21963 window,
21964 cx,
21965 )
21966 });
21967
21968 editor.update(cx, |editor, cx| {
21969 let autoscroll = match scroll_offset {
21970 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21971 None => Autoscroll::newest(),
21972 };
21973 let nav_history = editor.nav_history.take();
21974 editor.change_selections(
21975 SelectionEffects::scroll(autoscroll),
21976 window,
21977 cx,
21978 |s| {
21979 s.select_ranges(ranges.into_iter().map(|range| {
21980 // we checked that the editor is a singleton editor so the offsets are valid
21981 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21982 }));
21983 },
21984 );
21985 editor.nav_history = nav_history;
21986 });
21987 }
21988 })
21989 });
21990 }
21991
21992 // For now, don't allow opening excerpts in buffers that aren't backed by
21993 // regular project files.
21994 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21995 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21996 }
21997
21998 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21999 let snapshot = self.buffer.read(cx).read(cx);
22000 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22001 Some(
22002 ranges
22003 .iter()
22004 .map(move |range| {
22005 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22006 })
22007 .collect(),
22008 )
22009 }
22010
22011 fn selection_replacement_ranges(
22012 &self,
22013 range: Range<MultiBufferOffsetUtf16>,
22014 cx: &mut App,
22015 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22016 let selections = self
22017 .selections
22018 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22019 let newest_selection = selections
22020 .iter()
22021 .max_by_key(|selection| selection.id)
22022 .unwrap();
22023 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22024 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22025 let snapshot = self.buffer.read(cx).read(cx);
22026 selections
22027 .into_iter()
22028 .map(|mut selection| {
22029 selection.start.0.0 =
22030 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22031 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22032 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22033 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22034 })
22035 .collect()
22036 }
22037
22038 fn report_editor_event(
22039 &self,
22040 reported_event: ReportEditorEvent,
22041 file_extension: Option<String>,
22042 cx: &App,
22043 ) {
22044 if cfg!(any(test, feature = "test-support")) {
22045 return;
22046 }
22047
22048 let Some(project) = &self.project else { return };
22049
22050 // If None, we are in a file without an extension
22051 let file = self
22052 .buffer
22053 .read(cx)
22054 .as_singleton()
22055 .and_then(|b| b.read(cx).file());
22056 let file_extension = file_extension.or(file
22057 .as_ref()
22058 .and_then(|file| Path::new(file.file_name(cx)).extension())
22059 .and_then(|e| e.to_str())
22060 .map(|a| a.to_string()));
22061
22062 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22063 .map(|vim_mode| vim_mode.0)
22064 .unwrap_or(false);
22065
22066 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22067 let copilot_enabled = edit_predictions_provider
22068 == language::language_settings::EditPredictionProvider::Copilot;
22069 let copilot_enabled_for_language = self
22070 .buffer
22071 .read(cx)
22072 .language_settings(cx)
22073 .show_edit_predictions;
22074
22075 let project = project.read(cx);
22076 let event_type = reported_event.event_type();
22077
22078 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22079 telemetry::event!(
22080 event_type,
22081 type = if auto_saved {"autosave"} else {"manual"},
22082 file_extension,
22083 vim_mode,
22084 copilot_enabled,
22085 copilot_enabled_for_language,
22086 edit_predictions_provider,
22087 is_via_ssh = project.is_via_remote_server(),
22088 );
22089 } else {
22090 telemetry::event!(
22091 event_type,
22092 file_extension,
22093 vim_mode,
22094 copilot_enabled,
22095 copilot_enabled_for_language,
22096 edit_predictions_provider,
22097 is_via_ssh = project.is_via_remote_server(),
22098 );
22099 };
22100 }
22101
22102 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22103 /// with each line being an array of {text, highlight} objects.
22104 fn copy_highlight_json(
22105 &mut self,
22106 _: &CopyHighlightJson,
22107 window: &mut Window,
22108 cx: &mut Context<Self>,
22109 ) {
22110 #[derive(Serialize)]
22111 struct Chunk<'a> {
22112 text: String,
22113 highlight: Option<&'a str>,
22114 }
22115
22116 let snapshot = self.buffer.read(cx).snapshot(cx);
22117 let range = self
22118 .selected_text_range(false, window, cx)
22119 .and_then(|selection| {
22120 if selection.range.is_empty() {
22121 None
22122 } else {
22123 Some(
22124 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22125 selection.range.start,
22126 )))
22127 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22128 selection.range.end,
22129 ))),
22130 )
22131 }
22132 })
22133 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22134
22135 let chunks = snapshot.chunks(range, true);
22136 let mut lines = Vec::new();
22137 let mut line: VecDeque<Chunk> = VecDeque::new();
22138
22139 let Some(style) = self.style.as_ref() else {
22140 return;
22141 };
22142
22143 for chunk in chunks {
22144 let highlight = chunk
22145 .syntax_highlight_id
22146 .and_then(|id| id.name(&style.syntax));
22147 let mut chunk_lines = chunk.text.split('\n').peekable();
22148 while let Some(text) = chunk_lines.next() {
22149 let mut merged_with_last_token = false;
22150 if let Some(last_token) = line.back_mut()
22151 && last_token.highlight == highlight
22152 {
22153 last_token.text.push_str(text);
22154 merged_with_last_token = true;
22155 }
22156
22157 if !merged_with_last_token {
22158 line.push_back(Chunk {
22159 text: text.into(),
22160 highlight,
22161 });
22162 }
22163
22164 if chunk_lines.peek().is_some() {
22165 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22166 line.pop_front();
22167 }
22168 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22169 line.pop_back();
22170 }
22171
22172 lines.push(mem::take(&mut line));
22173 }
22174 }
22175 }
22176
22177 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22178 return;
22179 };
22180 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22181 }
22182
22183 pub fn open_context_menu(
22184 &mut self,
22185 _: &OpenContextMenu,
22186 window: &mut Window,
22187 cx: &mut Context<Self>,
22188 ) {
22189 self.request_autoscroll(Autoscroll::newest(), cx);
22190 let position = self
22191 .selections
22192 .newest_display(&self.display_snapshot(cx))
22193 .start;
22194 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22195 }
22196
22197 pub fn replay_insert_event(
22198 &mut self,
22199 text: &str,
22200 relative_utf16_range: Option<Range<isize>>,
22201 window: &mut Window,
22202 cx: &mut Context<Self>,
22203 ) {
22204 if !self.input_enabled {
22205 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22206 return;
22207 }
22208 if let Some(relative_utf16_range) = relative_utf16_range {
22209 let selections = self
22210 .selections
22211 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22212 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22213 let new_ranges = selections.into_iter().map(|range| {
22214 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22215 range
22216 .head()
22217 .0
22218 .0
22219 .saturating_add_signed(relative_utf16_range.start),
22220 ));
22221 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22222 range
22223 .head()
22224 .0
22225 .0
22226 .saturating_add_signed(relative_utf16_range.end),
22227 ));
22228 start..end
22229 });
22230 s.select_ranges(new_ranges);
22231 });
22232 }
22233
22234 self.handle_input(text, window, cx);
22235 }
22236
22237 pub fn is_focused(&self, window: &Window) -> bool {
22238 self.focus_handle.is_focused(window)
22239 }
22240
22241 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22242 cx.emit(EditorEvent::Focused);
22243
22244 if let Some(descendant) = self
22245 .last_focused_descendant
22246 .take()
22247 .and_then(|descendant| descendant.upgrade())
22248 {
22249 window.focus(&descendant);
22250 } else {
22251 if let Some(blame) = self.blame.as_ref() {
22252 blame.update(cx, GitBlame::focus)
22253 }
22254
22255 self.blink_manager.update(cx, BlinkManager::enable);
22256 self.show_cursor_names(window, cx);
22257 self.buffer.update(cx, |buffer, cx| {
22258 buffer.finalize_last_transaction(cx);
22259 if self.leader_id.is_none() {
22260 buffer.set_active_selections(
22261 &self.selections.disjoint_anchors_arc(),
22262 self.selections.line_mode(),
22263 self.cursor_shape,
22264 cx,
22265 );
22266 }
22267 });
22268
22269 if let Some(position_map) = self.last_position_map.clone() {
22270 EditorElement::mouse_moved(
22271 self,
22272 &MouseMoveEvent {
22273 position: window.mouse_position(),
22274 pressed_button: None,
22275 modifiers: window.modifiers(),
22276 },
22277 &position_map,
22278 window,
22279 cx,
22280 );
22281 }
22282 }
22283 }
22284
22285 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22286 cx.emit(EditorEvent::FocusedIn)
22287 }
22288
22289 fn handle_focus_out(
22290 &mut self,
22291 event: FocusOutEvent,
22292 _window: &mut Window,
22293 cx: &mut Context<Self>,
22294 ) {
22295 if event.blurred != self.focus_handle {
22296 self.last_focused_descendant = Some(event.blurred);
22297 }
22298 self.selection_drag_state = SelectionDragState::None;
22299 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22300 }
22301
22302 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22303 self.blink_manager.update(cx, BlinkManager::disable);
22304 self.buffer
22305 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22306
22307 if let Some(blame) = self.blame.as_ref() {
22308 blame.update(cx, GitBlame::blur)
22309 }
22310 if !self.hover_state.focused(window, cx) {
22311 hide_hover(self, cx);
22312 }
22313 if !self
22314 .context_menu
22315 .borrow()
22316 .as_ref()
22317 .is_some_and(|context_menu| context_menu.focused(window, cx))
22318 {
22319 self.hide_context_menu(window, cx);
22320 }
22321 self.take_active_edit_prediction(cx);
22322 cx.emit(EditorEvent::Blurred);
22323 cx.notify();
22324 }
22325
22326 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22327 let mut pending: String = window
22328 .pending_input_keystrokes()
22329 .into_iter()
22330 .flatten()
22331 .filter_map(|keystroke| keystroke.key_char.clone())
22332 .collect();
22333
22334 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22335 pending = "".to_string();
22336 }
22337
22338 let existing_pending = self
22339 .text_highlights::<PendingInput>(cx)
22340 .map(|(_, ranges)| ranges.to_vec());
22341 if existing_pending.is_none() && pending.is_empty() {
22342 return;
22343 }
22344 let transaction =
22345 self.transact(window, cx, |this, window, cx| {
22346 let selections = this
22347 .selections
22348 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22349 let edits = selections
22350 .iter()
22351 .map(|selection| (selection.end..selection.end, pending.clone()));
22352 this.edit(edits, cx);
22353 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22354 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22355 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22356 }));
22357 });
22358 if let Some(existing_ranges) = existing_pending {
22359 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22360 this.edit(edits, cx);
22361 }
22362 });
22363
22364 let snapshot = self.snapshot(window, cx);
22365 let ranges = self
22366 .selections
22367 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22368 .into_iter()
22369 .map(|selection| {
22370 snapshot.buffer_snapshot().anchor_after(selection.end)
22371 ..snapshot
22372 .buffer_snapshot()
22373 .anchor_before(selection.end + pending.len())
22374 })
22375 .collect();
22376
22377 if pending.is_empty() {
22378 self.clear_highlights::<PendingInput>(cx);
22379 } else {
22380 self.highlight_text::<PendingInput>(
22381 ranges,
22382 HighlightStyle {
22383 underline: Some(UnderlineStyle {
22384 thickness: px(1.),
22385 color: None,
22386 wavy: false,
22387 }),
22388 ..Default::default()
22389 },
22390 cx,
22391 );
22392 }
22393
22394 self.ime_transaction = self.ime_transaction.or(transaction);
22395 if let Some(transaction) = self.ime_transaction {
22396 self.buffer.update(cx, |buffer, cx| {
22397 buffer.group_until_transaction(transaction, cx);
22398 });
22399 }
22400
22401 if self.text_highlights::<PendingInput>(cx).is_none() {
22402 self.ime_transaction.take();
22403 }
22404 }
22405
22406 pub fn register_action_renderer(
22407 &mut self,
22408 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22409 ) -> Subscription {
22410 let id = self.next_editor_action_id.post_inc();
22411 self.editor_actions
22412 .borrow_mut()
22413 .insert(id, Box::new(listener));
22414
22415 let editor_actions = self.editor_actions.clone();
22416 Subscription::new(move || {
22417 editor_actions.borrow_mut().remove(&id);
22418 })
22419 }
22420
22421 pub fn register_action<A: Action>(
22422 &mut self,
22423 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22424 ) -> Subscription {
22425 let id = self.next_editor_action_id.post_inc();
22426 let listener = Arc::new(listener);
22427 self.editor_actions.borrow_mut().insert(
22428 id,
22429 Box::new(move |_, window, _| {
22430 let listener = listener.clone();
22431 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22432 let action = action.downcast_ref().unwrap();
22433 if phase == DispatchPhase::Bubble {
22434 listener(action, window, cx)
22435 }
22436 })
22437 }),
22438 );
22439
22440 let editor_actions = self.editor_actions.clone();
22441 Subscription::new(move || {
22442 editor_actions.borrow_mut().remove(&id);
22443 })
22444 }
22445
22446 pub fn file_header_size(&self) -> u32 {
22447 FILE_HEADER_HEIGHT
22448 }
22449
22450 pub fn restore(
22451 &mut self,
22452 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22453 window: &mut Window,
22454 cx: &mut Context<Self>,
22455 ) {
22456 let workspace = self.workspace();
22457 let project = self.project();
22458 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22459 let mut tasks = Vec::new();
22460 for (buffer_id, changes) in revert_changes {
22461 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22462 buffer.update(cx, |buffer, cx| {
22463 buffer.edit(
22464 changes
22465 .into_iter()
22466 .map(|(range, text)| (range, text.to_string())),
22467 None,
22468 cx,
22469 );
22470 });
22471
22472 if let Some(project) =
22473 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22474 {
22475 project.update(cx, |project, cx| {
22476 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22477 })
22478 }
22479 }
22480 }
22481 tasks
22482 });
22483 cx.spawn_in(window, async move |_, cx| {
22484 for (buffer, task) in save_tasks {
22485 let result = task.await;
22486 if result.is_err() {
22487 let Some(path) = buffer
22488 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22489 .ok()
22490 else {
22491 continue;
22492 };
22493 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22494 let Some(task) = cx
22495 .update_window_entity(workspace, |workspace, window, cx| {
22496 workspace
22497 .open_path_preview(path, None, false, false, false, window, cx)
22498 })
22499 .ok()
22500 else {
22501 continue;
22502 };
22503 task.await.log_err();
22504 }
22505 }
22506 }
22507 })
22508 .detach();
22509 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22510 selections.refresh()
22511 });
22512 }
22513
22514 pub fn to_pixel_point(
22515 &self,
22516 source: multi_buffer::Anchor,
22517 editor_snapshot: &EditorSnapshot,
22518 window: &mut Window,
22519 ) -> Option<gpui::Point<Pixels>> {
22520 let source_point = source.to_display_point(editor_snapshot);
22521 self.display_to_pixel_point(source_point, editor_snapshot, window)
22522 }
22523
22524 pub fn display_to_pixel_point(
22525 &self,
22526 source: DisplayPoint,
22527 editor_snapshot: &EditorSnapshot,
22528 window: &mut Window,
22529 ) -> Option<gpui::Point<Pixels>> {
22530 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22531 let text_layout_details = self.text_layout_details(window);
22532 let scroll_top = text_layout_details
22533 .scroll_anchor
22534 .scroll_position(editor_snapshot)
22535 .y;
22536
22537 if source.row().as_f64() < scroll_top.floor() {
22538 return None;
22539 }
22540 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22541 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22542 Some(gpui::Point::new(source_x, source_y))
22543 }
22544
22545 pub fn has_visible_completions_menu(&self) -> bool {
22546 !self.edit_prediction_preview_is_active()
22547 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22548 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22549 })
22550 }
22551
22552 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22553 if self.mode.is_minimap() {
22554 return;
22555 }
22556 self.addons
22557 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22558 }
22559
22560 pub fn unregister_addon<T: Addon>(&mut self) {
22561 self.addons.remove(&std::any::TypeId::of::<T>());
22562 }
22563
22564 pub fn addon<T: Addon>(&self) -> Option<&T> {
22565 let type_id = std::any::TypeId::of::<T>();
22566 self.addons
22567 .get(&type_id)
22568 .and_then(|item| item.to_any().downcast_ref::<T>())
22569 }
22570
22571 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22572 let type_id = std::any::TypeId::of::<T>();
22573 self.addons
22574 .get_mut(&type_id)
22575 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22576 }
22577
22578 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22579 let text_layout_details = self.text_layout_details(window);
22580 let style = &text_layout_details.editor_style;
22581 let font_id = window.text_system().resolve_font(&style.text.font());
22582 let font_size = style.text.font_size.to_pixels(window.rem_size());
22583 let line_height = style.text.line_height_in_pixels(window.rem_size());
22584 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22585 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22586
22587 CharacterDimensions {
22588 em_width,
22589 em_advance,
22590 line_height,
22591 }
22592 }
22593
22594 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22595 self.load_diff_task.clone()
22596 }
22597
22598 fn read_metadata_from_db(
22599 &mut self,
22600 item_id: u64,
22601 workspace_id: WorkspaceId,
22602 window: &mut Window,
22603 cx: &mut Context<Editor>,
22604 ) {
22605 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22606 && !self.mode.is_minimap()
22607 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22608 {
22609 let buffer_snapshot = OnceCell::new();
22610
22611 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22612 && !folds.is_empty()
22613 {
22614 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22615 self.fold_ranges(
22616 folds
22617 .into_iter()
22618 .map(|(start, end)| {
22619 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22620 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22621 })
22622 .collect(),
22623 false,
22624 window,
22625 cx,
22626 );
22627 }
22628
22629 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22630 && !selections.is_empty()
22631 {
22632 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22633 // skip adding the initial selection to selection history
22634 self.selection_history.mode = SelectionHistoryMode::Skipping;
22635 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22636 s.select_ranges(selections.into_iter().map(|(start, end)| {
22637 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22638 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22639 }));
22640 });
22641 self.selection_history.mode = SelectionHistoryMode::Normal;
22642 };
22643 }
22644
22645 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22646 }
22647
22648 fn update_lsp_data(
22649 &mut self,
22650 for_buffer: Option<BufferId>,
22651 window: &mut Window,
22652 cx: &mut Context<'_, Self>,
22653 ) {
22654 self.pull_diagnostics(for_buffer, window, cx);
22655 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22656 }
22657
22658 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22659 if self.ignore_lsp_data() {
22660 return;
22661 }
22662 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22663 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22664 }
22665 }
22666
22667 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22668 if self.ignore_lsp_data() {
22669 return;
22670 }
22671
22672 if !self.registered_buffers.contains_key(&buffer_id)
22673 && let Some(project) = self.project.as_ref()
22674 {
22675 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22676 project.update(cx, |project, cx| {
22677 self.registered_buffers.insert(
22678 buffer_id,
22679 project.register_buffer_with_language_servers(&buffer, cx),
22680 );
22681 });
22682 } else {
22683 self.registered_buffers.remove(&buffer_id);
22684 }
22685 }
22686 }
22687
22688 fn ignore_lsp_data(&self) -> bool {
22689 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22690 // skip any LSP updates for it.
22691 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22692 }
22693}
22694
22695fn edit_for_markdown_paste<'a>(
22696 buffer: &MultiBufferSnapshot,
22697 range: Range<MultiBufferOffset>,
22698 to_insert: &'a str,
22699 url: Option<url::Url>,
22700) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22701 if url.is_none() {
22702 return (range, Cow::Borrowed(to_insert));
22703 };
22704
22705 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22706
22707 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22708 Cow::Borrowed(to_insert)
22709 } else {
22710 Cow::Owned(format!("[{old_text}]({to_insert})"))
22711 };
22712 (range, new_text)
22713}
22714
22715fn process_completion_for_edit(
22716 completion: &Completion,
22717 intent: CompletionIntent,
22718 buffer: &Entity<Buffer>,
22719 cursor_position: &text::Anchor,
22720 cx: &mut Context<Editor>,
22721) -> CompletionEdit {
22722 let buffer = buffer.read(cx);
22723 let buffer_snapshot = buffer.snapshot();
22724 let (snippet, new_text) = if completion.is_snippet() {
22725 let mut snippet_source = completion.new_text.clone();
22726 // Workaround for typescript language server issues so that methods don't expand within
22727 // strings and functions with type expressions. The previous point is used because the query
22728 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22729 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22730 let previous_point = if previous_point.column > 0 {
22731 cursor_position.to_previous_offset(&buffer_snapshot)
22732 } else {
22733 cursor_position.to_offset(&buffer_snapshot)
22734 };
22735 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22736 && scope.prefers_label_for_snippet_in_completion()
22737 && let Some(label) = completion.label()
22738 && matches!(
22739 completion.kind(),
22740 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22741 )
22742 {
22743 snippet_source = label;
22744 }
22745 match Snippet::parse(&snippet_source).log_err() {
22746 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22747 None => (None, completion.new_text.clone()),
22748 }
22749 } else {
22750 (None, completion.new_text.clone())
22751 };
22752
22753 let mut range_to_replace = {
22754 let replace_range = &completion.replace_range;
22755 if let CompletionSource::Lsp {
22756 insert_range: Some(insert_range),
22757 ..
22758 } = &completion.source
22759 {
22760 debug_assert_eq!(
22761 insert_range.start, replace_range.start,
22762 "insert_range and replace_range should start at the same position"
22763 );
22764 debug_assert!(
22765 insert_range
22766 .start
22767 .cmp(cursor_position, &buffer_snapshot)
22768 .is_le(),
22769 "insert_range should start before or at cursor position"
22770 );
22771 debug_assert!(
22772 replace_range
22773 .start
22774 .cmp(cursor_position, &buffer_snapshot)
22775 .is_le(),
22776 "replace_range should start before or at cursor position"
22777 );
22778
22779 let should_replace = match intent {
22780 CompletionIntent::CompleteWithInsert => false,
22781 CompletionIntent::CompleteWithReplace => true,
22782 CompletionIntent::Complete | CompletionIntent::Compose => {
22783 let insert_mode =
22784 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22785 .completions
22786 .lsp_insert_mode;
22787 match insert_mode {
22788 LspInsertMode::Insert => false,
22789 LspInsertMode::Replace => true,
22790 LspInsertMode::ReplaceSubsequence => {
22791 let mut text_to_replace = buffer.chars_for_range(
22792 buffer.anchor_before(replace_range.start)
22793 ..buffer.anchor_after(replace_range.end),
22794 );
22795 let mut current_needle = text_to_replace.next();
22796 for haystack_ch in completion.label.text.chars() {
22797 if let Some(needle_ch) = current_needle
22798 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22799 {
22800 current_needle = text_to_replace.next();
22801 }
22802 }
22803 current_needle.is_none()
22804 }
22805 LspInsertMode::ReplaceSuffix => {
22806 if replace_range
22807 .end
22808 .cmp(cursor_position, &buffer_snapshot)
22809 .is_gt()
22810 {
22811 let range_after_cursor = *cursor_position..replace_range.end;
22812 let text_after_cursor = buffer
22813 .text_for_range(
22814 buffer.anchor_before(range_after_cursor.start)
22815 ..buffer.anchor_after(range_after_cursor.end),
22816 )
22817 .collect::<String>()
22818 .to_ascii_lowercase();
22819 completion
22820 .label
22821 .text
22822 .to_ascii_lowercase()
22823 .ends_with(&text_after_cursor)
22824 } else {
22825 true
22826 }
22827 }
22828 }
22829 }
22830 };
22831
22832 if should_replace {
22833 replace_range.clone()
22834 } else {
22835 insert_range.clone()
22836 }
22837 } else {
22838 replace_range.clone()
22839 }
22840 };
22841
22842 if range_to_replace
22843 .end
22844 .cmp(cursor_position, &buffer_snapshot)
22845 .is_lt()
22846 {
22847 range_to_replace.end = *cursor_position;
22848 }
22849
22850 let replace_range = range_to_replace.to_offset(buffer);
22851 CompletionEdit {
22852 new_text,
22853 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22854 snippet,
22855 }
22856}
22857
22858struct CompletionEdit {
22859 new_text: String,
22860 replace_range: Range<BufferOffset>,
22861 snippet: Option<Snippet>,
22862}
22863
22864fn insert_extra_newline_brackets(
22865 buffer: &MultiBufferSnapshot,
22866 range: Range<MultiBufferOffset>,
22867 language: &language::LanguageScope,
22868) -> bool {
22869 let leading_whitespace_len = buffer
22870 .reversed_chars_at(range.start)
22871 .take_while(|c| c.is_whitespace() && *c != '\n')
22872 .map(|c| c.len_utf8())
22873 .sum::<usize>();
22874 let trailing_whitespace_len = buffer
22875 .chars_at(range.end)
22876 .take_while(|c| c.is_whitespace() && *c != '\n')
22877 .map(|c| c.len_utf8())
22878 .sum::<usize>();
22879 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22880
22881 language.brackets().any(|(pair, enabled)| {
22882 let pair_start = pair.start.trim_end();
22883 let pair_end = pair.end.trim_start();
22884
22885 enabled
22886 && pair.newline
22887 && buffer.contains_str_at(range.end, pair_end)
22888 && buffer.contains_str_at(
22889 range.start.saturating_sub_usize(pair_start.len()),
22890 pair_start,
22891 )
22892 })
22893}
22894
22895fn insert_extra_newline_tree_sitter(
22896 buffer: &MultiBufferSnapshot,
22897 range: Range<MultiBufferOffset>,
22898) -> bool {
22899 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22900 [(buffer, range, _)] => (*buffer, range.clone()),
22901 _ => return false,
22902 };
22903 let pair = {
22904 let mut result: Option<BracketMatch<usize>> = None;
22905
22906 for pair in buffer
22907 .all_bracket_ranges(range.start.0..range.end.0)
22908 .filter(move |pair| {
22909 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22910 })
22911 {
22912 let len = pair.close_range.end - pair.open_range.start;
22913
22914 if let Some(existing) = &result {
22915 let existing_len = existing.close_range.end - existing.open_range.start;
22916 if len > existing_len {
22917 continue;
22918 }
22919 }
22920
22921 result = Some(pair);
22922 }
22923
22924 result
22925 };
22926 let Some(pair) = pair else {
22927 return false;
22928 };
22929 pair.newline_only
22930 && buffer
22931 .chars_for_range(pair.open_range.end..range.start.0)
22932 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22933 .all(|c| c.is_whitespace() && c != '\n')
22934}
22935
22936fn update_uncommitted_diff_for_buffer(
22937 editor: Entity<Editor>,
22938 project: &Entity<Project>,
22939 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22940 buffer: Entity<MultiBuffer>,
22941 cx: &mut App,
22942) -> Task<()> {
22943 let mut tasks = Vec::new();
22944 project.update(cx, |project, cx| {
22945 for buffer in buffers {
22946 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22947 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22948 }
22949 }
22950 });
22951 cx.spawn(async move |cx| {
22952 let diffs = future::join_all(tasks).await;
22953 if editor
22954 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22955 .unwrap_or(false)
22956 {
22957 return;
22958 }
22959
22960 buffer
22961 .update(cx, |buffer, cx| {
22962 for diff in diffs.into_iter().flatten() {
22963 buffer.add_diff(diff, cx);
22964 }
22965 })
22966 .ok();
22967 })
22968}
22969
22970fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22971 let tab_size = tab_size.get() as usize;
22972 let mut width = offset;
22973
22974 for ch in text.chars() {
22975 width += if ch == '\t' {
22976 tab_size - (width % tab_size)
22977 } else {
22978 1
22979 };
22980 }
22981
22982 width - offset
22983}
22984
22985#[cfg(test)]
22986mod tests {
22987 use super::*;
22988
22989 #[test]
22990 fn test_string_size_with_expanded_tabs() {
22991 let nz = |val| NonZeroU32::new(val).unwrap();
22992 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22993 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22994 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22995 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22996 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22997 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22998 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22999 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23000 }
23001}
23002
23003/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23004struct WordBreakingTokenizer<'a> {
23005 input: &'a str,
23006}
23007
23008impl<'a> WordBreakingTokenizer<'a> {
23009 fn new(input: &'a str) -> Self {
23010 Self { input }
23011 }
23012}
23013
23014fn is_char_ideographic(ch: char) -> bool {
23015 use unicode_script::Script::*;
23016 use unicode_script::UnicodeScript;
23017 matches!(ch.script(), Han | Tangut | Yi)
23018}
23019
23020fn is_grapheme_ideographic(text: &str) -> bool {
23021 text.chars().any(is_char_ideographic)
23022}
23023
23024fn is_grapheme_whitespace(text: &str) -> bool {
23025 text.chars().any(|x| x.is_whitespace())
23026}
23027
23028fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23029 text.chars()
23030 .next()
23031 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23032}
23033
23034#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23035enum WordBreakToken<'a> {
23036 Word { token: &'a str, grapheme_len: usize },
23037 InlineWhitespace { token: &'a str, grapheme_len: usize },
23038 Newline,
23039}
23040
23041impl<'a> Iterator for WordBreakingTokenizer<'a> {
23042 /// Yields a span, the count of graphemes in the token, and whether it was
23043 /// whitespace. Note that it also breaks at word boundaries.
23044 type Item = WordBreakToken<'a>;
23045
23046 fn next(&mut self) -> Option<Self::Item> {
23047 use unicode_segmentation::UnicodeSegmentation;
23048 if self.input.is_empty() {
23049 return None;
23050 }
23051
23052 let mut iter = self.input.graphemes(true).peekable();
23053 let mut offset = 0;
23054 let mut grapheme_len = 0;
23055 if let Some(first_grapheme) = iter.next() {
23056 let is_newline = first_grapheme == "\n";
23057 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23058 offset += first_grapheme.len();
23059 grapheme_len += 1;
23060 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23061 if let Some(grapheme) = iter.peek().copied()
23062 && should_stay_with_preceding_ideograph(grapheme)
23063 {
23064 offset += grapheme.len();
23065 grapheme_len += 1;
23066 }
23067 } else {
23068 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23069 let mut next_word_bound = words.peek().copied();
23070 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23071 next_word_bound = words.next();
23072 }
23073 while let Some(grapheme) = iter.peek().copied() {
23074 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23075 break;
23076 };
23077 if is_grapheme_whitespace(grapheme) != is_whitespace
23078 || (grapheme == "\n") != is_newline
23079 {
23080 break;
23081 };
23082 offset += grapheme.len();
23083 grapheme_len += 1;
23084 iter.next();
23085 }
23086 }
23087 let token = &self.input[..offset];
23088 self.input = &self.input[offset..];
23089 if token == "\n" {
23090 Some(WordBreakToken::Newline)
23091 } else if is_whitespace {
23092 Some(WordBreakToken::InlineWhitespace {
23093 token,
23094 grapheme_len,
23095 })
23096 } else {
23097 Some(WordBreakToken::Word {
23098 token,
23099 grapheme_len,
23100 })
23101 }
23102 } else {
23103 None
23104 }
23105 }
23106}
23107
23108#[test]
23109fn test_word_breaking_tokenizer() {
23110 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23111 ("", &[]),
23112 (" ", &[whitespace(" ", 2)]),
23113 ("Ʒ", &[word("Ʒ", 1)]),
23114 ("Ǽ", &[word("Ǽ", 1)]),
23115 ("⋑", &[word("⋑", 1)]),
23116 ("⋑⋑", &[word("⋑⋑", 2)]),
23117 (
23118 "原理,进而",
23119 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23120 ),
23121 (
23122 "hello world",
23123 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23124 ),
23125 (
23126 "hello, world",
23127 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23128 ),
23129 (
23130 " hello world",
23131 &[
23132 whitespace(" ", 2),
23133 word("hello", 5),
23134 whitespace(" ", 1),
23135 word("world", 5),
23136 ],
23137 ),
23138 (
23139 "这是什么 \n 钢笔",
23140 &[
23141 word("这", 1),
23142 word("是", 1),
23143 word("什", 1),
23144 word("么", 1),
23145 whitespace(" ", 1),
23146 newline(),
23147 whitespace(" ", 1),
23148 word("钢", 1),
23149 word("笔", 1),
23150 ],
23151 ),
23152 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23153 ];
23154
23155 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23156 WordBreakToken::Word {
23157 token,
23158 grapheme_len,
23159 }
23160 }
23161
23162 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23163 WordBreakToken::InlineWhitespace {
23164 token,
23165 grapheme_len,
23166 }
23167 }
23168
23169 fn newline() -> WordBreakToken<'static> {
23170 WordBreakToken::Newline
23171 }
23172
23173 for (input, result) in tests {
23174 assert_eq!(
23175 WordBreakingTokenizer::new(input)
23176 .collect::<Vec<_>>()
23177 .as_slice(),
23178 *result,
23179 );
23180 }
23181}
23182
23183fn wrap_with_prefix(
23184 first_line_prefix: String,
23185 subsequent_lines_prefix: String,
23186 unwrapped_text: String,
23187 wrap_column: usize,
23188 tab_size: NonZeroU32,
23189 preserve_existing_whitespace: bool,
23190) -> String {
23191 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23192 let subsequent_lines_prefix_len =
23193 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23194 let mut wrapped_text = String::new();
23195 let mut current_line = first_line_prefix;
23196 let mut is_first_line = true;
23197
23198 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23199 let mut current_line_len = first_line_prefix_len;
23200 let mut in_whitespace = false;
23201 for token in tokenizer {
23202 let have_preceding_whitespace = in_whitespace;
23203 match token {
23204 WordBreakToken::Word {
23205 token,
23206 grapheme_len,
23207 } => {
23208 in_whitespace = false;
23209 let current_prefix_len = if is_first_line {
23210 first_line_prefix_len
23211 } else {
23212 subsequent_lines_prefix_len
23213 };
23214 if current_line_len + grapheme_len > wrap_column
23215 && current_line_len != current_prefix_len
23216 {
23217 wrapped_text.push_str(current_line.trim_end());
23218 wrapped_text.push('\n');
23219 is_first_line = false;
23220 current_line = subsequent_lines_prefix.clone();
23221 current_line_len = subsequent_lines_prefix_len;
23222 }
23223 current_line.push_str(token);
23224 current_line_len += grapheme_len;
23225 }
23226 WordBreakToken::InlineWhitespace {
23227 mut token,
23228 mut grapheme_len,
23229 } => {
23230 in_whitespace = true;
23231 if have_preceding_whitespace && !preserve_existing_whitespace {
23232 continue;
23233 }
23234 if !preserve_existing_whitespace {
23235 // Keep a single whitespace grapheme as-is
23236 if let Some(first) =
23237 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23238 {
23239 token = first;
23240 } else {
23241 token = " ";
23242 }
23243 grapheme_len = 1;
23244 }
23245 let current_prefix_len = if is_first_line {
23246 first_line_prefix_len
23247 } else {
23248 subsequent_lines_prefix_len
23249 };
23250 if current_line_len + grapheme_len > wrap_column {
23251 wrapped_text.push_str(current_line.trim_end());
23252 wrapped_text.push('\n');
23253 is_first_line = false;
23254 current_line = subsequent_lines_prefix.clone();
23255 current_line_len = subsequent_lines_prefix_len;
23256 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23257 current_line.push_str(token);
23258 current_line_len += grapheme_len;
23259 }
23260 }
23261 WordBreakToken::Newline => {
23262 in_whitespace = true;
23263 let current_prefix_len = if is_first_line {
23264 first_line_prefix_len
23265 } else {
23266 subsequent_lines_prefix_len
23267 };
23268 if preserve_existing_whitespace {
23269 wrapped_text.push_str(current_line.trim_end());
23270 wrapped_text.push('\n');
23271 is_first_line = false;
23272 current_line = subsequent_lines_prefix.clone();
23273 current_line_len = subsequent_lines_prefix_len;
23274 } else if have_preceding_whitespace {
23275 continue;
23276 } else if current_line_len + 1 > wrap_column
23277 && current_line_len != current_prefix_len
23278 {
23279 wrapped_text.push_str(current_line.trim_end());
23280 wrapped_text.push('\n');
23281 is_first_line = false;
23282 current_line = subsequent_lines_prefix.clone();
23283 current_line_len = subsequent_lines_prefix_len;
23284 } else if current_line_len != current_prefix_len {
23285 current_line.push(' ');
23286 current_line_len += 1;
23287 }
23288 }
23289 }
23290 }
23291
23292 if !current_line.is_empty() {
23293 wrapped_text.push_str(¤t_line);
23294 }
23295 wrapped_text
23296}
23297
23298#[test]
23299fn test_wrap_with_prefix() {
23300 assert_eq!(
23301 wrap_with_prefix(
23302 "# ".to_string(),
23303 "# ".to_string(),
23304 "abcdefg".to_string(),
23305 4,
23306 NonZeroU32::new(4).unwrap(),
23307 false,
23308 ),
23309 "# abcdefg"
23310 );
23311 assert_eq!(
23312 wrap_with_prefix(
23313 "".to_string(),
23314 "".to_string(),
23315 "\thello world".to_string(),
23316 8,
23317 NonZeroU32::new(4).unwrap(),
23318 false,
23319 ),
23320 "hello\nworld"
23321 );
23322 assert_eq!(
23323 wrap_with_prefix(
23324 "// ".to_string(),
23325 "// ".to_string(),
23326 "xx \nyy zz aa bb cc".to_string(),
23327 12,
23328 NonZeroU32::new(4).unwrap(),
23329 false,
23330 ),
23331 "// xx yy zz\n// aa bb cc"
23332 );
23333 assert_eq!(
23334 wrap_with_prefix(
23335 String::new(),
23336 String::new(),
23337 "这是什么 \n 钢笔".to_string(),
23338 3,
23339 NonZeroU32::new(4).unwrap(),
23340 false,
23341 ),
23342 "这是什\n么 钢\n笔"
23343 );
23344 assert_eq!(
23345 wrap_with_prefix(
23346 String::new(),
23347 String::new(),
23348 format!("foo{}bar", '\u{2009}'), // thin space
23349 80,
23350 NonZeroU32::new(4).unwrap(),
23351 false,
23352 ),
23353 format!("foo{}bar", '\u{2009}')
23354 );
23355}
23356
23357pub trait CollaborationHub {
23358 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23359 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23360 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23361}
23362
23363impl CollaborationHub for Entity<Project> {
23364 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23365 self.read(cx).collaborators()
23366 }
23367
23368 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23369 self.read(cx).user_store().read(cx).participant_indices()
23370 }
23371
23372 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23373 let this = self.read(cx);
23374 let user_ids = this.collaborators().values().map(|c| c.user_id);
23375 this.user_store().read(cx).participant_names(user_ids, cx)
23376 }
23377}
23378
23379pub trait SemanticsProvider {
23380 fn hover(
23381 &self,
23382 buffer: &Entity<Buffer>,
23383 position: text::Anchor,
23384 cx: &mut App,
23385 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23386
23387 fn inline_values(
23388 &self,
23389 buffer_handle: Entity<Buffer>,
23390 range: Range<text::Anchor>,
23391 cx: &mut App,
23392 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23393
23394 fn applicable_inlay_chunks(
23395 &self,
23396 buffer: &Entity<Buffer>,
23397 ranges: &[Range<text::Anchor>],
23398 cx: &mut App,
23399 ) -> Vec<Range<BufferRow>>;
23400
23401 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23402
23403 fn inlay_hints(
23404 &self,
23405 invalidate: InvalidationStrategy,
23406 buffer: Entity<Buffer>,
23407 ranges: Vec<Range<text::Anchor>>,
23408 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23409 cx: &mut App,
23410 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23411
23412 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23413
23414 fn document_highlights(
23415 &self,
23416 buffer: &Entity<Buffer>,
23417 position: text::Anchor,
23418 cx: &mut App,
23419 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23420
23421 fn definitions(
23422 &self,
23423 buffer: &Entity<Buffer>,
23424 position: text::Anchor,
23425 kind: GotoDefinitionKind,
23426 cx: &mut App,
23427 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23428
23429 fn range_for_rename(
23430 &self,
23431 buffer: &Entity<Buffer>,
23432 position: text::Anchor,
23433 cx: &mut App,
23434 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23435
23436 fn perform_rename(
23437 &self,
23438 buffer: &Entity<Buffer>,
23439 position: text::Anchor,
23440 new_name: String,
23441 cx: &mut App,
23442 ) -> Option<Task<Result<ProjectTransaction>>>;
23443}
23444
23445pub trait CompletionProvider {
23446 fn completions(
23447 &self,
23448 excerpt_id: ExcerptId,
23449 buffer: &Entity<Buffer>,
23450 buffer_position: text::Anchor,
23451 trigger: CompletionContext,
23452 window: &mut Window,
23453 cx: &mut Context<Editor>,
23454 ) -> Task<Result<Vec<CompletionResponse>>>;
23455
23456 fn resolve_completions(
23457 &self,
23458 _buffer: Entity<Buffer>,
23459 _completion_indices: Vec<usize>,
23460 _completions: Rc<RefCell<Box<[Completion]>>>,
23461 _cx: &mut Context<Editor>,
23462 ) -> Task<Result<bool>> {
23463 Task::ready(Ok(false))
23464 }
23465
23466 fn apply_additional_edits_for_completion(
23467 &self,
23468 _buffer: Entity<Buffer>,
23469 _completions: Rc<RefCell<Box<[Completion]>>>,
23470 _completion_index: usize,
23471 _push_to_history: bool,
23472 _cx: &mut Context<Editor>,
23473 ) -> Task<Result<Option<language::Transaction>>> {
23474 Task::ready(Ok(None))
23475 }
23476
23477 fn is_completion_trigger(
23478 &self,
23479 buffer: &Entity<Buffer>,
23480 position: language::Anchor,
23481 text: &str,
23482 trigger_in_words: bool,
23483 menu_is_open: bool,
23484 cx: &mut Context<Editor>,
23485 ) -> bool;
23486
23487 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23488
23489 fn sort_completions(&self) -> bool {
23490 true
23491 }
23492
23493 fn filter_completions(&self) -> bool {
23494 true
23495 }
23496
23497 fn show_snippets(&self) -> bool {
23498 false
23499 }
23500}
23501
23502pub trait CodeActionProvider {
23503 fn id(&self) -> Arc<str>;
23504
23505 fn code_actions(
23506 &self,
23507 buffer: &Entity<Buffer>,
23508 range: Range<text::Anchor>,
23509 window: &mut Window,
23510 cx: &mut App,
23511 ) -> Task<Result<Vec<CodeAction>>>;
23512
23513 fn apply_code_action(
23514 &self,
23515 buffer_handle: Entity<Buffer>,
23516 action: CodeAction,
23517 excerpt_id: ExcerptId,
23518 push_to_history: bool,
23519 window: &mut Window,
23520 cx: &mut App,
23521 ) -> Task<Result<ProjectTransaction>>;
23522}
23523
23524impl CodeActionProvider for Entity<Project> {
23525 fn id(&self) -> Arc<str> {
23526 "project".into()
23527 }
23528
23529 fn code_actions(
23530 &self,
23531 buffer: &Entity<Buffer>,
23532 range: Range<text::Anchor>,
23533 _window: &mut Window,
23534 cx: &mut App,
23535 ) -> Task<Result<Vec<CodeAction>>> {
23536 self.update(cx, |project, cx| {
23537 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23538 let code_actions = project.code_actions(buffer, range, None, cx);
23539 cx.background_spawn(async move {
23540 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23541 Ok(code_lens_actions
23542 .context("code lens fetch")?
23543 .into_iter()
23544 .flatten()
23545 .chain(
23546 code_actions
23547 .context("code action fetch")?
23548 .into_iter()
23549 .flatten(),
23550 )
23551 .collect())
23552 })
23553 })
23554 }
23555
23556 fn apply_code_action(
23557 &self,
23558 buffer_handle: Entity<Buffer>,
23559 action: CodeAction,
23560 _excerpt_id: ExcerptId,
23561 push_to_history: bool,
23562 _window: &mut Window,
23563 cx: &mut App,
23564 ) -> Task<Result<ProjectTransaction>> {
23565 self.update(cx, |project, cx| {
23566 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23567 })
23568 }
23569}
23570
23571fn snippet_completions(
23572 project: &Project,
23573 buffer: &Entity<Buffer>,
23574 buffer_anchor: text::Anchor,
23575 classifier: CharClassifier,
23576 cx: &mut App,
23577) -> Task<Result<CompletionResponse>> {
23578 let languages = buffer.read(cx).languages_at(buffer_anchor);
23579 let snippet_store = project.snippets().read(cx);
23580
23581 let scopes: Vec<_> = languages
23582 .iter()
23583 .filter_map(|language| {
23584 let language_name = language.lsp_id();
23585 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23586
23587 if snippets.is_empty() {
23588 None
23589 } else {
23590 Some((language.default_scope(), snippets))
23591 }
23592 })
23593 .collect();
23594
23595 if scopes.is_empty() {
23596 return Task::ready(Ok(CompletionResponse {
23597 completions: vec![],
23598 display_options: CompletionDisplayOptions::default(),
23599 is_incomplete: false,
23600 }));
23601 }
23602
23603 let snapshot = buffer.read(cx).text_snapshot();
23604 let executor = cx.background_executor().clone();
23605
23606 cx.background_spawn(async move {
23607 let is_word_char = |c| classifier.is_word(c);
23608
23609 let mut is_incomplete = false;
23610 let mut completions: Vec<Completion> = Vec::new();
23611
23612 const MAX_PREFIX_LEN: usize = 128;
23613 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23614 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23615 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23616
23617 let max_buffer_window: String = snapshot
23618 .text_for_range(window_start..buffer_offset)
23619 .collect();
23620
23621 if max_buffer_window.is_empty() {
23622 return Ok(CompletionResponse {
23623 completions: vec![],
23624 display_options: CompletionDisplayOptions::default(),
23625 is_incomplete: true,
23626 });
23627 }
23628
23629 for (_scope, snippets) in scopes.into_iter() {
23630 // Sort snippets by word count to match longer snippet prefixes first.
23631 let mut sorted_snippet_candidates = snippets
23632 .iter()
23633 .enumerate()
23634 .flat_map(|(snippet_ix, snippet)| {
23635 snippet
23636 .prefix
23637 .iter()
23638 .enumerate()
23639 .map(move |(prefix_ix, prefix)| {
23640 let word_count =
23641 snippet_candidate_suffixes(prefix, is_word_char).count();
23642 ((snippet_ix, prefix_ix), prefix, word_count)
23643 })
23644 })
23645 .collect_vec();
23646 sorted_snippet_candidates
23647 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23648
23649 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23650
23651 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23652 .take(
23653 sorted_snippet_candidates
23654 .first()
23655 .map(|(_, _, word_count)| *word_count)
23656 .unwrap_or_default(),
23657 )
23658 .collect_vec();
23659
23660 const MAX_RESULTS: usize = 100;
23661 // Each match also remembers how many characters from the buffer it consumed
23662 let mut matches: Vec<(StringMatch, usize)> = vec![];
23663
23664 let mut snippet_list_cutoff_index = 0;
23665 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23666 let word_count = buffer_index + 1;
23667 // Increase `snippet_list_cutoff_index` until we have all of the
23668 // snippets with sufficiently many words.
23669 while sorted_snippet_candidates
23670 .get(snippet_list_cutoff_index)
23671 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23672 *snippet_word_count >= word_count
23673 })
23674 {
23675 snippet_list_cutoff_index += 1;
23676 }
23677
23678 // Take only the candidates with at least `word_count` many words
23679 let snippet_candidates_at_word_len =
23680 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23681
23682 let candidates = snippet_candidates_at_word_len
23683 .iter()
23684 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23685 .enumerate() // index in `sorted_snippet_candidates`
23686 // First char must match
23687 .filter(|(_ix, prefix)| {
23688 itertools::equal(
23689 prefix
23690 .chars()
23691 .next()
23692 .into_iter()
23693 .flat_map(|c| c.to_lowercase()),
23694 buffer_window
23695 .chars()
23696 .next()
23697 .into_iter()
23698 .flat_map(|c| c.to_lowercase()),
23699 )
23700 })
23701 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23702 .collect::<Vec<StringMatchCandidate>>();
23703
23704 matches.extend(
23705 fuzzy::match_strings(
23706 &candidates,
23707 &buffer_window,
23708 buffer_window.chars().any(|c| c.is_uppercase()),
23709 true,
23710 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23711 &Default::default(),
23712 executor.clone(),
23713 )
23714 .await
23715 .into_iter()
23716 .map(|string_match| (string_match, buffer_window.len())),
23717 );
23718
23719 if matches.len() >= MAX_RESULTS {
23720 break;
23721 }
23722 }
23723
23724 let to_lsp = |point: &text::Anchor| {
23725 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23726 point_to_lsp(end)
23727 };
23728 let lsp_end = to_lsp(&buffer_anchor);
23729
23730 if matches.len() >= MAX_RESULTS {
23731 is_incomplete = true;
23732 }
23733
23734 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23735 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23736 sorted_snippet_candidates[string_match.candidate_id];
23737 let snippet = &snippets[snippet_index];
23738 let start = buffer_offset - buffer_window_len;
23739 let start = snapshot.anchor_before(start);
23740 let range = start..buffer_anchor;
23741 let lsp_start = to_lsp(&start);
23742 let lsp_range = lsp::Range {
23743 start: lsp_start,
23744 end: lsp_end,
23745 };
23746 Completion {
23747 replace_range: range,
23748 new_text: snippet.body.clone(),
23749 source: CompletionSource::Lsp {
23750 insert_range: None,
23751 server_id: LanguageServerId(usize::MAX),
23752 resolved: true,
23753 lsp_completion: Box::new(lsp::CompletionItem {
23754 label: snippet.prefix.first().unwrap().clone(),
23755 kind: Some(CompletionItemKind::SNIPPET),
23756 label_details: snippet.description.as_ref().map(|description| {
23757 lsp::CompletionItemLabelDetails {
23758 detail: Some(description.clone()),
23759 description: None,
23760 }
23761 }),
23762 insert_text_format: Some(InsertTextFormat::SNIPPET),
23763 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23764 lsp::InsertReplaceEdit {
23765 new_text: snippet.body.clone(),
23766 insert: lsp_range,
23767 replace: lsp_range,
23768 },
23769 )),
23770 filter_text: Some(snippet.body.clone()),
23771 sort_text: Some(char::MAX.to_string()),
23772 ..lsp::CompletionItem::default()
23773 }),
23774 lsp_defaults: None,
23775 },
23776 label: CodeLabel {
23777 text: matching_prefix.clone(),
23778 runs: Vec::new(),
23779 filter_range: 0..matching_prefix.len(),
23780 },
23781 icon_path: None,
23782 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23783 single_line: snippet.name.clone().into(),
23784 plain_text: snippet
23785 .description
23786 .clone()
23787 .map(|description| description.into()),
23788 }),
23789 insert_text_mode: None,
23790 confirm: None,
23791 match_start: Some(start),
23792 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23793 }
23794 }));
23795 }
23796
23797 Ok(CompletionResponse {
23798 completions,
23799 display_options: CompletionDisplayOptions::default(),
23800 is_incomplete,
23801 })
23802 })
23803}
23804
23805impl CompletionProvider for Entity<Project> {
23806 fn completions(
23807 &self,
23808 _excerpt_id: ExcerptId,
23809 buffer: &Entity<Buffer>,
23810 buffer_position: text::Anchor,
23811 options: CompletionContext,
23812 _window: &mut Window,
23813 cx: &mut Context<Editor>,
23814 ) -> Task<Result<Vec<CompletionResponse>>> {
23815 self.update(cx, |project, cx| {
23816 let task = project.completions(buffer, buffer_position, options, cx);
23817 cx.background_spawn(task)
23818 })
23819 }
23820
23821 fn resolve_completions(
23822 &self,
23823 buffer: Entity<Buffer>,
23824 completion_indices: Vec<usize>,
23825 completions: Rc<RefCell<Box<[Completion]>>>,
23826 cx: &mut Context<Editor>,
23827 ) -> Task<Result<bool>> {
23828 self.update(cx, |project, cx| {
23829 project.lsp_store().update(cx, |lsp_store, cx| {
23830 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23831 })
23832 })
23833 }
23834
23835 fn apply_additional_edits_for_completion(
23836 &self,
23837 buffer: Entity<Buffer>,
23838 completions: Rc<RefCell<Box<[Completion]>>>,
23839 completion_index: usize,
23840 push_to_history: bool,
23841 cx: &mut Context<Editor>,
23842 ) -> Task<Result<Option<language::Transaction>>> {
23843 self.update(cx, |project, cx| {
23844 project.lsp_store().update(cx, |lsp_store, cx| {
23845 lsp_store.apply_additional_edits_for_completion(
23846 buffer,
23847 completions,
23848 completion_index,
23849 push_to_history,
23850 cx,
23851 )
23852 })
23853 })
23854 }
23855
23856 fn is_completion_trigger(
23857 &self,
23858 buffer: &Entity<Buffer>,
23859 position: language::Anchor,
23860 text: &str,
23861 trigger_in_words: bool,
23862 menu_is_open: bool,
23863 cx: &mut Context<Editor>,
23864 ) -> bool {
23865 let mut chars = text.chars();
23866 let char = if let Some(char) = chars.next() {
23867 char
23868 } else {
23869 return false;
23870 };
23871 if chars.next().is_some() {
23872 return false;
23873 }
23874
23875 let buffer = buffer.read(cx);
23876 let snapshot = buffer.snapshot();
23877 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23878 return false;
23879 }
23880 let classifier = snapshot
23881 .char_classifier_at(position)
23882 .scope_context(Some(CharScopeContext::Completion));
23883 if trigger_in_words && classifier.is_word(char) {
23884 return true;
23885 }
23886
23887 buffer.completion_triggers().contains(text)
23888 }
23889
23890 fn show_snippets(&self) -> bool {
23891 true
23892 }
23893}
23894
23895impl SemanticsProvider for Entity<Project> {
23896 fn hover(
23897 &self,
23898 buffer: &Entity<Buffer>,
23899 position: text::Anchor,
23900 cx: &mut App,
23901 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23902 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23903 }
23904
23905 fn document_highlights(
23906 &self,
23907 buffer: &Entity<Buffer>,
23908 position: text::Anchor,
23909 cx: &mut App,
23910 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23911 Some(self.update(cx, |project, cx| {
23912 project.document_highlights(buffer, position, cx)
23913 }))
23914 }
23915
23916 fn definitions(
23917 &self,
23918 buffer: &Entity<Buffer>,
23919 position: text::Anchor,
23920 kind: GotoDefinitionKind,
23921 cx: &mut App,
23922 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23923 Some(self.update(cx, |project, cx| match kind {
23924 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23925 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23926 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23927 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23928 }))
23929 }
23930
23931 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23932 self.update(cx, |project, cx| {
23933 if project
23934 .active_debug_session(cx)
23935 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23936 {
23937 return true;
23938 }
23939
23940 buffer.update(cx, |buffer, cx| {
23941 project.any_language_server_supports_inlay_hints(buffer, cx)
23942 })
23943 })
23944 }
23945
23946 fn inline_values(
23947 &self,
23948 buffer_handle: Entity<Buffer>,
23949 range: Range<text::Anchor>,
23950 cx: &mut App,
23951 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23952 self.update(cx, |project, cx| {
23953 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23954
23955 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23956 })
23957 }
23958
23959 fn applicable_inlay_chunks(
23960 &self,
23961 buffer: &Entity<Buffer>,
23962 ranges: &[Range<text::Anchor>],
23963 cx: &mut App,
23964 ) -> Vec<Range<BufferRow>> {
23965 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23966 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23967 })
23968 }
23969
23970 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23971 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23972 lsp_store.invalidate_inlay_hints(for_buffers)
23973 });
23974 }
23975
23976 fn inlay_hints(
23977 &self,
23978 invalidate: InvalidationStrategy,
23979 buffer: Entity<Buffer>,
23980 ranges: Vec<Range<text::Anchor>>,
23981 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23982 cx: &mut App,
23983 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23984 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23985 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23986 }))
23987 }
23988
23989 fn range_for_rename(
23990 &self,
23991 buffer: &Entity<Buffer>,
23992 position: text::Anchor,
23993 cx: &mut App,
23994 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23995 Some(self.update(cx, |project, cx| {
23996 let buffer = buffer.clone();
23997 let task = project.prepare_rename(buffer.clone(), position, cx);
23998 cx.spawn(async move |_, cx| {
23999 Ok(match task.await? {
24000 PrepareRenameResponse::Success(range) => Some(range),
24001 PrepareRenameResponse::InvalidPosition => None,
24002 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24003 // Fallback on using TreeSitter info to determine identifier range
24004 buffer.read_with(cx, |buffer, _| {
24005 let snapshot = buffer.snapshot();
24006 let (range, kind) = snapshot.surrounding_word(position, None);
24007 if kind != Some(CharKind::Word) {
24008 return None;
24009 }
24010 Some(
24011 snapshot.anchor_before(range.start)
24012 ..snapshot.anchor_after(range.end),
24013 )
24014 })?
24015 }
24016 })
24017 })
24018 }))
24019 }
24020
24021 fn perform_rename(
24022 &self,
24023 buffer: &Entity<Buffer>,
24024 position: text::Anchor,
24025 new_name: String,
24026 cx: &mut App,
24027 ) -> Option<Task<Result<ProjectTransaction>>> {
24028 Some(self.update(cx, |project, cx| {
24029 project.perform_rename(buffer.clone(), position, new_name, cx)
24030 }))
24031 }
24032}
24033
24034fn consume_contiguous_rows(
24035 contiguous_row_selections: &mut Vec<Selection<Point>>,
24036 selection: &Selection<Point>,
24037 display_map: &DisplaySnapshot,
24038 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24039) -> (MultiBufferRow, MultiBufferRow) {
24040 contiguous_row_selections.push(selection.clone());
24041 let start_row = starting_row(selection, display_map);
24042 let mut end_row = ending_row(selection, display_map);
24043
24044 while let Some(next_selection) = selections.peek() {
24045 if next_selection.start.row <= end_row.0 {
24046 end_row = ending_row(next_selection, display_map);
24047 contiguous_row_selections.push(selections.next().unwrap().clone());
24048 } else {
24049 break;
24050 }
24051 }
24052 (start_row, end_row)
24053}
24054
24055fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24056 if selection.start.column > 0 {
24057 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24058 } else {
24059 MultiBufferRow(selection.start.row)
24060 }
24061}
24062
24063fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24064 if next_selection.end.column > 0 || next_selection.is_empty() {
24065 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24066 } else {
24067 MultiBufferRow(next_selection.end.row)
24068 }
24069}
24070
24071impl EditorSnapshot {
24072 pub fn remote_selections_in_range<'a>(
24073 &'a self,
24074 range: &'a Range<Anchor>,
24075 collaboration_hub: &dyn CollaborationHub,
24076 cx: &'a App,
24077 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24078 let participant_names = collaboration_hub.user_names(cx);
24079 let participant_indices = collaboration_hub.user_participant_indices(cx);
24080 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24081 let collaborators_by_replica_id = collaborators_by_peer_id
24082 .values()
24083 .map(|collaborator| (collaborator.replica_id, collaborator))
24084 .collect::<HashMap<_, _>>();
24085 self.buffer_snapshot()
24086 .selections_in_range(range, false)
24087 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24088 if replica_id == ReplicaId::AGENT {
24089 Some(RemoteSelection {
24090 replica_id,
24091 selection,
24092 cursor_shape,
24093 line_mode,
24094 collaborator_id: CollaboratorId::Agent,
24095 user_name: Some("Agent".into()),
24096 color: cx.theme().players().agent(),
24097 })
24098 } else {
24099 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24100 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24101 let user_name = participant_names.get(&collaborator.user_id).cloned();
24102 Some(RemoteSelection {
24103 replica_id,
24104 selection,
24105 cursor_shape,
24106 line_mode,
24107 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24108 user_name,
24109 color: if let Some(index) = participant_index {
24110 cx.theme().players().color_for_participant(index.0)
24111 } else {
24112 cx.theme().players().absent()
24113 },
24114 })
24115 }
24116 })
24117 }
24118
24119 pub fn hunks_for_ranges(
24120 &self,
24121 ranges: impl IntoIterator<Item = Range<Point>>,
24122 ) -> Vec<MultiBufferDiffHunk> {
24123 let mut hunks = Vec::new();
24124 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24125 HashMap::default();
24126 for query_range in ranges {
24127 let query_rows =
24128 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24129 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24130 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24131 ) {
24132 // Include deleted hunks that are adjacent to the query range, because
24133 // otherwise they would be missed.
24134 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24135 if hunk.status().is_deleted() {
24136 intersects_range |= hunk.row_range.start == query_rows.end;
24137 intersects_range |= hunk.row_range.end == query_rows.start;
24138 }
24139 if intersects_range {
24140 if !processed_buffer_rows
24141 .entry(hunk.buffer_id)
24142 .or_default()
24143 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24144 {
24145 continue;
24146 }
24147 hunks.push(hunk);
24148 }
24149 }
24150 }
24151
24152 hunks
24153 }
24154
24155 fn display_diff_hunks_for_rows<'a>(
24156 &'a self,
24157 display_rows: Range<DisplayRow>,
24158 folded_buffers: &'a HashSet<BufferId>,
24159 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24160 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24161 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24162
24163 self.buffer_snapshot()
24164 .diff_hunks_in_range(buffer_start..buffer_end)
24165 .filter_map(|hunk| {
24166 if folded_buffers.contains(&hunk.buffer_id) {
24167 return None;
24168 }
24169
24170 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24171 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24172
24173 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24174 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24175
24176 let display_hunk = if hunk_display_start.column() != 0 {
24177 DisplayDiffHunk::Folded {
24178 display_row: hunk_display_start.row(),
24179 }
24180 } else {
24181 let mut end_row = hunk_display_end.row();
24182 if hunk_display_end.column() > 0 {
24183 end_row.0 += 1;
24184 }
24185 let is_created_file = hunk.is_created_file();
24186
24187 DisplayDiffHunk::Unfolded {
24188 status: hunk.status(),
24189 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24190 ..hunk.diff_base_byte_range.end.0,
24191 word_diffs: hunk.word_diffs,
24192 display_row_range: hunk_display_start.row()..end_row,
24193 multi_buffer_range: Anchor::range_in_buffer(
24194 hunk.excerpt_id,
24195 hunk.buffer_range,
24196 ),
24197 is_created_file,
24198 }
24199 };
24200
24201 Some(display_hunk)
24202 })
24203 }
24204
24205 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24206 self.display_snapshot
24207 .buffer_snapshot()
24208 .language_at(position)
24209 }
24210
24211 pub fn is_focused(&self) -> bool {
24212 self.is_focused
24213 }
24214
24215 pub fn placeholder_text(&self) -> Option<String> {
24216 self.placeholder_display_snapshot
24217 .as_ref()
24218 .map(|display_map| display_map.text())
24219 }
24220
24221 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24222 self.scroll_anchor.scroll_position(&self.display_snapshot)
24223 }
24224
24225 fn gutter_dimensions(
24226 &self,
24227 font_id: FontId,
24228 font_size: Pixels,
24229 max_line_number_width: Pixels,
24230 cx: &App,
24231 ) -> Option<GutterDimensions> {
24232 if !self.show_gutter {
24233 return None;
24234 }
24235
24236 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24237 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24238
24239 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24240 matches!(
24241 ProjectSettings::get_global(cx).git.git_gutter,
24242 GitGutterSetting::TrackedFiles
24243 )
24244 });
24245 let gutter_settings = EditorSettings::get_global(cx).gutter;
24246 let show_line_numbers = self
24247 .show_line_numbers
24248 .unwrap_or(gutter_settings.line_numbers);
24249 let line_gutter_width = if show_line_numbers {
24250 // Avoid flicker-like gutter resizes when the line number gains another digit by
24251 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24252 let min_width_for_number_on_gutter =
24253 ch_advance * gutter_settings.min_line_number_digits as f32;
24254 max_line_number_width.max(min_width_for_number_on_gutter)
24255 } else {
24256 0.0.into()
24257 };
24258
24259 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24260 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24261
24262 let git_blame_entries_width =
24263 self.git_blame_gutter_max_author_length
24264 .map(|max_author_length| {
24265 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24266 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24267
24268 /// The number of characters to dedicate to gaps and margins.
24269 const SPACING_WIDTH: usize = 4;
24270
24271 let max_char_count = max_author_length.min(renderer.max_author_length())
24272 + ::git::SHORT_SHA_LENGTH
24273 + MAX_RELATIVE_TIMESTAMP.len()
24274 + SPACING_WIDTH;
24275
24276 ch_advance * max_char_count
24277 });
24278
24279 let is_singleton = self.buffer_snapshot().is_singleton();
24280
24281 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24282 left_padding += if !is_singleton {
24283 ch_width * 4.0
24284 } else if show_runnables || show_breakpoints {
24285 ch_width * 3.0
24286 } else if show_git_gutter && show_line_numbers {
24287 ch_width * 2.0
24288 } else if show_git_gutter || show_line_numbers {
24289 ch_width
24290 } else {
24291 px(0.)
24292 };
24293
24294 let shows_folds = is_singleton && gutter_settings.folds;
24295
24296 let right_padding = if shows_folds && show_line_numbers {
24297 ch_width * 4.0
24298 } else if shows_folds || (!is_singleton && show_line_numbers) {
24299 ch_width * 3.0
24300 } else if show_line_numbers {
24301 ch_width
24302 } else {
24303 px(0.)
24304 };
24305
24306 Some(GutterDimensions {
24307 left_padding,
24308 right_padding,
24309 width: line_gutter_width + left_padding + right_padding,
24310 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24311 git_blame_entries_width,
24312 })
24313 }
24314
24315 pub fn render_crease_toggle(
24316 &self,
24317 buffer_row: MultiBufferRow,
24318 row_contains_cursor: bool,
24319 editor: Entity<Editor>,
24320 window: &mut Window,
24321 cx: &mut App,
24322 ) -> Option<AnyElement> {
24323 let folded = self.is_line_folded(buffer_row);
24324 let mut is_foldable = false;
24325
24326 if let Some(crease) = self
24327 .crease_snapshot
24328 .query_row(buffer_row, self.buffer_snapshot())
24329 {
24330 is_foldable = true;
24331 match crease {
24332 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24333 if let Some(render_toggle) = render_toggle {
24334 let toggle_callback =
24335 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24336 if folded {
24337 editor.update(cx, |editor, cx| {
24338 editor.fold_at(buffer_row, window, cx)
24339 });
24340 } else {
24341 editor.update(cx, |editor, cx| {
24342 editor.unfold_at(buffer_row, window, cx)
24343 });
24344 }
24345 });
24346 return Some((render_toggle)(
24347 buffer_row,
24348 folded,
24349 toggle_callback,
24350 window,
24351 cx,
24352 ));
24353 }
24354 }
24355 }
24356 }
24357
24358 is_foldable |= self.starts_indent(buffer_row);
24359
24360 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24361 Some(
24362 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24363 .toggle_state(folded)
24364 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24365 if folded {
24366 this.unfold_at(buffer_row, window, cx);
24367 } else {
24368 this.fold_at(buffer_row, window, cx);
24369 }
24370 }))
24371 .into_any_element(),
24372 )
24373 } else {
24374 None
24375 }
24376 }
24377
24378 pub fn render_crease_trailer(
24379 &self,
24380 buffer_row: MultiBufferRow,
24381 window: &mut Window,
24382 cx: &mut App,
24383 ) -> Option<AnyElement> {
24384 let folded = self.is_line_folded(buffer_row);
24385 if let Crease::Inline { render_trailer, .. } = self
24386 .crease_snapshot
24387 .query_row(buffer_row, self.buffer_snapshot())?
24388 {
24389 let render_trailer = render_trailer.as_ref()?;
24390 Some(render_trailer(buffer_row, folded, window, cx))
24391 } else {
24392 None
24393 }
24394 }
24395}
24396
24397impl Deref for EditorSnapshot {
24398 type Target = DisplaySnapshot;
24399
24400 fn deref(&self) -> &Self::Target {
24401 &self.display_snapshot
24402 }
24403}
24404
24405#[derive(Clone, Debug, PartialEq, Eq)]
24406pub enum EditorEvent {
24407 InputIgnored {
24408 text: Arc<str>,
24409 },
24410 InputHandled {
24411 utf16_range_to_replace: Option<Range<isize>>,
24412 text: Arc<str>,
24413 },
24414 ExcerptsAdded {
24415 buffer: Entity<Buffer>,
24416 predecessor: ExcerptId,
24417 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24418 },
24419 ExcerptsRemoved {
24420 ids: Vec<ExcerptId>,
24421 removed_buffer_ids: Vec<BufferId>,
24422 },
24423 BufferFoldToggled {
24424 ids: Vec<ExcerptId>,
24425 folded: bool,
24426 },
24427 ExcerptsEdited {
24428 ids: Vec<ExcerptId>,
24429 },
24430 ExcerptsExpanded {
24431 ids: Vec<ExcerptId>,
24432 },
24433 BufferEdited,
24434 Edited {
24435 transaction_id: clock::Lamport,
24436 },
24437 Reparsed(BufferId),
24438 Focused,
24439 FocusedIn,
24440 Blurred,
24441 DirtyChanged,
24442 Saved,
24443 TitleChanged,
24444 SelectionsChanged {
24445 local: bool,
24446 },
24447 ScrollPositionChanged {
24448 local: bool,
24449 autoscroll: bool,
24450 },
24451 TransactionUndone {
24452 transaction_id: clock::Lamport,
24453 },
24454 TransactionBegun {
24455 transaction_id: clock::Lamport,
24456 },
24457 CursorShapeChanged,
24458 BreadcrumbsChanged,
24459 PushedToNavHistory {
24460 anchor: Anchor,
24461 is_deactivate: bool,
24462 },
24463}
24464
24465impl EventEmitter<EditorEvent> for Editor {}
24466
24467impl Focusable for Editor {
24468 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24469 self.focus_handle.clone()
24470 }
24471}
24472
24473impl Render for Editor {
24474 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24475 let settings = ThemeSettings::get_global(cx);
24476
24477 let mut text_style = match self.mode {
24478 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24479 color: cx.theme().colors().editor_foreground,
24480 font_family: settings.ui_font.family.clone(),
24481 font_features: settings.ui_font.features.clone(),
24482 font_fallbacks: settings.ui_font.fallbacks.clone(),
24483 font_size: rems(0.875).into(),
24484 font_weight: settings.ui_font.weight,
24485 line_height: relative(settings.buffer_line_height.value()),
24486 ..Default::default()
24487 },
24488 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24489 color: cx.theme().colors().editor_foreground,
24490 font_family: settings.buffer_font.family.clone(),
24491 font_features: settings.buffer_font.features.clone(),
24492 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24493 font_size: settings.buffer_font_size(cx).into(),
24494 font_weight: settings.buffer_font.weight,
24495 line_height: relative(settings.buffer_line_height.value()),
24496 ..Default::default()
24497 },
24498 };
24499 if let Some(text_style_refinement) = &self.text_style_refinement {
24500 text_style.refine(text_style_refinement)
24501 }
24502
24503 let background = match self.mode {
24504 EditorMode::SingleLine => cx.theme().system().transparent,
24505 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24506 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24507 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24508 };
24509
24510 EditorElement::new(
24511 &cx.entity(),
24512 EditorStyle {
24513 background,
24514 border: cx.theme().colors().border,
24515 local_player: cx.theme().players().local(),
24516 text: text_style,
24517 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24518 syntax: cx.theme().syntax().clone(),
24519 status: cx.theme().status().clone(),
24520 inlay_hints_style: make_inlay_hints_style(cx),
24521 edit_prediction_styles: make_suggestion_styles(cx),
24522 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24523 show_underlines: self.diagnostics_enabled(),
24524 },
24525 )
24526 }
24527}
24528
24529impl EntityInputHandler for Editor {
24530 fn text_for_range(
24531 &mut self,
24532 range_utf16: Range<usize>,
24533 adjusted_range: &mut Option<Range<usize>>,
24534 _: &mut Window,
24535 cx: &mut Context<Self>,
24536 ) -> Option<String> {
24537 let snapshot = self.buffer.read(cx).read(cx);
24538 let start = snapshot.clip_offset_utf16(
24539 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24540 Bias::Left,
24541 );
24542 let end = snapshot.clip_offset_utf16(
24543 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24544 Bias::Right,
24545 );
24546 if (start.0.0..end.0.0) != range_utf16 {
24547 adjusted_range.replace(start.0.0..end.0.0);
24548 }
24549 Some(snapshot.text_for_range(start..end).collect())
24550 }
24551
24552 fn selected_text_range(
24553 &mut self,
24554 ignore_disabled_input: bool,
24555 _: &mut Window,
24556 cx: &mut Context<Self>,
24557 ) -> Option<UTF16Selection> {
24558 // Prevent the IME menu from appearing when holding down an alphabetic key
24559 // while input is disabled.
24560 if !ignore_disabled_input && !self.input_enabled {
24561 return None;
24562 }
24563
24564 let selection = self
24565 .selections
24566 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24567 let range = selection.range();
24568
24569 Some(UTF16Selection {
24570 range: range.start.0.0..range.end.0.0,
24571 reversed: selection.reversed,
24572 })
24573 }
24574
24575 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24576 let snapshot = self.buffer.read(cx).read(cx);
24577 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24578 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24579 }
24580
24581 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24582 self.clear_highlights::<InputComposition>(cx);
24583 self.ime_transaction.take();
24584 }
24585
24586 fn replace_text_in_range(
24587 &mut self,
24588 range_utf16: Option<Range<usize>>,
24589 text: &str,
24590 window: &mut Window,
24591 cx: &mut Context<Self>,
24592 ) {
24593 if !self.input_enabled {
24594 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24595 return;
24596 }
24597
24598 self.transact(window, cx, |this, window, cx| {
24599 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24600 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24601 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24602 Some(this.selection_replacement_ranges(range_utf16, cx))
24603 } else {
24604 this.marked_text_ranges(cx)
24605 };
24606
24607 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24608 let newest_selection_id = this.selections.newest_anchor().id;
24609 this.selections
24610 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24611 .iter()
24612 .zip(ranges_to_replace.iter())
24613 .find_map(|(selection, range)| {
24614 if selection.id == newest_selection_id {
24615 Some(
24616 (range.start.0.0 as isize - selection.head().0.0 as isize)
24617 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24618 )
24619 } else {
24620 None
24621 }
24622 })
24623 });
24624
24625 cx.emit(EditorEvent::InputHandled {
24626 utf16_range_to_replace: range_to_replace,
24627 text: text.into(),
24628 });
24629
24630 if let Some(new_selected_ranges) = new_selected_ranges {
24631 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24632 selections.select_ranges(new_selected_ranges)
24633 });
24634 this.backspace(&Default::default(), window, cx);
24635 }
24636
24637 this.handle_input(text, window, cx);
24638 });
24639
24640 if let Some(transaction) = self.ime_transaction {
24641 self.buffer.update(cx, |buffer, cx| {
24642 buffer.group_until_transaction(transaction, cx);
24643 });
24644 }
24645
24646 self.unmark_text(window, cx);
24647 }
24648
24649 fn replace_and_mark_text_in_range(
24650 &mut self,
24651 range_utf16: Option<Range<usize>>,
24652 text: &str,
24653 new_selected_range_utf16: Option<Range<usize>>,
24654 window: &mut Window,
24655 cx: &mut Context<Self>,
24656 ) {
24657 if !self.input_enabled {
24658 return;
24659 }
24660
24661 let transaction = self.transact(window, cx, |this, window, cx| {
24662 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24663 let snapshot = this.buffer.read(cx).read(cx);
24664 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24665 for marked_range in &mut marked_ranges {
24666 marked_range.end = marked_range.start + relative_range_utf16.end;
24667 marked_range.start += relative_range_utf16.start;
24668 marked_range.start =
24669 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24670 marked_range.end =
24671 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24672 }
24673 }
24674 Some(marked_ranges)
24675 } else if let Some(range_utf16) = range_utf16 {
24676 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24677 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24678 Some(this.selection_replacement_ranges(range_utf16, cx))
24679 } else {
24680 None
24681 };
24682
24683 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24684 let newest_selection_id = this.selections.newest_anchor().id;
24685 this.selections
24686 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24687 .iter()
24688 .zip(ranges_to_replace.iter())
24689 .find_map(|(selection, range)| {
24690 if selection.id == newest_selection_id {
24691 Some(
24692 (range.start.0.0 as isize - selection.head().0.0 as isize)
24693 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24694 )
24695 } else {
24696 None
24697 }
24698 })
24699 });
24700
24701 cx.emit(EditorEvent::InputHandled {
24702 utf16_range_to_replace: range_to_replace,
24703 text: text.into(),
24704 });
24705
24706 if let Some(ranges) = ranges_to_replace {
24707 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24708 s.select_ranges(ranges)
24709 });
24710 }
24711
24712 let marked_ranges = {
24713 let snapshot = this.buffer.read(cx).read(cx);
24714 this.selections
24715 .disjoint_anchors_arc()
24716 .iter()
24717 .map(|selection| {
24718 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24719 })
24720 .collect::<Vec<_>>()
24721 };
24722
24723 if text.is_empty() {
24724 this.unmark_text(window, cx);
24725 } else {
24726 this.highlight_text::<InputComposition>(
24727 marked_ranges.clone(),
24728 HighlightStyle {
24729 underline: Some(UnderlineStyle {
24730 thickness: px(1.),
24731 color: None,
24732 wavy: false,
24733 }),
24734 ..Default::default()
24735 },
24736 cx,
24737 );
24738 }
24739
24740 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24741 let use_autoclose = this.use_autoclose;
24742 let use_auto_surround = this.use_auto_surround;
24743 this.set_use_autoclose(false);
24744 this.set_use_auto_surround(false);
24745 this.handle_input(text, window, cx);
24746 this.set_use_autoclose(use_autoclose);
24747 this.set_use_auto_surround(use_auto_surround);
24748
24749 if let Some(new_selected_range) = new_selected_range_utf16 {
24750 let snapshot = this.buffer.read(cx).read(cx);
24751 let new_selected_ranges = marked_ranges
24752 .into_iter()
24753 .map(|marked_range| {
24754 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24755 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24756 insertion_start.0 + new_selected_range.start,
24757 ));
24758 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24759 insertion_start.0 + new_selected_range.end,
24760 ));
24761 snapshot.clip_offset_utf16(new_start, Bias::Left)
24762 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24763 })
24764 .collect::<Vec<_>>();
24765
24766 drop(snapshot);
24767 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24768 selections.select_ranges(new_selected_ranges)
24769 });
24770 }
24771 });
24772
24773 self.ime_transaction = self.ime_transaction.or(transaction);
24774 if let Some(transaction) = self.ime_transaction {
24775 self.buffer.update(cx, |buffer, cx| {
24776 buffer.group_until_transaction(transaction, cx);
24777 });
24778 }
24779
24780 if self.text_highlights::<InputComposition>(cx).is_none() {
24781 self.ime_transaction.take();
24782 }
24783 }
24784
24785 fn bounds_for_range(
24786 &mut self,
24787 range_utf16: Range<usize>,
24788 element_bounds: gpui::Bounds<Pixels>,
24789 window: &mut Window,
24790 cx: &mut Context<Self>,
24791 ) -> Option<gpui::Bounds<Pixels>> {
24792 let text_layout_details = self.text_layout_details(window);
24793 let CharacterDimensions {
24794 em_width,
24795 em_advance,
24796 line_height,
24797 } = self.character_dimensions(window);
24798
24799 let snapshot = self.snapshot(window, cx);
24800 let scroll_position = snapshot.scroll_position();
24801 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24802
24803 let start =
24804 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24805 let x = Pixels::from(
24806 ScrollOffset::from(
24807 snapshot.x_for_display_point(start, &text_layout_details)
24808 + self.gutter_dimensions.full_width(),
24809 ) - scroll_left,
24810 );
24811 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24812
24813 Some(Bounds {
24814 origin: element_bounds.origin + point(x, y),
24815 size: size(em_width, line_height),
24816 })
24817 }
24818
24819 fn character_index_for_point(
24820 &mut self,
24821 point: gpui::Point<Pixels>,
24822 _window: &mut Window,
24823 _cx: &mut Context<Self>,
24824 ) -> Option<usize> {
24825 let position_map = self.last_position_map.as_ref()?;
24826 if !position_map.text_hitbox.contains(&point) {
24827 return None;
24828 }
24829 let display_point = position_map.point_for_position(point).previous_valid;
24830 let anchor = position_map
24831 .snapshot
24832 .display_point_to_anchor(display_point, Bias::Left);
24833 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24834 Some(utf16_offset.0.0)
24835 }
24836
24837 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24838 self.input_enabled
24839 }
24840}
24841
24842trait SelectionExt {
24843 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24844 fn spanned_rows(
24845 &self,
24846 include_end_if_at_line_start: bool,
24847 map: &DisplaySnapshot,
24848 ) -> Range<MultiBufferRow>;
24849}
24850
24851impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24852 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24853 let start = self
24854 .start
24855 .to_point(map.buffer_snapshot())
24856 .to_display_point(map);
24857 let end = self
24858 .end
24859 .to_point(map.buffer_snapshot())
24860 .to_display_point(map);
24861 if self.reversed {
24862 end..start
24863 } else {
24864 start..end
24865 }
24866 }
24867
24868 fn spanned_rows(
24869 &self,
24870 include_end_if_at_line_start: bool,
24871 map: &DisplaySnapshot,
24872 ) -> Range<MultiBufferRow> {
24873 let start = self.start.to_point(map.buffer_snapshot());
24874 let mut end = self.end.to_point(map.buffer_snapshot());
24875 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24876 end.row -= 1;
24877 }
24878
24879 let buffer_start = map.prev_line_boundary(start).0;
24880 let buffer_end = map.next_line_boundary(end).0;
24881 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24882 }
24883}
24884
24885impl<T: InvalidationRegion> InvalidationStack<T> {
24886 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24887 where
24888 S: Clone + ToOffset,
24889 {
24890 while let Some(region) = self.last() {
24891 let all_selections_inside_invalidation_ranges =
24892 if selections.len() == region.ranges().len() {
24893 selections
24894 .iter()
24895 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24896 .all(|(selection, invalidation_range)| {
24897 let head = selection.head().to_offset(buffer);
24898 invalidation_range.start <= head && invalidation_range.end >= head
24899 })
24900 } else {
24901 false
24902 };
24903
24904 if all_selections_inside_invalidation_ranges {
24905 break;
24906 } else {
24907 self.pop();
24908 }
24909 }
24910 }
24911}
24912
24913impl<T> Default for InvalidationStack<T> {
24914 fn default() -> Self {
24915 Self(Default::default())
24916 }
24917}
24918
24919impl<T> Deref for InvalidationStack<T> {
24920 type Target = Vec<T>;
24921
24922 fn deref(&self) -> &Self::Target {
24923 &self.0
24924 }
24925}
24926
24927impl<T> DerefMut for InvalidationStack<T> {
24928 fn deref_mut(&mut self) -> &mut Self::Target {
24929 &mut self.0
24930 }
24931}
24932
24933impl InvalidationRegion for SnippetState {
24934 fn ranges(&self) -> &[Range<Anchor>] {
24935 &self.ranges[self.active_index]
24936 }
24937}
24938
24939fn edit_prediction_edit_text(
24940 current_snapshot: &BufferSnapshot,
24941 edits: &[(Range<Anchor>, impl AsRef<str>)],
24942 edit_preview: &EditPreview,
24943 include_deletions: bool,
24944 cx: &App,
24945) -> HighlightedText {
24946 let edits = edits
24947 .iter()
24948 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24949 .collect::<Vec<_>>();
24950
24951 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24952}
24953
24954fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24955 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24956 // Just show the raw edit text with basic styling
24957 let mut text = String::new();
24958 let mut highlights = Vec::new();
24959
24960 let insertion_highlight_style = HighlightStyle {
24961 color: Some(cx.theme().colors().text),
24962 ..Default::default()
24963 };
24964
24965 for (_, edit_text) in edits {
24966 let start_offset = text.len();
24967 text.push_str(edit_text);
24968 let end_offset = text.len();
24969
24970 if start_offset < end_offset {
24971 highlights.push((start_offset..end_offset, insertion_highlight_style));
24972 }
24973 }
24974
24975 HighlightedText {
24976 text: text.into(),
24977 highlights,
24978 }
24979}
24980
24981pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24982 match severity {
24983 lsp::DiagnosticSeverity::ERROR => colors.error,
24984 lsp::DiagnosticSeverity::WARNING => colors.warning,
24985 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24986 lsp::DiagnosticSeverity::HINT => colors.info,
24987 _ => colors.ignored,
24988 }
24989}
24990
24991pub fn styled_runs_for_code_label<'a>(
24992 label: &'a CodeLabel,
24993 syntax_theme: &'a theme::SyntaxTheme,
24994 local_player: &'a theme::PlayerColor,
24995) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24996 let fade_out = HighlightStyle {
24997 fade_out: Some(0.35),
24998 ..Default::default()
24999 };
25000
25001 let mut prev_end = label.filter_range.end;
25002 label
25003 .runs
25004 .iter()
25005 .enumerate()
25006 .flat_map(move |(ix, (range, highlight_id))| {
25007 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25008 HighlightStyle {
25009 color: Some(local_player.cursor),
25010 ..Default::default()
25011 }
25012 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25013 HighlightStyle {
25014 background_color: Some(local_player.selection),
25015 ..Default::default()
25016 }
25017 } else if let Some(style) = highlight_id.style(syntax_theme) {
25018 style
25019 } else {
25020 return Default::default();
25021 };
25022 let muted_style = style.highlight(fade_out);
25023
25024 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25025 if range.start >= label.filter_range.end {
25026 if range.start > prev_end {
25027 runs.push((prev_end..range.start, fade_out));
25028 }
25029 runs.push((range.clone(), muted_style));
25030 } else if range.end <= label.filter_range.end {
25031 runs.push((range.clone(), style));
25032 } else {
25033 runs.push((range.start..label.filter_range.end, style));
25034 runs.push((label.filter_range.end..range.end, muted_style));
25035 }
25036 prev_end = cmp::max(prev_end, range.end);
25037
25038 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25039 runs.push((prev_end..label.text.len(), fade_out));
25040 }
25041
25042 runs
25043 })
25044}
25045
25046pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25047 let mut prev_index = 0;
25048 let mut prev_codepoint: Option<char> = None;
25049 text.char_indices()
25050 .chain([(text.len(), '\0')])
25051 .filter_map(move |(index, codepoint)| {
25052 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25053 let is_boundary = index == text.len()
25054 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25055 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25056 if is_boundary {
25057 let chunk = &text[prev_index..index];
25058 prev_index = index;
25059 Some(chunk)
25060 } else {
25061 None
25062 }
25063 })
25064}
25065
25066/// Given a string of text immediately before the cursor, iterates over possible
25067/// strings a snippet could match to. More precisely: returns an iterator over
25068/// suffixes of `text` created by splitting at word boundaries (before & after
25069/// every non-word character).
25070///
25071/// Shorter suffixes are returned first.
25072pub(crate) fn snippet_candidate_suffixes(
25073 text: &str,
25074 is_word_char: impl Fn(char) -> bool,
25075) -> impl std::iter::Iterator<Item = &str> {
25076 let mut prev_index = text.len();
25077 let mut prev_codepoint = None;
25078 text.char_indices()
25079 .rev()
25080 .chain([(0, '\0')])
25081 .filter_map(move |(index, codepoint)| {
25082 let prev_index = std::mem::replace(&mut prev_index, index);
25083 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25084 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25085 None
25086 } else {
25087 let chunk = &text[prev_index..]; // go to end of string
25088 Some(chunk)
25089 }
25090 })
25091}
25092
25093pub trait RangeToAnchorExt: Sized {
25094 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25095
25096 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25097 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25098 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25099 }
25100}
25101
25102impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25103 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25104 let start_offset = self.start.to_offset(snapshot);
25105 let end_offset = self.end.to_offset(snapshot);
25106 if start_offset == end_offset {
25107 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25108 } else {
25109 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25110 }
25111 }
25112}
25113
25114pub trait RowExt {
25115 fn as_f64(&self) -> f64;
25116
25117 fn next_row(&self) -> Self;
25118
25119 fn previous_row(&self) -> Self;
25120
25121 fn minus(&self, other: Self) -> u32;
25122}
25123
25124impl RowExt for DisplayRow {
25125 fn as_f64(&self) -> f64 {
25126 self.0 as _
25127 }
25128
25129 fn next_row(&self) -> Self {
25130 Self(self.0 + 1)
25131 }
25132
25133 fn previous_row(&self) -> Self {
25134 Self(self.0.saturating_sub(1))
25135 }
25136
25137 fn minus(&self, other: Self) -> u32 {
25138 self.0 - other.0
25139 }
25140}
25141
25142impl RowExt for MultiBufferRow {
25143 fn as_f64(&self) -> f64 {
25144 self.0 as _
25145 }
25146
25147 fn next_row(&self) -> Self {
25148 Self(self.0 + 1)
25149 }
25150
25151 fn previous_row(&self) -> Self {
25152 Self(self.0.saturating_sub(1))
25153 }
25154
25155 fn minus(&self, other: Self) -> u32 {
25156 self.0 - other.0
25157 }
25158}
25159
25160trait RowRangeExt {
25161 type Row;
25162
25163 fn len(&self) -> usize;
25164
25165 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25166}
25167
25168impl RowRangeExt for Range<MultiBufferRow> {
25169 type Row = MultiBufferRow;
25170
25171 fn len(&self) -> usize {
25172 (self.end.0 - self.start.0) as usize
25173 }
25174
25175 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25176 (self.start.0..self.end.0).map(MultiBufferRow)
25177 }
25178}
25179
25180impl RowRangeExt for Range<DisplayRow> {
25181 type Row = DisplayRow;
25182
25183 fn len(&self) -> usize {
25184 (self.end.0 - self.start.0) as usize
25185 }
25186
25187 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25188 (self.start.0..self.end.0).map(DisplayRow)
25189 }
25190}
25191
25192/// If select range has more than one line, we
25193/// just point the cursor to range.start.
25194fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25195 if range.start.row == range.end.row {
25196 range
25197 } else {
25198 range.start..range.start
25199 }
25200}
25201pub struct KillRing(ClipboardItem);
25202impl Global for KillRing {}
25203
25204const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25205
25206enum BreakpointPromptEditAction {
25207 Log,
25208 Condition,
25209 HitCondition,
25210}
25211
25212struct BreakpointPromptEditor {
25213 pub(crate) prompt: Entity<Editor>,
25214 editor: WeakEntity<Editor>,
25215 breakpoint_anchor: Anchor,
25216 breakpoint: Breakpoint,
25217 edit_action: BreakpointPromptEditAction,
25218 block_ids: HashSet<CustomBlockId>,
25219 editor_margins: Arc<Mutex<EditorMargins>>,
25220 _subscriptions: Vec<Subscription>,
25221}
25222
25223impl BreakpointPromptEditor {
25224 const MAX_LINES: u8 = 4;
25225
25226 fn new(
25227 editor: WeakEntity<Editor>,
25228 breakpoint_anchor: Anchor,
25229 breakpoint: Breakpoint,
25230 edit_action: BreakpointPromptEditAction,
25231 window: &mut Window,
25232 cx: &mut Context<Self>,
25233 ) -> Self {
25234 let base_text = match edit_action {
25235 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25236 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25237 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25238 }
25239 .map(|msg| msg.to_string())
25240 .unwrap_or_default();
25241
25242 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25243 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25244
25245 let prompt = cx.new(|cx| {
25246 let mut prompt = Editor::new(
25247 EditorMode::AutoHeight {
25248 min_lines: 1,
25249 max_lines: Some(Self::MAX_LINES as usize),
25250 },
25251 buffer,
25252 None,
25253 window,
25254 cx,
25255 );
25256 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25257 prompt.set_show_cursor_when_unfocused(false, cx);
25258 prompt.set_placeholder_text(
25259 match edit_action {
25260 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25261 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25262 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25263 },
25264 window,
25265 cx,
25266 );
25267
25268 prompt
25269 });
25270
25271 Self {
25272 prompt,
25273 editor,
25274 breakpoint_anchor,
25275 breakpoint,
25276 edit_action,
25277 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25278 block_ids: Default::default(),
25279 _subscriptions: vec![],
25280 }
25281 }
25282
25283 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25284 self.block_ids.extend(block_ids)
25285 }
25286
25287 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25288 if let Some(editor) = self.editor.upgrade() {
25289 let message = self
25290 .prompt
25291 .read(cx)
25292 .buffer
25293 .read(cx)
25294 .as_singleton()
25295 .expect("A multi buffer in breakpoint prompt isn't possible")
25296 .read(cx)
25297 .as_rope()
25298 .to_string();
25299
25300 editor.update(cx, |editor, cx| {
25301 editor.edit_breakpoint_at_anchor(
25302 self.breakpoint_anchor,
25303 self.breakpoint.clone(),
25304 match self.edit_action {
25305 BreakpointPromptEditAction::Log => {
25306 BreakpointEditAction::EditLogMessage(message.into())
25307 }
25308 BreakpointPromptEditAction::Condition => {
25309 BreakpointEditAction::EditCondition(message.into())
25310 }
25311 BreakpointPromptEditAction::HitCondition => {
25312 BreakpointEditAction::EditHitCondition(message.into())
25313 }
25314 },
25315 cx,
25316 );
25317
25318 editor.remove_blocks(self.block_ids.clone(), None, cx);
25319 cx.focus_self(window);
25320 });
25321 }
25322 }
25323
25324 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25325 self.editor
25326 .update(cx, |editor, cx| {
25327 editor.remove_blocks(self.block_ids.clone(), None, cx);
25328 window.focus(&editor.focus_handle);
25329 })
25330 .log_err();
25331 }
25332
25333 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25334 let settings = ThemeSettings::get_global(cx);
25335 let text_style = TextStyle {
25336 color: if self.prompt.read(cx).read_only(cx) {
25337 cx.theme().colors().text_disabled
25338 } else {
25339 cx.theme().colors().text
25340 },
25341 font_family: settings.buffer_font.family.clone(),
25342 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25343 font_size: settings.buffer_font_size(cx).into(),
25344 font_weight: settings.buffer_font.weight,
25345 line_height: relative(settings.buffer_line_height.value()),
25346 ..Default::default()
25347 };
25348 EditorElement::new(
25349 &self.prompt,
25350 EditorStyle {
25351 background: cx.theme().colors().editor_background,
25352 local_player: cx.theme().players().local(),
25353 text: text_style,
25354 ..Default::default()
25355 },
25356 )
25357 }
25358}
25359
25360impl Render for BreakpointPromptEditor {
25361 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25362 let editor_margins = *self.editor_margins.lock();
25363 let gutter_dimensions = editor_margins.gutter;
25364 h_flex()
25365 .key_context("Editor")
25366 .bg(cx.theme().colors().editor_background)
25367 .border_y_1()
25368 .border_color(cx.theme().status().info_border)
25369 .size_full()
25370 .py(window.line_height() / 2.5)
25371 .on_action(cx.listener(Self::confirm))
25372 .on_action(cx.listener(Self::cancel))
25373 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25374 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25375 }
25376}
25377
25378impl Focusable for BreakpointPromptEditor {
25379 fn focus_handle(&self, cx: &App) -> FocusHandle {
25380 self.prompt.focus_handle(cx)
25381 }
25382}
25383
25384fn all_edits_insertions_or_deletions(
25385 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25386 snapshot: &MultiBufferSnapshot,
25387) -> bool {
25388 let mut all_insertions = true;
25389 let mut all_deletions = true;
25390
25391 for (range, new_text) in edits.iter() {
25392 let range_is_empty = range.to_offset(snapshot).is_empty();
25393 let text_is_empty = new_text.is_empty();
25394
25395 if range_is_empty != text_is_empty {
25396 if range_is_empty {
25397 all_deletions = false;
25398 } else {
25399 all_insertions = false;
25400 }
25401 } else {
25402 return false;
25403 }
25404
25405 if !all_insertions && !all_deletions {
25406 return false;
25407 }
25408 }
25409 all_insertions || all_deletions
25410}
25411
25412struct MissingEditPredictionKeybindingTooltip;
25413
25414impl Render for MissingEditPredictionKeybindingTooltip {
25415 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25416 ui::tooltip_container(cx, |container, cx| {
25417 container
25418 .flex_shrink_0()
25419 .max_w_80()
25420 .min_h(rems_from_px(124.))
25421 .justify_between()
25422 .child(
25423 v_flex()
25424 .flex_1()
25425 .text_ui_sm(cx)
25426 .child(Label::new("Conflict with Accept Keybinding"))
25427 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25428 )
25429 .child(
25430 h_flex()
25431 .pb_1()
25432 .gap_1()
25433 .items_end()
25434 .w_full()
25435 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25436 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25437 }))
25438 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25439 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25440 })),
25441 )
25442 })
25443 }
25444}
25445
25446#[derive(Debug, Clone, Copy, PartialEq)]
25447pub struct LineHighlight {
25448 pub background: Background,
25449 pub border: Option<gpui::Hsla>,
25450 pub include_gutter: bool,
25451 pub type_id: Option<TypeId>,
25452}
25453
25454struct LineManipulationResult {
25455 pub new_text: String,
25456 pub line_count_before: usize,
25457 pub line_count_after: usize,
25458}
25459
25460fn render_diff_hunk_controls(
25461 row: u32,
25462 status: &DiffHunkStatus,
25463 hunk_range: Range<Anchor>,
25464 is_created_file: bool,
25465 line_height: Pixels,
25466 editor: &Entity<Editor>,
25467 _window: &mut Window,
25468 cx: &mut App,
25469) -> AnyElement {
25470 h_flex()
25471 .h(line_height)
25472 .mr_1()
25473 .gap_1()
25474 .px_0p5()
25475 .pb_1()
25476 .border_x_1()
25477 .border_b_1()
25478 .border_color(cx.theme().colors().border_variant)
25479 .rounded_b_lg()
25480 .bg(cx.theme().colors().editor_background)
25481 .gap_1()
25482 .block_mouse_except_scroll()
25483 .shadow_md()
25484 .child(if status.has_secondary_hunk() {
25485 Button::new(("stage", row as u64), "Stage")
25486 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25487 .tooltip({
25488 let focus_handle = editor.focus_handle(cx);
25489 move |_window, cx| {
25490 Tooltip::for_action_in(
25491 "Stage Hunk",
25492 &::git::ToggleStaged,
25493 &focus_handle,
25494 cx,
25495 )
25496 }
25497 })
25498 .on_click({
25499 let editor = editor.clone();
25500 move |_event, _window, cx| {
25501 editor.update(cx, |editor, cx| {
25502 editor.stage_or_unstage_diff_hunks(
25503 true,
25504 vec![hunk_range.start..hunk_range.start],
25505 cx,
25506 );
25507 });
25508 }
25509 })
25510 } else {
25511 Button::new(("unstage", row as u64), "Unstage")
25512 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25513 .tooltip({
25514 let focus_handle = editor.focus_handle(cx);
25515 move |_window, cx| {
25516 Tooltip::for_action_in(
25517 "Unstage Hunk",
25518 &::git::ToggleStaged,
25519 &focus_handle,
25520 cx,
25521 )
25522 }
25523 })
25524 .on_click({
25525 let editor = editor.clone();
25526 move |_event, _window, cx| {
25527 editor.update(cx, |editor, cx| {
25528 editor.stage_or_unstage_diff_hunks(
25529 false,
25530 vec![hunk_range.start..hunk_range.start],
25531 cx,
25532 );
25533 });
25534 }
25535 })
25536 })
25537 .child(
25538 Button::new(("restore", row as u64), "Restore")
25539 .tooltip({
25540 let focus_handle = editor.focus_handle(cx);
25541 move |_window, cx| {
25542 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25543 }
25544 })
25545 .on_click({
25546 let editor = editor.clone();
25547 move |_event, window, cx| {
25548 editor.update(cx, |editor, cx| {
25549 let snapshot = editor.snapshot(window, cx);
25550 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25551 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25552 });
25553 }
25554 })
25555 .disabled(is_created_file),
25556 )
25557 .when(
25558 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25559 |el| {
25560 el.child(
25561 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25562 .shape(IconButtonShape::Square)
25563 .icon_size(IconSize::Small)
25564 // .disabled(!has_multiple_hunks)
25565 .tooltip({
25566 let focus_handle = editor.focus_handle(cx);
25567 move |_window, cx| {
25568 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25569 }
25570 })
25571 .on_click({
25572 let editor = editor.clone();
25573 move |_event, window, cx| {
25574 editor.update(cx, |editor, cx| {
25575 let snapshot = editor.snapshot(window, cx);
25576 let position =
25577 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25578 editor.go_to_hunk_before_or_after_position(
25579 &snapshot,
25580 position,
25581 Direction::Next,
25582 window,
25583 cx,
25584 );
25585 editor.expand_selected_diff_hunks(cx);
25586 });
25587 }
25588 }),
25589 )
25590 .child(
25591 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25592 .shape(IconButtonShape::Square)
25593 .icon_size(IconSize::Small)
25594 // .disabled(!has_multiple_hunks)
25595 .tooltip({
25596 let focus_handle = editor.focus_handle(cx);
25597 move |_window, cx| {
25598 Tooltip::for_action_in(
25599 "Previous Hunk",
25600 &GoToPreviousHunk,
25601 &focus_handle,
25602 cx,
25603 )
25604 }
25605 })
25606 .on_click({
25607 let editor = editor.clone();
25608 move |_event, window, cx| {
25609 editor.update(cx, |editor, cx| {
25610 let snapshot = editor.snapshot(window, cx);
25611 let point =
25612 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25613 editor.go_to_hunk_before_or_after_position(
25614 &snapshot,
25615 point,
25616 Direction::Prev,
25617 window,
25618 cx,
25619 );
25620 editor.expand_selected_diff_hunks(cx);
25621 });
25622 }
25623 }),
25624 )
25625 },
25626 )
25627 .into_any_element()
25628}
25629
25630pub fn multibuffer_context_lines(cx: &App) -> u32 {
25631 EditorSettings::try_get(cx)
25632 .map(|settings| settings.excerpt_context_lines)
25633 .unwrap_or(2)
25634 .min(32)
25635}