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_types::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, bail};
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_types::{EditPredictionDelegate, EditPredictionDelegateHandle};
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, PressureStage,
111 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
112 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
113 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
114 size,
115};
116use hover_links::{HoverLink, HoveredLinkState, find_file};
117use hover_popover::{HoverState, hide_hover};
118use indent_guides::ActiveIndentGuidesState;
119use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
120use itertools::{Either, Itertools};
121use language::{
122 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
123 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
124 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
125 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
126 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
127 language_settings::{
128 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
129 all_language_settings, language_settings,
130 },
131 point_from_lsp, point_to_lsp, text_diff_with_options,
132};
133use linked_editing_ranges::refresh_linked_ranges;
134use lsp::{
135 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
136 LanguageServerId,
137};
138use lsp_colors::LspColorData;
139use markdown::Markdown;
140use mouse_context_menu::MouseContextMenu;
141use movement::TextLayoutDetails;
142use multi_buffer::{
143 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
150 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
151 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::GitStoreEvent,
160 lsp_store::{
161 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
162 OpenLspBufferHandle,
163 },
164 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
165};
166use rand::seq::SliceRandom;
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
169use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
170use serde::{Deserialize, Serialize};
171use settings::{
172 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
173 update_settings_file,
174};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::{Any, TypeId},
179 borrow::Cow,
180 cell::{OnceCell, RefCell},
181 cmp::{self, Ordering, Reverse},
182 collections::hash_map,
183 iter::{self, Peekable},
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
194use theme::{
195 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::{
213 code_context_menus::CompletionsMenuSource,
214 editor_settings::MultiCursorModifier,
215 hover_links::{find_url, find_url_from_range},
216 inlays::{
217 InlineValueCache,
218 inlay_hints::{LspInlayHintData, inlay_hint_settings},
219 },
220 scroll::{ScrollOffset, ScrollPixelOffset},
221 selections_collection::resolve_selections_wrapping_blocks,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
228const MAX_LINE_LEN: usize = 1024;
229const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
230const MAX_SELECTION_HISTORY_LEN: usize = 1024;
231pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
232#[doc(hidden)]
233pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
234pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
235
236pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
239pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258enum ReportEditorEvent {
259 Saved { auto_saved: bool },
260 EditorOpened,
261 Closed,
262}
263
264impl ReportEditorEvent {
265 pub fn event_type(&self) -> &'static str {
266 match self {
267 Self::Saved { .. } => "Editor Saved",
268 Self::EditorOpened => "Editor Opened",
269 Self::Closed => "Editor Closed",
270 }
271 }
272}
273
274pub enum ActiveDebugLine {}
275pub enum DebugStackFrameLine {}
276enum DocumentHighlightRead {}
277enum DocumentHighlightWrite {}
278enum InputComposition {}
279pub enum PendingInput {}
280enum SelectedTextHighlight {}
281
282pub enum ConflictsOuter {}
283pub enum ConflictsOurs {}
284pub enum ConflictsTheirs {}
285pub enum ConflictsOursMarker {}
286pub enum ConflictsTheirsMarker {}
287
288pub struct HunkAddedColor;
289pub struct HunkRemovedColor;
290
291#[derive(Debug, Copy, Clone, PartialEq, Eq)]
292pub enum Navigated {
293 Yes,
294 No,
295}
296
297impl Navigated {
298 pub fn from_bool(yes: bool) -> Navigated {
299 if yes { Navigated::Yes } else { Navigated::No }
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Eq)]
304enum DisplayDiffHunk {
305 Folded {
306 display_row: DisplayRow,
307 },
308 Unfolded {
309 is_created_file: bool,
310 diff_base_byte_range: Range<usize>,
311 display_row_range: Range<DisplayRow>,
312 multi_buffer_range: Range<Anchor>,
313 status: DiffHunkStatus,
314 word_diffs: Vec<Range<MultiBufferOffset>>,
315 },
316}
317
318pub enum HideMouseCursorOrigin {
319 TypingAction,
320 MovementAction,
321}
322
323pub fn init(cx: &mut App) {
324 cx.set_global(GlobalBlameRenderer(Arc::new(())));
325
326 workspace::register_project_item::<Editor>(cx);
327 workspace::FollowableViewRegistry::register::<Editor>(cx);
328 workspace::register_serializable_item::<Editor>(cx);
329
330 cx.observe_new(
331 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
332 workspace.register_action(Editor::new_file);
333 workspace.register_action(Editor::new_file_split);
334 workspace.register_action(Editor::new_file_vertical);
335 workspace.register_action(Editor::new_file_horizontal);
336 workspace.register_action(Editor::cancel_language_server_work);
337 workspace.register_action(Editor::toggle_focus);
338 },
339 )
340 .detach();
341
342 cx.on_action(move |_: &workspace::NewFile, cx| {
343 let app_state = workspace::AppState::global(cx);
344 if let Some(app_state) = app_state.upgrade() {
345 workspace::open_new(
346 Default::default(),
347 app_state,
348 cx,
349 |workspace, window, cx| {
350 Editor::new_file(workspace, &Default::default(), window, cx)
351 },
352 )
353 .detach();
354 }
355 })
356 .on_action(move |_: &workspace::NewWindow, cx| {
357 let app_state = workspace::AppState::global(cx);
358 if let Some(app_state) = app_state.upgrade() {
359 workspace::open_new(
360 Default::default(),
361 app_state,
362 cx,
363 |workspace, window, cx| {
364 cx.activate(true);
365 Editor::new_file(workspace, &Default::default(), window, cx)
366 },
367 )
368 .detach();
369 }
370 });
371}
372
373pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
374 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
375}
376
377pub trait DiagnosticRenderer {
378 fn render_group(
379 &self,
380 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
381 buffer_id: BufferId,
382 snapshot: EditorSnapshot,
383 editor: WeakEntity<Editor>,
384 language_registry: Option<Arc<LanguageRegistry>>,
385 cx: &mut App,
386 ) -> Vec<BlockProperties<Anchor>>;
387
388 fn render_hover(
389 &self,
390 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
391 range: Range<Point>,
392 buffer_id: BufferId,
393 language_registry: Option<Arc<LanguageRegistry>>,
394 cx: &mut App,
395 ) -> Option<Entity<markdown::Markdown>>;
396
397 fn open_link(
398 &self,
399 editor: &mut Editor,
400 link: SharedString,
401 window: &mut Window,
402 cx: &mut Context<Editor>,
403 );
404}
405
406pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
407
408impl GlobalDiagnosticRenderer {
409 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
410 cx.try_global::<Self>().map(|g| g.0.clone())
411 }
412}
413
414impl gpui::Global for GlobalDiagnosticRenderer {}
415pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
416 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
417}
418
419pub struct SearchWithinRange;
420
421trait InvalidationRegion {
422 fn ranges(&self) -> &[Range<Anchor>];
423}
424
425#[derive(Clone, Debug, PartialEq)]
426pub enum SelectPhase {
427 Begin {
428 position: DisplayPoint,
429 add: bool,
430 click_count: usize,
431 },
432 BeginColumnar {
433 position: DisplayPoint,
434 reset: bool,
435 mode: ColumnarMode,
436 goal_column: u32,
437 },
438 Extend {
439 position: DisplayPoint,
440 click_count: usize,
441 },
442 Update {
443 position: DisplayPoint,
444 goal_column: u32,
445 scroll_delta: gpui::Point<f32>,
446 },
447 End,
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum ColumnarMode {
452 FromMouse,
453 FromSelection,
454}
455
456#[derive(Clone, Debug)]
457pub enum SelectMode {
458 Character,
459 Word(Range<Anchor>),
460 Line(Range<Anchor>),
461 All,
462}
463
464#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
465pub enum SizingBehavior {
466 /// The editor will layout itself using `size_full` and will include the vertical
467 /// scroll margin as requested by user settings.
468 #[default]
469 Default,
470 /// The editor will layout itself using `size_full`, but will not have any
471 /// vertical overscroll.
472 ExcludeOverscrollMargin,
473 /// The editor will request a vertical size according to its content and will be
474 /// layouted without a vertical scroll margin.
475 SizeByContent,
476}
477
478#[derive(Clone, PartialEq, Eq, Debug)]
479pub enum EditorMode {
480 SingleLine,
481 AutoHeight {
482 min_lines: usize,
483 max_lines: Option<usize>,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// Determines the sizing behavior for this editor
491 sizing_behavior: SizingBehavior,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sizing_behavior: SizingBehavior::Default,
504 }
505 }
506
507 #[inline]
508 pub fn is_full(&self) -> bool {
509 matches!(self, Self::Full { .. })
510 }
511
512 #[inline]
513 pub fn is_single_line(&self) -> bool {
514 matches!(self, Self::SingleLine { .. })
515 }
516
517 #[inline]
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub border: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub edit_prediction_styles: EditPredictionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 border: Hsla::default(),
560 local_player: PlayerColor::default(),
561 text: TextStyle::default(),
562 scrollbar_width: Pixels::default(),
563 syntax: Default::default(),
564 // HACK: Status colors don't have a real default.
565 // We should look into removing the status colors from the editor
566 // style and retrieve them directly from the theme.
567 status: StatusColors::dark(),
568 inlay_hints_style: HighlightStyle::default(),
569 edit_prediction_styles: EditPredictionStyles {
570 insertion: HighlightStyle::default(),
571 whitespace: HighlightStyle::default(),
572 },
573 unnecessary_code_fade: Default::default(),
574 show_underlines: true,
575 }
576 }
577}
578
579pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
580 let show_background = language_settings::language_settings(None, None, cx)
581 .inlay_hints
582 .show_background;
583
584 let mut style = cx.theme().syntax().get("hint");
585
586 if style.color.is_none() {
587 style.color = Some(cx.theme().status().hint);
588 }
589
590 if !show_background {
591 style.background_color = None;
592 return style;
593 }
594
595 if style.background_color.is_none() {
596 style.background_color = Some(cx.theme().status().hint_background);
597 }
598
599 style
600}
601
602pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
603 EditPredictionStyles {
604 insertion: HighlightStyle {
605 color: Some(cx.theme().status().predictive),
606 ..HighlightStyle::default()
607 },
608 whitespace: HighlightStyle {
609 background_color: Some(cx.theme().status().created_background),
610 ..HighlightStyle::default()
611 },
612 }
613}
614
615type CompletionId = usize;
616
617pub(crate) enum EditDisplayMode {
618 TabAccept,
619 DiffPopover,
620 Inline,
621}
622
623enum EditPrediction {
624 Edit {
625 edits: Vec<(Range<Anchor>, Arc<str>)>,
626 edit_preview: Option<EditPreview>,
627 display_mode: EditDisplayMode,
628 snapshot: BufferSnapshot,
629 },
630 /// Move to a specific location in the active editor
631 MoveWithin {
632 target: Anchor,
633 snapshot: BufferSnapshot,
634 },
635 /// Move to a specific location in a different editor (not the active one)
636 MoveOutside {
637 target: language::Anchor,
638 snapshot: BufferSnapshot,
639 },
640}
641
642struct EditPredictionState {
643 inlay_ids: Vec<InlayId>,
644 completion: EditPrediction,
645 completion_id: Option<SharedString>,
646 invalidation_range: Option<Range<Anchor>>,
647}
648
649enum EditPredictionSettings {
650 Disabled,
651 Enabled {
652 show_in_menu: bool,
653 preview_requires_modifier: bool,
654 },
655}
656
657enum EditPredictionHighlight {}
658
659#[derive(Debug, Clone)]
660struct InlineDiagnostic {
661 message: SharedString,
662 group_id: usize,
663 is_primary: bool,
664 start: Point,
665 severity: lsp::DiagnosticSeverity,
666}
667
668pub enum MenuEditPredictionsPolicy {
669 Never,
670 ByProvider,
671}
672
673pub enum EditPredictionPreview {
674 /// Modifier is not pressed
675 Inactive { released_too_fast: bool },
676 /// Modifier pressed
677 Active {
678 since: Instant,
679 previous_scroll_position: Option<ScrollAnchor>,
680 },
681}
682
683impl EditPredictionPreview {
684 pub fn released_too_fast(&self) -> bool {
685 match self {
686 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
687 EditPredictionPreview::Active { .. } => false,
688 }
689 }
690
691 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
692 if let EditPredictionPreview::Active {
693 previous_scroll_position,
694 ..
695 } = self
696 {
697 *previous_scroll_position = scroll_position;
698 }
699 }
700}
701
702pub struct ContextMenuOptions {
703 pub min_entries_visible: usize,
704 pub max_entries_visible: usize,
705 pub placement: Option<ContextMenuPlacement>,
706}
707
708#[derive(Debug, Clone, PartialEq, Eq)]
709pub enum ContextMenuPlacement {
710 Above,
711 Below,
712}
713
714#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
715struct EditorActionId(usize);
716
717impl EditorActionId {
718 pub fn post_inc(&mut self) -> Self {
719 let answer = self.0;
720
721 *self = Self(answer + 1);
722
723 Self(answer)
724 }
725}
726
727// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
728// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
729
730type BackgroundHighlight = (
731 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
732 Arc<[Range<Anchor>]>,
733);
734type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
735
736#[derive(Default)]
737struct ScrollbarMarkerState {
738 scrollbar_size: Size<Pixels>,
739 dirty: bool,
740 markers: Arc<[PaintQuad]>,
741 pending_refresh: Option<Task<Result<()>>>,
742}
743
744impl ScrollbarMarkerState {
745 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
746 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
747 }
748}
749
750#[derive(Clone, Copy, PartialEq, Eq)]
751pub enum MinimapVisibility {
752 Disabled,
753 Enabled {
754 /// The configuration currently present in the users settings.
755 setting_configuration: bool,
756 /// Whether to override the currently set visibility from the users setting.
757 toggle_override: bool,
758 },
759}
760
761impl MinimapVisibility {
762 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
763 if mode.is_full() {
764 Self::Enabled {
765 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
766 toggle_override: false,
767 }
768 } else {
769 Self::Disabled
770 }
771 }
772
773 fn hidden(&self) -> Self {
774 match *self {
775 Self::Enabled {
776 setting_configuration,
777 ..
778 } => Self::Enabled {
779 setting_configuration,
780 toggle_override: setting_configuration,
781 },
782 Self::Disabled => Self::Disabled,
783 }
784 }
785
786 fn disabled(&self) -> bool {
787 matches!(*self, Self::Disabled)
788 }
789
790 fn settings_visibility(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 ..
795 } => setting_configuration,
796 _ => false,
797 }
798 }
799
800 fn visible(&self) -> bool {
801 match *self {
802 Self::Enabled {
803 setting_configuration,
804 toggle_override,
805 } => setting_configuration ^ toggle_override,
806 _ => false,
807 }
808 }
809
810 fn toggle_visibility(&self) -> Self {
811 match *self {
812 Self::Enabled {
813 toggle_override,
814 setting_configuration,
815 } => Self::Enabled {
816 setting_configuration,
817 toggle_override: !toggle_override,
818 },
819 Self::Disabled => Self::Disabled,
820 }
821 }
822}
823
824#[derive(Debug, Clone, Copy, PartialEq, Eq)]
825pub enum BufferSerialization {
826 All,
827 NonDirtyBuffers,
828}
829
830impl BufferSerialization {
831 fn new(restore_unsaved_buffers: bool) -> Self {
832 if restore_unsaved_buffers {
833 Self::All
834 } else {
835 Self::NonDirtyBuffers
836 }
837 }
838}
839
840#[derive(Clone, Debug)]
841struct RunnableTasks {
842 templates: Vec<(TaskSourceKind, TaskTemplate)>,
843 offset: multi_buffer::Anchor,
844 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
845 column: u32,
846 // Values of all named captures, including those starting with '_'
847 extra_variables: HashMap<String, String>,
848 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
849 context_range: Range<BufferOffset>,
850}
851
852impl RunnableTasks {
853 fn resolve<'a>(
854 &'a self,
855 cx: &'a task::TaskContext,
856 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
857 self.templates.iter().filter_map(|(kind, template)| {
858 template
859 .resolve_task(&kind.to_id_base(), cx)
860 .map(|task| (kind.clone(), task))
861 })
862 }
863}
864
865#[derive(Clone)]
866pub struct ResolvedTasks {
867 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
868 position: Anchor,
869}
870
871/// Addons allow storing per-editor state in other crates (e.g. Vim)
872pub trait Addon: 'static {
873 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
874
875 fn render_buffer_header_controls(
876 &self,
877 _: &ExcerptInfo,
878 _: &Window,
879 _: &App,
880 ) -> Option<AnyElement> {
881 None
882 }
883
884 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
885 None
886 }
887
888 fn to_any(&self) -> &dyn std::any::Any;
889
890 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
891 None
892 }
893}
894
895struct ChangeLocation {
896 current: Option<Vec<Anchor>>,
897 original: Vec<Anchor>,
898}
899impl ChangeLocation {
900 fn locations(&self) -> &[Anchor] {
901 self.current.as_ref().unwrap_or(&self.original)
902 }
903}
904
905/// A set of caret positions, registered when the editor was edited.
906pub struct ChangeList {
907 changes: Vec<ChangeLocation>,
908 /// Currently "selected" change.
909 position: Option<usize>,
910}
911
912impl ChangeList {
913 pub fn new() -> Self {
914 Self {
915 changes: Vec::new(),
916 position: None,
917 }
918 }
919
920 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
921 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
922 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
923 if self.changes.is_empty() {
924 return None;
925 }
926
927 let prev = self.position.unwrap_or(self.changes.len());
928 let next = if direction == Direction::Prev {
929 prev.saturating_sub(count)
930 } else {
931 (prev + count).min(self.changes.len() - 1)
932 };
933 self.position = Some(next);
934 self.changes.get(next).map(|change| change.locations())
935 }
936
937 /// Adds a new change to the list, resetting the change list position.
938 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
939 self.position.take();
940 if let Some(last) = self.changes.last_mut()
941 && group
942 {
943 last.current = Some(new_positions)
944 } else {
945 self.changes.push(ChangeLocation {
946 original: new_positions,
947 current: None,
948 });
949 }
950 }
951
952 pub fn last(&self) -> Option<&[Anchor]> {
953 self.changes.last().map(|change| change.locations())
954 }
955
956 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
957 self.changes.last().map(|change| change.original.as_slice())
958 }
959
960 pub fn invert_last_group(&mut self) {
961 if let Some(last) = self.changes.last_mut()
962 && let Some(current) = last.current.as_mut()
963 {
964 mem::swap(&mut last.original, current);
965 }
966 }
967}
968
969#[derive(Clone)]
970struct InlineBlamePopoverState {
971 scroll_handle: ScrollHandle,
972 commit_message: Option<ParsedCommitMessage>,
973 markdown: Entity<Markdown>,
974}
975
976struct InlineBlamePopover {
977 position: gpui::Point<Pixels>,
978 hide_task: Option<Task<()>>,
979 popover_bounds: Option<Bounds<Pixels>>,
980 popover_state: InlineBlamePopoverState,
981 keyboard_grace: bool,
982}
983
984enum SelectionDragState {
985 /// State when no drag related activity is detected.
986 None,
987 /// State when the mouse is down on a selection that is about to be dragged.
988 ReadyToDrag {
989 selection: Selection<Anchor>,
990 click_position: gpui::Point<Pixels>,
991 mouse_down_time: Instant,
992 },
993 /// State when the mouse is dragging the selection in the editor.
994 Dragging {
995 selection: Selection<Anchor>,
996 drop_cursor: Selection<Anchor>,
997 hide_drop_cursor: bool,
998 },
999}
1000
1001enum ColumnarSelectionState {
1002 FromMouse {
1003 selection_tail: Anchor,
1004 display_point: Option<DisplayPoint>,
1005 },
1006 FromSelection {
1007 selection_tail: Anchor,
1008 },
1009}
1010
1011/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1012/// a breakpoint on them.
1013#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1014struct PhantomBreakpointIndicator {
1015 display_row: DisplayRow,
1016 /// There's a small debounce between hovering over the line and showing the indicator.
1017 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1018 is_active: bool,
1019 collides_with_existing_breakpoint: bool,
1020}
1021
1022/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1023///
1024/// See the [module level documentation](self) for more information.
1025pub struct Editor {
1026 focus_handle: FocusHandle,
1027 last_focused_descendant: Option<WeakFocusHandle>,
1028 /// The text buffer being edited
1029 buffer: Entity<MultiBuffer>,
1030 /// Map of how text in the buffer should be displayed.
1031 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1032 pub display_map: Entity<DisplayMap>,
1033 placeholder_display_map: Option<Entity<DisplayMap>>,
1034 pub selections: SelectionsCollection,
1035 pub scroll_manager: ScrollManager,
1036 /// When inline assist editors are linked, they all render cursors because
1037 /// typing enters text into each of them, even the ones that aren't focused.
1038 pub(crate) show_cursor_when_unfocused: bool,
1039 columnar_selection_state: Option<ColumnarSelectionState>,
1040 add_selections_state: Option<AddSelectionsState>,
1041 select_next_state: Option<SelectNextState>,
1042 select_prev_state: Option<SelectNextState>,
1043 selection_history: SelectionHistory,
1044 defer_selection_effects: bool,
1045 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1046 autoclose_regions: Vec<AutocloseRegion>,
1047 snippet_stack: InvalidationStack<SnippetState>,
1048 select_syntax_node_history: SelectSyntaxNodeHistory,
1049 ime_transaction: Option<TransactionId>,
1050 pub diagnostics_max_severity: DiagnosticSeverity,
1051 active_diagnostics: ActiveDiagnostic,
1052 show_inline_diagnostics: bool,
1053 inline_diagnostics_update: Task<()>,
1054 inline_diagnostics_enabled: bool,
1055 diagnostics_enabled: bool,
1056 word_completions_enabled: bool,
1057 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1058 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1059 hard_wrap: Option<usize>,
1060 project: Option<Entity<Project>>,
1061 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1062 completion_provider: Option<Rc<dyn CompletionProvider>>,
1063 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1064 blink_manager: Entity<BlinkManager>,
1065 show_cursor_names: bool,
1066 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1067 pub show_local_selections: bool,
1068 mode: EditorMode,
1069 show_breadcrumbs: bool,
1070 show_gutter: bool,
1071 show_scrollbars: ScrollbarAxes,
1072 minimap_visibility: MinimapVisibility,
1073 offset_content: bool,
1074 disable_expand_excerpt_buttons: bool,
1075 show_line_numbers: Option<bool>,
1076 use_relative_line_numbers: Option<bool>,
1077 show_git_diff_gutter: Option<bool>,
1078 show_code_actions: Option<bool>,
1079 show_runnables: Option<bool>,
1080 show_breakpoints: Option<bool>,
1081 show_wrap_guides: Option<bool>,
1082 show_indent_guides: Option<bool>,
1083 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1084 highlight_order: usize,
1085 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1086 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1087 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1088 scrollbar_marker_state: ScrollbarMarkerState,
1089 active_indent_guides_state: ActiveIndentGuidesState,
1090 nav_history: Option<ItemNavHistory>,
1091 context_menu: RefCell<Option<CodeContextMenu>>,
1092 context_menu_options: Option<ContextMenuOptions>,
1093 mouse_context_menu: Option<MouseContextMenu>,
1094 completion_tasks: Vec<(CompletionId, Task<()>)>,
1095 inline_blame_popover: Option<InlineBlamePopover>,
1096 inline_blame_popover_show_task: Option<Task<()>>,
1097 signature_help_state: SignatureHelpState,
1098 auto_signature_help: Option<bool>,
1099 find_all_references_task_sources: Vec<Anchor>,
1100 next_completion_id: CompletionId,
1101 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1102 code_actions_task: Option<Task<Result<()>>>,
1103 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1105 document_highlights_task: Option<Task<()>>,
1106 linked_editing_range_task: Option<Task<Option<()>>>,
1107 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1108 pending_rename: Option<RenameState>,
1109 searchable: bool,
1110 cursor_shape: CursorShape,
1111 /// Whether the cursor is offset one character to the left when something is
1112 /// selected (needed for vim visual mode)
1113 cursor_offset_on_selection: bool,
1114 current_line_highlight: Option<CurrentLineHighlight>,
1115 pub collapse_matches: bool,
1116 autoindent_mode: Option<AutoindentMode>,
1117 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1118 input_enabled: bool,
1119 use_modal_editing: bool,
1120 read_only: bool,
1121 leader_id: Option<CollaboratorId>,
1122 remote_id: Option<ViewId>,
1123 pub hover_state: HoverState,
1124 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1125 prev_pressure_stage: Option<PressureStage>,
1126 gutter_hovered: bool,
1127 hovered_link_state: Option<HoveredLinkState>,
1128 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1129 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1130 active_edit_prediction: Option<EditPredictionState>,
1131 /// Used to prevent flickering as the user types while the menu is open
1132 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1133 edit_prediction_settings: EditPredictionSettings,
1134 edit_predictions_hidden_for_vim_mode: bool,
1135 show_edit_predictions_override: Option<bool>,
1136 show_completions_on_input_override: Option<bool>,
1137 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1138 edit_prediction_preview: EditPredictionPreview,
1139 edit_prediction_indent_conflict: bool,
1140 edit_prediction_requires_modifier_in_indent_conflict: bool,
1141 next_inlay_id: usize,
1142 next_color_inlay_id: usize,
1143 _subscriptions: Vec<Subscription>,
1144 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1145 gutter_dimensions: GutterDimensions,
1146 style: Option<EditorStyle>,
1147 text_style_refinement: Option<TextStyleRefinement>,
1148 next_editor_action_id: EditorActionId,
1149 editor_actions: Rc<
1150 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1151 >,
1152 use_autoclose: bool,
1153 use_auto_surround: bool,
1154 auto_replace_emoji_shortcode: bool,
1155 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1156 show_git_blame_gutter: bool,
1157 show_git_blame_inline: bool,
1158 show_git_blame_inline_delay_task: Option<Task<()>>,
1159 git_blame_inline_enabled: bool,
1160 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1161 buffer_serialization: Option<BufferSerialization>,
1162 show_selection_menu: Option<bool>,
1163 blame: Option<Entity<GitBlame>>,
1164 blame_subscription: Option<Subscription>,
1165 custom_context_menu: Option<
1166 Box<
1167 dyn 'static
1168 + Fn(
1169 &mut Self,
1170 DisplayPoint,
1171 &mut Window,
1172 &mut Context<Self>,
1173 ) -> Option<Entity<ui::ContextMenu>>,
1174 >,
1175 >,
1176 last_bounds: Option<Bounds<Pixels>>,
1177 last_position_map: Option<Rc<PositionMap>>,
1178 expect_bounds_change: Option<Bounds<Pixels>>,
1179 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1180 tasks_update_task: Option<Task<()>>,
1181 breakpoint_store: Option<Entity<BreakpointStore>>,
1182 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1183 hovered_diff_hunk_row: Option<DisplayRow>,
1184 pull_diagnostics_task: Task<()>,
1185 pull_diagnostics_background_task: Task<()>,
1186 in_project_search: bool,
1187 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1188 breadcrumb_header: Option<String>,
1189 focused_block: Option<FocusedBlock>,
1190 next_scroll_position: NextScrollCursorCenterTopBottom,
1191 addons: HashMap<TypeId, Box<dyn Addon>>,
1192 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1193 load_diff_task: Option<Shared<Task<()>>>,
1194 /// Whether we are temporarily displaying a diff other than git's
1195 temporary_diff_override: bool,
1196 selection_mark_mode: bool,
1197 toggle_fold_multiple_buffers: Task<()>,
1198 _scroll_cursor_center_top_bottom_task: Task<()>,
1199 serialize_selections: Task<()>,
1200 serialize_folds: Task<()>,
1201 mouse_cursor_hidden: bool,
1202 minimap: Option<Entity<Self>>,
1203 hide_mouse_mode: HideMouseMode,
1204 pub change_list: ChangeList,
1205 inline_value_cache: InlineValueCache,
1206
1207 selection_drag_state: SelectionDragState,
1208 colors: Option<LspColorData>,
1209 post_scroll_update: Task<()>,
1210 refresh_colors_task: Task<()>,
1211 inlay_hints: Option<LspInlayHintData>,
1212 folding_newlines: Task<()>,
1213 select_next_is_case_sensitive: Option<bool>,
1214 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1215 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1216 accent_data: Option<AccentData>,
1217 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1218 use_base_text_line_numbers: bool,
1219}
1220
1221#[derive(Debug, PartialEq)]
1222struct AccentData {
1223 colors: AccentColors,
1224 overrides: Vec<SharedString>,
1225}
1226
1227fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1228 if debounce_ms > 0 {
1229 Some(Duration::from_millis(debounce_ms))
1230 } else {
1231 None
1232 }
1233}
1234
1235#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1236enum NextScrollCursorCenterTopBottom {
1237 #[default]
1238 Center,
1239 Top,
1240 Bottom,
1241}
1242
1243impl NextScrollCursorCenterTopBottom {
1244 fn next(&self) -> Self {
1245 match self {
1246 Self::Center => Self::Top,
1247 Self::Top => Self::Bottom,
1248 Self::Bottom => Self::Center,
1249 }
1250 }
1251}
1252
1253#[derive(Clone)]
1254pub struct EditorSnapshot {
1255 pub mode: EditorMode,
1256 show_gutter: bool,
1257 offset_content: bool,
1258 show_line_numbers: Option<bool>,
1259 show_git_diff_gutter: Option<bool>,
1260 show_code_actions: Option<bool>,
1261 show_runnables: Option<bool>,
1262 show_breakpoints: Option<bool>,
1263 git_blame_gutter_max_author_length: Option<usize>,
1264 pub display_snapshot: DisplaySnapshot,
1265 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1266 is_focused: bool,
1267 scroll_anchor: ScrollAnchor,
1268 ongoing_scroll: OngoingScroll,
1269 current_line_highlight: CurrentLineHighlight,
1270 gutter_hovered: bool,
1271}
1272
1273#[derive(Default, Debug, Clone, Copy)]
1274pub struct GutterDimensions {
1275 pub left_padding: Pixels,
1276 pub right_padding: Pixels,
1277 pub width: Pixels,
1278 pub margin: Pixels,
1279 pub git_blame_entries_width: Option<Pixels>,
1280}
1281
1282impl GutterDimensions {
1283 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1284 Self {
1285 margin: Self::default_gutter_margin(font_id, font_size, cx),
1286 ..Default::default()
1287 }
1288 }
1289
1290 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1291 -cx.text_system().descent(font_id, font_size)
1292 }
1293 /// The full width of the space taken up by the gutter.
1294 pub fn full_width(&self) -> Pixels {
1295 self.margin + self.width
1296 }
1297
1298 /// The width of the space reserved for the fold indicators,
1299 /// use alongside 'justify_end' and `gutter_width` to
1300 /// right align content with the line numbers
1301 pub fn fold_area_width(&self) -> Pixels {
1302 self.margin + self.right_padding
1303 }
1304}
1305
1306struct CharacterDimensions {
1307 em_width: Pixels,
1308 em_advance: Pixels,
1309 line_height: Pixels,
1310}
1311
1312#[derive(Debug)]
1313pub struct RemoteSelection {
1314 pub replica_id: ReplicaId,
1315 pub selection: Selection<Anchor>,
1316 pub cursor_shape: CursorShape,
1317 pub collaborator_id: CollaboratorId,
1318 pub line_mode: bool,
1319 pub user_name: Option<SharedString>,
1320 pub color: PlayerColor,
1321}
1322
1323#[derive(Clone, Debug)]
1324struct SelectionHistoryEntry {
1325 selections: Arc<[Selection<Anchor>]>,
1326 select_next_state: Option<SelectNextState>,
1327 select_prev_state: Option<SelectNextState>,
1328 add_selections_state: Option<AddSelectionsState>,
1329}
1330
1331#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1332enum SelectionHistoryMode {
1333 #[default]
1334 Normal,
1335 Undoing,
1336 Redoing,
1337 Skipping,
1338}
1339
1340#[derive(Clone, PartialEq, Eq, Hash)]
1341struct HoveredCursor {
1342 replica_id: ReplicaId,
1343 selection_id: usize,
1344}
1345
1346#[derive(Debug)]
1347/// SelectionEffects controls the side-effects of updating the selection.
1348///
1349/// The default behaviour does "what you mostly want":
1350/// - it pushes to the nav history if the cursor moved by >10 lines
1351/// - it re-triggers completion requests
1352/// - it scrolls to fit
1353///
1354/// You might want to modify these behaviours. For example when doing a "jump"
1355/// like go to definition, we always want to add to nav history; but when scrolling
1356/// in vim mode we never do.
1357///
1358/// Similarly, you might want to disable scrolling if you don't want the viewport to
1359/// move.
1360#[derive(Clone)]
1361pub struct SelectionEffects {
1362 nav_history: Option<bool>,
1363 completions: bool,
1364 scroll: Option<Autoscroll>,
1365}
1366
1367impl Default for SelectionEffects {
1368 fn default() -> Self {
1369 Self {
1370 nav_history: None,
1371 completions: true,
1372 scroll: Some(Autoscroll::fit()),
1373 }
1374 }
1375}
1376impl SelectionEffects {
1377 pub fn scroll(scroll: Autoscroll) -> Self {
1378 Self {
1379 scroll: Some(scroll),
1380 ..Default::default()
1381 }
1382 }
1383
1384 pub fn no_scroll() -> Self {
1385 Self {
1386 scroll: None,
1387 ..Default::default()
1388 }
1389 }
1390
1391 pub fn completions(self, completions: bool) -> Self {
1392 Self {
1393 completions,
1394 ..self
1395 }
1396 }
1397
1398 pub fn nav_history(self, nav_history: bool) -> Self {
1399 Self {
1400 nav_history: Some(nav_history),
1401 ..self
1402 }
1403 }
1404}
1405
1406struct DeferredSelectionEffectsState {
1407 changed: bool,
1408 effects: SelectionEffects,
1409 old_cursor_position: Anchor,
1410 history_entry: SelectionHistoryEntry,
1411}
1412
1413#[derive(Default)]
1414struct SelectionHistory {
1415 #[allow(clippy::type_complexity)]
1416 selections_by_transaction:
1417 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1418 mode: SelectionHistoryMode,
1419 undo_stack: VecDeque<SelectionHistoryEntry>,
1420 redo_stack: VecDeque<SelectionHistoryEntry>,
1421}
1422
1423impl SelectionHistory {
1424 #[track_caller]
1425 fn insert_transaction(
1426 &mut self,
1427 transaction_id: TransactionId,
1428 selections: Arc<[Selection<Anchor>]>,
1429 ) {
1430 if selections.is_empty() {
1431 log::error!(
1432 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1433 std::panic::Location::caller()
1434 );
1435 return;
1436 }
1437 self.selections_by_transaction
1438 .insert(transaction_id, (selections, None));
1439 }
1440
1441 #[allow(clippy::type_complexity)]
1442 fn transaction(
1443 &self,
1444 transaction_id: TransactionId,
1445 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1446 self.selections_by_transaction.get(&transaction_id)
1447 }
1448
1449 #[allow(clippy::type_complexity)]
1450 fn transaction_mut(
1451 &mut self,
1452 transaction_id: TransactionId,
1453 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1454 self.selections_by_transaction.get_mut(&transaction_id)
1455 }
1456
1457 fn push(&mut self, entry: SelectionHistoryEntry) {
1458 if !entry.selections.is_empty() {
1459 match self.mode {
1460 SelectionHistoryMode::Normal => {
1461 self.push_undo(entry);
1462 self.redo_stack.clear();
1463 }
1464 SelectionHistoryMode::Undoing => self.push_redo(entry),
1465 SelectionHistoryMode::Redoing => self.push_undo(entry),
1466 SelectionHistoryMode::Skipping => {}
1467 }
1468 }
1469 }
1470
1471 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1472 if self
1473 .undo_stack
1474 .back()
1475 .is_none_or(|e| e.selections != entry.selections)
1476 {
1477 self.undo_stack.push_back(entry);
1478 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1479 self.undo_stack.pop_front();
1480 }
1481 }
1482 }
1483
1484 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1485 if self
1486 .redo_stack
1487 .back()
1488 .is_none_or(|e| e.selections != entry.selections)
1489 {
1490 self.redo_stack.push_back(entry);
1491 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1492 self.redo_stack.pop_front();
1493 }
1494 }
1495 }
1496}
1497
1498#[derive(Clone, Copy)]
1499pub struct RowHighlightOptions {
1500 pub autoscroll: bool,
1501 pub include_gutter: bool,
1502}
1503
1504impl Default for RowHighlightOptions {
1505 fn default() -> Self {
1506 Self {
1507 autoscroll: Default::default(),
1508 include_gutter: true,
1509 }
1510 }
1511}
1512
1513struct RowHighlight {
1514 index: usize,
1515 range: Range<Anchor>,
1516 color: Hsla,
1517 options: RowHighlightOptions,
1518 type_id: TypeId,
1519}
1520
1521#[derive(Clone, Debug)]
1522struct AddSelectionsState {
1523 groups: Vec<AddSelectionsGroup>,
1524}
1525
1526#[derive(Clone, Debug)]
1527struct AddSelectionsGroup {
1528 above: bool,
1529 stack: Vec<usize>,
1530}
1531
1532#[derive(Clone)]
1533struct SelectNextState {
1534 query: AhoCorasick,
1535 wordwise: bool,
1536 done: bool,
1537}
1538
1539impl std::fmt::Debug for SelectNextState {
1540 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1541 f.debug_struct(std::any::type_name::<Self>())
1542 .field("wordwise", &self.wordwise)
1543 .field("done", &self.done)
1544 .finish()
1545 }
1546}
1547
1548#[derive(Debug)]
1549struct AutocloseRegion {
1550 selection_id: usize,
1551 range: Range<Anchor>,
1552 pair: BracketPair,
1553}
1554
1555#[derive(Debug)]
1556struct SnippetState {
1557 ranges: Vec<Vec<Range<Anchor>>>,
1558 active_index: usize,
1559 choices: Vec<Option<Vec<String>>>,
1560}
1561
1562#[doc(hidden)]
1563pub struct RenameState {
1564 pub range: Range<Anchor>,
1565 pub old_name: Arc<str>,
1566 pub editor: Entity<Editor>,
1567 block_id: CustomBlockId,
1568}
1569
1570struct InvalidationStack<T>(Vec<T>);
1571
1572struct RegisteredEditPredictionDelegate {
1573 provider: Arc<dyn EditPredictionDelegateHandle>,
1574 _subscription: Subscription,
1575}
1576
1577#[derive(Debug, PartialEq, Eq)]
1578pub struct ActiveDiagnosticGroup {
1579 pub active_range: Range<Anchor>,
1580 pub active_message: String,
1581 pub group_id: usize,
1582 pub blocks: HashSet<CustomBlockId>,
1583}
1584
1585#[derive(Debug, PartialEq, Eq)]
1586
1587pub(crate) enum ActiveDiagnostic {
1588 None,
1589 All,
1590 Group(ActiveDiagnosticGroup),
1591}
1592
1593#[derive(Serialize, Deserialize, Clone, Debug)]
1594pub struct ClipboardSelection {
1595 /// The number of bytes in this selection.
1596 pub len: usize,
1597 /// Whether this was a full-line selection.
1598 pub is_entire_line: bool,
1599 /// The indentation of the first line when this content was originally copied.
1600 pub first_line_indent: u32,
1601 #[serde(default)]
1602 pub file_path: Option<PathBuf>,
1603 #[serde(default)]
1604 pub line_range: Option<RangeInclusive<u32>>,
1605}
1606
1607impl ClipboardSelection {
1608 pub fn for_buffer(
1609 len: usize,
1610 is_entire_line: bool,
1611 range: Range<Point>,
1612 buffer: &MultiBufferSnapshot,
1613 project: Option<&Entity<Project>>,
1614 cx: &App,
1615 ) -> Self {
1616 let first_line_indent = buffer
1617 .indent_size_for_line(MultiBufferRow(range.start.row))
1618 .len;
1619
1620 let file_path = util::maybe!({
1621 let project = project?.read(cx);
1622 let file = buffer.file_at(range.start)?;
1623 let project_path = ProjectPath {
1624 worktree_id: file.worktree_id(cx),
1625 path: file.path().clone(),
1626 };
1627 project.absolute_path(&project_path, cx)
1628 });
1629
1630 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1631
1632 Self {
1633 len,
1634 is_entire_line,
1635 first_line_indent,
1636 file_path,
1637 line_range,
1638 }
1639 }
1640}
1641
1642// selections, scroll behavior, was newest selection reversed
1643type SelectSyntaxNodeHistoryState = (
1644 Box<[Selection<MultiBufferOffset>]>,
1645 SelectSyntaxNodeScrollBehavior,
1646 bool,
1647);
1648
1649#[derive(Default)]
1650struct SelectSyntaxNodeHistory {
1651 stack: Vec<SelectSyntaxNodeHistoryState>,
1652 // disable temporarily to allow changing selections without losing the stack
1653 pub disable_clearing: bool,
1654}
1655
1656impl SelectSyntaxNodeHistory {
1657 pub fn try_clear(&mut self) {
1658 if !self.disable_clearing {
1659 self.stack.clear();
1660 }
1661 }
1662
1663 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1664 self.stack.push(selection);
1665 }
1666
1667 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1668 self.stack.pop()
1669 }
1670}
1671
1672enum SelectSyntaxNodeScrollBehavior {
1673 CursorTop,
1674 FitSelection,
1675 CursorBottom,
1676}
1677
1678#[derive(Debug)]
1679pub(crate) struct NavigationData {
1680 cursor_anchor: Anchor,
1681 cursor_position: Point,
1682 scroll_anchor: ScrollAnchor,
1683 scroll_top_row: u32,
1684}
1685
1686#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1687pub enum GotoDefinitionKind {
1688 Symbol,
1689 Declaration,
1690 Type,
1691 Implementation,
1692}
1693
1694pub enum FormatTarget {
1695 Buffers(HashSet<Entity<Buffer>>),
1696 Ranges(Vec<Range<MultiBufferPoint>>),
1697}
1698
1699pub(crate) struct FocusedBlock {
1700 id: BlockId,
1701 focus_handle: WeakFocusHandle,
1702}
1703
1704#[derive(Clone, Debug)]
1705enum JumpData {
1706 MultiBufferRow {
1707 row: MultiBufferRow,
1708 line_offset_from_top: u32,
1709 },
1710 MultiBufferPoint {
1711 excerpt_id: ExcerptId,
1712 position: Point,
1713 anchor: text::Anchor,
1714 line_offset_from_top: u32,
1715 },
1716}
1717
1718pub enum MultibufferSelectionMode {
1719 First,
1720 All,
1721}
1722
1723#[derive(Clone, Copy, Debug, Default)]
1724pub struct RewrapOptions {
1725 pub override_language_settings: bool,
1726 pub preserve_existing_whitespace: bool,
1727}
1728
1729impl Editor {
1730 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1731 let buffer = cx.new(|cx| Buffer::local("", cx));
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1734 }
1735
1736 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let buffer = cx.new(|cx| Buffer::local("", cx));
1738 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1739 Self::new(EditorMode::full(), buffer, None, window, cx)
1740 }
1741
1742 pub fn auto_height(
1743 min_lines: usize,
1744 max_lines: usize,
1745 window: &mut Window,
1746 cx: &mut Context<Self>,
1747 ) -> Self {
1748 let buffer = cx.new(|cx| Buffer::local("", cx));
1749 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1750 Self::new(
1751 EditorMode::AutoHeight {
1752 min_lines,
1753 max_lines: Some(max_lines),
1754 },
1755 buffer,
1756 None,
1757 window,
1758 cx,
1759 )
1760 }
1761
1762 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1763 /// The editor grows as tall as needed to fit its content.
1764 pub fn auto_height_unbounded(
1765 min_lines: usize,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 let buffer = cx.new(|cx| Buffer::local("", cx));
1770 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1771 Self::new(
1772 EditorMode::AutoHeight {
1773 min_lines,
1774 max_lines: None,
1775 },
1776 buffer,
1777 None,
1778 window,
1779 cx,
1780 )
1781 }
1782
1783 pub fn for_buffer(
1784 buffer: Entity<Buffer>,
1785 project: Option<Entity<Project>>,
1786 window: &mut Window,
1787 cx: &mut Context<Self>,
1788 ) -> Self {
1789 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1790 Self::new(EditorMode::full(), buffer, project, window, cx)
1791 }
1792
1793 pub fn for_multibuffer(
1794 buffer: Entity<MultiBuffer>,
1795 project: Option<Entity<Project>>,
1796 window: &mut Window,
1797 cx: &mut Context<Self>,
1798 ) -> Self {
1799 Self::new(EditorMode::full(), buffer, project, window, cx)
1800 }
1801
1802 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1803 let mut clone = Self::new(
1804 self.mode.clone(),
1805 self.buffer.clone(),
1806 self.project.clone(),
1807 window,
1808 cx,
1809 );
1810 self.display_map.update(cx, |display_map, cx| {
1811 let snapshot = display_map.snapshot(cx);
1812 clone.display_map.update(cx, |display_map, cx| {
1813 display_map.set_state(&snapshot, cx);
1814 });
1815 });
1816 clone.folds_did_change(cx);
1817 clone.selections.clone_state(&self.selections);
1818 clone.scroll_manager.clone_state(&self.scroll_manager);
1819 clone.searchable = self.searchable;
1820 clone.read_only = self.read_only;
1821 clone
1822 }
1823
1824 pub fn new(
1825 mode: EditorMode,
1826 buffer: Entity<MultiBuffer>,
1827 project: Option<Entity<Project>>,
1828 window: &mut Window,
1829 cx: &mut Context<Self>,
1830 ) -> Self {
1831 Editor::new_internal(mode, buffer, project, None, window, cx)
1832 }
1833
1834 pub fn sticky_headers(
1835 &self,
1836 style: &EditorStyle,
1837 cx: &App,
1838 ) -> Option<Vec<OutlineItem<Anchor>>> {
1839 let multi_buffer = self.buffer().read(cx);
1840 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1841 let multi_buffer_visible_start = self
1842 .scroll_manager
1843 .anchor()
1844 .anchor
1845 .to_point(&multi_buffer_snapshot);
1846 let max_row = multi_buffer_snapshot.max_point().row;
1847
1848 let start_row = (multi_buffer_visible_start.row).min(max_row);
1849 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1850
1851 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1852 let outline_items = buffer
1853 .outline_items_containing(
1854 Point::new(start_row, 0)..Point::new(end_row, 0),
1855 true,
1856 Some(style.syntax.as_ref()),
1857 )
1858 .into_iter()
1859 .map(|outline_item| OutlineItem {
1860 depth: outline_item.depth,
1861 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1862 source_range_for_text: Anchor::range_in_buffer(
1863 *excerpt_id,
1864 outline_item.source_range_for_text,
1865 ),
1866 text: outline_item.text,
1867 highlight_ranges: outline_item.highlight_ranges,
1868 name_ranges: outline_item.name_ranges,
1869 body_range: outline_item
1870 .body_range
1871 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1872 annotation_range: outline_item
1873 .annotation_range
1874 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1875 });
1876 return Some(outline_items.collect());
1877 }
1878
1879 None
1880 }
1881
1882 fn new_internal(
1883 mode: EditorMode,
1884 multi_buffer: Entity<MultiBuffer>,
1885 project: Option<Entity<Project>>,
1886 display_map: Option<Entity<DisplayMap>>,
1887 window: &mut Window,
1888 cx: &mut Context<Self>,
1889 ) -> Self {
1890 debug_assert!(
1891 display_map.is_none() || mode.is_minimap(),
1892 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1893 );
1894
1895 let full_mode = mode.is_full();
1896 let is_minimap = mode.is_minimap();
1897 let diagnostics_max_severity = if full_mode {
1898 EditorSettings::get_global(cx)
1899 .diagnostics_max_severity
1900 .unwrap_or(DiagnosticSeverity::Hint)
1901 } else {
1902 DiagnosticSeverity::Off
1903 };
1904 let style = window.text_style();
1905 let font_size = style.font_size.to_pixels(window.rem_size());
1906 let editor = cx.entity().downgrade();
1907 let fold_placeholder = FoldPlaceholder {
1908 constrain_width: false,
1909 render: Arc::new(move |fold_id, fold_range, cx| {
1910 let editor = editor.clone();
1911 div()
1912 .id(fold_id)
1913 .bg(cx.theme().colors().ghost_element_background)
1914 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1915 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1916 .rounded_xs()
1917 .size_full()
1918 .cursor_pointer()
1919 .child("⋯")
1920 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1921 .on_click(move |_, _window, cx| {
1922 editor
1923 .update(cx, |editor, cx| {
1924 editor.unfold_ranges(
1925 &[fold_range.start..fold_range.end],
1926 true,
1927 false,
1928 cx,
1929 );
1930 cx.stop_propagation();
1931 })
1932 .ok();
1933 })
1934 .into_any()
1935 }),
1936 merge_adjacent: true,
1937 ..FoldPlaceholder::default()
1938 };
1939 let display_map = display_map.unwrap_or_else(|| {
1940 cx.new(|cx| {
1941 DisplayMap::new(
1942 multi_buffer.clone(),
1943 style.font(),
1944 font_size,
1945 None,
1946 FILE_HEADER_HEIGHT,
1947 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1948 fold_placeholder,
1949 diagnostics_max_severity,
1950 cx,
1951 )
1952 })
1953 });
1954
1955 let selections = SelectionsCollection::new();
1956
1957 let blink_manager = cx.new(|cx| {
1958 let mut blink_manager = BlinkManager::new(
1959 CURSOR_BLINK_INTERVAL,
1960 |cx| EditorSettings::get_global(cx).cursor_blink,
1961 cx,
1962 );
1963 if is_minimap {
1964 blink_manager.disable(cx);
1965 }
1966 blink_manager
1967 });
1968
1969 let soft_wrap_mode_override =
1970 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1971
1972 let mut project_subscriptions = Vec::new();
1973 if full_mode && let Some(project) = project.as_ref() {
1974 project_subscriptions.push(cx.subscribe_in(
1975 project,
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 project::Event::RefreshCodeLens => {
1979 // we always query lens with actions, without storing them, always refreshing them
1980 }
1981 project::Event::RefreshInlayHints {
1982 server_id,
1983 request_id,
1984 } => {
1985 editor.refresh_inlay_hints(
1986 InlayHintRefreshReason::RefreshRequested {
1987 server_id: *server_id,
1988 request_id: *request_id,
1989 },
1990 cx,
1991 );
1992 }
1993 project::Event::LanguageServerRemoved(..) => {
1994 if editor.tasks_update_task.is_none() {
1995 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1996 }
1997 editor.registered_buffers.clear();
1998 editor.register_visible_buffers(cx);
1999 }
2000 project::Event::LanguageServerAdded(..) => {
2001 if editor.tasks_update_task.is_none() {
2002 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2003 }
2004 }
2005 project::Event::SnippetEdit(id, snippet_edits) => {
2006 // todo(lw): Non singletons
2007 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2008 let snapshot = buffer.read(cx).snapshot();
2009 let focus_handle = editor.focus_handle(cx);
2010 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2011 for (range, snippet) in snippet_edits {
2012 let buffer_range =
2013 language::range_from_lsp(*range).to_offset(&snapshot);
2014 editor
2015 .insert_snippet(
2016 &[MultiBufferOffset(buffer_range.start)
2017 ..MultiBufferOffset(buffer_range.end)],
2018 snippet.clone(),
2019 window,
2020 cx,
2021 )
2022 .ok();
2023 }
2024 }
2025 }
2026 }
2027 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2028 let buffer_id = *buffer_id;
2029 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2030 editor.register_buffer(buffer_id, cx);
2031 editor.update_lsp_data(Some(buffer_id), window, cx);
2032 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2033 refresh_linked_ranges(editor, window, cx);
2034 editor.refresh_code_actions(window, cx);
2035 editor.refresh_document_highlights(cx);
2036 }
2037 }
2038
2039 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2040 let Some(workspace) = editor.workspace() else {
2041 return;
2042 };
2043 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2044 else {
2045 return;
2046 };
2047
2048 if active_editor.entity_id() == cx.entity_id() {
2049 let entity_id = cx.entity_id();
2050 workspace.update(cx, |this, cx| {
2051 this.panes_mut()
2052 .iter_mut()
2053 .filter(|pane| pane.entity_id() != entity_id)
2054 .for_each(|p| {
2055 p.update(cx, |pane, _| {
2056 pane.nav_history_mut().rename_item(
2057 entity_id,
2058 project_path.clone(),
2059 abs_path.clone().into(),
2060 );
2061 })
2062 });
2063 });
2064 let edited_buffers_already_open = {
2065 let other_editors: Vec<Entity<Editor>> = workspace
2066 .read(cx)
2067 .panes()
2068 .iter()
2069 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2070 .filter(|editor| editor.entity_id() != cx.entity_id())
2071 .collect();
2072
2073 transaction.0.keys().all(|buffer| {
2074 other_editors.iter().any(|editor| {
2075 let multi_buffer = editor.read(cx).buffer();
2076 multi_buffer.read(cx).is_singleton()
2077 && multi_buffer.read(cx).as_singleton().map_or(
2078 false,
2079 |singleton| {
2080 singleton.entity_id() == buffer.entity_id()
2081 },
2082 )
2083 })
2084 })
2085 };
2086 if !edited_buffers_already_open {
2087 let workspace = workspace.downgrade();
2088 let transaction = transaction.clone();
2089 cx.defer_in(window, move |_, window, cx| {
2090 cx.spawn_in(window, async move |editor, cx| {
2091 Self::open_project_transaction(
2092 &editor,
2093 workspace,
2094 transaction,
2095 "Rename".to_string(),
2096 cx,
2097 )
2098 .await
2099 .ok()
2100 })
2101 .detach();
2102 });
2103 }
2104 }
2105 }
2106
2107 _ => {}
2108 },
2109 ));
2110 if let Some(task_inventory) = project
2111 .read(cx)
2112 .task_store()
2113 .read(cx)
2114 .task_inventory()
2115 .cloned()
2116 {
2117 project_subscriptions.push(cx.observe_in(
2118 &task_inventory,
2119 window,
2120 |editor, _, window, cx| {
2121 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2122 },
2123 ));
2124 };
2125
2126 project_subscriptions.push(cx.subscribe_in(
2127 &project.read(cx).breakpoint_store(),
2128 window,
2129 |editor, _, event, window, cx| match event {
2130 BreakpointStoreEvent::ClearDebugLines => {
2131 editor.clear_row_highlights::<ActiveDebugLine>();
2132 editor.refresh_inline_values(cx);
2133 }
2134 BreakpointStoreEvent::SetDebugLine => {
2135 if editor.go_to_active_debug_line(window, cx) {
2136 cx.stop_propagation();
2137 }
2138
2139 editor.refresh_inline_values(cx);
2140 }
2141 _ => {}
2142 },
2143 ));
2144 let git_store = project.read(cx).git_store().clone();
2145 let project = project.clone();
2146 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2147 if let GitStoreEvent::RepositoryAdded = event {
2148 this.load_diff_task = Some(
2149 update_uncommitted_diff_for_buffer(
2150 cx.entity(),
2151 &project,
2152 this.buffer.read(cx).all_buffers(),
2153 this.buffer.clone(),
2154 cx,
2155 )
2156 .shared(),
2157 );
2158 }
2159 }));
2160 }
2161
2162 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2163
2164 let inlay_hint_settings =
2165 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2166 let focus_handle = cx.focus_handle();
2167 if !is_minimap {
2168 cx.on_focus(&focus_handle, window, Self::handle_focus)
2169 .detach();
2170 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2171 .detach();
2172 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2173 .detach();
2174 cx.on_blur(&focus_handle, window, Self::handle_blur)
2175 .detach();
2176 cx.observe_pending_input(window, Self::observe_pending_input)
2177 .detach();
2178 }
2179
2180 let show_indent_guides =
2181 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2182 Some(false)
2183 } else {
2184 None
2185 };
2186
2187 let breakpoint_store = match (&mode, project.as_ref()) {
2188 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2189 _ => None,
2190 };
2191
2192 let mut code_action_providers = Vec::new();
2193 let mut load_uncommitted_diff = None;
2194 if let Some(project) = project.clone() {
2195 load_uncommitted_diff = Some(
2196 update_uncommitted_diff_for_buffer(
2197 cx.entity(),
2198 &project,
2199 multi_buffer.read(cx).all_buffers(),
2200 multi_buffer.clone(),
2201 cx,
2202 )
2203 .shared(),
2204 );
2205 code_action_providers.push(Rc::new(project) as Rc<_>);
2206 }
2207
2208 let mut editor = Self {
2209 focus_handle,
2210 show_cursor_when_unfocused: false,
2211 last_focused_descendant: None,
2212 buffer: multi_buffer.clone(),
2213 display_map: display_map.clone(),
2214 placeholder_display_map: None,
2215 selections,
2216 scroll_manager: ScrollManager::new(cx),
2217 columnar_selection_state: None,
2218 add_selections_state: None,
2219 select_next_state: None,
2220 select_prev_state: None,
2221 selection_history: SelectionHistory::default(),
2222 defer_selection_effects: false,
2223 deferred_selection_effects_state: None,
2224 autoclose_regions: Vec::new(),
2225 snippet_stack: InvalidationStack::default(),
2226 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2227 ime_transaction: None,
2228 active_diagnostics: ActiveDiagnostic::None,
2229 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2230 inline_diagnostics_update: Task::ready(()),
2231 inline_diagnostics: Vec::new(),
2232 soft_wrap_mode_override,
2233 diagnostics_max_severity,
2234 hard_wrap: None,
2235 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2236 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2237 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2238 project,
2239 blink_manager: blink_manager.clone(),
2240 show_local_selections: true,
2241 show_scrollbars: ScrollbarAxes {
2242 horizontal: full_mode,
2243 vertical: full_mode,
2244 },
2245 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2246 offset_content: !matches!(mode, EditorMode::SingleLine),
2247 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2248 show_gutter: full_mode,
2249 show_line_numbers: (!full_mode).then_some(false),
2250 use_relative_line_numbers: None,
2251 disable_expand_excerpt_buttons: !full_mode,
2252 show_git_diff_gutter: None,
2253 show_code_actions: None,
2254 show_runnables: None,
2255 show_breakpoints: None,
2256 show_wrap_guides: None,
2257 show_indent_guides,
2258 buffers_with_disabled_indent_guides: HashSet::default(),
2259 highlight_order: 0,
2260 highlighted_rows: HashMap::default(),
2261 background_highlights: HashMap::default(),
2262 gutter_highlights: HashMap::default(),
2263 scrollbar_marker_state: ScrollbarMarkerState::default(),
2264 active_indent_guides_state: ActiveIndentGuidesState::default(),
2265 nav_history: None,
2266 context_menu: RefCell::new(None),
2267 context_menu_options: None,
2268 mouse_context_menu: None,
2269 completion_tasks: Vec::new(),
2270 inline_blame_popover: None,
2271 inline_blame_popover_show_task: None,
2272 signature_help_state: SignatureHelpState::default(),
2273 auto_signature_help: None,
2274 find_all_references_task_sources: Vec::new(),
2275 next_completion_id: 0,
2276 next_inlay_id: 0,
2277 code_action_providers,
2278 available_code_actions: None,
2279 code_actions_task: None,
2280 quick_selection_highlight_task: None,
2281 debounced_selection_highlight_task: None,
2282 document_highlights_task: None,
2283 linked_editing_range_task: None,
2284 pending_rename: None,
2285 searchable: !is_minimap,
2286 cursor_shape: EditorSettings::get_global(cx)
2287 .cursor_shape
2288 .unwrap_or_default(),
2289 cursor_offset_on_selection: false,
2290 current_line_highlight: None,
2291 autoindent_mode: Some(AutoindentMode::EachLine),
2292 collapse_matches: false,
2293 workspace: None,
2294 input_enabled: !is_minimap,
2295 use_modal_editing: full_mode,
2296 read_only: is_minimap,
2297 use_autoclose: true,
2298 use_auto_surround: true,
2299 auto_replace_emoji_shortcode: false,
2300 jsx_tag_auto_close_enabled_in_any_buffer: false,
2301 leader_id: None,
2302 remote_id: None,
2303 hover_state: HoverState::default(),
2304 pending_mouse_down: None,
2305 prev_pressure_stage: None,
2306 hovered_link_state: None,
2307 edit_prediction_provider: None,
2308 active_edit_prediction: None,
2309 stale_edit_prediction_in_menu: None,
2310 edit_prediction_preview: EditPredictionPreview::Inactive {
2311 released_too_fast: false,
2312 },
2313 inline_diagnostics_enabled: full_mode,
2314 diagnostics_enabled: full_mode,
2315 word_completions_enabled: full_mode,
2316 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2317 gutter_hovered: false,
2318 pixel_position_of_newest_cursor: None,
2319 last_bounds: None,
2320 last_position_map: None,
2321 expect_bounds_change: None,
2322 gutter_dimensions: GutterDimensions::default(),
2323 style: None,
2324 show_cursor_names: false,
2325 hovered_cursors: HashMap::default(),
2326 next_editor_action_id: EditorActionId::default(),
2327 editor_actions: Rc::default(),
2328 edit_predictions_hidden_for_vim_mode: false,
2329 show_edit_predictions_override: None,
2330 show_completions_on_input_override: None,
2331 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2332 edit_prediction_settings: EditPredictionSettings::Disabled,
2333 edit_prediction_indent_conflict: false,
2334 edit_prediction_requires_modifier_in_indent_conflict: true,
2335 custom_context_menu: None,
2336 show_git_blame_gutter: false,
2337 show_git_blame_inline: false,
2338 show_selection_menu: None,
2339 show_git_blame_inline_delay_task: None,
2340 git_blame_inline_enabled: full_mode
2341 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2342 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2343 buffer_serialization: is_minimap.not().then(|| {
2344 BufferSerialization::new(
2345 ProjectSettings::get_global(cx)
2346 .session
2347 .restore_unsaved_buffers,
2348 )
2349 }),
2350 blame: None,
2351 blame_subscription: None,
2352 tasks: BTreeMap::default(),
2353
2354 breakpoint_store,
2355 gutter_breakpoint_indicator: (None, None),
2356 hovered_diff_hunk_row: None,
2357 _subscriptions: (!is_minimap)
2358 .then(|| {
2359 vec![
2360 cx.observe(&multi_buffer, Self::on_buffer_changed),
2361 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2362 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2363 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2364 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2365 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2366 cx.observe_window_activation(window, |editor, window, cx| {
2367 let active = window.is_window_active();
2368 editor.blink_manager.update(cx, |blink_manager, cx| {
2369 if active {
2370 blink_manager.enable(cx);
2371 } else {
2372 blink_manager.disable(cx);
2373 }
2374 });
2375 if active {
2376 editor.show_mouse_cursor(cx);
2377 }
2378 }),
2379 ]
2380 })
2381 .unwrap_or_default(),
2382 tasks_update_task: None,
2383 pull_diagnostics_task: Task::ready(()),
2384 pull_diagnostics_background_task: Task::ready(()),
2385 colors: None,
2386 refresh_colors_task: Task::ready(()),
2387 inlay_hints: None,
2388 next_color_inlay_id: 0,
2389 post_scroll_update: Task::ready(()),
2390 linked_edit_ranges: Default::default(),
2391 in_project_search: false,
2392 previous_search_ranges: None,
2393 breadcrumb_header: None,
2394 focused_block: None,
2395 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2396 addons: HashMap::default(),
2397 registered_buffers: HashMap::default(),
2398 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2399 selection_mark_mode: false,
2400 toggle_fold_multiple_buffers: Task::ready(()),
2401 serialize_selections: Task::ready(()),
2402 serialize_folds: Task::ready(()),
2403 text_style_refinement: None,
2404 load_diff_task: load_uncommitted_diff,
2405 temporary_diff_override: false,
2406 mouse_cursor_hidden: false,
2407 minimap: None,
2408 hide_mouse_mode: EditorSettings::get_global(cx)
2409 .hide_mouse
2410 .unwrap_or_default(),
2411 change_list: ChangeList::new(),
2412 mode,
2413 selection_drag_state: SelectionDragState::None,
2414 folding_newlines: Task::ready(()),
2415 lookup_key: None,
2416 select_next_is_case_sensitive: None,
2417 applicable_language_settings: HashMap::default(),
2418 accent_data: None,
2419 fetched_tree_sitter_chunks: HashMap::default(),
2420 use_base_text_line_numbers: false,
2421 };
2422
2423 if is_minimap {
2424 return editor;
2425 }
2426
2427 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2428 editor.accent_data = editor.fetch_accent_data(cx);
2429
2430 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2431 editor
2432 ._subscriptions
2433 .push(cx.observe(breakpoints, |_, _, cx| {
2434 cx.notify();
2435 }));
2436 }
2437 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2438 editor._subscriptions.extend(project_subscriptions);
2439
2440 editor._subscriptions.push(cx.subscribe_in(
2441 &cx.entity(),
2442 window,
2443 |editor, _, e: &EditorEvent, window, cx| match e {
2444 EditorEvent::ScrollPositionChanged { local, .. } => {
2445 if *local {
2446 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2447 editor.inline_blame_popover.take();
2448 let new_anchor = editor.scroll_manager.anchor();
2449 let snapshot = editor.snapshot(window, cx);
2450 editor.update_restoration_data(cx, move |data| {
2451 data.scroll_position = (
2452 new_anchor.top_row(snapshot.buffer_snapshot()),
2453 new_anchor.offset,
2454 );
2455 });
2456
2457 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2458 cx.background_executor()
2459 .timer(Duration::from_millis(50))
2460 .await;
2461 editor
2462 .update_in(cx, |editor, window, cx| {
2463 editor.register_visible_buffers(cx);
2464 editor.refresh_colors_for_visible_range(None, window, cx);
2465 editor.refresh_inlay_hints(
2466 InlayHintRefreshReason::NewLinesShown,
2467 cx,
2468 );
2469 editor.colorize_brackets(false, cx);
2470 })
2471 .ok();
2472 });
2473 }
2474 }
2475 EditorEvent::Edited { .. } => {
2476 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2477 .map(|vim_mode| vim_mode.0)
2478 .unwrap_or(false);
2479 if !vim_mode {
2480 let display_map = editor.display_snapshot(cx);
2481 let selections = editor.selections.all_adjusted_display(&display_map);
2482 let pop_state = editor
2483 .change_list
2484 .last()
2485 .map(|previous| {
2486 previous.len() == selections.len()
2487 && previous.iter().enumerate().all(|(ix, p)| {
2488 p.to_display_point(&display_map).row()
2489 == selections[ix].head().row()
2490 })
2491 })
2492 .unwrap_or(false);
2493 let new_positions = selections
2494 .into_iter()
2495 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2496 .collect();
2497 editor
2498 .change_list
2499 .push_to_change_list(pop_state, new_positions);
2500 }
2501 }
2502 _ => (),
2503 },
2504 ));
2505
2506 if let Some(dap_store) = editor
2507 .project
2508 .as_ref()
2509 .map(|project| project.read(cx).dap_store())
2510 {
2511 let weak_editor = cx.weak_entity();
2512
2513 editor
2514 ._subscriptions
2515 .push(
2516 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2517 let session_entity = cx.entity();
2518 weak_editor
2519 .update(cx, |editor, cx| {
2520 editor._subscriptions.push(
2521 cx.subscribe(&session_entity, Self::on_debug_session_event),
2522 );
2523 })
2524 .ok();
2525 }),
2526 );
2527
2528 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2529 editor
2530 ._subscriptions
2531 .push(cx.subscribe(&session, Self::on_debug_session_event));
2532 }
2533 }
2534
2535 // skip adding the initial selection to selection history
2536 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2537 editor.end_selection(window, cx);
2538 editor.selection_history.mode = SelectionHistoryMode::Normal;
2539
2540 editor.scroll_manager.show_scrollbars(window, cx);
2541 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2542
2543 if full_mode {
2544 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2545 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2546
2547 if editor.git_blame_inline_enabled {
2548 editor.start_git_blame_inline(false, window, cx);
2549 }
2550
2551 editor.go_to_active_debug_line(window, cx);
2552
2553 editor.minimap =
2554 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2555 editor.colors = Some(LspColorData::new(cx));
2556 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2557
2558 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2559 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2560 }
2561 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2562 }
2563
2564 editor
2565 }
2566
2567 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2568 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2569 }
2570
2571 pub fn deploy_mouse_context_menu(
2572 &mut self,
2573 position: gpui::Point<Pixels>,
2574 context_menu: Entity<ContextMenu>,
2575 window: &mut Window,
2576 cx: &mut Context<Self>,
2577 ) {
2578 self.mouse_context_menu = Some(MouseContextMenu::new(
2579 self,
2580 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2581 context_menu,
2582 window,
2583 cx,
2584 ));
2585 }
2586
2587 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2588 self.mouse_context_menu
2589 .as_ref()
2590 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2591 }
2592
2593 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2594 if self
2595 .selections
2596 .pending_anchor()
2597 .is_some_and(|pending_selection| {
2598 let snapshot = self.buffer().read(cx).snapshot(cx);
2599 pending_selection.range().includes(range, &snapshot)
2600 })
2601 {
2602 return true;
2603 }
2604
2605 self.selections
2606 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2607 .into_iter()
2608 .any(|selection| {
2609 // This is needed to cover a corner case, if we just check for an existing
2610 // selection in the fold range, having a cursor at the start of the fold
2611 // marks it as selected. Non-empty selections don't cause this.
2612 let length = selection.end - selection.start;
2613 length > 0
2614 })
2615 }
2616
2617 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2618 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2619 }
2620
2621 fn key_context_internal(
2622 &self,
2623 has_active_edit_prediction: bool,
2624 window: &mut Window,
2625 cx: &mut App,
2626 ) -> KeyContext {
2627 let mut key_context = KeyContext::new_with_defaults();
2628 key_context.add("Editor");
2629 let mode = match self.mode {
2630 EditorMode::SingleLine => "single_line",
2631 EditorMode::AutoHeight { .. } => "auto_height",
2632 EditorMode::Minimap { .. } => "minimap",
2633 EditorMode::Full { .. } => "full",
2634 };
2635
2636 if EditorSettings::jupyter_enabled(cx) {
2637 key_context.add("jupyter");
2638 }
2639
2640 key_context.set("mode", mode);
2641 if self.pending_rename.is_some() {
2642 key_context.add("renaming");
2643 }
2644
2645 if let Some(snippet_stack) = self.snippet_stack.last() {
2646 key_context.add("in_snippet");
2647
2648 if snippet_stack.active_index > 0 {
2649 key_context.add("has_previous_tabstop");
2650 }
2651
2652 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2653 key_context.add("has_next_tabstop");
2654 }
2655 }
2656
2657 match self.context_menu.borrow().as_ref() {
2658 Some(CodeContextMenu::Completions(menu)) => {
2659 if menu.visible() {
2660 key_context.add("menu");
2661 key_context.add("showing_completions");
2662 }
2663 }
2664 Some(CodeContextMenu::CodeActions(menu)) => {
2665 if menu.visible() {
2666 key_context.add("menu");
2667 key_context.add("showing_code_actions")
2668 }
2669 }
2670 None => {}
2671 }
2672
2673 if self.signature_help_state.has_multiple_signatures() {
2674 key_context.add("showing_signature_help");
2675 }
2676
2677 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2678 if !self.focus_handle(cx).contains_focused(window, cx)
2679 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2680 {
2681 for addon in self.addons.values() {
2682 addon.extend_key_context(&mut key_context, cx)
2683 }
2684 }
2685
2686 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2687 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2688 Some(
2689 file.full_path(cx)
2690 .extension()?
2691 .to_string_lossy()
2692 .into_owned(),
2693 )
2694 }) {
2695 key_context.set("extension", extension);
2696 }
2697 } else {
2698 key_context.add("multibuffer");
2699 }
2700
2701 if has_active_edit_prediction {
2702 if self.edit_prediction_in_conflict() {
2703 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2704 } else {
2705 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2706 key_context.add("copilot_suggestion");
2707 }
2708 }
2709
2710 if self.selection_mark_mode {
2711 key_context.add("selection_mode");
2712 }
2713
2714 let disjoint = self.selections.disjoint_anchors();
2715 let snapshot = self.snapshot(window, cx);
2716 let snapshot = snapshot.buffer_snapshot();
2717 if self.mode == EditorMode::SingleLine
2718 && let [selection] = disjoint
2719 && selection.start == selection.end
2720 && selection.end.to_offset(snapshot) == snapshot.len()
2721 {
2722 key_context.add("end_of_input");
2723 }
2724
2725 if self.has_any_expanded_diff_hunks(cx) {
2726 key_context.add("diffs_expanded");
2727 }
2728
2729 key_context
2730 }
2731
2732 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2733 self.last_bounds.as_ref()
2734 }
2735
2736 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2737 if self.mouse_cursor_hidden {
2738 self.mouse_cursor_hidden = false;
2739 cx.notify();
2740 }
2741 }
2742
2743 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2744 let hide_mouse_cursor = match origin {
2745 HideMouseCursorOrigin::TypingAction => {
2746 matches!(
2747 self.hide_mouse_mode,
2748 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2749 )
2750 }
2751 HideMouseCursorOrigin::MovementAction => {
2752 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2753 }
2754 };
2755 if self.mouse_cursor_hidden != hide_mouse_cursor {
2756 self.mouse_cursor_hidden = hide_mouse_cursor;
2757 cx.notify();
2758 }
2759 }
2760
2761 pub fn edit_prediction_in_conflict(&self) -> bool {
2762 if !self.show_edit_predictions_in_menu() {
2763 return false;
2764 }
2765
2766 let showing_completions = self
2767 .context_menu
2768 .borrow()
2769 .as_ref()
2770 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2771
2772 showing_completions
2773 || self.edit_prediction_requires_modifier()
2774 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2775 // bindings to insert tab characters.
2776 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2777 }
2778
2779 pub fn accept_edit_prediction_keybind(
2780 &self,
2781 accept_partial: bool,
2782 window: &mut Window,
2783 cx: &mut App,
2784 ) -> AcceptEditPredictionBinding {
2785 let key_context = self.key_context_internal(true, window, cx);
2786 let in_conflict = self.edit_prediction_in_conflict();
2787
2788 let bindings = if accept_partial {
2789 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2790 } else {
2791 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2792 };
2793
2794 // TODO: if the binding contains multiple keystrokes, display all of them, not
2795 // just the first one.
2796 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2797 !in_conflict
2798 || binding
2799 .keystrokes()
2800 .first()
2801 .is_some_and(|keystroke| keystroke.modifiers().modified())
2802 }))
2803 }
2804
2805 pub fn new_file(
2806 workspace: &mut Workspace,
2807 _: &workspace::NewFile,
2808 window: &mut Window,
2809 cx: &mut Context<Workspace>,
2810 ) {
2811 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2812 "Failed to create buffer",
2813 window,
2814 cx,
2815 |e, _, _| match e.error_code() {
2816 ErrorCode::RemoteUpgradeRequired => Some(format!(
2817 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2818 e.error_tag("required").unwrap_or("the latest version")
2819 )),
2820 _ => None,
2821 },
2822 );
2823 }
2824
2825 pub fn new_in_workspace(
2826 workspace: &mut Workspace,
2827 window: &mut Window,
2828 cx: &mut Context<Workspace>,
2829 ) -> Task<Result<Entity<Editor>>> {
2830 let project = workspace.project().clone();
2831 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2832
2833 cx.spawn_in(window, async move |workspace, cx| {
2834 let buffer = create.await?;
2835 workspace.update_in(cx, |workspace, window, cx| {
2836 let editor =
2837 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2838 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2839 editor
2840 })
2841 })
2842 }
2843
2844 fn new_file_vertical(
2845 workspace: &mut Workspace,
2846 _: &workspace::NewFileSplitVertical,
2847 window: &mut Window,
2848 cx: &mut Context<Workspace>,
2849 ) {
2850 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2851 }
2852
2853 fn new_file_horizontal(
2854 workspace: &mut Workspace,
2855 _: &workspace::NewFileSplitHorizontal,
2856 window: &mut Window,
2857 cx: &mut Context<Workspace>,
2858 ) {
2859 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2860 }
2861
2862 fn new_file_split(
2863 workspace: &mut Workspace,
2864 action: &workspace::NewFileSplit,
2865 window: &mut Window,
2866 cx: &mut Context<Workspace>,
2867 ) {
2868 Self::new_file_in_direction(workspace, action.0, window, cx)
2869 }
2870
2871 fn new_file_in_direction(
2872 workspace: &mut Workspace,
2873 direction: SplitDirection,
2874 window: &mut Window,
2875 cx: &mut Context<Workspace>,
2876 ) {
2877 let project = workspace.project().clone();
2878 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2879
2880 cx.spawn_in(window, async move |workspace, cx| {
2881 let buffer = create.await?;
2882 workspace.update_in(cx, move |workspace, window, cx| {
2883 workspace.split_item(
2884 direction,
2885 Box::new(
2886 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2887 ),
2888 window,
2889 cx,
2890 )
2891 })?;
2892 anyhow::Ok(())
2893 })
2894 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2895 match e.error_code() {
2896 ErrorCode::RemoteUpgradeRequired => Some(format!(
2897 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2898 e.error_tag("required").unwrap_or("the latest version")
2899 )),
2900 _ => None,
2901 }
2902 });
2903 }
2904
2905 pub fn leader_id(&self) -> Option<CollaboratorId> {
2906 self.leader_id
2907 }
2908
2909 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2910 &self.buffer
2911 }
2912
2913 pub fn project(&self) -> Option<&Entity<Project>> {
2914 self.project.as_ref()
2915 }
2916
2917 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2918 self.workspace.as_ref()?.0.upgrade()
2919 }
2920
2921 /// Returns the workspace serialization ID if this editor should be serialized.
2922 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2923 self.workspace
2924 .as_ref()
2925 .filter(|_| self.should_serialize_buffer())
2926 .and_then(|workspace| workspace.1)
2927 }
2928
2929 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2930 self.buffer().read(cx).title(cx)
2931 }
2932
2933 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2934 let git_blame_gutter_max_author_length = self
2935 .render_git_blame_gutter(cx)
2936 .then(|| {
2937 if let Some(blame) = self.blame.as_ref() {
2938 let max_author_length =
2939 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2940 Some(max_author_length)
2941 } else {
2942 None
2943 }
2944 })
2945 .flatten();
2946
2947 EditorSnapshot {
2948 mode: self.mode.clone(),
2949 show_gutter: self.show_gutter,
2950 offset_content: self.offset_content,
2951 show_line_numbers: self.show_line_numbers,
2952 show_git_diff_gutter: self.show_git_diff_gutter,
2953 show_code_actions: self.show_code_actions,
2954 show_runnables: self.show_runnables,
2955 show_breakpoints: self.show_breakpoints,
2956 git_blame_gutter_max_author_length,
2957 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2958 placeholder_display_snapshot: self
2959 .placeholder_display_map
2960 .as_ref()
2961 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2962 scroll_anchor: self.scroll_manager.anchor(),
2963 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2964 is_focused: self.focus_handle.is_focused(window),
2965 current_line_highlight: self
2966 .current_line_highlight
2967 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2968 gutter_hovered: self.gutter_hovered,
2969 }
2970 }
2971
2972 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2973 self.buffer.read(cx).language_at(point, cx)
2974 }
2975
2976 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2977 self.buffer.read(cx).read(cx).file_at(point).cloned()
2978 }
2979
2980 pub fn active_excerpt(
2981 &self,
2982 cx: &App,
2983 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2984 self.buffer
2985 .read(cx)
2986 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2987 }
2988
2989 pub fn mode(&self) -> &EditorMode {
2990 &self.mode
2991 }
2992
2993 pub fn set_mode(&mut self, mode: EditorMode) {
2994 self.mode = mode;
2995 }
2996
2997 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2998 self.collaboration_hub.as_deref()
2999 }
3000
3001 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3002 self.collaboration_hub = Some(hub);
3003 }
3004
3005 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3006 self.in_project_search = in_project_search;
3007 }
3008
3009 pub fn set_custom_context_menu(
3010 &mut self,
3011 f: impl 'static
3012 + Fn(
3013 &mut Self,
3014 DisplayPoint,
3015 &mut Window,
3016 &mut Context<Self>,
3017 ) -> Option<Entity<ui::ContextMenu>>,
3018 ) {
3019 self.custom_context_menu = Some(Box::new(f))
3020 }
3021
3022 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3023 self.completion_provider = provider;
3024 }
3025
3026 #[cfg(any(test, feature = "test-support"))]
3027 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3028 self.completion_provider.clone()
3029 }
3030
3031 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3032 self.semantics_provider.clone()
3033 }
3034
3035 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3036 self.semantics_provider = provider;
3037 }
3038
3039 pub fn set_edit_prediction_provider<T>(
3040 &mut self,
3041 provider: Option<Entity<T>>,
3042 window: &mut Window,
3043 cx: &mut Context<Self>,
3044 ) where
3045 T: EditPredictionDelegate,
3046 {
3047 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3048 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3049 if this.focus_handle.is_focused(window) {
3050 this.update_visible_edit_prediction(window, cx);
3051 }
3052 }),
3053 provider: Arc::new(provider),
3054 });
3055 self.update_edit_prediction_settings(cx);
3056 self.refresh_edit_prediction(false, false, window, cx);
3057 }
3058
3059 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3060 self.placeholder_display_map
3061 .as_ref()
3062 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3063 }
3064
3065 pub fn set_placeholder_text(
3066 &mut self,
3067 placeholder_text: &str,
3068 window: &mut Window,
3069 cx: &mut Context<Self>,
3070 ) {
3071 let multibuffer = cx
3072 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3073
3074 let style = window.text_style();
3075
3076 self.placeholder_display_map = Some(cx.new(|cx| {
3077 DisplayMap::new(
3078 multibuffer,
3079 style.font(),
3080 style.font_size.to_pixels(window.rem_size()),
3081 None,
3082 FILE_HEADER_HEIGHT,
3083 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3084 Default::default(),
3085 DiagnosticSeverity::Off,
3086 cx,
3087 )
3088 }));
3089 cx.notify();
3090 }
3091
3092 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3093 self.cursor_shape = cursor_shape;
3094
3095 // Disrupt blink for immediate user feedback that the cursor shape has changed
3096 self.blink_manager.update(cx, BlinkManager::show_cursor);
3097
3098 cx.notify();
3099 }
3100
3101 pub fn cursor_shape(&self) -> CursorShape {
3102 self.cursor_shape
3103 }
3104
3105 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3106 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3107 }
3108
3109 pub fn set_current_line_highlight(
3110 &mut self,
3111 current_line_highlight: Option<CurrentLineHighlight>,
3112 ) {
3113 self.current_line_highlight = current_line_highlight;
3114 }
3115
3116 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3117 self.collapse_matches = collapse_matches;
3118 }
3119
3120 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3121 if self.collapse_matches {
3122 return range.start..range.start;
3123 }
3124 range.clone()
3125 }
3126
3127 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3128 self.display_map.read(cx).clip_at_line_ends
3129 }
3130
3131 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3132 if self.display_map.read(cx).clip_at_line_ends != clip {
3133 self.display_map
3134 .update(cx, |map, _| map.clip_at_line_ends = clip);
3135 }
3136 }
3137
3138 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3139 self.input_enabled = input_enabled;
3140 }
3141
3142 pub fn set_edit_predictions_hidden_for_vim_mode(
3143 &mut self,
3144 hidden: bool,
3145 window: &mut Window,
3146 cx: &mut Context<Self>,
3147 ) {
3148 if hidden != self.edit_predictions_hidden_for_vim_mode {
3149 self.edit_predictions_hidden_for_vim_mode = hidden;
3150 if hidden {
3151 self.update_visible_edit_prediction(window, cx);
3152 } else {
3153 self.refresh_edit_prediction(true, false, window, cx);
3154 }
3155 }
3156 }
3157
3158 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3159 self.menu_edit_predictions_policy = value;
3160 }
3161
3162 pub fn set_autoindent(&mut self, autoindent: bool) {
3163 if autoindent {
3164 self.autoindent_mode = Some(AutoindentMode::EachLine);
3165 } else {
3166 self.autoindent_mode = None;
3167 }
3168 }
3169
3170 pub fn read_only(&self, cx: &App) -> bool {
3171 self.read_only || self.buffer.read(cx).read_only()
3172 }
3173
3174 pub fn set_read_only(&mut self, read_only: bool) {
3175 self.read_only = read_only;
3176 }
3177
3178 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3179 self.use_autoclose = autoclose;
3180 }
3181
3182 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3183 self.use_auto_surround = auto_surround;
3184 }
3185
3186 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3187 self.auto_replace_emoji_shortcode = auto_replace;
3188 }
3189
3190 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3191 self.buffer_serialization = should_serialize.then(|| {
3192 BufferSerialization::new(
3193 ProjectSettings::get_global(cx)
3194 .session
3195 .restore_unsaved_buffers,
3196 )
3197 })
3198 }
3199
3200 fn should_serialize_buffer(&self) -> bool {
3201 self.buffer_serialization.is_some()
3202 }
3203
3204 pub fn toggle_edit_predictions(
3205 &mut self,
3206 _: &ToggleEditPrediction,
3207 window: &mut Window,
3208 cx: &mut Context<Self>,
3209 ) {
3210 if self.show_edit_predictions_override.is_some() {
3211 self.set_show_edit_predictions(None, window, cx);
3212 } else {
3213 let show_edit_predictions = !self.edit_predictions_enabled();
3214 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3215 }
3216 }
3217
3218 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3219 self.show_completions_on_input_override = show_completions_on_input;
3220 }
3221
3222 pub fn set_show_edit_predictions(
3223 &mut self,
3224 show_edit_predictions: Option<bool>,
3225 window: &mut Window,
3226 cx: &mut Context<Self>,
3227 ) {
3228 self.show_edit_predictions_override = show_edit_predictions;
3229 self.update_edit_prediction_settings(cx);
3230
3231 if let Some(false) = show_edit_predictions {
3232 self.discard_edit_prediction(false, cx);
3233 } else {
3234 self.refresh_edit_prediction(false, true, window, cx);
3235 }
3236 }
3237
3238 fn edit_predictions_disabled_in_scope(
3239 &self,
3240 buffer: &Entity<Buffer>,
3241 buffer_position: language::Anchor,
3242 cx: &App,
3243 ) -> bool {
3244 let snapshot = buffer.read(cx).snapshot();
3245 let settings = snapshot.settings_at(buffer_position, cx);
3246
3247 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3248 return false;
3249 };
3250
3251 scope.override_name().is_some_and(|scope_name| {
3252 settings
3253 .edit_predictions_disabled_in
3254 .iter()
3255 .any(|s| s == scope_name)
3256 })
3257 }
3258
3259 pub fn set_use_modal_editing(&mut self, to: bool) {
3260 self.use_modal_editing = to;
3261 }
3262
3263 pub fn use_modal_editing(&self) -> bool {
3264 self.use_modal_editing
3265 }
3266
3267 fn selections_did_change(
3268 &mut self,
3269 local: bool,
3270 old_cursor_position: &Anchor,
3271 effects: SelectionEffects,
3272 window: &mut Window,
3273 cx: &mut Context<Self>,
3274 ) {
3275 window.invalidate_character_coordinates();
3276
3277 // Copy selections to primary selection buffer
3278 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3279 if local {
3280 let selections = self
3281 .selections
3282 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3283 let buffer_handle = self.buffer.read(cx).read(cx);
3284
3285 let mut text = String::new();
3286 for (index, selection) in selections.iter().enumerate() {
3287 let text_for_selection = buffer_handle
3288 .text_for_range(selection.start..selection.end)
3289 .collect::<String>();
3290
3291 text.push_str(&text_for_selection);
3292 if index != selections.len() - 1 {
3293 text.push('\n');
3294 }
3295 }
3296
3297 if !text.is_empty() {
3298 cx.write_to_primary(ClipboardItem::new_string(text));
3299 }
3300 }
3301
3302 let selection_anchors = self.selections.disjoint_anchors_arc();
3303
3304 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3305 self.buffer.update(cx, |buffer, cx| {
3306 buffer.set_active_selections(
3307 &selection_anchors,
3308 self.selections.line_mode(),
3309 self.cursor_shape,
3310 cx,
3311 )
3312 });
3313 }
3314 let display_map = self
3315 .display_map
3316 .update(cx, |display_map, cx| display_map.snapshot(cx));
3317 let buffer = display_map.buffer_snapshot();
3318 if self.selections.count() == 1 {
3319 self.add_selections_state = None;
3320 }
3321 self.select_next_state = None;
3322 self.select_prev_state = None;
3323 self.select_syntax_node_history.try_clear();
3324 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3325 self.snippet_stack.invalidate(&selection_anchors, buffer);
3326 self.take_rename(false, window, cx);
3327
3328 let newest_selection = self.selections.newest_anchor();
3329 let new_cursor_position = newest_selection.head();
3330 let selection_start = newest_selection.start;
3331
3332 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3333 self.push_to_nav_history(
3334 *old_cursor_position,
3335 Some(new_cursor_position.to_point(buffer)),
3336 false,
3337 effects.nav_history == Some(true),
3338 cx,
3339 );
3340 }
3341
3342 if local {
3343 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3344 self.register_buffer(buffer_id, cx);
3345 }
3346
3347 let mut context_menu = self.context_menu.borrow_mut();
3348 let completion_menu = match context_menu.as_ref() {
3349 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3350 Some(CodeContextMenu::CodeActions(_)) => {
3351 *context_menu = None;
3352 None
3353 }
3354 None => None,
3355 };
3356 let completion_position = completion_menu.map(|menu| menu.initial_position);
3357 drop(context_menu);
3358
3359 if effects.completions
3360 && let Some(completion_position) = completion_position
3361 {
3362 let start_offset = selection_start.to_offset(buffer);
3363 let position_matches = start_offset == completion_position.to_offset(buffer);
3364 let continue_showing = if position_matches {
3365 if self.snippet_stack.is_empty() {
3366 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3367 == Some(CharKind::Word)
3368 } else {
3369 // Snippet choices can be shown even when the cursor is in whitespace.
3370 // Dismissing the menu with actions like backspace is handled by
3371 // invalidation regions.
3372 true
3373 }
3374 } else {
3375 false
3376 };
3377
3378 if continue_showing {
3379 self.open_or_update_completions_menu(None, None, false, window, cx);
3380 } else {
3381 self.hide_context_menu(window, cx);
3382 }
3383 }
3384
3385 hide_hover(self, cx);
3386
3387 if old_cursor_position.to_display_point(&display_map).row()
3388 != new_cursor_position.to_display_point(&display_map).row()
3389 {
3390 self.available_code_actions.take();
3391 }
3392 self.refresh_code_actions(window, cx);
3393 self.refresh_document_highlights(cx);
3394 refresh_linked_ranges(self, window, cx);
3395
3396 self.refresh_selected_text_highlights(false, window, cx);
3397 self.refresh_matching_bracket_highlights(window, cx);
3398 self.update_visible_edit_prediction(window, cx);
3399 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3400 self.inline_blame_popover.take();
3401 if self.git_blame_inline_enabled {
3402 self.start_inline_blame_timer(window, cx);
3403 }
3404 }
3405
3406 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3407 cx.emit(EditorEvent::SelectionsChanged { local });
3408
3409 let selections = &self.selections.disjoint_anchors_arc();
3410 if selections.len() == 1 {
3411 cx.emit(SearchEvent::ActiveMatchChanged)
3412 }
3413 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3414 let inmemory_selections = selections
3415 .iter()
3416 .map(|s| {
3417 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3418 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3419 })
3420 .collect();
3421 self.update_restoration_data(cx, |data| {
3422 data.selections = inmemory_selections;
3423 });
3424
3425 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3426 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3427 {
3428 let snapshot = self.buffer().read(cx).snapshot(cx);
3429 let selections = selections.clone();
3430 let background_executor = cx.background_executor().clone();
3431 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3432 self.serialize_selections = cx.background_spawn(async move {
3433 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3434 let db_selections = selections
3435 .iter()
3436 .map(|selection| {
3437 (
3438 selection.start.to_offset(&snapshot).0,
3439 selection.end.to_offset(&snapshot).0,
3440 )
3441 })
3442 .collect();
3443
3444 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3445 .await
3446 .with_context(|| {
3447 format!(
3448 "persisting editor selections for editor {editor_id}, \
3449 workspace {workspace_id:?}"
3450 )
3451 })
3452 .log_err();
3453 });
3454 }
3455 }
3456
3457 cx.notify();
3458 }
3459
3460 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3461 use text::ToOffset as _;
3462 use text::ToPoint as _;
3463
3464 if self.mode.is_minimap()
3465 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3466 {
3467 return;
3468 }
3469
3470 if !self.buffer().read(cx).is_singleton() {
3471 return;
3472 }
3473
3474 let display_snapshot = self
3475 .display_map
3476 .update(cx, |display_map, cx| display_map.snapshot(cx));
3477 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3478 return;
3479 };
3480 let inmemory_folds = display_snapshot
3481 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3482 .map(|fold| {
3483 fold.range.start.text_anchor.to_point(&snapshot)
3484 ..fold.range.end.text_anchor.to_point(&snapshot)
3485 })
3486 .collect();
3487 self.update_restoration_data(cx, |data| {
3488 data.folds = inmemory_folds;
3489 });
3490
3491 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3492 return;
3493 };
3494 let background_executor = cx.background_executor().clone();
3495 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3496 let db_folds = display_snapshot
3497 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3498 .map(|fold| {
3499 (
3500 fold.range.start.text_anchor.to_offset(&snapshot),
3501 fold.range.end.text_anchor.to_offset(&snapshot),
3502 )
3503 })
3504 .collect();
3505 self.serialize_folds = cx.background_spawn(async move {
3506 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3507 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3508 .await
3509 .with_context(|| {
3510 format!(
3511 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3512 )
3513 })
3514 .log_err();
3515 });
3516 }
3517
3518 pub fn sync_selections(
3519 &mut self,
3520 other: Entity<Editor>,
3521 cx: &mut Context<Self>,
3522 ) -> gpui::Subscription {
3523 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3524 if !other_selections.is_empty() {
3525 self.selections
3526 .change_with(&self.display_snapshot(cx), |selections| {
3527 selections.select_anchors(other_selections);
3528 });
3529 }
3530
3531 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3532 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3533 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3534 if other_selections.is_empty() {
3535 return;
3536 }
3537 let snapshot = this.display_snapshot(cx);
3538 this.selections.change_with(&snapshot, |selections| {
3539 selections.select_anchors(other_selections);
3540 });
3541 }
3542 });
3543
3544 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3545 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3546 let these_selections = this.selections.disjoint_anchors().to_vec();
3547 if these_selections.is_empty() {
3548 return;
3549 }
3550 other.update(cx, |other_editor, cx| {
3551 let snapshot = other_editor.display_snapshot(cx);
3552 other_editor
3553 .selections
3554 .change_with(&snapshot, |selections| {
3555 selections.select_anchors(these_selections);
3556 })
3557 });
3558 }
3559 });
3560
3561 Subscription::join(other_subscription, this_subscription)
3562 }
3563
3564 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3565 if self.buffer().read(cx).is_singleton() {
3566 return;
3567 }
3568 let snapshot = self.buffer.read(cx).snapshot(cx);
3569 let buffer_ids: HashSet<BufferId> = self
3570 .selections
3571 .disjoint_anchor_ranges()
3572 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3573 .collect();
3574 for buffer_id in buffer_ids {
3575 self.unfold_buffer(buffer_id, cx);
3576 }
3577 }
3578
3579 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3580 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3581 /// effects of selection change occur at the end of the transaction.
3582 pub fn change_selections<R>(
3583 &mut self,
3584 effects: SelectionEffects,
3585 window: &mut Window,
3586 cx: &mut Context<Self>,
3587 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3588 ) -> R {
3589 let snapshot = self.display_snapshot(cx);
3590 if let Some(state) = &mut self.deferred_selection_effects_state {
3591 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3592 state.effects.completions = effects.completions;
3593 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3594 let (changed, result) = self.selections.change_with(&snapshot, change);
3595 state.changed |= changed;
3596 return result;
3597 }
3598 let mut state = DeferredSelectionEffectsState {
3599 changed: false,
3600 effects,
3601 old_cursor_position: self.selections.newest_anchor().head(),
3602 history_entry: SelectionHistoryEntry {
3603 selections: self.selections.disjoint_anchors_arc(),
3604 select_next_state: self.select_next_state.clone(),
3605 select_prev_state: self.select_prev_state.clone(),
3606 add_selections_state: self.add_selections_state.clone(),
3607 },
3608 };
3609 let (changed, result) = self.selections.change_with(&snapshot, change);
3610 state.changed = state.changed || changed;
3611 if self.defer_selection_effects {
3612 self.deferred_selection_effects_state = Some(state);
3613 } else {
3614 self.apply_selection_effects(state, window, cx);
3615 }
3616 result
3617 }
3618
3619 /// Defers the effects of selection change, so that the effects of multiple calls to
3620 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3621 /// to selection history and the state of popovers based on selection position aren't
3622 /// erroneously updated.
3623 pub fn with_selection_effects_deferred<R>(
3624 &mut self,
3625 window: &mut Window,
3626 cx: &mut Context<Self>,
3627 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3628 ) -> R {
3629 let already_deferred = self.defer_selection_effects;
3630 self.defer_selection_effects = true;
3631 let result = update(self, window, cx);
3632 if !already_deferred {
3633 self.defer_selection_effects = false;
3634 if let Some(state) = self.deferred_selection_effects_state.take() {
3635 self.apply_selection_effects(state, window, cx);
3636 }
3637 }
3638 result
3639 }
3640
3641 fn apply_selection_effects(
3642 &mut self,
3643 state: DeferredSelectionEffectsState,
3644 window: &mut Window,
3645 cx: &mut Context<Self>,
3646 ) {
3647 if state.changed {
3648 self.selection_history.push(state.history_entry);
3649
3650 if let Some(autoscroll) = state.effects.scroll {
3651 self.request_autoscroll(autoscroll, cx);
3652 }
3653
3654 let old_cursor_position = &state.old_cursor_position;
3655
3656 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3657
3658 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3659 self.show_signature_help(&ShowSignatureHelp, window, cx);
3660 }
3661 }
3662 }
3663
3664 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3665 where
3666 I: IntoIterator<Item = (Range<S>, T)>,
3667 S: ToOffset,
3668 T: Into<Arc<str>>,
3669 {
3670 if self.read_only(cx) {
3671 return;
3672 }
3673
3674 self.buffer
3675 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3676 }
3677
3678 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3679 where
3680 I: IntoIterator<Item = (Range<S>, T)>,
3681 S: ToOffset,
3682 T: Into<Arc<str>>,
3683 {
3684 if self.read_only(cx) {
3685 return;
3686 }
3687
3688 self.buffer.update(cx, |buffer, cx| {
3689 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3690 });
3691 }
3692
3693 pub fn edit_with_block_indent<I, S, T>(
3694 &mut self,
3695 edits: I,
3696 original_indent_columns: Vec<Option<u32>>,
3697 cx: &mut Context<Self>,
3698 ) where
3699 I: IntoIterator<Item = (Range<S>, T)>,
3700 S: ToOffset,
3701 T: Into<Arc<str>>,
3702 {
3703 if self.read_only(cx) {
3704 return;
3705 }
3706
3707 self.buffer.update(cx, |buffer, cx| {
3708 buffer.edit(
3709 edits,
3710 Some(AutoindentMode::Block {
3711 original_indent_columns,
3712 }),
3713 cx,
3714 )
3715 });
3716 }
3717
3718 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3719 self.hide_context_menu(window, cx);
3720
3721 match phase {
3722 SelectPhase::Begin {
3723 position,
3724 add,
3725 click_count,
3726 } => self.begin_selection(position, add, click_count, window, cx),
3727 SelectPhase::BeginColumnar {
3728 position,
3729 goal_column,
3730 reset,
3731 mode,
3732 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3733 SelectPhase::Extend {
3734 position,
3735 click_count,
3736 } => self.extend_selection(position, click_count, window, cx),
3737 SelectPhase::Update {
3738 position,
3739 goal_column,
3740 scroll_delta,
3741 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3742 SelectPhase::End => self.end_selection(window, cx),
3743 }
3744 }
3745
3746 fn extend_selection(
3747 &mut self,
3748 position: DisplayPoint,
3749 click_count: usize,
3750 window: &mut Window,
3751 cx: &mut Context<Self>,
3752 ) {
3753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3754 let tail = self
3755 .selections
3756 .newest::<MultiBufferOffset>(&display_map)
3757 .tail();
3758 let click_count = click_count.max(match self.selections.select_mode() {
3759 SelectMode::Character => 1,
3760 SelectMode::Word(_) => 2,
3761 SelectMode::Line(_) => 3,
3762 SelectMode::All => 4,
3763 });
3764 self.begin_selection(position, false, click_count, window, cx);
3765
3766 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3767
3768 let current_selection = match self.selections.select_mode() {
3769 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3770 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3771 };
3772
3773 let mut pending_selection = self
3774 .selections
3775 .pending_anchor()
3776 .cloned()
3777 .expect("extend_selection not called with pending selection");
3778
3779 if pending_selection
3780 .start
3781 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3782 == Ordering::Greater
3783 {
3784 pending_selection.start = current_selection.start;
3785 }
3786 if pending_selection
3787 .end
3788 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3789 == Ordering::Less
3790 {
3791 pending_selection.end = current_selection.end;
3792 pending_selection.reversed = true;
3793 }
3794
3795 let mut pending_mode = self.selections.pending_mode().unwrap();
3796 match &mut pending_mode {
3797 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3798 _ => {}
3799 }
3800
3801 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3802 SelectionEffects::scroll(Autoscroll::fit())
3803 } else {
3804 SelectionEffects::no_scroll()
3805 };
3806
3807 self.change_selections(effects, window, cx, |s| {
3808 s.set_pending(pending_selection.clone(), pending_mode);
3809 s.set_is_extending(true);
3810 });
3811 }
3812
3813 fn begin_selection(
3814 &mut self,
3815 position: DisplayPoint,
3816 add: bool,
3817 click_count: usize,
3818 window: &mut Window,
3819 cx: &mut Context<Self>,
3820 ) {
3821 if !self.focus_handle.is_focused(window) {
3822 self.last_focused_descendant = None;
3823 window.focus(&self.focus_handle);
3824 }
3825
3826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3827 let buffer = display_map.buffer_snapshot();
3828 let position = display_map.clip_point(position, Bias::Left);
3829
3830 let start;
3831 let end;
3832 let mode;
3833 let mut auto_scroll;
3834 match click_count {
3835 1 => {
3836 start = buffer.anchor_before(position.to_point(&display_map));
3837 end = start;
3838 mode = SelectMode::Character;
3839 auto_scroll = true;
3840 }
3841 2 => {
3842 let position = display_map
3843 .clip_point(position, Bias::Left)
3844 .to_offset(&display_map, Bias::Left);
3845 let (range, _) = buffer.surrounding_word(position, None);
3846 start = buffer.anchor_before(range.start);
3847 end = buffer.anchor_before(range.end);
3848 mode = SelectMode::Word(start..end);
3849 auto_scroll = true;
3850 }
3851 3 => {
3852 let position = display_map
3853 .clip_point(position, Bias::Left)
3854 .to_point(&display_map);
3855 let line_start = display_map.prev_line_boundary(position).0;
3856 let next_line_start = buffer.clip_point(
3857 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3858 Bias::Left,
3859 );
3860 start = buffer.anchor_before(line_start);
3861 end = buffer.anchor_before(next_line_start);
3862 mode = SelectMode::Line(start..end);
3863 auto_scroll = true;
3864 }
3865 _ => {
3866 start = buffer.anchor_before(MultiBufferOffset(0));
3867 end = buffer.anchor_before(buffer.len());
3868 mode = SelectMode::All;
3869 auto_scroll = false;
3870 }
3871 }
3872 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3873
3874 let point_to_delete: Option<usize> = {
3875 let selected_points: Vec<Selection<Point>> =
3876 self.selections.disjoint_in_range(start..end, &display_map);
3877
3878 if !add || click_count > 1 {
3879 None
3880 } else if !selected_points.is_empty() {
3881 Some(selected_points[0].id)
3882 } else {
3883 let clicked_point_already_selected =
3884 self.selections.disjoint_anchors().iter().find(|selection| {
3885 selection.start.to_point(buffer) == start.to_point(buffer)
3886 || selection.end.to_point(buffer) == end.to_point(buffer)
3887 });
3888
3889 clicked_point_already_selected.map(|selection| selection.id)
3890 }
3891 };
3892
3893 let selections_count = self.selections.count();
3894 let effects = if auto_scroll {
3895 SelectionEffects::default()
3896 } else {
3897 SelectionEffects::no_scroll()
3898 };
3899
3900 self.change_selections(effects, window, cx, |s| {
3901 if let Some(point_to_delete) = point_to_delete {
3902 s.delete(point_to_delete);
3903
3904 if selections_count == 1 {
3905 s.set_pending_anchor_range(start..end, mode);
3906 }
3907 } else {
3908 if !add {
3909 s.clear_disjoint();
3910 }
3911
3912 s.set_pending_anchor_range(start..end, mode);
3913 }
3914 });
3915 }
3916
3917 fn begin_columnar_selection(
3918 &mut self,
3919 position: DisplayPoint,
3920 goal_column: u32,
3921 reset: bool,
3922 mode: ColumnarMode,
3923 window: &mut Window,
3924 cx: &mut Context<Self>,
3925 ) {
3926 if !self.focus_handle.is_focused(window) {
3927 self.last_focused_descendant = None;
3928 window.focus(&self.focus_handle);
3929 }
3930
3931 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3932
3933 if reset {
3934 let pointer_position = display_map
3935 .buffer_snapshot()
3936 .anchor_before(position.to_point(&display_map));
3937
3938 self.change_selections(
3939 SelectionEffects::scroll(Autoscroll::newest()),
3940 window,
3941 cx,
3942 |s| {
3943 s.clear_disjoint();
3944 s.set_pending_anchor_range(
3945 pointer_position..pointer_position,
3946 SelectMode::Character,
3947 );
3948 },
3949 );
3950 };
3951
3952 let tail = self.selections.newest::<Point>(&display_map).tail();
3953 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3954 self.columnar_selection_state = match mode {
3955 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3956 selection_tail: selection_anchor,
3957 display_point: if reset {
3958 if position.column() != goal_column {
3959 Some(DisplayPoint::new(position.row(), goal_column))
3960 } else {
3961 None
3962 }
3963 } else {
3964 None
3965 },
3966 }),
3967 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3968 selection_tail: selection_anchor,
3969 }),
3970 };
3971
3972 if !reset {
3973 self.select_columns(position, goal_column, &display_map, window, cx);
3974 }
3975 }
3976
3977 fn update_selection(
3978 &mut self,
3979 position: DisplayPoint,
3980 goal_column: u32,
3981 scroll_delta: gpui::Point<f32>,
3982 window: &mut Window,
3983 cx: &mut Context<Self>,
3984 ) {
3985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3986
3987 if self.columnar_selection_state.is_some() {
3988 self.select_columns(position, goal_column, &display_map, window, cx);
3989 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3990 let buffer = display_map.buffer_snapshot();
3991 let head;
3992 let tail;
3993 let mode = self.selections.pending_mode().unwrap();
3994 match &mode {
3995 SelectMode::Character => {
3996 head = position.to_point(&display_map);
3997 tail = pending.tail().to_point(buffer);
3998 }
3999 SelectMode::Word(original_range) => {
4000 let offset = display_map
4001 .clip_point(position, Bias::Left)
4002 .to_offset(&display_map, Bias::Left);
4003 let original_range = original_range.to_offset(buffer);
4004
4005 let head_offset = if buffer.is_inside_word(offset, None)
4006 || original_range.contains(&offset)
4007 {
4008 let (word_range, _) = buffer.surrounding_word(offset, None);
4009 if word_range.start < original_range.start {
4010 word_range.start
4011 } else {
4012 word_range.end
4013 }
4014 } else {
4015 offset
4016 };
4017
4018 head = head_offset.to_point(buffer);
4019 if head_offset <= original_range.start {
4020 tail = original_range.end.to_point(buffer);
4021 } else {
4022 tail = original_range.start.to_point(buffer);
4023 }
4024 }
4025 SelectMode::Line(original_range) => {
4026 let original_range = original_range.to_point(display_map.buffer_snapshot());
4027
4028 let position = display_map
4029 .clip_point(position, Bias::Left)
4030 .to_point(&display_map);
4031 let line_start = display_map.prev_line_boundary(position).0;
4032 let next_line_start = buffer.clip_point(
4033 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4034 Bias::Left,
4035 );
4036
4037 if line_start < original_range.start {
4038 head = line_start
4039 } else {
4040 head = next_line_start
4041 }
4042
4043 if head <= original_range.start {
4044 tail = original_range.end;
4045 } else {
4046 tail = original_range.start;
4047 }
4048 }
4049 SelectMode::All => {
4050 return;
4051 }
4052 };
4053
4054 if head < tail {
4055 pending.start = buffer.anchor_before(head);
4056 pending.end = buffer.anchor_before(tail);
4057 pending.reversed = true;
4058 } else {
4059 pending.start = buffer.anchor_before(tail);
4060 pending.end = buffer.anchor_before(head);
4061 pending.reversed = false;
4062 }
4063
4064 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4065 s.set_pending(pending.clone(), mode);
4066 });
4067 } else {
4068 log::error!("update_selection dispatched with no pending selection");
4069 return;
4070 }
4071
4072 self.apply_scroll_delta(scroll_delta, window, cx);
4073 cx.notify();
4074 }
4075
4076 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4077 self.columnar_selection_state.take();
4078 if let Some(pending_mode) = self.selections.pending_mode() {
4079 let selections = self
4080 .selections
4081 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4083 s.select(selections);
4084 s.clear_pending();
4085 if s.is_extending() {
4086 s.set_is_extending(false);
4087 } else {
4088 s.set_select_mode(pending_mode);
4089 }
4090 });
4091 }
4092 }
4093
4094 fn select_columns(
4095 &mut self,
4096 head: DisplayPoint,
4097 goal_column: u32,
4098 display_map: &DisplaySnapshot,
4099 window: &mut Window,
4100 cx: &mut Context<Self>,
4101 ) {
4102 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4103 return;
4104 };
4105
4106 let tail = match columnar_state {
4107 ColumnarSelectionState::FromMouse {
4108 selection_tail,
4109 display_point,
4110 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4111 ColumnarSelectionState::FromSelection { selection_tail } => {
4112 selection_tail.to_display_point(display_map)
4113 }
4114 };
4115
4116 let start_row = cmp::min(tail.row(), head.row());
4117 let end_row = cmp::max(tail.row(), head.row());
4118 let start_column = cmp::min(tail.column(), goal_column);
4119 let end_column = cmp::max(tail.column(), goal_column);
4120 let reversed = start_column < tail.column();
4121
4122 let selection_ranges = (start_row.0..=end_row.0)
4123 .map(DisplayRow)
4124 .filter_map(|row| {
4125 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4126 || start_column <= display_map.line_len(row))
4127 && !display_map.is_block_line(row)
4128 {
4129 let start = display_map
4130 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4131 .to_point(display_map);
4132 let end = display_map
4133 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4134 .to_point(display_map);
4135 if reversed {
4136 Some(end..start)
4137 } else {
4138 Some(start..end)
4139 }
4140 } else {
4141 None
4142 }
4143 })
4144 .collect::<Vec<_>>();
4145 if selection_ranges.is_empty() {
4146 return;
4147 }
4148
4149 let ranges = match columnar_state {
4150 ColumnarSelectionState::FromMouse { .. } => {
4151 let mut non_empty_ranges = selection_ranges
4152 .iter()
4153 .filter(|selection_range| selection_range.start != selection_range.end)
4154 .peekable();
4155 if non_empty_ranges.peek().is_some() {
4156 non_empty_ranges.cloned().collect()
4157 } else {
4158 selection_ranges
4159 }
4160 }
4161 _ => selection_ranges,
4162 };
4163
4164 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4165 s.select_ranges(ranges);
4166 });
4167 cx.notify();
4168 }
4169
4170 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4171 self.selections
4172 .all_adjusted(snapshot)
4173 .iter()
4174 .any(|selection| !selection.is_empty())
4175 }
4176
4177 pub fn has_pending_nonempty_selection(&self) -> bool {
4178 let pending_nonempty_selection = match self.selections.pending_anchor() {
4179 Some(Selection { start, end, .. }) => start != end,
4180 None => false,
4181 };
4182
4183 pending_nonempty_selection
4184 || (self.columnar_selection_state.is_some()
4185 && self.selections.disjoint_anchors().len() > 1)
4186 }
4187
4188 pub fn has_pending_selection(&self) -> bool {
4189 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4190 }
4191
4192 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4193 self.selection_mark_mode = false;
4194 self.selection_drag_state = SelectionDragState::None;
4195
4196 if self.dismiss_menus_and_popups(true, window, cx) {
4197 cx.notify();
4198 return;
4199 }
4200 if self.clear_expanded_diff_hunks(cx) {
4201 cx.notify();
4202 return;
4203 }
4204 if self.show_git_blame_gutter {
4205 self.show_git_blame_gutter = false;
4206 cx.notify();
4207 return;
4208 }
4209
4210 if self.mode.is_full()
4211 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4212 {
4213 cx.notify();
4214 return;
4215 }
4216
4217 cx.propagate();
4218 }
4219
4220 pub fn dismiss_menus_and_popups(
4221 &mut self,
4222 is_user_requested: bool,
4223 window: &mut Window,
4224 cx: &mut Context<Self>,
4225 ) -> bool {
4226 let mut dismissed = false;
4227
4228 dismissed |= self.take_rename(false, window, cx).is_some();
4229 dismissed |= self.hide_blame_popover(true, cx);
4230 dismissed |= hide_hover(self, cx);
4231 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4232 dismissed |= self.hide_context_menu(window, cx).is_some();
4233 dismissed |= self.mouse_context_menu.take().is_some();
4234 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4235 dismissed |= self.snippet_stack.pop().is_some();
4236
4237 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4238 self.dismiss_diagnostics(cx);
4239 dismissed = true;
4240 }
4241
4242 dismissed
4243 }
4244
4245 fn linked_editing_ranges_for(
4246 &self,
4247 selection: Range<text::Anchor>,
4248 cx: &App,
4249 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4250 if self.linked_edit_ranges.is_empty() {
4251 return None;
4252 }
4253 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4254 selection.end.buffer_id.and_then(|end_buffer_id| {
4255 if selection.start.buffer_id != Some(end_buffer_id) {
4256 return None;
4257 }
4258 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4259 let snapshot = buffer.read(cx).snapshot();
4260 self.linked_edit_ranges
4261 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4262 .map(|ranges| (ranges, snapshot, buffer))
4263 })?;
4264 use text::ToOffset as TO;
4265 // find offset from the start of current range to current cursor position
4266 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4267
4268 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4269 let start_difference = start_offset - start_byte_offset;
4270 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4271 let end_difference = end_offset - start_byte_offset;
4272 // Current range has associated linked ranges.
4273 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4274 for range in linked_ranges.iter() {
4275 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4276 let end_offset = start_offset + end_difference;
4277 let start_offset = start_offset + start_difference;
4278 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4279 continue;
4280 }
4281 if self.selections.disjoint_anchor_ranges().any(|s| {
4282 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4283 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4284 {
4285 return false;
4286 }
4287 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4288 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4289 }) {
4290 continue;
4291 }
4292 let start = buffer_snapshot.anchor_after(start_offset);
4293 let end = buffer_snapshot.anchor_after(end_offset);
4294 linked_edits
4295 .entry(buffer.clone())
4296 .or_default()
4297 .push(start..end);
4298 }
4299 Some(linked_edits)
4300 }
4301
4302 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4303 let text: Arc<str> = text.into();
4304
4305 if self.read_only(cx) {
4306 return;
4307 }
4308
4309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4310
4311 self.unfold_buffers_with_selections(cx);
4312
4313 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4314 let mut bracket_inserted = false;
4315 let mut edits = Vec::new();
4316 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4317 let mut new_selections = Vec::with_capacity(selections.len());
4318 let mut new_autoclose_regions = Vec::new();
4319 let snapshot = self.buffer.read(cx).read(cx);
4320 let mut clear_linked_edit_ranges = false;
4321
4322 for (selection, autoclose_region) in
4323 self.selections_with_autoclose_regions(selections, &snapshot)
4324 {
4325 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4326 // Determine if the inserted text matches the opening or closing
4327 // bracket of any of this language's bracket pairs.
4328 let mut bracket_pair = None;
4329 let mut is_bracket_pair_start = false;
4330 let mut is_bracket_pair_end = false;
4331 if !text.is_empty() {
4332 let mut bracket_pair_matching_end = None;
4333 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4334 // and they are removing the character that triggered IME popup.
4335 for (pair, enabled) in scope.brackets() {
4336 if !pair.close && !pair.surround {
4337 continue;
4338 }
4339
4340 if enabled && pair.start.ends_with(text.as_ref()) {
4341 let prefix_len = pair.start.len() - text.len();
4342 let preceding_text_matches_prefix = prefix_len == 0
4343 || (selection.start.column >= (prefix_len as u32)
4344 && snapshot.contains_str_at(
4345 Point::new(
4346 selection.start.row,
4347 selection.start.column - (prefix_len as u32),
4348 ),
4349 &pair.start[..prefix_len],
4350 ));
4351 if preceding_text_matches_prefix {
4352 bracket_pair = Some(pair.clone());
4353 is_bracket_pair_start = true;
4354 break;
4355 }
4356 }
4357 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4358 {
4359 // take first bracket pair matching end, but don't break in case a later bracket
4360 // pair matches start
4361 bracket_pair_matching_end = Some(pair.clone());
4362 }
4363 }
4364 if let Some(end) = bracket_pair_matching_end
4365 && bracket_pair.is_none()
4366 {
4367 bracket_pair = Some(end);
4368 is_bracket_pair_end = true;
4369 }
4370 }
4371
4372 if let Some(bracket_pair) = bracket_pair {
4373 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4374 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4375 let auto_surround =
4376 self.use_auto_surround && snapshot_settings.use_auto_surround;
4377 if selection.is_empty() {
4378 if is_bracket_pair_start {
4379 // If the inserted text is a suffix of an opening bracket and the
4380 // selection is preceded by the rest of the opening bracket, then
4381 // insert the closing bracket.
4382 let following_text_allows_autoclose = snapshot
4383 .chars_at(selection.start)
4384 .next()
4385 .is_none_or(|c| scope.should_autoclose_before(c));
4386
4387 let preceding_text_allows_autoclose = selection.start.column == 0
4388 || snapshot
4389 .reversed_chars_at(selection.start)
4390 .next()
4391 .is_none_or(|c| {
4392 bracket_pair.start != bracket_pair.end
4393 || !snapshot
4394 .char_classifier_at(selection.start)
4395 .is_word(c)
4396 });
4397
4398 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4399 && bracket_pair.start.len() == 1
4400 {
4401 let target = bracket_pair.start.chars().next().unwrap();
4402 let current_line_count = snapshot
4403 .reversed_chars_at(selection.start)
4404 .take_while(|&c| c != '\n')
4405 .filter(|&c| c == target)
4406 .count();
4407 current_line_count % 2 == 1
4408 } else {
4409 false
4410 };
4411
4412 if autoclose
4413 && bracket_pair.close
4414 && following_text_allows_autoclose
4415 && preceding_text_allows_autoclose
4416 && !is_closing_quote
4417 {
4418 let anchor = snapshot.anchor_before(selection.end);
4419 new_selections.push((selection.map(|_| anchor), text.len()));
4420 new_autoclose_regions.push((
4421 anchor,
4422 text.len(),
4423 selection.id,
4424 bracket_pair.clone(),
4425 ));
4426 edits.push((
4427 selection.range(),
4428 format!("{}{}", text, bracket_pair.end).into(),
4429 ));
4430 bracket_inserted = true;
4431 continue;
4432 }
4433 }
4434
4435 if let Some(region) = autoclose_region {
4436 // If the selection is followed by an auto-inserted closing bracket,
4437 // then don't insert that closing bracket again; just move the selection
4438 // past the closing bracket.
4439 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4440 && text.as_ref() == region.pair.end.as_str()
4441 && snapshot.contains_str_at(region.range.end, text.as_ref());
4442 if should_skip {
4443 let anchor = snapshot.anchor_after(selection.end);
4444 new_selections
4445 .push((selection.map(|_| anchor), region.pair.end.len()));
4446 continue;
4447 }
4448 }
4449
4450 let always_treat_brackets_as_autoclosed = snapshot
4451 .language_settings_at(selection.start, cx)
4452 .always_treat_brackets_as_autoclosed;
4453 if always_treat_brackets_as_autoclosed
4454 && is_bracket_pair_end
4455 && snapshot.contains_str_at(selection.end, text.as_ref())
4456 {
4457 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4458 // and the inserted text is a closing bracket and the selection is followed
4459 // by the closing bracket then move the selection past the closing bracket.
4460 let anchor = snapshot.anchor_after(selection.end);
4461 new_selections.push((selection.map(|_| anchor), text.len()));
4462 continue;
4463 }
4464 }
4465 // If an opening bracket is 1 character long and is typed while
4466 // text is selected, then surround that text with the bracket pair.
4467 else if auto_surround
4468 && bracket_pair.surround
4469 && is_bracket_pair_start
4470 && bracket_pair.start.chars().count() == 1
4471 {
4472 edits.push((selection.start..selection.start, text.clone()));
4473 edits.push((
4474 selection.end..selection.end,
4475 bracket_pair.end.as_str().into(),
4476 ));
4477 bracket_inserted = true;
4478 new_selections.push((
4479 Selection {
4480 id: selection.id,
4481 start: snapshot.anchor_after(selection.start),
4482 end: snapshot.anchor_before(selection.end),
4483 reversed: selection.reversed,
4484 goal: selection.goal,
4485 },
4486 0,
4487 ));
4488 continue;
4489 }
4490 }
4491 }
4492
4493 if self.auto_replace_emoji_shortcode
4494 && selection.is_empty()
4495 && text.as_ref().ends_with(':')
4496 && let Some(possible_emoji_short_code) =
4497 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4498 && !possible_emoji_short_code.is_empty()
4499 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4500 {
4501 let emoji_shortcode_start = Point::new(
4502 selection.start.row,
4503 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4504 );
4505
4506 // Remove shortcode from buffer
4507 edits.push((
4508 emoji_shortcode_start..selection.start,
4509 "".to_string().into(),
4510 ));
4511 new_selections.push((
4512 Selection {
4513 id: selection.id,
4514 start: snapshot.anchor_after(emoji_shortcode_start),
4515 end: snapshot.anchor_before(selection.start),
4516 reversed: selection.reversed,
4517 goal: selection.goal,
4518 },
4519 0,
4520 ));
4521
4522 // Insert emoji
4523 let selection_start_anchor = snapshot.anchor_after(selection.start);
4524 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4525 edits.push((selection.start..selection.end, emoji.to_string().into()));
4526
4527 continue;
4528 }
4529
4530 // If not handling any auto-close operation, then just replace the selected
4531 // text with the given input and move the selection to the end of the
4532 // newly inserted text.
4533 let anchor = snapshot.anchor_after(selection.end);
4534 if !self.linked_edit_ranges.is_empty() {
4535 let start_anchor = snapshot.anchor_before(selection.start);
4536
4537 let is_word_char = text.chars().next().is_none_or(|char| {
4538 let classifier = snapshot
4539 .char_classifier_at(start_anchor.to_offset(&snapshot))
4540 .scope_context(Some(CharScopeContext::LinkedEdit));
4541 classifier.is_word(char)
4542 });
4543
4544 if is_word_char {
4545 if let Some(ranges) = self
4546 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4547 {
4548 for (buffer, edits) in ranges {
4549 linked_edits
4550 .entry(buffer.clone())
4551 .or_default()
4552 .extend(edits.into_iter().map(|range| (range, text.clone())));
4553 }
4554 }
4555 } else {
4556 clear_linked_edit_ranges = true;
4557 }
4558 }
4559
4560 new_selections.push((selection.map(|_| anchor), 0));
4561 edits.push((selection.start..selection.end, text.clone()));
4562 }
4563
4564 drop(snapshot);
4565
4566 self.transact(window, cx, |this, window, cx| {
4567 if clear_linked_edit_ranges {
4568 this.linked_edit_ranges.clear();
4569 }
4570 let initial_buffer_versions =
4571 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4572
4573 this.buffer.update(cx, |buffer, cx| {
4574 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4575 });
4576 for (buffer, edits) in linked_edits {
4577 buffer.update(cx, |buffer, cx| {
4578 let snapshot = buffer.snapshot();
4579 let edits = edits
4580 .into_iter()
4581 .map(|(range, text)| {
4582 use text::ToPoint as TP;
4583 let end_point = TP::to_point(&range.end, &snapshot);
4584 let start_point = TP::to_point(&range.start, &snapshot);
4585 (start_point..end_point, text)
4586 })
4587 .sorted_by_key(|(range, _)| range.start);
4588 buffer.edit(edits, None, cx);
4589 })
4590 }
4591 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4592 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4593 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4594 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4595 new_anchor_selections,
4596 &map,
4597 )
4598 .zip(new_selection_deltas)
4599 .map(|(selection, delta)| Selection {
4600 id: selection.id,
4601 start: selection.start + delta,
4602 end: selection.end + delta,
4603 reversed: selection.reversed,
4604 goal: SelectionGoal::None,
4605 })
4606 .collect::<Vec<_>>();
4607
4608 let mut i = 0;
4609 for (position, delta, selection_id, pair) in new_autoclose_regions {
4610 let position = position.to_offset(map.buffer_snapshot()) + delta;
4611 let start = map.buffer_snapshot().anchor_before(position);
4612 let end = map.buffer_snapshot().anchor_after(position);
4613 while let Some(existing_state) = this.autoclose_regions.get(i) {
4614 match existing_state
4615 .range
4616 .start
4617 .cmp(&start, map.buffer_snapshot())
4618 {
4619 Ordering::Less => i += 1,
4620 Ordering::Greater => break,
4621 Ordering::Equal => {
4622 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4623 Ordering::Less => i += 1,
4624 Ordering::Equal => break,
4625 Ordering::Greater => break,
4626 }
4627 }
4628 }
4629 }
4630 this.autoclose_regions.insert(
4631 i,
4632 AutocloseRegion {
4633 selection_id,
4634 range: start..end,
4635 pair,
4636 },
4637 );
4638 }
4639
4640 let had_active_edit_prediction = this.has_active_edit_prediction();
4641 this.change_selections(
4642 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4643 window,
4644 cx,
4645 |s| s.select(new_selections),
4646 );
4647
4648 if !bracket_inserted
4649 && let Some(on_type_format_task) =
4650 this.trigger_on_type_formatting(text.to_string(), window, cx)
4651 {
4652 on_type_format_task.detach_and_log_err(cx);
4653 }
4654
4655 let editor_settings = EditorSettings::get_global(cx);
4656 if bracket_inserted
4657 && (editor_settings.auto_signature_help
4658 || editor_settings.show_signature_help_after_edits)
4659 {
4660 this.show_signature_help(&ShowSignatureHelp, window, cx);
4661 }
4662
4663 let trigger_in_words =
4664 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4665 if this.hard_wrap.is_some() {
4666 let latest: Range<Point> = this.selections.newest(&map).range();
4667 if latest.is_empty()
4668 && this
4669 .buffer()
4670 .read(cx)
4671 .snapshot(cx)
4672 .line_len(MultiBufferRow(latest.start.row))
4673 == latest.start.column
4674 {
4675 this.rewrap_impl(
4676 RewrapOptions {
4677 override_language_settings: true,
4678 preserve_existing_whitespace: true,
4679 },
4680 cx,
4681 )
4682 }
4683 }
4684 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4685 refresh_linked_ranges(this, window, cx);
4686 this.refresh_edit_prediction(true, false, window, cx);
4687 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4688 });
4689 }
4690
4691 fn find_possible_emoji_shortcode_at_position(
4692 snapshot: &MultiBufferSnapshot,
4693 position: Point,
4694 ) -> Option<String> {
4695 let mut chars = Vec::new();
4696 let mut found_colon = false;
4697 for char in snapshot.reversed_chars_at(position).take(100) {
4698 // Found a possible emoji shortcode in the middle of the buffer
4699 if found_colon {
4700 if char.is_whitespace() {
4701 chars.reverse();
4702 return Some(chars.iter().collect());
4703 }
4704 // If the previous character is not a whitespace, we are in the middle of a word
4705 // and we only want to complete the shortcode if the word is made up of other emojis
4706 let mut containing_word = String::new();
4707 for ch in snapshot
4708 .reversed_chars_at(position)
4709 .skip(chars.len() + 1)
4710 .take(100)
4711 {
4712 if ch.is_whitespace() {
4713 break;
4714 }
4715 containing_word.push(ch);
4716 }
4717 let containing_word = containing_word.chars().rev().collect::<String>();
4718 if util::word_consists_of_emojis(containing_word.as_str()) {
4719 chars.reverse();
4720 return Some(chars.iter().collect());
4721 }
4722 }
4723
4724 if char.is_whitespace() || !char.is_ascii() {
4725 return None;
4726 }
4727 if char == ':' {
4728 found_colon = true;
4729 } else {
4730 chars.push(char);
4731 }
4732 }
4733 // Found a possible emoji shortcode at the beginning of the buffer
4734 chars.reverse();
4735 Some(chars.iter().collect())
4736 }
4737
4738 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4739 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4740 self.transact(window, cx, |this, window, cx| {
4741 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4742 let selections = this
4743 .selections
4744 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4745 let multi_buffer = this.buffer.read(cx);
4746 let buffer = multi_buffer.snapshot(cx);
4747 selections
4748 .iter()
4749 .map(|selection| {
4750 let start_point = selection.start.to_point(&buffer);
4751 let mut existing_indent =
4752 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4753 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4754 let start = selection.start;
4755 let end = selection.end;
4756 let selection_is_empty = start == end;
4757 let language_scope = buffer.language_scope_at(start);
4758 let (
4759 comment_delimiter,
4760 doc_delimiter,
4761 insert_extra_newline,
4762 indent_on_newline,
4763 indent_on_extra_newline,
4764 ) = if let Some(language) = &language_scope {
4765 let mut insert_extra_newline =
4766 insert_extra_newline_brackets(&buffer, start..end, language)
4767 || insert_extra_newline_tree_sitter(&buffer, start..end);
4768
4769 // Comment extension on newline is allowed only for cursor selections
4770 let comment_delimiter = maybe!({
4771 if !selection_is_empty {
4772 return None;
4773 }
4774
4775 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4776 return None;
4777 }
4778
4779 let delimiters = language.line_comment_prefixes();
4780 let max_len_of_delimiter =
4781 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
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 let comment_candidate = snapshot
4790 .chars_for_range(range.clone())
4791 .skip(num_of_whitespaces)
4792 .take(max_len_of_delimiter)
4793 .collect::<String>();
4794 let (delimiter, trimmed_len) = delimiters
4795 .iter()
4796 .filter_map(|delimiter| {
4797 let prefix = delimiter.trim_end();
4798 if comment_candidate.starts_with(prefix) {
4799 Some((delimiter, prefix.len()))
4800 } else {
4801 None
4802 }
4803 })
4804 .max_by_key(|(_, len)| *len)?;
4805
4806 if let Some(BlockCommentConfig {
4807 start: block_start, ..
4808 }) = language.block_comment()
4809 {
4810 let block_start_trimmed = block_start.trim_end();
4811 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4812 let line_content = snapshot
4813 .chars_for_range(range)
4814 .skip(num_of_whitespaces)
4815 .take(block_start_trimmed.len())
4816 .collect::<String>();
4817
4818 if line_content.starts_with(block_start_trimmed) {
4819 return None;
4820 }
4821 }
4822 }
4823
4824 let cursor_is_placed_after_comment_marker =
4825 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4826 if cursor_is_placed_after_comment_marker {
4827 Some(delimiter.clone())
4828 } else {
4829 None
4830 }
4831 });
4832
4833 let mut indent_on_newline = IndentSize::spaces(0);
4834 let mut indent_on_extra_newline = IndentSize::spaces(0);
4835
4836 let doc_delimiter = maybe!({
4837 if !selection_is_empty {
4838 return None;
4839 }
4840
4841 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4842 return None;
4843 }
4844
4845 let BlockCommentConfig {
4846 start: start_tag,
4847 end: end_tag,
4848 prefix: delimiter,
4849 tab_size: len,
4850 } = language.documentation_comment()?;
4851 let is_within_block_comment = buffer
4852 .language_scope_at(start_point)
4853 .is_some_and(|scope| scope.override_name() == Some("comment"));
4854 if !is_within_block_comment {
4855 return None;
4856 }
4857
4858 let (snapshot, range) =
4859 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4860
4861 let num_of_whitespaces = snapshot
4862 .chars_for_range(range.clone())
4863 .take_while(|c| c.is_whitespace())
4864 .count();
4865
4866 // 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.
4867 let column = start_point.column;
4868 let cursor_is_after_start_tag = {
4869 let start_tag_len = start_tag.len();
4870 let start_tag_line = snapshot
4871 .chars_for_range(range.clone())
4872 .skip(num_of_whitespaces)
4873 .take(start_tag_len)
4874 .collect::<String>();
4875 if start_tag_line.starts_with(start_tag.as_ref()) {
4876 num_of_whitespaces + start_tag_len <= column as usize
4877 } else {
4878 false
4879 }
4880 };
4881
4882 let cursor_is_after_delimiter = {
4883 let delimiter_trim = delimiter.trim_end();
4884 let delimiter_line = snapshot
4885 .chars_for_range(range.clone())
4886 .skip(num_of_whitespaces)
4887 .take(delimiter_trim.len())
4888 .collect::<String>();
4889 if delimiter_line.starts_with(delimiter_trim) {
4890 num_of_whitespaces + delimiter_trim.len() <= column as usize
4891 } else {
4892 false
4893 }
4894 };
4895
4896 let cursor_is_before_end_tag_if_exists = {
4897 let mut char_position = 0u32;
4898 let mut end_tag_offset = None;
4899
4900 'outer: for chunk in snapshot.text_for_range(range) {
4901 if let Some(byte_pos) = chunk.find(&**end_tag) {
4902 let chars_before_match =
4903 chunk[..byte_pos].chars().count() as u32;
4904 end_tag_offset =
4905 Some(char_position + chars_before_match);
4906 break 'outer;
4907 }
4908 char_position += chunk.chars().count() as u32;
4909 }
4910
4911 if let Some(end_tag_offset) = end_tag_offset {
4912 let cursor_is_before_end_tag = column <= end_tag_offset;
4913 if cursor_is_after_start_tag {
4914 if cursor_is_before_end_tag {
4915 insert_extra_newline = true;
4916 }
4917 let cursor_is_at_start_of_end_tag =
4918 column == end_tag_offset;
4919 if cursor_is_at_start_of_end_tag {
4920 indent_on_extra_newline.len = *len;
4921 }
4922 }
4923 cursor_is_before_end_tag
4924 } else {
4925 true
4926 }
4927 };
4928
4929 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4930 && cursor_is_before_end_tag_if_exists
4931 {
4932 if cursor_is_after_start_tag {
4933 indent_on_newline.len = *len;
4934 }
4935 Some(delimiter.clone())
4936 } else {
4937 None
4938 }
4939 });
4940
4941 (
4942 comment_delimiter,
4943 doc_delimiter,
4944 insert_extra_newline,
4945 indent_on_newline,
4946 indent_on_extra_newline,
4947 )
4948 } else {
4949 (
4950 None,
4951 None,
4952 false,
4953 IndentSize::default(),
4954 IndentSize::default(),
4955 )
4956 };
4957
4958 let prevent_auto_indent = doc_delimiter.is_some();
4959 let delimiter = comment_delimiter.or(doc_delimiter);
4960
4961 let capacity_for_delimiter =
4962 delimiter.as_deref().map(str::len).unwrap_or_default();
4963 let mut new_text = String::with_capacity(
4964 1 + capacity_for_delimiter
4965 + existing_indent.len as usize
4966 + indent_on_newline.len as usize
4967 + indent_on_extra_newline.len as usize,
4968 );
4969 new_text.push('\n');
4970 new_text.extend(existing_indent.chars());
4971 new_text.extend(indent_on_newline.chars());
4972
4973 if let Some(delimiter) = &delimiter {
4974 new_text.push_str(delimiter);
4975 }
4976
4977 if insert_extra_newline {
4978 new_text.push('\n');
4979 new_text.extend(existing_indent.chars());
4980 new_text.extend(indent_on_extra_newline.chars());
4981 }
4982
4983 let anchor = buffer.anchor_after(end);
4984 let new_selection = selection.map(|_| anchor);
4985 (
4986 ((start..end, new_text), prevent_auto_indent),
4987 (insert_extra_newline, new_selection),
4988 )
4989 })
4990 .unzip()
4991 };
4992
4993 let mut auto_indent_edits = Vec::new();
4994 let mut edits = Vec::new();
4995 for (edit, prevent_auto_indent) in edits_with_flags {
4996 if prevent_auto_indent {
4997 edits.push(edit);
4998 } else {
4999 auto_indent_edits.push(edit);
5000 }
5001 }
5002 if !edits.is_empty() {
5003 this.edit(edits, cx);
5004 }
5005 if !auto_indent_edits.is_empty() {
5006 this.edit_with_autoindent(auto_indent_edits, cx);
5007 }
5008
5009 let buffer = this.buffer.read(cx).snapshot(cx);
5010 let new_selections = selection_info
5011 .into_iter()
5012 .map(|(extra_newline_inserted, new_selection)| {
5013 let mut cursor = new_selection.end.to_point(&buffer);
5014 if extra_newline_inserted {
5015 cursor.row -= 1;
5016 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5017 }
5018 new_selection.map(|_| cursor)
5019 })
5020 .collect();
5021
5022 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5023 this.refresh_edit_prediction(true, false, window, cx);
5024 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5025 task.detach_and_log_err(cx);
5026 }
5027 });
5028 }
5029
5030 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5032
5033 let buffer = self.buffer.read(cx);
5034 let snapshot = buffer.snapshot(cx);
5035
5036 let mut edits = Vec::new();
5037 let mut rows = Vec::new();
5038
5039 for (rows_inserted, selection) in self
5040 .selections
5041 .all_adjusted(&self.display_snapshot(cx))
5042 .into_iter()
5043 .enumerate()
5044 {
5045 let cursor = selection.head();
5046 let row = cursor.row;
5047
5048 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5049
5050 let newline = "\n".to_string();
5051 edits.push((start_of_line..start_of_line, newline));
5052
5053 rows.push(row + rows_inserted as u32);
5054 }
5055
5056 self.transact(window, cx, |editor, window, cx| {
5057 editor.edit(edits, cx);
5058
5059 editor.change_selections(Default::default(), window, cx, |s| {
5060 let mut index = 0;
5061 s.move_cursors_with(|map, _, _| {
5062 let row = rows[index];
5063 index += 1;
5064
5065 let point = Point::new(row, 0);
5066 let boundary = map.next_line_boundary(point).1;
5067 let clipped = map.clip_point(boundary, Bias::Left);
5068
5069 (clipped, SelectionGoal::None)
5070 });
5071 });
5072
5073 let mut indent_edits = Vec::new();
5074 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5075 for row in rows {
5076 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5077 for (row, indent) in indents {
5078 if indent.len == 0 {
5079 continue;
5080 }
5081
5082 let text = match indent.kind {
5083 IndentKind::Space => " ".repeat(indent.len as usize),
5084 IndentKind::Tab => "\t".repeat(indent.len as usize),
5085 };
5086 let point = Point::new(row.0, 0);
5087 indent_edits.push((point..point, text));
5088 }
5089 }
5090 editor.edit(indent_edits, cx);
5091 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5092 format.detach_and_log_err(cx);
5093 }
5094 });
5095 }
5096
5097 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5099
5100 let buffer = self.buffer.read(cx);
5101 let snapshot = buffer.snapshot(cx);
5102
5103 let mut edits = Vec::new();
5104 let mut rows = Vec::new();
5105 let mut rows_inserted = 0;
5106
5107 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5108 let cursor = selection.head();
5109 let row = cursor.row;
5110
5111 let point = Point::new(row + 1, 0);
5112 let start_of_line = snapshot.clip_point(point, Bias::Left);
5113
5114 let newline = "\n".to_string();
5115 edits.push((start_of_line..start_of_line, newline));
5116
5117 rows_inserted += 1;
5118 rows.push(row + rows_inserted);
5119 }
5120
5121 self.transact(window, cx, |editor, window, cx| {
5122 editor.edit(edits, cx);
5123
5124 editor.change_selections(Default::default(), window, cx, |s| {
5125 let mut index = 0;
5126 s.move_cursors_with(|map, _, _| {
5127 let row = rows[index];
5128 index += 1;
5129
5130 let point = Point::new(row, 0);
5131 let boundary = map.next_line_boundary(point).1;
5132 let clipped = map.clip_point(boundary, Bias::Left);
5133
5134 (clipped, SelectionGoal::None)
5135 });
5136 });
5137
5138 let mut indent_edits = Vec::new();
5139 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5140 for row in rows {
5141 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5142 for (row, indent) in indents {
5143 if indent.len == 0 {
5144 continue;
5145 }
5146
5147 let text = match indent.kind {
5148 IndentKind::Space => " ".repeat(indent.len as usize),
5149 IndentKind::Tab => "\t".repeat(indent.len as usize),
5150 };
5151 let point = Point::new(row.0, 0);
5152 indent_edits.push((point..point, text));
5153 }
5154 }
5155 editor.edit(indent_edits, cx);
5156 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5157 format.detach_and_log_err(cx);
5158 }
5159 });
5160 }
5161
5162 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5163 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5164 original_indent_columns: Vec::new(),
5165 });
5166 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5167 }
5168
5169 fn insert_with_autoindent_mode(
5170 &mut self,
5171 text: &str,
5172 autoindent_mode: Option<AutoindentMode>,
5173 window: &mut Window,
5174 cx: &mut Context<Self>,
5175 ) {
5176 if self.read_only(cx) {
5177 return;
5178 }
5179
5180 let text: Arc<str> = text.into();
5181 self.transact(window, cx, |this, window, cx| {
5182 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5183 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5184 let anchors = {
5185 let snapshot = buffer.read(cx);
5186 old_selections
5187 .iter()
5188 .map(|s| {
5189 let anchor = snapshot.anchor_after(s.head());
5190 s.map(|_| anchor)
5191 })
5192 .collect::<Vec<_>>()
5193 };
5194 buffer.edit(
5195 old_selections
5196 .iter()
5197 .map(|s| (s.start..s.end, text.clone())),
5198 autoindent_mode,
5199 cx,
5200 );
5201 anchors
5202 });
5203
5204 this.change_selections(Default::default(), window, cx, |s| {
5205 s.select_anchors(selection_anchors);
5206 });
5207
5208 cx.notify();
5209 });
5210 }
5211
5212 fn trigger_completion_on_input(
5213 &mut self,
5214 text: &str,
5215 trigger_in_words: bool,
5216 window: &mut Window,
5217 cx: &mut Context<Self>,
5218 ) {
5219 let completions_source = self
5220 .context_menu
5221 .borrow()
5222 .as_ref()
5223 .and_then(|menu| match menu {
5224 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5225 CodeContextMenu::CodeActions(_) => None,
5226 });
5227
5228 match completions_source {
5229 Some(CompletionsMenuSource::Words { .. }) => {
5230 self.open_or_update_completions_menu(
5231 Some(CompletionsMenuSource::Words {
5232 ignore_threshold: false,
5233 }),
5234 None,
5235 trigger_in_words,
5236 window,
5237 cx,
5238 );
5239 }
5240 _ => self.open_or_update_completions_menu(
5241 None,
5242 Some(text.to_owned()).filter(|x| !x.is_empty()),
5243 true,
5244 window,
5245 cx,
5246 ),
5247 }
5248 }
5249
5250 /// If any empty selections is touching the start of its innermost containing autoclose
5251 /// region, expand it to select the brackets.
5252 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5253 let selections = self
5254 .selections
5255 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5256 let buffer = self.buffer.read(cx).read(cx);
5257 let new_selections = self
5258 .selections_with_autoclose_regions(selections, &buffer)
5259 .map(|(mut selection, region)| {
5260 if !selection.is_empty() {
5261 return selection;
5262 }
5263
5264 if let Some(region) = region {
5265 let mut range = region.range.to_offset(&buffer);
5266 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5267 range.start -= region.pair.start.len();
5268 if buffer.contains_str_at(range.start, ®ion.pair.start)
5269 && buffer.contains_str_at(range.end, ®ion.pair.end)
5270 {
5271 range.end += region.pair.end.len();
5272 selection.start = range.start;
5273 selection.end = range.end;
5274
5275 return selection;
5276 }
5277 }
5278 }
5279
5280 let always_treat_brackets_as_autoclosed = buffer
5281 .language_settings_at(selection.start, cx)
5282 .always_treat_brackets_as_autoclosed;
5283
5284 if !always_treat_brackets_as_autoclosed {
5285 return selection;
5286 }
5287
5288 if let Some(scope) = buffer.language_scope_at(selection.start) {
5289 for (pair, enabled) in scope.brackets() {
5290 if !enabled || !pair.close {
5291 continue;
5292 }
5293
5294 if buffer.contains_str_at(selection.start, &pair.end) {
5295 let pair_start_len = pair.start.len();
5296 if buffer.contains_str_at(
5297 selection.start.saturating_sub_usize(pair_start_len),
5298 &pair.start,
5299 ) {
5300 selection.start -= pair_start_len;
5301 selection.end += pair.end.len();
5302
5303 return selection;
5304 }
5305 }
5306 }
5307 }
5308
5309 selection
5310 })
5311 .collect();
5312
5313 drop(buffer);
5314 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5315 selections.select(new_selections)
5316 });
5317 }
5318
5319 /// Iterate the given selections, and for each one, find the smallest surrounding
5320 /// autoclose region. This uses the ordering of the selections and the autoclose
5321 /// regions to avoid repeated comparisons.
5322 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5323 &'a self,
5324 selections: impl IntoIterator<Item = Selection<D>>,
5325 buffer: &'a MultiBufferSnapshot,
5326 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5327 let mut i = 0;
5328 let mut regions = self.autoclose_regions.as_slice();
5329 selections.into_iter().map(move |selection| {
5330 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5331
5332 let mut enclosing = None;
5333 while let Some(pair_state) = regions.get(i) {
5334 if pair_state.range.end.to_offset(buffer) < range.start {
5335 regions = ®ions[i + 1..];
5336 i = 0;
5337 } else if pair_state.range.start.to_offset(buffer) > range.end {
5338 break;
5339 } else {
5340 if pair_state.selection_id == selection.id {
5341 enclosing = Some(pair_state);
5342 }
5343 i += 1;
5344 }
5345 }
5346
5347 (selection, enclosing)
5348 })
5349 }
5350
5351 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5352 fn invalidate_autoclose_regions(
5353 &mut self,
5354 mut selections: &[Selection<Anchor>],
5355 buffer: &MultiBufferSnapshot,
5356 ) {
5357 self.autoclose_regions.retain(|state| {
5358 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5359 return false;
5360 }
5361
5362 let mut i = 0;
5363 while let Some(selection) = selections.get(i) {
5364 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5365 selections = &selections[1..];
5366 continue;
5367 }
5368 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5369 break;
5370 }
5371 if selection.id == state.selection_id {
5372 return true;
5373 } else {
5374 i += 1;
5375 }
5376 }
5377 false
5378 });
5379 }
5380
5381 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5382 let offset = position.to_offset(buffer);
5383 let (word_range, kind) =
5384 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5385 if offset > word_range.start && kind == Some(CharKind::Word) {
5386 Some(
5387 buffer
5388 .text_for_range(word_range.start..offset)
5389 .collect::<String>(),
5390 )
5391 } else {
5392 None
5393 }
5394 }
5395
5396 pub fn visible_excerpts(
5397 &self,
5398 lsp_related_only: bool,
5399 cx: &mut Context<Editor>,
5400 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5401 let project = self.project().cloned();
5402 let multi_buffer = self.buffer().read(cx);
5403 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5404 let multi_buffer_visible_start = self
5405 .scroll_manager
5406 .anchor()
5407 .anchor
5408 .to_point(&multi_buffer_snapshot);
5409 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5410 multi_buffer_visible_start
5411 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5412 Bias::Left,
5413 );
5414 multi_buffer_snapshot
5415 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5416 .into_iter()
5417 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5418 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5419 if !lsp_related_only {
5420 return Some((
5421 excerpt_id,
5422 (
5423 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5424 buffer.version().clone(),
5425 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5426 ),
5427 ));
5428 }
5429
5430 let project = project.as_ref()?.read(cx);
5431 let buffer_file = project::File::from_dyn(buffer.file())?;
5432 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5433 let worktree_entry = buffer_worktree
5434 .read(cx)
5435 .entry_for_id(buffer_file.project_entry_id()?)?;
5436 if worktree_entry.is_ignored {
5437 None
5438 } else {
5439 Some((
5440 excerpt_id,
5441 (
5442 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5443 buffer.version().clone(),
5444 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5445 ),
5446 ))
5447 }
5448 })
5449 .collect()
5450 }
5451
5452 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5453 TextLayoutDetails {
5454 text_system: window.text_system().clone(),
5455 editor_style: self.style.clone().unwrap(),
5456 rem_size: window.rem_size(),
5457 scroll_anchor: self.scroll_manager.anchor(),
5458 visible_rows: self.visible_line_count(),
5459 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5460 }
5461 }
5462
5463 fn trigger_on_type_formatting(
5464 &self,
5465 input: String,
5466 window: &mut Window,
5467 cx: &mut Context<Self>,
5468 ) -> Option<Task<Result<()>>> {
5469 if input.chars().count() != 1 {
5470 return None;
5471 }
5472
5473 let project = self.project()?;
5474 let position = self.selections.newest_anchor().head();
5475 let (buffer, buffer_position) = self
5476 .buffer
5477 .read(cx)
5478 .text_anchor_for_position(position, cx)?;
5479
5480 let settings = language_settings::language_settings(
5481 buffer
5482 .read(cx)
5483 .language_at(buffer_position)
5484 .map(|l| l.name()),
5485 buffer.read(cx).file(),
5486 cx,
5487 );
5488 if !settings.use_on_type_format {
5489 return None;
5490 }
5491
5492 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5493 // hence we do LSP request & edit on host side only — add formats to host's history.
5494 let push_to_lsp_host_history = true;
5495 // If this is not the host, append its history with new edits.
5496 let push_to_client_history = project.read(cx).is_via_collab();
5497
5498 let on_type_formatting = project.update(cx, |project, cx| {
5499 project.on_type_format(
5500 buffer.clone(),
5501 buffer_position,
5502 input,
5503 push_to_lsp_host_history,
5504 cx,
5505 )
5506 });
5507 Some(cx.spawn_in(window, async move |editor, cx| {
5508 if let Some(transaction) = on_type_formatting.await? {
5509 if push_to_client_history {
5510 buffer
5511 .update(cx, |buffer, _| {
5512 buffer.push_transaction(transaction, Instant::now());
5513 buffer.finalize_last_transaction();
5514 })
5515 .ok();
5516 }
5517 editor.update(cx, |editor, cx| {
5518 editor.refresh_document_highlights(cx);
5519 })?;
5520 }
5521 Ok(())
5522 }))
5523 }
5524
5525 pub fn show_word_completions(
5526 &mut self,
5527 _: &ShowWordCompletions,
5528 window: &mut Window,
5529 cx: &mut Context<Self>,
5530 ) {
5531 self.open_or_update_completions_menu(
5532 Some(CompletionsMenuSource::Words {
5533 ignore_threshold: true,
5534 }),
5535 None,
5536 false,
5537 window,
5538 cx,
5539 );
5540 }
5541
5542 pub fn show_completions(
5543 &mut self,
5544 _: &ShowCompletions,
5545 window: &mut Window,
5546 cx: &mut Context<Self>,
5547 ) {
5548 self.open_or_update_completions_menu(None, None, false, window, cx);
5549 }
5550
5551 fn open_or_update_completions_menu(
5552 &mut self,
5553 requested_source: Option<CompletionsMenuSource>,
5554 trigger: Option<String>,
5555 trigger_in_words: bool,
5556 window: &mut Window,
5557 cx: &mut Context<Self>,
5558 ) {
5559 if self.pending_rename.is_some() {
5560 return;
5561 }
5562
5563 let completions_source = self
5564 .context_menu
5565 .borrow()
5566 .as_ref()
5567 .and_then(|menu| match menu {
5568 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5569 CodeContextMenu::CodeActions(_) => None,
5570 });
5571
5572 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5573
5574 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5575 // inserted and selected. To handle that case, the start of the selection is used so that
5576 // the menu starts with all choices.
5577 let position = self
5578 .selections
5579 .newest_anchor()
5580 .start
5581 .bias_right(&multibuffer_snapshot);
5582 if position.diff_base_anchor.is_some() {
5583 return;
5584 }
5585 let buffer_position = multibuffer_snapshot.anchor_before(position);
5586 let Some(buffer) = buffer_position
5587 .text_anchor
5588 .buffer_id
5589 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5590 else {
5591 return;
5592 };
5593 let buffer_snapshot = buffer.read(cx).snapshot();
5594
5595 let menu_is_open = matches!(
5596 self.context_menu.borrow().as_ref(),
5597 Some(CodeContextMenu::Completions(_))
5598 );
5599
5600 let language = buffer_snapshot
5601 .language_at(buffer_position.text_anchor)
5602 .map(|language| language.name());
5603
5604 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5605 let completion_settings = language_settings.completions.clone();
5606
5607 let show_completions_on_input = self
5608 .show_completions_on_input_override
5609 .unwrap_or(language_settings.show_completions_on_input);
5610 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5611 return;
5612 }
5613
5614 let query: Option<Arc<String>> =
5615 Self::completion_query(&multibuffer_snapshot, buffer_position)
5616 .map(|query| query.into());
5617
5618 drop(multibuffer_snapshot);
5619
5620 // Hide the current completions menu when query is empty. Without this, cached
5621 // completions from before the trigger char may be reused (#32774).
5622 if query.is_none() && menu_is_open {
5623 self.hide_context_menu(window, cx);
5624 }
5625
5626 let mut ignore_word_threshold = false;
5627 let provider = match requested_source {
5628 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5629 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5630 ignore_word_threshold = ignore_threshold;
5631 None
5632 }
5633 Some(CompletionsMenuSource::SnippetChoices)
5634 | Some(CompletionsMenuSource::SnippetsOnly) => {
5635 log::error!("bug: SnippetChoices requested_source is not handled");
5636 None
5637 }
5638 };
5639
5640 let sort_completions = provider
5641 .as_ref()
5642 .is_some_and(|provider| provider.sort_completions());
5643
5644 let filter_completions = provider
5645 .as_ref()
5646 .is_none_or(|provider| provider.filter_completions());
5647
5648 let was_snippets_only = matches!(
5649 completions_source,
5650 Some(CompletionsMenuSource::SnippetsOnly)
5651 );
5652
5653 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5654 if filter_completions {
5655 menu.filter(
5656 query.clone().unwrap_or_default(),
5657 buffer_position.text_anchor,
5658 &buffer,
5659 provider.clone(),
5660 window,
5661 cx,
5662 );
5663 }
5664 // When `is_incomplete` is false, no need to re-query completions when the current query
5665 // is a suffix of the initial query.
5666 let was_complete = !menu.is_incomplete;
5667 if was_complete && !was_snippets_only {
5668 // If the new query is a suffix of the old query (typing more characters) and
5669 // the previous result was complete, the existing completions can be filtered.
5670 //
5671 // Note that snippet completions are always complete.
5672 let query_matches = match (&menu.initial_query, &query) {
5673 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5674 (None, _) => true,
5675 _ => false,
5676 };
5677 if query_matches {
5678 let position_matches = if menu.initial_position == position {
5679 true
5680 } else {
5681 let snapshot = self.buffer.read(cx).read(cx);
5682 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5683 };
5684 if position_matches {
5685 return;
5686 }
5687 }
5688 }
5689 };
5690
5691 let Anchor {
5692 excerpt_id: buffer_excerpt_id,
5693 text_anchor: buffer_position,
5694 ..
5695 } = buffer_position;
5696
5697 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5698 buffer_snapshot.surrounding_word(buffer_position, None)
5699 {
5700 let word_to_exclude = buffer_snapshot
5701 .text_for_range(word_range.clone())
5702 .collect::<String>();
5703 (
5704 buffer_snapshot.anchor_before(word_range.start)
5705 ..buffer_snapshot.anchor_after(buffer_position),
5706 Some(word_to_exclude),
5707 )
5708 } else {
5709 (buffer_position..buffer_position, None)
5710 };
5711
5712 let show_completion_documentation = buffer_snapshot
5713 .settings_at(buffer_position, cx)
5714 .show_completion_documentation;
5715
5716 // The document can be large, so stay in reasonable bounds when searching for words,
5717 // otherwise completion pop-up might be slow to appear.
5718 const WORD_LOOKUP_ROWS: u32 = 5_000;
5719 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5720 let min_word_search = buffer_snapshot.clip_point(
5721 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5722 Bias::Left,
5723 );
5724 let max_word_search = buffer_snapshot.clip_point(
5725 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5726 Bias::Right,
5727 );
5728 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5729 ..buffer_snapshot.point_to_offset(max_word_search);
5730
5731 let skip_digits = query
5732 .as_ref()
5733 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5734
5735 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5736 trigger.as_ref().is_none_or(|trigger| {
5737 provider.is_completion_trigger(
5738 &buffer,
5739 position.text_anchor,
5740 trigger,
5741 trigger_in_words,
5742 cx,
5743 )
5744 })
5745 });
5746
5747 let provider_responses = if let Some(provider) = &provider
5748 && load_provider_completions
5749 {
5750 let trigger_character =
5751 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5752 let completion_context = CompletionContext {
5753 trigger_kind: match &trigger_character {
5754 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5755 None => CompletionTriggerKind::INVOKED,
5756 },
5757 trigger_character,
5758 };
5759
5760 provider.completions(
5761 buffer_excerpt_id,
5762 &buffer,
5763 buffer_position,
5764 completion_context,
5765 window,
5766 cx,
5767 )
5768 } else {
5769 Task::ready(Ok(Vec::new()))
5770 };
5771
5772 let load_word_completions = if !self.word_completions_enabled {
5773 false
5774 } else if requested_source
5775 == Some(CompletionsMenuSource::Words {
5776 ignore_threshold: true,
5777 })
5778 {
5779 true
5780 } else {
5781 load_provider_completions
5782 && completion_settings.words != WordsCompletionMode::Disabled
5783 && (ignore_word_threshold || {
5784 let words_min_length = completion_settings.words_min_length;
5785 // check whether word has at least `words_min_length` characters
5786 let query_chars = query.iter().flat_map(|q| q.chars());
5787 query_chars.take(words_min_length).count() == words_min_length
5788 })
5789 };
5790
5791 let mut words = if load_word_completions {
5792 cx.background_spawn({
5793 let buffer_snapshot = buffer_snapshot.clone();
5794 async move {
5795 buffer_snapshot.words_in_range(WordsQuery {
5796 fuzzy_contents: None,
5797 range: word_search_range,
5798 skip_digits,
5799 })
5800 }
5801 })
5802 } else {
5803 Task::ready(BTreeMap::default())
5804 };
5805
5806 let snippets = if let Some(provider) = &provider
5807 && provider.show_snippets()
5808 && let Some(project) = self.project()
5809 {
5810 let char_classifier = buffer_snapshot
5811 .char_classifier_at(buffer_position)
5812 .scope_context(Some(CharScopeContext::Completion));
5813 project.update(cx, |project, cx| {
5814 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5815 })
5816 } else {
5817 Task::ready(Ok(CompletionResponse {
5818 completions: Vec::new(),
5819 display_options: Default::default(),
5820 is_incomplete: false,
5821 }))
5822 };
5823
5824 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5825
5826 let id = post_inc(&mut self.next_completion_id);
5827 let task = cx.spawn_in(window, async move |editor, cx| {
5828 let Ok(()) = editor.update(cx, |this, _| {
5829 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5830 }) else {
5831 return;
5832 };
5833
5834 // TODO: Ideally completions from different sources would be selectively re-queried, so
5835 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5836 let mut completions = Vec::new();
5837 let mut is_incomplete = false;
5838 let mut display_options: Option<CompletionDisplayOptions> = None;
5839 if let Some(provider_responses) = provider_responses.await.log_err()
5840 && !provider_responses.is_empty()
5841 {
5842 for response in provider_responses {
5843 completions.extend(response.completions);
5844 is_incomplete = is_incomplete || response.is_incomplete;
5845 match display_options.as_mut() {
5846 None => {
5847 display_options = Some(response.display_options);
5848 }
5849 Some(options) => options.merge(&response.display_options),
5850 }
5851 }
5852 if completion_settings.words == WordsCompletionMode::Fallback {
5853 words = Task::ready(BTreeMap::default());
5854 }
5855 }
5856 let display_options = display_options.unwrap_or_default();
5857
5858 let mut words = words.await;
5859 if let Some(word_to_exclude) = &word_to_exclude {
5860 words.remove(word_to_exclude);
5861 }
5862 for lsp_completion in &completions {
5863 words.remove(&lsp_completion.new_text);
5864 }
5865 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5866 replace_range: word_replace_range.clone(),
5867 new_text: word.clone(),
5868 label: CodeLabel::plain(word, None),
5869 match_start: None,
5870 snippet_deduplication_key: None,
5871 icon_path: None,
5872 documentation: None,
5873 source: CompletionSource::BufferWord {
5874 word_range,
5875 resolved: false,
5876 },
5877 insert_text_mode: Some(InsertTextMode::AS_IS),
5878 confirm: None,
5879 }));
5880
5881 completions.extend(
5882 snippets
5883 .await
5884 .into_iter()
5885 .flat_map(|response| response.completions),
5886 );
5887
5888 let menu = if completions.is_empty() {
5889 None
5890 } else {
5891 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5892 let languages = editor
5893 .workspace
5894 .as_ref()
5895 .and_then(|(workspace, _)| workspace.upgrade())
5896 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5897 let menu = CompletionsMenu::new(
5898 id,
5899 requested_source.unwrap_or(if load_provider_completions {
5900 CompletionsMenuSource::Normal
5901 } else {
5902 CompletionsMenuSource::SnippetsOnly
5903 }),
5904 sort_completions,
5905 show_completion_documentation,
5906 position,
5907 query.clone(),
5908 is_incomplete,
5909 buffer.clone(),
5910 completions.into(),
5911 editor
5912 .context_menu()
5913 .borrow_mut()
5914 .as_ref()
5915 .map(|menu| menu.primary_scroll_handle()),
5916 display_options,
5917 snippet_sort_order,
5918 languages,
5919 language,
5920 cx,
5921 );
5922
5923 let query = if filter_completions { query } else { None };
5924 let matches_task = menu.do_async_filtering(
5925 query.unwrap_or_default(),
5926 buffer_position,
5927 &buffer,
5928 cx,
5929 );
5930 (menu, matches_task)
5931 }) else {
5932 return;
5933 };
5934
5935 let matches = matches_task.await;
5936
5937 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5938 // Newer menu already set, so exit.
5939 if let Some(CodeContextMenu::Completions(prev_menu)) =
5940 editor.context_menu.borrow().as_ref()
5941 && prev_menu.id > id
5942 {
5943 return;
5944 };
5945
5946 // Only valid to take prev_menu because either the new menu is immediately set
5947 // below, or the menu is hidden.
5948 if let Some(CodeContextMenu::Completions(prev_menu)) =
5949 editor.context_menu.borrow_mut().take()
5950 {
5951 let position_matches =
5952 if prev_menu.initial_position == menu.initial_position {
5953 true
5954 } else {
5955 let snapshot = editor.buffer.read(cx).read(cx);
5956 prev_menu.initial_position.to_offset(&snapshot)
5957 == menu.initial_position.to_offset(&snapshot)
5958 };
5959 if position_matches {
5960 // Preserve markdown cache before `set_filter_results` because it will
5961 // try to populate the documentation cache.
5962 menu.preserve_markdown_cache(prev_menu);
5963 }
5964 };
5965
5966 menu.set_filter_results(matches, provider, window, cx);
5967 }) else {
5968 return;
5969 };
5970
5971 menu.visible().then_some(menu)
5972 };
5973
5974 editor
5975 .update_in(cx, |editor, window, cx| {
5976 if editor.focus_handle.is_focused(window)
5977 && let Some(menu) = menu
5978 {
5979 *editor.context_menu.borrow_mut() =
5980 Some(CodeContextMenu::Completions(menu));
5981
5982 crate::hover_popover::hide_hover(editor, cx);
5983 if editor.show_edit_predictions_in_menu() {
5984 editor.update_visible_edit_prediction(window, cx);
5985 } else {
5986 editor.discard_edit_prediction(false, cx);
5987 }
5988
5989 cx.notify();
5990 return;
5991 }
5992
5993 if editor.completion_tasks.len() <= 1 {
5994 // If there are no more completion tasks and the last menu was empty, we should hide it.
5995 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5996 // If it was already hidden and we don't show edit predictions in the menu,
5997 // we should also show the edit prediction when available.
5998 if was_hidden && editor.show_edit_predictions_in_menu() {
5999 editor.update_visible_edit_prediction(window, cx);
6000 }
6001 }
6002 })
6003 .ok();
6004 });
6005
6006 self.completion_tasks.push((id, task));
6007 }
6008
6009 #[cfg(feature = "test-support")]
6010 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6011 let menu = self.context_menu.borrow();
6012 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6013 let completions = menu.completions.borrow();
6014 Some(completions.to_vec())
6015 } else {
6016 None
6017 }
6018 }
6019
6020 pub fn with_completions_menu_matching_id<R>(
6021 &self,
6022 id: CompletionId,
6023 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6024 ) -> R {
6025 let mut context_menu = self.context_menu.borrow_mut();
6026 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6027 return f(None);
6028 };
6029 if completions_menu.id != id {
6030 return f(None);
6031 }
6032 f(Some(completions_menu))
6033 }
6034
6035 pub fn confirm_completion(
6036 &mut self,
6037 action: &ConfirmCompletion,
6038 window: &mut Window,
6039 cx: &mut Context<Self>,
6040 ) -> Option<Task<Result<()>>> {
6041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6042 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6043 }
6044
6045 pub fn confirm_completion_insert(
6046 &mut self,
6047 _: &ConfirmCompletionInsert,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) -> Option<Task<Result<()>>> {
6051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6052 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6053 }
6054
6055 pub fn confirm_completion_replace(
6056 &mut self,
6057 _: &ConfirmCompletionReplace,
6058 window: &mut Window,
6059 cx: &mut Context<Self>,
6060 ) -> Option<Task<Result<()>>> {
6061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6062 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6063 }
6064
6065 pub fn compose_completion(
6066 &mut self,
6067 action: &ComposeCompletion,
6068 window: &mut Window,
6069 cx: &mut Context<Self>,
6070 ) -> Option<Task<Result<()>>> {
6071 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6072 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6073 }
6074
6075 fn do_completion(
6076 &mut self,
6077 item_ix: Option<usize>,
6078 intent: CompletionIntent,
6079 window: &mut Window,
6080 cx: &mut Context<Editor>,
6081 ) -> Option<Task<Result<()>>> {
6082 use language::ToOffset as _;
6083
6084 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6085 else {
6086 return None;
6087 };
6088
6089 let candidate_id = {
6090 let entries = completions_menu.entries.borrow();
6091 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6092 if self.show_edit_predictions_in_menu() {
6093 self.discard_edit_prediction(true, cx);
6094 }
6095 mat.candidate_id
6096 };
6097
6098 let completion = completions_menu
6099 .completions
6100 .borrow()
6101 .get(candidate_id)?
6102 .clone();
6103 cx.stop_propagation();
6104
6105 let buffer_handle = completions_menu.buffer.clone();
6106
6107 let CompletionEdit {
6108 new_text,
6109 snippet,
6110 replace_range,
6111 } = process_completion_for_edit(
6112 &completion,
6113 intent,
6114 &buffer_handle,
6115 &completions_menu.initial_position.text_anchor,
6116 cx,
6117 );
6118
6119 let buffer = buffer_handle.read(cx);
6120 let snapshot = self.buffer.read(cx).snapshot(cx);
6121 let newest_anchor = self.selections.newest_anchor();
6122 let replace_range_multibuffer = {
6123 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6124 excerpt.map_range_from_buffer(replace_range.clone())
6125 };
6126 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6127 return None;
6128 }
6129
6130 let old_text = buffer
6131 .text_for_range(replace_range.clone())
6132 .collect::<String>();
6133 let lookbehind = newest_anchor
6134 .start
6135 .text_anchor
6136 .to_offset(buffer)
6137 .saturating_sub(replace_range.start.0);
6138 let lookahead = replace_range
6139 .end
6140 .0
6141 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6142 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6143 let suffix = &old_text[lookbehind.min(old_text.len())..];
6144
6145 let selections = self
6146 .selections
6147 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6148 let mut ranges = Vec::new();
6149 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6150
6151 for selection in &selections {
6152 let range = if selection.id == newest_anchor.id {
6153 replace_range_multibuffer.clone()
6154 } else {
6155 let mut range = selection.range();
6156
6157 // if prefix is present, don't duplicate it
6158 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6159 range.start = range.start.saturating_sub_usize(lookbehind);
6160
6161 // if suffix is also present, mimic the newest cursor and replace it
6162 if selection.id != newest_anchor.id
6163 && snapshot.contains_str_at(range.end, suffix)
6164 {
6165 range.end += lookahead;
6166 }
6167 }
6168 range
6169 };
6170
6171 ranges.push(range.clone());
6172
6173 if !self.linked_edit_ranges.is_empty() {
6174 let start_anchor = snapshot.anchor_before(range.start);
6175 let end_anchor = snapshot.anchor_after(range.end);
6176 if let Some(ranges) = self
6177 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6178 {
6179 for (buffer, edits) in ranges {
6180 linked_edits
6181 .entry(buffer.clone())
6182 .or_default()
6183 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6184 }
6185 }
6186 }
6187 }
6188
6189 let common_prefix_len = old_text
6190 .chars()
6191 .zip(new_text.chars())
6192 .take_while(|(a, b)| a == b)
6193 .map(|(a, _)| a.len_utf8())
6194 .sum::<usize>();
6195
6196 cx.emit(EditorEvent::InputHandled {
6197 utf16_range_to_replace: None,
6198 text: new_text[common_prefix_len..].into(),
6199 });
6200
6201 self.transact(window, cx, |editor, window, cx| {
6202 if let Some(mut snippet) = snippet {
6203 snippet.text = new_text.to_string();
6204 editor
6205 .insert_snippet(&ranges, snippet, window, cx)
6206 .log_err();
6207 } else {
6208 editor.buffer.update(cx, |multi_buffer, cx| {
6209 let auto_indent = match completion.insert_text_mode {
6210 Some(InsertTextMode::AS_IS) => None,
6211 _ => editor.autoindent_mode.clone(),
6212 };
6213 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6214 multi_buffer.edit(edits, auto_indent, cx);
6215 });
6216 }
6217 for (buffer, edits) in linked_edits {
6218 buffer.update(cx, |buffer, cx| {
6219 let snapshot = buffer.snapshot();
6220 let edits = edits
6221 .into_iter()
6222 .map(|(range, text)| {
6223 use text::ToPoint as TP;
6224 let end_point = TP::to_point(&range.end, &snapshot);
6225 let start_point = TP::to_point(&range.start, &snapshot);
6226 (start_point..end_point, text)
6227 })
6228 .sorted_by_key(|(range, _)| range.start);
6229 buffer.edit(edits, None, cx);
6230 })
6231 }
6232
6233 editor.refresh_edit_prediction(true, false, window, cx);
6234 });
6235 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6236
6237 let show_new_completions_on_confirm = completion
6238 .confirm
6239 .as_ref()
6240 .is_some_and(|confirm| confirm(intent, window, cx));
6241 if show_new_completions_on_confirm {
6242 self.open_or_update_completions_menu(None, None, false, window, cx);
6243 }
6244
6245 let provider = self.completion_provider.as_ref()?;
6246
6247 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6248 let command = lsp_store.as_ref().and_then(|lsp_store| {
6249 let CompletionSource::Lsp {
6250 lsp_completion,
6251 server_id,
6252 ..
6253 } = &completion.source
6254 else {
6255 return None;
6256 };
6257 let lsp_command = lsp_completion.command.as_ref()?;
6258 let available_commands = lsp_store
6259 .read(cx)
6260 .lsp_server_capabilities
6261 .get(server_id)
6262 .and_then(|server_capabilities| {
6263 server_capabilities
6264 .execute_command_provider
6265 .as_ref()
6266 .map(|options| options.commands.as_slice())
6267 })?;
6268 if available_commands.contains(&lsp_command.command) {
6269 Some(CodeAction {
6270 server_id: *server_id,
6271 range: language::Anchor::MIN..language::Anchor::MIN,
6272 lsp_action: LspAction::Command(lsp_command.clone()),
6273 resolved: false,
6274 })
6275 } else {
6276 None
6277 }
6278 });
6279
6280 drop(completion);
6281 let apply_edits = provider.apply_additional_edits_for_completion(
6282 buffer_handle.clone(),
6283 completions_menu.completions.clone(),
6284 candidate_id,
6285 true,
6286 cx,
6287 );
6288
6289 let editor_settings = EditorSettings::get_global(cx);
6290 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6291 // After the code completion is finished, users often want to know what signatures are needed.
6292 // so we should automatically call signature_help
6293 self.show_signature_help(&ShowSignatureHelp, window, cx);
6294 }
6295
6296 Some(cx.spawn_in(window, async move |editor, cx| {
6297 apply_edits.await?;
6298
6299 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6300 let title = command.lsp_action.title().to_owned();
6301 let project_transaction = lsp_store
6302 .update(cx, |lsp_store, cx| {
6303 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6304 })?
6305 .await
6306 .context("applying post-completion command")?;
6307 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6308 Self::open_project_transaction(
6309 &editor,
6310 workspace.downgrade(),
6311 project_transaction,
6312 title,
6313 cx,
6314 )
6315 .await?;
6316 }
6317 }
6318
6319 Ok(())
6320 }))
6321 }
6322
6323 pub fn toggle_code_actions(
6324 &mut self,
6325 action: &ToggleCodeActions,
6326 window: &mut Window,
6327 cx: &mut Context<Self>,
6328 ) {
6329 let quick_launch = action.quick_launch;
6330 let mut context_menu = self.context_menu.borrow_mut();
6331 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6332 if code_actions.deployed_from == action.deployed_from {
6333 // Toggle if we're selecting the same one
6334 *context_menu = None;
6335 cx.notify();
6336 return;
6337 } else {
6338 // Otherwise, clear it and start a new one
6339 *context_menu = None;
6340 cx.notify();
6341 }
6342 }
6343 drop(context_menu);
6344 let snapshot = self.snapshot(window, cx);
6345 let deployed_from = action.deployed_from.clone();
6346 let action = action.clone();
6347 self.completion_tasks.clear();
6348 self.discard_edit_prediction(false, cx);
6349
6350 let multibuffer_point = match &action.deployed_from {
6351 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6352 DisplayPoint::new(*row, 0).to_point(&snapshot)
6353 }
6354 _ => self
6355 .selections
6356 .newest::<Point>(&snapshot.display_snapshot)
6357 .head(),
6358 };
6359 let Some((buffer, buffer_row)) = snapshot
6360 .buffer_snapshot()
6361 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6362 .and_then(|(buffer_snapshot, range)| {
6363 self.buffer()
6364 .read(cx)
6365 .buffer(buffer_snapshot.remote_id())
6366 .map(|buffer| (buffer, range.start.row))
6367 })
6368 else {
6369 return;
6370 };
6371 let buffer_id = buffer.read(cx).remote_id();
6372 let tasks = self
6373 .tasks
6374 .get(&(buffer_id, buffer_row))
6375 .map(|t| Arc::new(t.to_owned()));
6376
6377 if !self.focus_handle.is_focused(window) {
6378 return;
6379 }
6380 let project = self.project.clone();
6381
6382 let code_actions_task = match deployed_from {
6383 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6384 _ => self.code_actions(buffer_row, window, cx),
6385 };
6386
6387 let runnable_task = match deployed_from {
6388 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6389 _ => {
6390 let mut task_context_task = Task::ready(None);
6391 if let Some(tasks) = &tasks
6392 && let Some(project) = project
6393 {
6394 task_context_task =
6395 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6396 }
6397
6398 cx.spawn_in(window, {
6399 let buffer = buffer.clone();
6400 async move |editor, cx| {
6401 let task_context = task_context_task.await;
6402
6403 let resolved_tasks =
6404 tasks
6405 .zip(task_context.clone())
6406 .map(|(tasks, task_context)| ResolvedTasks {
6407 templates: tasks.resolve(&task_context).collect(),
6408 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6409 multibuffer_point.row,
6410 tasks.column,
6411 )),
6412 });
6413 let debug_scenarios = editor
6414 .update(cx, |editor, cx| {
6415 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6416 })?
6417 .await;
6418 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6419 }
6420 })
6421 }
6422 };
6423
6424 cx.spawn_in(window, async move |editor, cx| {
6425 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6426 let code_actions = code_actions_task.await;
6427 let spawn_straight_away = quick_launch
6428 && resolved_tasks
6429 .as_ref()
6430 .is_some_and(|tasks| tasks.templates.len() == 1)
6431 && code_actions
6432 .as_ref()
6433 .is_none_or(|actions| actions.is_empty())
6434 && debug_scenarios.is_empty();
6435
6436 editor.update_in(cx, |editor, window, cx| {
6437 crate::hover_popover::hide_hover(editor, cx);
6438 let actions = CodeActionContents::new(
6439 resolved_tasks,
6440 code_actions,
6441 debug_scenarios,
6442 task_context.unwrap_or_default(),
6443 );
6444
6445 // Don't show the menu if there are no actions available
6446 if actions.is_empty() {
6447 cx.notify();
6448 return Task::ready(Ok(()));
6449 }
6450
6451 *editor.context_menu.borrow_mut() =
6452 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6453 buffer,
6454 actions,
6455 selected_item: Default::default(),
6456 scroll_handle: UniformListScrollHandle::default(),
6457 deployed_from,
6458 }));
6459 cx.notify();
6460 if spawn_straight_away
6461 && let Some(task) = editor.confirm_code_action(
6462 &ConfirmCodeAction { item_ix: Some(0) },
6463 window,
6464 cx,
6465 )
6466 {
6467 return task;
6468 }
6469
6470 Task::ready(Ok(()))
6471 })
6472 })
6473 .detach_and_log_err(cx);
6474 }
6475
6476 fn debug_scenarios(
6477 &mut self,
6478 resolved_tasks: &Option<ResolvedTasks>,
6479 buffer: &Entity<Buffer>,
6480 cx: &mut App,
6481 ) -> Task<Vec<task::DebugScenario>> {
6482 maybe!({
6483 let project = self.project()?;
6484 let dap_store = project.read(cx).dap_store();
6485 let mut scenarios = vec![];
6486 let resolved_tasks = resolved_tasks.as_ref()?;
6487 let buffer = buffer.read(cx);
6488 let language = buffer.language()?;
6489 let file = buffer.file();
6490 let debug_adapter = language_settings(language.name().into(), file, cx)
6491 .debuggers
6492 .first()
6493 .map(SharedString::from)
6494 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6495
6496 dap_store.update(cx, |dap_store, cx| {
6497 for (_, task) in &resolved_tasks.templates {
6498 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6499 task.original_task().clone(),
6500 debug_adapter.clone().into(),
6501 task.display_label().to_owned().into(),
6502 cx,
6503 );
6504 scenarios.push(maybe_scenario);
6505 }
6506 });
6507 Some(cx.background_spawn(async move {
6508 futures::future::join_all(scenarios)
6509 .await
6510 .into_iter()
6511 .flatten()
6512 .collect::<Vec<_>>()
6513 }))
6514 })
6515 .unwrap_or_else(|| Task::ready(vec![]))
6516 }
6517
6518 fn code_actions(
6519 &mut self,
6520 buffer_row: u32,
6521 window: &mut Window,
6522 cx: &mut Context<Self>,
6523 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6524 let mut task = self.code_actions_task.take();
6525 cx.spawn_in(window, async move |editor, cx| {
6526 while let Some(prev_task) = task {
6527 prev_task.await.log_err();
6528 task = editor
6529 .update(cx, |this, _| this.code_actions_task.take())
6530 .ok()?;
6531 }
6532
6533 editor
6534 .update(cx, |editor, cx| {
6535 editor
6536 .available_code_actions
6537 .clone()
6538 .and_then(|(location, code_actions)| {
6539 let snapshot = location.buffer.read(cx).snapshot();
6540 let point_range = location.range.to_point(&snapshot);
6541 let point_range = point_range.start.row..=point_range.end.row;
6542 if point_range.contains(&buffer_row) {
6543 Some(code_actions)
6544 } else {
6545 None
6546 }
6547 })
6548 })
6549 .ok()
6550 .flatten()
6551 })
6552 }
6553
6554 pub fn confirm_code_action(
6555 &mut self,
6556 action: &ConfirmCodeAction,
6557 window: &mut Window,
6558 cx: &mut Context<Self>,
6559 ) -> Option<Task<Result<()>>> {
6560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6561
6562 let actions_menu =
6563 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6564 menu
6565 } else {
6566 return None;
6567 };
6568
6569 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6570 let action = actions_menu.actions.get(action_ix)?;
6571 let title = action.label();
6572 let buffer = actions_menu.buffer;
6573 let workspace = self.workspace()?;
6574
6575 match action {
6576 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6577 workspace.update(cx, |workspace, cx| {
6578 workspace.schedule_resolved_task(
6579 task_source_kind,
6580 resolved_task,
6581 false,
6582 window,
6583 cx,
6584 );
6585
6586 Some(Task::ready(Ok(())))
6587 })
6588 }
6589 CodeActionsItem::CodeAction {
6590 excerpt_id,
6591 action,
6592 provider,
6593 } => {
6594 let apply_code_action =
6595 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6596 let workspace = workspace.downgrade();
6597 Some(cx.spawn_in(window, async move |editor, cx| {
6598 let project_transaction = apply_code_action.await?;
6599 Self::open_project_transaction(
6600 &editor,
6601 workspace,
6602 project_transaction,
6603 title,
6604 cx,
6605 )
6606 .await
6607 }))
6608 }
6609 CodeActionsItem::DebugScenario(scenario) => {
6610 let context = actions_menu.actions.context;
6611
6612 workspace.update(cx, |workspace, cx| {
6613 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6614 workspace.start_debug_session(
6615 scenario,
6616 context,
6617 Some(buffer),
6618 None,
6619 window,
6620 cx,
6621 );
6622 });
6623 Some(Task::ready(Ok(())))
6624 }
6625 }
6626 }
6627
6628 pub async fn open_project_transaction(
6629 editor: &WeakEntity<Editor>,
6630 workspace: WeakEntity<Workspace>,
6631 transaction: ProjectTransaction,
6632 title: String,
6633 cx: &mut AsyncWindowContext,
6634 ) -> Result<()> {
6635 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6636 cx.update(|_, cx| {
6637 entries.sort_unstable_by_key(|(buffer, _)| {
6638 buffer.read(cx).file().map(|f| f.path().clone())
6639 });
6640 })?;
6641 if entries.is_empty() {
6642 return Ok(());
6643 }
6644
6645 // If the project transaction's edits are all contained within this editor, then
6646 // avoid opening a new editor to display them.
6647
6648 if let [(buffer, transaction)] = &*entries {
6649 let excerpt = editor.update(cx, |editor, cx| {
6650 editor
6651 .buffer()
6652 .read(cx)
6653 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6654 })?;
6655 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6656 && excerpted_buffer == *buffer
6657 {
6658 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6659 let excerpt_range = excerpt_range.to_offset(buffer);
6660 buffer
6661 .edited_ranges_for_transaction::<usize>(transaction)
6662 .all(|range| {
6663 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6664 })
6665 })?;
6666
6667 if all_edits_within_excerpt {
6668 return Ok(());
6669 }
6670 }
6671 }
6672
6673 let mut ranges_to_highlight = Vec::new();
6674 let excerpt_buffer = cx.new(|cx| {
6675 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6676 for (buffer_handle, transaction) in &entries {
6677 let edited_ranges = buffer_handle
6678 .read(cx)
6679 .edited_ranges_for_transaction::<Point>(transaction)
6680 .collect::<Vec<_>>();
6681 let (ranges, _) = multibuffer.set_excerpts_for_path(
6682 PathKey::for_buffer(buffer_handle, cx),
6683 buffer_handle.clone(),
6684 edited_ranges,
6685 multibuffer_context_lines(cx),
6686 cx,
6687 );
6688
6689 ranges_to_highlight.extend(ranges);
6690 }
6691 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6692 multibuffer
6693 })?;
6694
6695 workspace.update_in(cx, |workspace, window, cx| {
6696 let project = workspace.project().clone();
6697 let editor =
6698 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6699 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6700 editor.update(cx, |editor, cx| {
6701 editor.highlight_background::<Self>(
6702 &ranges_to_highlight,
6703 |_, theme| theme.colors().editor_highlighted_line_background,
6704 cx,
6705 );
6706 });
6707 })?;
6708
6709 Ok(())
6710 }
6711
6712 pub fn clear_code_action_providers(&mut self) {
6713 self.code_action_providers.clear();
6714 self.available_code_actions.take();
6715 }
6716
6717 pub fn add_code_action_provider(
6718 &mut self,
6719 provider: Rc<dyn CodeActionProvider>,
6720 window: &mut Window,
6721 cx: &mut Context<Self>,
6722 ) {
6723 if self
6724 .code_action_providers
6725 .iter()
6726 .any(|existing_provider| existing_provider.id() == provider.id())
6727 {
6728 return;
6729 }
6730
6731 self.code_action_providers.push(provider);
6732 self.refresh_code_actions(window, cx);
6733 }
6734
6735 pub fn remove_code_action_provider(
6736 &mut self,
6737 id: Arc<str>,
6738 window: &mut Window,
6739 cx: &mut Context<Self>,
6740 ) {
6741 self.code_action_providers
6742 .retain(|provider| provider.id() != id);
6743 self.refresh_code_actions(window, cx);
6744 }
6745
6746 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6747 !self.code_action_providers.is_empty()
6748 && EditorSettings::get_global(cx).toolbar.code_actions
6749 }
6750
6751 pub fn has_available_code_actions(&self) -> bool {
6752 self.available_code_actions
6753 .as_ref()
6754 .is_some_and(|(_, actions)| !actions.is_empty())
6755 }
6756
6757 fn render_inline_code_actions(
6758 &self,
6759 icon_size: ui::IconSize,
6760 display_row: DisplayRow,
6761 is_active: bool,
6762 cx: &mut Context<Self>,
6763 ) -> AnyElement {
6764 let show_tooltip = !self.context_menu_visible();
6765 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6766 .icon_size(icon_size)
6767 .shape(ui::IconButtonShape::Square)
6768 .icon_color(ui::Color::Hidden)
6769 .toggle_state(is_active)
6770 .when(show_tooltip, |this| {
6771 this.tooltip({
6772 let focus_handle = self.focus_handle.clone();
6773 move |_window, cx| {
6774 Tooltip::for_action_in(
6775 "Toggle Code Actions",
6776 &ToggleCodeActions {
6777 deployed_from: None,
6778 quick_launch: false,
6779 },
6780 &focus_handle,
6781 cx,
6782 )
6783 }
6784 })
6785 })
6786 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6787 window.focus(&editor.focus_handle(cx));
6788 editor.toggle_code_actions(
6789 &crate::actions::ToggleCodeActions {
6790 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6791 display_row,
6792 )),
6793 quick_launch: false,
6794 },
6795 window,
6796 cx,
6797 );
6798 }))
6799 .into_any_element()
6800 }
6801
6802 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6803 &self.context_menu
6804 }
6805
6806 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6807 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6808 cx.background_executor()
6809 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6810 .await;
6811
6812 let (start_buffer, start, _, end, newest_selection) = this
6813 .update(cx, |this, cx| {
6814 let newest_selection = this.selections.newest_anchor().clone();
6815 if newest_selection.head().diff_base_anchor.is_some() {
6816 return None;
6817 }
6818 let display_snapshot = this.display_snapshot(cx);
6819 let newest_selection_adjusted =
6820 this.selections.newest_adjusted(&display_snapshot);
6821 let buffer = this.buffer.read(cx);
6822
6823 let (start_buffer, start) =
6824 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6825 let (end_buffer, end) =
6826 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6827
6828 Some((start_buffer, start, end_buffer, end, newest_selection))
6829 })?
6830 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6831 .context(
6832 "Expected selection to lie in a single buffer when refreshing code actions",
6833 )?;
6834 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6835 let providers = this.code_action_providers.clone();
6836 let tasks = this
6837 .code_action_providers
6838 .iter()
6839 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6840 .collect::<Vec<_>>();
6841 (providers, tasks)
6842 })?;
6843
6844 let mut actions = Vec::new();
6845 for (provider, provider_actions) in
6846 providers.into_iter().zip(future::join_all(tasks).await)
6847 {
6848 if let Some(provider_actions) = provider_actions.log_err() {
6849 actions.extend(provider_actions.into_iter().map(|action| {
6850 AvailableCodeAction {
6851 excerpt_id: newest_selection.start.excerpt_id,
6852 action,
6853 provider: provider.clone(),
6854 }
6855 }));
6856 }
6857 }
6858
6859 this.update(cx, |this, cx| {
6860 this.available_code_actions = if actions.is_empty() {
6861 None
6862 } else {
6863 Some((
6864 Location {
6865 buffer: start_buffer,
6866 range: start..end,
6867 },
6868 actions.into(),
6869 ))
6870 };
6871 cx.notify();
6872 })
6873 }));
6874 }
6875
6876 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6877 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6878 self.show_git_blame_inline = false;
6879
6880 self.show_git_blame_inline_delay_task =
6881 Some(cx.spawn_in(window, async move |this, cx| {
6882 cx.background_executor().timer(delay).await;
6883
6884 this.update(cx, |this, cx| {
6885 this.show_git_blame_inline = true;
6886 cx.notify();
6887 })
6888 .log_err();
6889 }));
6890 }
6891 }
6892
6893 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6894 let snapshot = self.snapshot(window, cx);
6895 let cursor = self
6896 .selections
6897 .newest::<Point>(&snapshot.display_snapshot)
6898 .head();
6899 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6900 else {
6901 return;
6902 };
6903
6904 if self.blame.is_none() {
6905 self.start_git_blame(true, window, cx);
6906 }
6907 let Some(blame) = self.blame.as_ref() else {
6908 return;
6909 };
6910
6911 let row_info = RowInfo {
6912 buffer_id: Some(buffer.remote_id()),
6913 buffer_row: Some(point.row),
6914 ..Default::default()
6915 };
6916 let Some((buffer, blame_entry)) = blame
6917 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6918 .flatten()
6919 else {
6920 return;
6921 };
6922
6923 let anchor = self.selections.newest_anchor().head();
6924 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6925 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6926 self.show_blame_popover(
6927 buffer,
6928 &blame_entry,
6929 position + last_bounds.origin,
6930 true,
6931 cx,
6932 );
6933 };
6934 }
6935
6936 fn show_blame_popover(
6937 &mut self,
6938 buffer: BufferId,
6939 blame_entry: &BlameEntry,
6940 position: gpui::Point<Pixels>,
6941 ignore_timeout: bool,
6942 cx: &mut Context<Self>,
6943 ) {
6944 if let Some(state) = &mut self.inline_blame_popover {
6945 state.hide_task.take();
6946 } else {
6947 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6948 let blame_entry = blame_entry.clone();
6949 let show_task = cx.spawn(async move |editor, cx| {
6950 if !ignore_timeout {
6951 cx.background_executor()
6952 .timer(std::time::Duration::from_millis(blame_popover_delay))
6953 .await;
6954 }
6955 editor
6956 .update(cx, |editor, cx| {
6957 editor.inline_blame_popover_show_task.take();
6958 let Some(blame) = editor.blame.as_ref() else {
6959 return;
6960 };
6961 let blame = blame.read(cx);
6962 let details = blame.details_for_entry(buffer, &blame_entry);
6963 let markdown = cx.new(|cx| {
6964 Markdown::new(
6965 details
6966 .as_ref()
6967 .map(|message| message.message.clone())
6968 .unwrap_or_default(),
6969 None,
6970 None,
6971 cx,
6972 )
6973 });
6974 editor.inline_blame_popover = Some(InlineBlamePopover {
6975 position,
6976 hide_task: None,
6977 popover_bounds: None,
6978 popover_state: InlineBlamePopoverState {
6979 scroll_handle: ScrollHandle::new(),
6980 commit_message: details,
6981 markdown,
6982 },
6983 keyboard_grace: ignore_timeout,
6984 });
6985 cx.notify();
6986 })
6987 .ok();
6988 });
6989 self.inline_blame_popover_show_task = Some(show_task);
6990 }
6991 }
6992
6993 pub fn has_mouse_context_menu(&self) -> bool {
6994 self.mouse_context_menu.is_some()
6995 }
6996
6997 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6998 self.inline_blame_popover_show_task.take();
6999 if let Some(state) = &mut self.inline_blame_popover {
7000 let hide_task = cx.spawn(async move |editor, cx| {
7001 if !ignore_timeout {
7002 cx.background_executor()
7003 .timer(std::time::Duration::from_millis(100))
7004 .await;
7005 }
7006 editor
7007 .update(cx, |editor, cx| {
7008 editor.inline_blame_popover.take();
7009 cx.notify();
7010 })
7011 .ok();
7012 });
7013 state.hide_task = Some(hide_task);
7014 true
7015 } else {
7016 false
7017 }
7018 }
7019
7020 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7021 if self.pending_rename.is_some() {
7022 return None;
7023 }
7024
7025 let provider = self.semantics_provider.clone()?;
7026 let buffer = self.buffer.read(cx);
7027 let newest_selection = self.selections.newest_anchor().clone();
7028 let cursor_position = newest_selection.head();
7029 let (cursor_buffer, cursor_buffer_position) =
7030 buffer.text_anchor_for_position(cursor_position, cx)?;
7031 let (tail_buffer, tail_buffer_position) =
7032 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7033 if cursor_buffer != tail_buffer {
7034 return None;
7035 }
7036
7037 let snapshot = cursor_buffer.read(cx).snapshot();
7038 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7039 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7040 if start_word_range != end_word_range {
7041 self.document_highlights_task.take();
7042 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7043 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7044 return None;
7045 }
7046
7047 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7048 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7049 cx.background_executor()
7050 .timer(Duration::from_millis(debounce))
7051 .await;
7052
7053 let highlights = if let Some(highlights) = cx
7054 .update(|cx| {
7055 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7056 })
7057 .ok()
7058 .flatten()
7059 {
7060 highlights.await.log_err()
7061 } else {
7062 None
7063 };
7064
7065 if let Some(highlights) = highlights {
7066 this.update(cx, |this, cx| {
7067 if this.pending_rename.is_some() {
7068 return;
7069 }
7070
7071 let buffer = this.buffer.read(cx);
7072 if buffer
7073 .text_anchor_for_position(cursor_position, cx)
7074 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7075 {
7076 return;
7077 }
7078
7079 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7080 let mut write_ranges = Vec::new();
7081 let mut read_ranges = Vec::new();
7082 for highlight in highlights {
7083 let buffer_id = cursor_buffer.read(cx).remote_id();
7084 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7085 {
7086 let start = highlight
7087 .range
7088 .start
7089 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7090 let end = highlight
7091 .range
7092 .end
7093 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7094 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7095 continue;
7096 }
7097
7098 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7099 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7100 write_ranges.push(range);
7101 } else {
7102 read_ranges.push(range);
7103 }
7104 }
7105 }
7106
7107 this.highlight_background::<DocumentHighlightRead>(
7108 &read_ranges,
7109 |_, theme| theme.colors().editor_document_highlight_read_background,
7110 cx,
7111 );
7112 this.highlight_background::<DocumentHighlightWrite>(
7113 &write_ranges,
7114 |_, theme| theme.colors().editor_document_highlight_write_background,
7115 cx,
7116 );
7117 cx.notify();
7118 })
7119 .log_err();
7120 }
7121 }));
7122 None
7123 }
7124
7125 fn prepare_highlight_query_from_selection(
7126 &mut self,
7127 window: &Window,
7128 cx: &mut Context<Editor>,
7129 ) -> Option<(String, Range<Anchor>)> {
7130 if matches!(self.mode, EditorMode::SingleLine) {
7131 return None;
7132 }
7133 if !EditorSettings::get_global(cx).selection_highlight {
7134 return None;
7135 }
7136 if self.selections.count() != 1 || self.selections.line_mode() {
7137 return None;
7138 }
7139 let snapshot = self.snapshot(window, cx);
7140 let selection = self.selections.newest::<Point>(&snapshot);
7141 // If the selection spans multiple rows OR it is empty
7142 if selection.start.row != selection.end.row
7143 || selection.start.column == selection.end.column
7144 {
7145 return None;
7146 }
7147 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7148 let query = snapshot
7149 .buffer_snapshot()
7150 .text_for_range(selection_anchor_range.clone())
7151 .collect::<String>();
7152 if query.trim().is_empty() {
7153 return None;
7154 }
7155 Some((query, selection_anchor_range))
7156 }
7157
7158 #[ztracing::instrument(skip_all)]
7159 fn update_selection_occurrence_highlights(
7160 &mut self,
7161 query_text: String,
7162 query_range: Range<Anchor>,
7163 multi_buffer_range_to_query: Range<Point>,
7164 use_debounce: bool,
7165 window: &mut Window,
7166 cx: &mut Context<Editor>,
7167 ) -> Task<()> {
7168 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7169 cx.spawn_in(window, async move |editor, cx| {
7170 if use_debounce {
7171 cx.background_executor()
7172 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7173 .await;
7174 }
7175 let match_task = cx.background_spawn(async move {
7176 let buffer_ranges = multi_buffer_snapshot
7177 .range_to_buffer_ranges(multi_buffer_range_to_query)
7178 .into_iter()
7179 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7180 let mut match_ranges = Vec::new();
7181 let Ok(regex) = project::search::SearchQuery::text(
7182 query_text.clone(),
7183 false,
7184 false,
7185 false,
7186 Default::default(),
7187 Default::default(),
7188 false,
7189 None,
7190 ) else {
7191 return Vec::default();
7192 };
7193 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7194 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7195 match_ranges.extend(
7196 regex
7197 .search(
7198 buffer_snapshot,
7199 Some(search_range.start.0..search_range.end.0),
7200 )
7201 .await
7202 .into_iter()
7203 .filter_map(|match_range| {
7204 let match_start = buffer_snapshot
7205 .anchor_after(search_range.start + match_range.start);
7206 let match_end = buffer_snapshot
7207 .anchor_before(search_range.start + match_range.end);
7208 let match_anchor_range =
7209 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7210 (match_anchor_range != query_range).then_some(match_anchor_range)
7211 }),
7212 );
7213 }
7214 match_ranges
7215 });
7216 let match_ranges = match_task.await;
7217 editor
7218 .update_in(cx, |editor, _, cx| {
7219 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7220 if !match_ranges.is_empty() {
7221 editor.highlight_background::<SelectedTextHighlight>(
7222 &match_ranges,
7223 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7224 cx,
7225 )
7226 }
7227 })
7228 .log_err();
7229 })
7230 }
7231
7232 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7233 struct NewlineFold;
7234 let type_id = std::any::TypeId::of::<NewlineFold>();
7235 if !self.mode.is_single_line() {
7236 return;
7237 }
7238 let snapshot = self.snapshot(window, cx);
7239 if snapshot.buffer_snapshot().max_point().row == 0 {
7240 return;
7241 }
7242 let task = cx.background_spawn(async move {
7243 let new_newlines = snapshot
7244 .buffer_chars_at(MultiBufferOffset(0))
7245 .filter_map(|(c, i)| {
7246 if c == '\n' {
7247 Some(
7248 snapshot.buffer_snapshot().anchor_after(i)
7249 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7250 )
7251 } else {
7252 None
7253 }
7254 })
7255 .collect::<Vec<_>>();
7256 let existing_newlines = snapshot
7257 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7258 .filter_map(|fold| {
7259 if fold.placeholder.type_tag == Some(type_id) {
7260 Some(fold.range.start..fold.range.end)
7261 } else {
7262 None
7263 }
7264 })
7265 .collect::<Vec<_>>();
7266
7267 (new_newlines, existing_newlines)
7268 });
7269 self.folding_newlines = cx.spawn(async move |this, cx| {
7270 let (new_newlines, existing_newlines) = task.await;
7271 if new_newlines == existing_newlines {
7272 return;
7273 }
7274 let placeholder = FoldPlaceholder {
7275 render: Arc::new(move |_, _, cx| {
7276 div()
7277 .bg(cx.theme().status().hint_background)
7278 .border_b_1()
7279 .size_full()
7280 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7281 .border_color(cx.theme().status().hint)
7282 .child("\\n")
7283 .into_any()
7284 }),
7285 constrain_width: false,
7286 merge_adjacent: false,
7287 type_tag: Some(type_id),
7288 };
7289 let creases = new_newlines
7290 .into_iter()
7291 .map(|range| Crease::simple(range, placeholder.clone()))
7292 .collect();
7293 this.update(cx, |this, cx| {
7294 this.display_map.update(cx, |display_map, cx| {
7295 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7296 display_map.fold(creases, cx);
7297 });
7298 })
7299 .ok();
7300 });
7301 }
7302
7303 #[ztracing::instrument(skip_all)]
7304 fn refresh_selected_text_highlights(
7305 &mut self,
7306 on_buffer_edit: bool,
7307 window: &mut Window,
7308 cx: &mut Context<Editor>,
7309 ) {
7310 let Some((query_text, query_range)) =
7311 self.prepare_highlight_query_from_selection(window, cx)
7312 else {
7313 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7314 self.quick_selection_highlight_task.take();
7315 self.debounced_selection_highlight_task.take();
7316 return;
7317 };
7318 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7319 if on_buffer_edit
7320 || self
7321 .quick_selection_highlight_task
7322 .as_ref()
7323 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7324 {
7325 let multi_buffer_visible_start = self
7326 .scroll_manager
7327 .anchor()
7328 .anchor
7329 .to_point(&multi_buffer_snapshot);
7330 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7331 multi_buffer_visible_start
7332 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7333 Bias::Left,
7334 );
7335 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7336 self.quick_selection_highlight_task = Some((
7337 query_range.clone(),
7338 self.update_selection_occurrence_highlights(
7339 query_text.clone(),
7340 query_range.clone(),
7341 multi_buffer_visible_range,
7342 false,
7343 window,
7344 cx,
7345 ),
7346 ));
7347 }
7348 if on_buffer_edit
7349 || self
7350 .debounced_selection_highlight_task
7351 .as_ref()
7352 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7353 {
7354 let multi_buffer_start = multi_buffer_snapshot
7355 .anchor_before(MultiBufferOffset(0))
7356 .to_point(&multi_buffer_snapshot);
7357 let multi_buffer_end = multi_buffer_snapshot
7358 .anchor_after(multi_buffer_snapshot.len())
7359 .to_point(&multi_buffer_snapshot);
7360 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7361 self.debounced_selection_highlight_task = Some((
7362 query_range.clone(),
7363 self.update_selection_occurrence_highlights(
7364 query_text,
7365 query_range,
7366 multi_buffer_full_range,
7367 true,
7368 window,
7369 cx,
7370 ),
7371 ));
7372 }
7373 }
7374
7375 pub fn refresh_edit_prediction(
7376 &mut self,
7377 debounce: bool,
7378 user_requested: bool,
7379 window: &mut Window,
7380 cx: &mut Context<Self>,
7381 ) -> Option<()> {
7382 if DisableAiSettings::get_global(cx).disable_ai {
7383 return None;
7384 }
7385
7386 let provider = self.edit_prediction_provider()?;
7387 let cursor = self.selections.newest_anchor().head();
7388 let (buffer, cursor_buffer_position) =
7389 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7390
7391 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7392 self.discard_edit_prediction(false, cx);
7393 return None;
7394 }
7395
7396 self.update_visible_edit_prediction(window, cx);
7397
7398 if !user_requested
7399 && (!self.should_show_edit_predictions()
7400 || !self.is_focused(window)
7401 || buffer.read(cx).is_empty())
7402 {
7403 self.discard_edit_prediction(false, cx);
7404 return None;
7405 }
7406
7407 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7408 Some(())
7409 }
7410
7411 fn show_edit_predictions_in_menu(&self) -> bool {
7412 match self.edit_prediction_settings {
7413 EditPredictionSettings::Disabled => false,
7414 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7415 }
7416 }
7417
7418 pub fn edit_predictions_enabled(&self) -> bool {
7419 match self.edit_prediction_settings {
7420 EditPredictionSettings::Disabled => false,
7421 EditPredictionSettings::Enabled { .. } => true,
7422 }
7423 }
7424
7425 fn edit_prediction_requires_modifier(&self) -> bool {
7426 match self.edit_prediction_settings {
7427 EditPredictionSettings::Disabled => false,
7428 EditPredictionSettings::Enabled {
7429 preview_requires_modifier,
7430 ..
7431 } => preview_requires_modifier,
7432 }
7433 }
7434
7435 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7436 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7437 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7438 self.discard_edit_prediction(false, cx);
7439 } else {
7440 let selection = self.selections.newest_anchor();
7441 let cursor = selection.head();
7442
7443 if let Some((buffer, cursor_buffer_position)) =
7444 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7445 {
7446 self.edit_prediction_settings =
7447 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7448 }
7449 }
7450 }
7451
7452 fn edit_prediction_settings_at_position(
7453 &self,
7454 buffer: &Entity<Buffer>,
7455 buffer_position: language::Anchor,
7456 cx: &App,
7457 ) -> EditPredictionSettings {
7458 if !self.mode.is_full()
7459 || !self.show_edit_predictions_override.unwrap_or(true)
7460 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7461 {
7462 return EditPredictionSettings::Disabled;
7463 }
7464
7465 let buffer = buffer.read(cx);
7466
7467 let file = buffer.file();
7468
7469 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7470 return EditPredictionSettings::Disabled;
7471 };
7472
7473 let by_provider = matches!(
7474 self.menu_edit_predictions_policy,
7475 MenuEditPredictionsPolicy::ByProvider
7476 );
7477
7478 let show_in_menu = by_provider
7479 && self
7480 .edit_prediction_provider
7481 .as_ref()
7482 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7483
7484 let preview_requires_modifier =
7485 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7486
7487 EditPredictionSettings::Enabled {
7488 show_in_menu,
7489 preview_requires_modifier,
7490 }
7491 }
7492
7493 fn should_show_edit_predictions(&self) -> bool {
7494 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7495 }
7496
7497 pub fn edit_prediction_preview_is_active(&self) -> bool {
7498 matches!(
7499 self.edit_prediction_preview,
7500 EditPredictionPreview::Active { .. }
7501 )
7502 }
7503
7504 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7505 let cursor = self.selections.newest_anchor().head();
7506 if let Some((buffer, cursor_position)) =
7507 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7508 {
7509 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7510 } else {
7511 false
7512 }
7513 }
7514
7515 pub fn supports_minimap(&self, cx: &App) -> bool {
7516 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7517 }
7518
7519 fn edit_predictions_enabled_in_buffer(
7520 &self,
7521 buffer: &Entity<Buffer>,
7522 buffer_position: language::Anchor,
7523 cx: &App,
7524 ) -> bool {
7525 maybe!({
7526 if self.read_only(cx) {
7527 return Some(false);
7528 }
7529 let provider = self.edit_prediction_provider()?;
7530 if !provider.is_enabled(buffer, buffer_position, cx) {
7531 return Some(false);
7532 }
7533 let buffer = buffer.read(cx);
7534 let Some(file) = buffer.file() else {
7535 return Some(true);
7536 };
7537 let settings = all_language_settings(Some(file), cx);
7538 Some(settings.edit_predictions_enabled_for_file(file, cx))
7539 })
7540 .unwrap_or(false)
7541 }
7542
7543 fn cycle_edit_prediction(
7544 &mut self,
7545 direction: Direction,
7546 window: &mut Window,
7547 cx: &mut Context<Self>,
7548 ) -> Option<()> {
7549 let provider = self.edit_prediction_provider()?;
7550 let cursor = self.selections.newest_anchor().head();
7551 let (buffer, cursor_buffer_position) =
7552 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7553 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7554 return None;
7555 }
7556
7557 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7558 self.update_visible_edit_prediction(window, cx);
7559
7560 Some(())
7561 }
7562
7563 pub fn show_edit_prediction(
7564 &mut self,
7565 _: &ShowEditPrediction,
7566 window: &mut Window,
7567 cx: &mut Context<Self>,
7568 ) {
7569 if !self.has_active_edit_prediction() {
7570 self.refresh_edit_prediction(false, true, window, cx);
7571 return;
7572 }
7573
7574 self.update_visible_edit_prediction(window, cx);
7575 }
7576
7577 pub fn display_cursor_names(
7578 &mut self,
7579 _: &DisplayCursorNames,
7580 window: &mut Window,
7581 cx: &mut Context<Self>,
7582 ) {
7583 self.show_cursor_names(window, cx);
7584 }
7585
7586 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7587 self.show_cursor_names = true;
7588 cx.notify();
7589 cx.spawn_in(window, async move |this, cx| {
7590 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7591 this.update(cx, |this, cx| {
7592 this.show_cursor_names = false;
7593 cx.notify()
7594 })
7595 .ok()
7596 })
7597 .detach();
7598 }
7599
7600 pub fn next_edit_prediction(
7601 &mut self,
7602 _: &NextEditPrediction,
7603 window: &mut Window,
7604 cx: &mut Context<Self>,
7605 ) {
7606 if self.has_active_edit_prediction() {
7607 self.cycle_edit_prediction(Direction::Next, window, cx);
7608 } else {
7609 let is_copilot_disabled = self
7610 .refresh_edit_prediction(false, true, window, cx)
7611 .is_none();
7612 if is_copilot_disabled {
7613 cx.propagate();
7614 }
7615 }
7616 }
7617
7618 pub fn previous_edit_prediction(
7619 &mut self,
7620 _: &PreviousEditPrediction,
7621 window: &mut Window,
7622 cx: &mut Context<Self>,
7623 ) {
7624 if self.has_active_edit_prediction() {
7625 self.cycle_edit_prediction(Direction::Prev, window, cx);
7626 } else {
7627 let is_copilot_disabled = self
7628 .refresh_edit_prediction(false, true, window, cx)
7629 .is_none();
7630 if is_copilot_disabled {
7631 cx.propagate();
7632 }
7633 }
7634 }
7635
7636 pub fn accept_edit_prediction(
7637 &mut self,
7638 _: &AcceptEditPrediction,
7639 window: &mut Window,
7640 cx: &mut Context<Self>,
7641 ) {
7642 if self.show_edit_predictions_in_menu() {
7643 self.hide_context_menu(window, cx);
7644 }
7645
7646 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7647 return;
7648 };
7649
7650 match &active_edit_prediction.completion {
7651 EditPrediction::MoveWithin { target, .. } => {
7652 let target = *target;
7653
7654 if let Some(position_map) = &self.last_position_map {
7655 if position_map
7656 .visible_row_range
7657 .contains(&target.to_display_point(&position_map.snapshot).row())
7658 || !self.edit_prediction_requires_modifier()
7659 {
7660 self.unfold_ranges(&[target..target], true, false, cx);
7661 // Note that this is also done in vim's handler of the Tab action.
7662 self.change_selections(
7663 SelectionEffects::scroll(Autoscroll::newest()),
7664 window,
7665 cx,
7666 |selections| {
7667 selections.select_anchor_ranges([target..target]);
7668 },
7669 );
7670 self.clear_row_highlights::<EditPredictionPreview>();
7671
7672 self.edit_prediction_preview
7673 .set_previous_scroll_position(None);
7674 } else {
7675 self.edit_prediction_preview
7676 .set_previous_scroll_position(Some(
7677 position_map.snapshot.scroll_anchor,
7678 ));
7679
7680 self.highlight_rows::<EditPredictionPreview>(
7681 target..target,
7682 cx.theme().colors().editor_highlighted_line_background,
7683 RowHighlightOptions {
7684 autoscroll: true,
7685 ..Default::default()
7686 },
7687 cx,
7688 );
7689 self.request_autoscroll(Autoscroll::fit(), cx);
7690 }
7691 }
7692 }
7693 EditPrediction::MoveOutside { snapshot, target } => {
7694 if let Some(workspace) = self.workspace() {
7695 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7696 .detach_and_log_err(cx);
7697 }
7698 }
7699 EditPrediction::Edit { edits, .. } => {
7700 self.report_edit_prediction_event(
7701 active_edit_prediction.completion_id.clone(),
7702 true,
7703 cx,
7704 );
7705
7706 if let Some(provider) = self.edit_prediction_provider() {
7707 provider.accept(cx);
7708 }
7709
7710 // Store the transaction ID and selections before applying the edit
7711 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7712
7713 let snapshot = self.buffer.read(cx).snapshot(cx);
7714 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7715
7716 self.buffer.update(cx, |buffer, cx| {
7717 buffer.edit(edits.iter().cloned(), None, cx)
7718 });
7719
7720 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7721 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7722 });
7723
7724 let selections = self.selections.disjoint_anchors_arc();
7725 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7726 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7727 if has_new_transaction {
7728 self.selection_history
7729 .insert_transaction(transaction_id_now, selections);
7730 }
7731 }
7732
7733 self.update_visible_edit_prediction(window, cx);
7734 if self.active_edit_prediction.is_none() {
7735 self.refresh_edit_prediction(true, true, window, cx);
7736 }
7737
7738 cx.notify();
7739 }
7740 }
7741
7742 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7743 }
7744
7745 pub fn accept_partial_edit_prediction(
7746 &mut self,
7747 _: &AcceptPartialEditPrediction,
7748 window: &mut Window,
7749 cx: &mut Context<Self>,
7750 ) {
7751 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7752 return;
7753 };
7754 if self.selections.count() != 1 {
7755 return;
7756 }
7757
7758 match &active_edit_prediction.completion {
7759 EditPrediction::MoveWithin { target, .. } => {
7760 let target = *target;
7761 self.change_selections(
7762 SelectionEffects::scroll(Autoscroll::newest()),
7763 window,
7764 cx,
7765 |selections| {
7766 selections.select_anchor_ranges([target..target]);
7767 },
7768 );
7769 }
7770 EditPrediction::MoveOutside { snapshot, target } => {
7771 if let Some(workspace) = self.workspace() {
7772 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7773 .detach_and_log_err(cx);
7774 }
7775 }
7776 EditPrediction::Edit { edits, .. } => {
7777 self.report_edit_prediction_event(
7778 active_edit_prediction.completion_id.clone(),
7779 true,
7780 cx,
7781 );
7782
7783 // Find an insertion that starts at the cursor position.
7784 let snapshot = self.buffer.read(cx).snapshot(cx);
7785 let cursor_offset = self
7786 .selections
7787 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7788 .head();
7789 let insertion = edits.iter().find_map(|(range, text)| {
7790 let range = range.to_offset(&snapshot);
7791 if range.is_empty() && range.start == cursor_offset {
7792 Some(text)
7793 } else {
7794 None
7795 }
7796 });
7797
7798 if let Some(text) = insertion {
7799 let mut partial_completion = text
7800 .chars()
7801 .by_ref()
7802 .take_while(|c| c.is_alphabetic())
7803 .collect::<String>();
7804 if partial_completion.is_empty() {
7805 partial_completion = text
7806 .chars()
7807 .by_ref()
7808 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7809 .collect::<String>();
7810 }
7811
7812 cx.emit(EditorEvent::InputHandled {
7813 utf16_range_to_replace: None,
7814 text: partial_completion.clone().into(),
7815 });
7816
7817 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7818
7819 self.refresh_edit_prediction(true, true, window, cx);
7820 cx.notify();
7821 } else {
7822 self.accept_edit_prediction(&Default::default(), window, cx);
7823 }
7824 }
7825 }
7826 }
7827
7828 fn discard_edit_prediction(
7829 &mut self,
7830 should_report_edit_prediction_event: bool,
7831 cx: &mut Context<Self>,
7832 ) -> bool {
7833 if should_report_edit_prediction_event {
7834 let completion_id = self
7835 .active_edit_prediction
7836 .as_ref()
7837 .and_then(|active_completion| active_completion.completion_id.clone());
7838
7839 self.report_edit_prediction_event(completion_id, false, cx);
7840 }
7841
7842 if let Some(provider) = self.edit_prediction_provider() {
7843 provider.discard(cx);
7844 }
7845
7846 self.take_active_edit_prediction(cx)
7847 }
7848
7849 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7850 let Some(provider) = self.edit_prediction_provider() else {
7851 return;
7852 };
7853
7854 let Some((_, buffer, _)) = self
7855 .buffer
7856 .read(cx)
7857 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7858 else {
7859 return;
7860 };
7861
7862 let extension = buffer
7863 .read(cx)
7864 .file()
7865 .and_then(|file| Some(file.path().extension()?.to_string()));
7866
7867 let event_type = match accepted {
7868 true => "Edit Prediction Accepted",
7869 false => "Edit Prediction Discarded",
7870 };
7871 telemetry::event!(
7872 event_type,
7873 provider = provider.name(),
7874 prediction_id = id,
7875 suggestion_accepted = accepted,
7876 file_extension = extension,
7877 );
7878 }
7879
7880 fn open_editor_at_anchor(
7881 snapshot: &language::BufferSnapshot,
7882 target: language::Anchor,
7883 workspace: &Entity<Workspace>,
7884 window: &mut Window,
7885 cx: &mut App,
7886 ) -> Task<Result<()>> {
7887 workspace.update(cx, |workspace, cx| {
7888 let path = snapshot.file().map(|file| file.full_path(cx));
7889 let Some(path) =
7890 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7891 else {
7892 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7893 };
7894 let target = text::ToPoint::to_point(&target, snapshot);
7895 let item = workspace.open_path(path, None, true, window, cx);
7896 window.spawn(cx, async move |cx| {
7897 let Some(editor) = item.await?.downcast::<Editor>() else {
7898 return Ok(());
7899 };
7900 editor
7901 .update_in(cx, |editor, window, cx| {
7902 editor.go_to_singleton_buffer_point(target, window, cx);
7903 })
7904 .ok();
7905 anyhow::Ok(())
7906 })
7907 })
7908 }
7909
7910 pub fn has_active_edit_prediction(&self) -> bool {
7911 self.active_edit_prediction.is_some()
7912 }
7913
7914 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7915 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7916 return false;
7917 };
7918
7919 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7920 self.clear_highlights::<EditPredictionHighlight>(cx);
7921 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7922 true
7923 }
7924
7925 /// Returns true when we're displaying the edit prediction popover below the cursor
7926 /// like we are not previewing and the LSP autocomplete menu is visible
7927 /// or we are in `when_holding_modifier` mode.
7928 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7929 if self.edit_prediction_preview_is_active()
7930 || !self.show_edit_predictions_in_menu()
7931 || !self.edit_predictions_enabled()
7932 {
7933 return false;
7934 }
7935
7936 if self.has_visible_completions_menu() {
7937 return true;
7938 }
7939
7940 has_completion && self.edit_prediction_requires_modifier()
7941 }
7942
7943 fn handle_modifiers_changed(
7944 &mut self,
7945 modifiers: Modifiers,
7946 position_map: &PositionMap,
7947 window: &mut Window,
7948 cx: &mut Context<Self>,
7949 ) {
7950 // Ensure that the edit prediction preview is updated, even when not
7951 // enabled, if there's an active edit prediction preview.
7952 if self.show_edit_predictions_in_menu()
7953 || matches!(
7954 self.edit_prediction_preview,
7955 EditPredictionPreview::Active { .. }
7956 )
7957 {
7958 self.update_edit_prediction_preview(&modifiers, window, cx);
7959 }
7960
7961 self.update_selection_mode(&modifiers, position_map, window, cx);
7962
7963 let mouse_position = window.mouse_position();
7964 if !position_map.text_hitbox.is_hovered(window) {
7965 return;
7966 }
7967
7968 self.update_hovered_link(
7969 position_map.point_for_position(mouse_position),
7970 &position_map.snapshot,
7971 modifiers,
7972 window,
7973 cx,
7974 )
7975 }
7976
7977 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7978 match EditorSettings::get_global(cx).multi_cursor_modifier {
7979 MultiCursorModifier::Alt => modifiers.secondary(),
7980 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7981 }
7982 }
7983
7984 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7985 match EditorSettings::get_global(cx).multi_cursor_modifier {
7986 MultiCursorModifier::Alt => modifiers.alt,
7987 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7988 }
7989 }
7990
7991 fn columnar_selection_mode(
7992 modifiers: &Modifiers,
7993 cx: &mut Context<Self>,
7994 ) -> Option<ColumnarMode> {
7995 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7996 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7997 Some(ColumnarMode::FromMouse)
7998 } else if Self::is_alt_pressed(modifiers, cx) {
7999 Some(ColumnarMode::FromSelection)
8000 } else {
8001 None
8002 }
8003 } else {
8004 None
8005 }
8006 }
8007
8008 fn update_selection_mode(
8009 &mut self,
8010 modifiers: &Modifiers,
8011 position_map: &PositionMap,
8012 window: &mut Window,
8013 cx: &mut Context<Self>,
8014 ) {
8015 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8016 return;
8017 };
8018 if self.selections.pending_anchor().is_none() {
8019 return;
8020 }
8021
8022 let mouse_position = window.mouse_position();
8023 let point_for_position = position_map.point_for_position(mouse_position);
8024 let position = point_for_position.previous_valid;
8025
8026 self.select(
8027 SelectPhase::BeginColumnar {
8028 position,
8029 reset: false,
8030 mode,
8031 goal_column: point_for_position.exact_unclipped.column(),
8032 },
8033 window,
8034 cx,
8035 );
8036 }
8037
8038 fn update_edit_prediction_preview(
8039 &mut self,
8040 modifiers: &Modifiers,
8041 window: &mut Window,
8042 cx: &mut Context<Self>,
8043 ) {
8044 let mut modifiers_held = false;
8045 if let Some(accept_keystroke) = self
8046 .accept_edit_prediction_keybind(false, window, cx)
8047 .keystroke()
8048 {
8049 modifiers_held = modifiers_held
8050 || (accept_keystroke.modifiers() == modifiers
8051 && accept_keystroke.modifiers().modified());
8052 };
8053 if let Some(accept_partial_keystroke) = self
8054 .accept_edit_prediction_keybind(true, window, cx)
8055 .keystroke()
8056 {
8057 modifiers_held = modifiers_held
8058 || (accept_partial_keystroke.modifiers() == modifiers
8059 && accept_partial_keystroke.modifiers().modified());
8060 }
8061
8062 if modifiers_held {
8063 if matches!(
8064 self.edit_prediction_preview,
8065 EditPredictionPreview::Inactive { .. }
8066 ) {
8067 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8068 provider.provider.did_show(cx)
8069 }
8070
8071 self.edit_prediction_preview = EditPredictionPreview::Active {
8072 previous_scroll_position: None,
8073 since: Instant::now(),
8074 };
8075
8076 self.update_visible_edit_prediction(window, cx);
8077 cx.notify();
8078 }
8079 } else if let EditPredictionPreview::Active {
8080 previous_scroll_position,
8081 since,
8082 } = self.edit_prediction_preview
8083 {
8084 if let (Some(previous_scroll_position), Some(position_map)) =
8085 (previous_scroll_position, self.last_position_map.as_ref())
8086 {
8087 self.set_scroll_position(
8088 previous_scroll_position
8089 .scroll_position(&position_map.snapshot.display_snapshot),
8090 window,
8091 cx,
8092 );
8093 }
8094
8095 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8096 released_too_fast: since.elapsed() < Duration::from_millis(200),
8097 };
8098 self.clear_row_highlights::<EditPredictionPreview>();
8099 self.update_visible_edit_prediction(window, cx);
8100 cx.notify();
8101 }
8102 }
8103
8104 fn update_visible_edit_prediction(
8105 &mut self,
8106 _window: &mut Window,
8107 cx: &mut Context<Self>,
8108 ) -> Option<()> {
8109 if DisableAiSettings::get_global(cx).disable_ai {
8110 return None;
8111 }
8112
8113 if self.ime_transaction.is_some() {
8114 self.discard_edit_prediction(false, cx);
8115 return None;
8116 }
8117
8118 let selection = self.selections.newest_anchor();
8119 let cursor = selection.head();
8120 let multibuffer = self.buffer.read(cx).snapshot(cx);
8121 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8122 let excerpt_id = cursor.excerpt_id;
8123
8124 let show_in_menu = self.show_edit_predictions_in_menu();
8125 let completions_menu_has_precedence = !show_in_menu
8126 && (self.context_menu.borrow().is_some()
8127 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8128
8129 if completions_menu_has_precedence
8130 || !offset_selection.is_empty()
8131 || self
8132 .active_edit_prediction
8133 .as_ref()
8134 .is_some_and(|completion| {
8135 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8136 return false;
8137 };
8138 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8139 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8140 !invalidation_range.contains(&offset_selection.head())
8141 })
8142 {
8143 self.discard_edit_prediction(false, cx);
8144 return None;
8145 }
8146
8147 self.take_active_edit_prediction(cx);
8148 let Some(provider) = self.edit_prediction_provider() else {
8149 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8150 return None;
8151 };
8152
8153 let (buffer, cursor_buffer_position) =
8154 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8155
8156 self.edit_prediction_settings =
8157 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8158
8159 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8160
8161 if self.edit_prediction_indent_conflict {
8162 let cursor_point = cursor.to_point(&multibuffer);
8163 let mut suggested_indent = None;
8164 multibuffer.suggested_indents_callback(
8165 cursor_point.row..cursor_point.row + 1,
8166 |_, indent| {
8167 suggested_indent = Some(indent);
8168 ControlFlow::Break(())
8169 },
8170 cx,
8171 );
8172
8173 if let Some(indent) = suggested_indent
8174 && indent.len == cursor_point.column
8175 {
8176 self.edit_prediction_indent_conflict = false;
8177 }
8178 }
8179
8180 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8181
8182 let (completion_id, edits, edit_preview) = match edit_prediction {
8183 edit_prediction_types::EditPrediction::Local {
8184 id,
8185 edits,
8186 edit_preview,
8187 } => (id, edits, edit_preview),
8188 edit_prediction_types::EditPrediction::Jump {
8189 id,
8190 snapshot,
8191 target,
8192 } => {
8193 self.stale_edit_prediction_in_menu = None;
8194 self.active_edit_prediction = Some(EditPredictionState {
8195 inlay_ids: vec![],
8196 completion: EditPrediction::MoveOutside { snapshot, target },
8197 completion_id: id,
8198 invalidation_range: None,
8199 });
8200 cx.notify();
8201 return Some(());
8202 }
8203 };
8204
8205 let edits = edits
8206 .into_iter()
8207 .flat_map(|(range, new_text)| {
8208 Some((
8209 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8210 new_text,
8211 ))
8212 })
8213 .collect::<Vec<_>>();
8214 if edits.is_empty() {
8215 return None;
8216 }
8217
8218 let first_edit_start = edits.first().unwrap().0.start;
8219 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8220 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8221
8222 let last_edit_end = edits.last().unwrap().0.end;
8223 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8224 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8225
8226 let cursor_row = cursor.to_point(&multibuffer).row;
8227
8228 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8229
8230 let mut inlay_ids = Vec::new();
8231 let invalidation_row_range;
8232 let move_invalidation_row_range = if cursor_row < edit_start_row {
8233 Some(cursor_row..edit_end_row)
8234 } else if cursor_row > edit_end_row {
8235 Some(edit_start_row..cursor_row)
8236 } else {
8237 None
8238 };
8239 let supports_jump = self
8240 .edit_prediction_provider
8241 .as_ref()
8242 .map(|provider| provider.provider.supports_jump_to_edit())
8243 .unwrap_or(true);
8244
8245 let is_move = supports_jump
8246 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8247 let completion = if is_move {
8248 invalidation_row_range =
8249 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8250 let target = first_edit_start;
8251 EditPrediction::MoveWithin { target, snapshot }
8252 } else {
8253 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8254 && !self.edit_predictions_hidden_for_vim_mode;
8255
8256 if show_completions_in_buffer {
8257 if let Some(provider) = &self.edit_prediction_provider {
8258 provider.provider.did_show(cx);
8259 }
8260 if edits
8261 .iter()
8262 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8263 {
8264 let mut inlays = Vec::new();
8265 for (range, new_text) in &edits {
8266 let inlay = Inlay::edit_prediction(
8267 post_inc(&mut self.next_inlay_id),
8268 range.start,
8269 new_text.as_ref(),
8270 );
8271 inlay_ids.push(inlay.id);
8272 inlays.push(inlay);
8273 }
8274
8275 self.splice_inlays(&[], inlays, cx);
8276 } else {
8277 let background_color = cx.theme().status().deleted_background;
8278 self.highlight_text::<EditPredictionHighlight>(
8279 edits.iter().map(|(range, _)| range.clone()).collect(),
8280 HighlightStyle {
8281 background_color: Some(background_color),
8282 ..Default::default()
8283 },
8284 cx,
8285 );
8286 }
8287 }
8288
8289 invalidation_row_range = edit_start_row..edit_end_row;
8290
8291 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8292 if provider.show_tab_accept_marker() {
8293 EditDisplayMode::TabAccept
8294 } else {
8295 EditDisplayMode::Inline
8296 }
8297 } else {
8298 EditDisplayMode::DiffPopover
8299 };
8300
8301 EditPrediction::Edit {
8302 edits,
8303 edit_preview,
8304 display_mode,
8305 snapshot,
8306 }
8307 };
8308
8309 let invalidation_range = multibuffer
8310 .anchor_before(Point::new(invalidation_row_range.start, 0))
8311 ..multibuffer.anchor_after(Point::new(
8312 invalidation_row_range.end,
8313 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8314 ));
8315
8316 self.stale_edit_prediction_in_menu = None;
8317 self.active_edit_prediction = Some(EditPredictionState {
8318 inlay_ids,
8319 completion,
8320 completion_id,
8321 invalidation_range: Some(invalidation_range),
8322 });
8323
8324 cx.notify();
8325
8326 Some(())
8327 }
8328
8329 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8330 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8331 }
8332
8333 fn clear_tasks(&mut self) {
8334 self.tasks.clear()
8335 }
8336
8337 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8338 if self.tasks.insert(key, value).is_some() {
8339 // This case should hopefully be rare, but just in case...
8340 log::error!(
8341 "multiple different run targets found on a single line, only the last target will be rendered"
8342 )
8343 }
8344 }
8345
8346 /// Get all display points of breakpoints that will be rendered within editor
8347 ///
8348 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8349 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8350 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8351 fn active_breakpoints(
8352 &self,
8353 range: Range<DisplayRow>,
8354 window: &mut Window,
8355 cx: &mut Context<Self>,
8356 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8357 let mut breakpoint_display_points = HashMap::default();
8358
8359 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8360 return breakpoint_display_points;
8361 };
8362
8363 let snapshot = self.snapshot(window, cx);
8364
8365 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8366 let Some(project) = self.project() else {
8367 return breakpoint_display_points;
8368 };
8369
8370 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8371 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8372
8373 for (buffer_snapshot, range, excerpt_id) in
8374 multi_buffer_snapshot.range_to_buffer_ranges(range)
8375 {
8376 let Some(buffer) = project
8377 .read(cx)
8378 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8379 else {
8380 continue;
8381 };
8382 let breakpoints = breakpoint_store.read(cx).breakpoints(
8383 &buffer,
8384 Some(
8385 buffer_snapshot.anchor_before(range.start)
8386 ..buffer_snapshot.anchor_after(range.end),
8387 ),
8388 buffer_snapshot,
8389 cx,
8390 );
8391 for (breakpoint, state) in breakpoints {
8392 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8393 let position = multi_buffer_anchor
8394 .to_point(&multi_buffer_snapshot)
8395 .to_display_point(&snapshot);
8396
8397 breakpoint_display_points.insert(
8398 position.row(),
8399 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8400 );
8401 }
8402 }
8403
8404 breakpoint_display_points
8405 }
8406
8407 fn breakpoint_context_menu(
8408 &self,
8409 anchor: Anchor,
8410 window: &mut Window,
8411 cx: &mut Context<Self>,
8412 ) -> Entity<ui::ContextMenu> {
8413 let weak_editor = cx.weak_entity();
8414 let focus_handle = self.focus_handle(cx);
8415
8416 let row = self
8417 .buffer
8418 .read(cx)
8419 .snapshot(cx)
8420 .summary_for_anchor::<Point>(&anchor)
8421 .row;
8422
8423 let breakpoint = self
8424 .breakpoint_at_row(row, window, cx)
8425 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8426
8427 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8428 "Edit Log Breakpoint"
8429 } else {
8430 "Set Log Breakpoint"
8431 };
8432
8433 let condition_breakpoint_msg = if breakpoint
8434 .as_ref()
8435 .is_some_and(|bp| bp.1.condition.is_some())
8436 {
8437 "Edit Condition Breakpoint"
8438 } else {
8439 "Set Condition Breakpoint"
8440 };
8441
8442 let hit_condition_breakpoint_msg = if breakpoint
8443 .as_ref()
8444 .is_some_and(|bp| bp.1.hit_condition.is_some())
8445 {
8446 "Edit Hit Condition Breakpoint"
8447 } else {
8448 "Set Hit Condition Breakpoint"
8449 };
8450
8451 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8452 "Unset Breakpoint"
8453 } else {
8454 "Set Breakpoint"
8455 };
8456
8457 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8458
8459 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8460 BreakpointState::Enabled => Some("Disable"),
8461 BreakpointState::Disabled => Some("Enable"),
8462 });
8463
8464 let (anchor, breakpoint) =
8465 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8466
8467 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8468 menu.on_blur_subscription(Subscription::new(|| {}))
8469 .context(focus_handle)
8470 .when(run_to_cursor, |this| {
8471 let weak_editor = weak_editor.clone();
8472 this.entry("Run to cursor", None, move |window, cx| {
8473 weak_editor
8474 .update(cx, |editor, cx| {
8475 editor.change_selections(
8476 SelectionEffects::no_scroll(),
8477 window,
8478 cx,
8479 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8480 );
8481 })
8482 .ok();
8483
8484 window.dispatch_action(Box::new(RunToCursor), cx);
8485 })
8486 .separator()
8487 })
8488 .when_some(toggle_state_msg, |this, msg| {
8489 this.entry(msg, None, {
8490 let weak_editor = weak_editor.clone();
8491 let breakpoint = breakpoint.clone();
8492 move |_window, cx| {
8493 weak_editor
8494 .update(cx, |this, cx| {
8495 this.edit_breakpoint_at_anchor(
8496 anchor,
8497 breakpoint.as_ref().clone(),
8498 BreakpointEditAction::InvertState,
8499 cx,
8500 );
8501 })
8502 .log_err();
8503 }
8504 })
8505 })
8506 .entry(set_breakpoint_msg, None, {
8507 let weak_editor = weak_editor.clone();
8508 let breakpoint = breakpoint.clone();
8509 move |_window, cx| {
8510 weak_editor
8511 .update(cx, |this, cx| {
8512 this.edit_breakpoint_at_anchor(
8513 anchor,
8514 breakpoint.as_ref().clone(),
8515 BreakpointEditAction::Toggle,
8516 cx,
8517 );
8518 })
8519 .log_err();
8520 }
8521 })
8522 .entry(log_breakpoint_msg, None, {
8523 let breakpoint = breakpoint.clone();
8524 let weak_editor = weak_editor.clone();
8525 move |window, cx| {
8526 weak_editor
8527 .update(cx, |this, cx| {
8528 this.add_edit_breakpoint_block(
8529 anchor,
8530 breakpoint.as_ref(),
8531 BreakpointPromptEditAction::Log,
8532 window,
8533 cx,
8534 );
8535 })
8536 .log_err();
8537 }
8538 })
8539 .entry(condition_breakpoint_msg, None, {
8540 let breakpoint = breakpoint.clone();
8541 let weak_editor = weak_editor.clone();
8542 move |window, cx| {
8543 weak_editor
8544 .update(cx, |this, cx| {
8545 this.add_edit_breakpoint_block(
8546 anchor,
8547 breakpoint.as_ref(),
8548 BreakpointPromptEditAction::Condition,
8549 window,
8550 cx,
8551 );
8552 })
8553 .log_err();
8554 }
8555 })
8556 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8557 weak_editor
8558 .update(cx, |this, cx| {
8559 this.add_edit_breakpoint_block(
8560 anchor,
8561 breakpoint.as_ref(),
8562 BreakpointPromptEditAction::HitCondition,
8563 window,
8564 cx,
8565 );
8566 })
8567 .log_err();
8568 })
8569 })
8570 }
8571
8572 fn render_breakpoint(
8573 &self,
8574 position: Anchor,
8575 row: DisplayRow,
8576 breakpoint: &Breakpoint,
8577 state: Option<BreakpointSessionState>,
8578 cx: &mut Context<Self>,
8579 ) -> IconButton {
8580 let is_rejected = state.is_some_and(|s| !s.verified);
8581 // Is it a breakpoint that shows up when hovering over gutter?
8582 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8583 (false, false),
8584 |PhantomBreakpointIndicator {
8585 is_active,
8586 display_row,
8587 collides_with_existing_breakpoint,
8588 }| {
8589 (
8590 is_active && display_row == row,
8591 collides_with_existing_breakpoint,
8592 )
8593 },
8594 );
8595
8596 let (color, icon) = {
8597 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8598 (false, false) => ui::IconName::DebugBreakpoint,
8599 (true, false) => ui::IconName::DebugLogBreakpoint,
8600 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8601 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8602 };
8603
8604 let color = cx.theme().colors();
8605
8606 let color = if is_phantom {
8607 if collides_with_existing {
8608 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8609 } else {
8610 Color::Hint
8611 }
8612 } else if is_rejected {
8613 Color::Disabled
8614 } else {
8615 Color::Debugger
8616 };
8617
8618 (color, icon)
8619 };
8620
8621 let breakpoint = Arc::from(breakpoint.clone());
8622
8623 let alt_as_text = gpui::Keystroke {
8624 modifiers: Modifiers::secondary_key(),
8625 ..Default::default()
8626 };
8627 let primary_action_text = if breakpoint.is_disabled() {
8628 "Enable breakpoint"
8629 } else if is_phantom && !collides_with_existing {
8630 "Set breakpoint"
8631 } else {
8632 "Unset breakpoint"
8633 };
8634 let focus_handle = self.focus_handle.clone();
8635
8636 let meta = if is_rejected {
8637 SharedString::from("No executable code is associated with this line.")
8638 } else if collides_with_existing && !breakpoint.is_disabled() {
8639 SharedString::from(format!(
8640 "{alt_as_text}-click to disable,\nright-click for more options."
8641 ))
8642 } else {
8643 SharedString::from("Right-click for more options.")
8644 };
8645 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8646 .icon_size(IconSize::XSmall)
8647 .size(ui::ButtonSize::None)
8648 .when(is_rejected, |this| {
8649 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8650 })
8651 .icon_color(color)
8652 .style(ButtonStyle::Transparent)
8653 .on_click(cx.listener({
8654 move |editor, event: &ClickEvent, window, cx| {
8655 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8656 BreakpointEditAction::InvertState
8657 } else {
8658 BreakpointEditAction::Toggle
8659 };
8660
8661 window.focus(&editor.focus_handle(cx));
8662 editor.edit_breakpoint_at_anchor(
8663 position,
8664 breakpoint.as_ref().clone(),
8665 edit_action,
8666 cx,
8667 );
8668 }
8669 }))
8670 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8671 editor.set_breakpoint_context_menu(
8672 row,
8673 Some(position),
8674 event.position(),
8675 window,
8676 cx,
8677 );
8678 }))
8679 .tooltip(move |_window, cx| {
8680 Tooltip::with_meta_in(
8681 primary_action_text,
8682 Some(&ToggleBreakpoint),
8683 meta.clone(),
8684 &focus_handle,
8685 cx,
8686 )
8687 })
8688 }
8689
8690 fn build_tasks_context(
8691 project: &Entity<Project>,
8692 buffer: &Entity<Buffer>,
8693 buffer_row: u32,
8694 tasks: &Arc<RunnableTasks>,
8695 cx: &mut Context<Self>,
8696 ) -> Task<Option<task::TaskContext>> {
8697 let position = Point::new(buffer_row, tasks.column);
8698 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8699 let location = Location {
8700 buffer: buffer.clone(),
8701 range: range_start..range_start,
8702 };
8703 // Fill in the environmental variables from the tree-sitter captures
8704 let mut captured_task_variables = TaskVariables::default();
8705 for (capture_name, value) in tasks.extra_variables.clone() {
8706 captured_task_variables.insert(
8707 task::VariableName::Custom(capture_name.into()),
8708 value.clone(),
8709 );
8710 }
8711 project.update(cx, |project, cx| {
8712 project.task_store().update(cx, |task_store, cx| {
8713 task_store.task_context_for_location(captured_task_variables, location, cx)
8714 })
8715 })
8716 }
8717
8718 pub fn spawn_nearest_task(
8719 &mut self,
8720 action: &SpawnNearestTask,
8721 window: &mut Window,
8722 cx: &mut Context<Self>,
8723 ) {
8724 let Some((workspace, _)) = self.workspace.clone() else {
8725 return;
8726 };
8727 let Some(project) = self.project.clone() else {
8728 return;
8729 };
8730
8731 // Try to find a closest, enclosing node using tree-sitter that has a task
8732 let Some((buffer, buffer_row, tasks)) = self
8733 .find_enclosing_node_task(cx)
8734 // Or find the task that's closest in row-distance.
8735 .or_else(|| self.find_closest_task(cx))
8736 else {
8737 return;
8738 };
8739
8740 let reveal_strategy = action.reveal;
8741 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8742 cx.spawn_in(window, async move |_, cx| {
8743 let context = task_context.await?;
8744 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8745
8746 let resolved = &mut resolved_task.resolved;
8747 resolved.reveal = reveal_strategy;
8748
8749 workspace
8750 .update_in(cx, |workspace, window, cx| {
8751 workspace.schedule_resolved_task(
8752 task_source_kind,
8753 resolved_task,
8754 false,
8755 window,
8756 cx,
8757 );
8758 })
8759 .ok()
8760 })
8761 .detach();
8762 }
8763
8764 fn find_closest_task(
8765 &mut self,
8766 cx: &mut Context<Self>,
8767 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8768 let cursor_row = self
8769 .selections
8770 .newest_adjusted(&self.display_snapshot(cx))
8771 .head()
8772 .row;
8773
8774 let ((buffer_id, row), tasks) = self
8775 .tasks
8776 .iter()
8777 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8778
8779 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8780 let tasks = Arc::new(tasks.to_owned());
8781 Some((buffer, *row, tasks))
8782 }
8783
8784 fn find_enclosing_node_task(
8785 &mut self,
8786 cx: &mut Context<Self>,
8787 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8788 let snapshot = self.buffer.read(cx).snapshot(cx);
8789 let offset = self
8790 .selections
8791 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8792 .head();
8793 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8794 let offset = excerpt.map_offset_to_buffer(offset);
8795 let buffer_id = excerpt.buffer().remote_id();
8796
8797 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8798 let mut cursor = layer.node().walk();
8799
8800 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8801 if cursor.node().end_byte() == offset.0 {
8802 cursor.goto_next_sibling();
8803 }
8804 }
8805
8806 // Ascend to the smallest ancestor that contains the range and has a task.
8807 loop {
8808 let node = cursor.node();
8809 let node_range = node.byte_range();
8810 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8811
8812 // Check if this node contains our offset
8813 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8814 // If it contains offset, check for task
8815 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8816 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8817 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8818 }
8819 }
8820
8821 if !cursor.goto_parent() {
8822 break;
8823 }
8824 }
8825 None
8826 }
8827
8828 fn render_run_indicator(
8829 &self,
8830 _style: &EditorStyle,
8831 is_active: bool,
8832 row: DisplayRow,
8833 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8834 cx: &mut Context<Self>,
8835 ) -> IconButton {
8836 let color = Color::Muted;
8837 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8838
8839 IconButton::new(
8840 ("run_indicator", row.0 as usize),
8841 ui::IconName::PlayOutlined,
8842 )
8843 .shape(ui::IconButtonShape::Square)
8844 .icon_size(IconSize::XSmall)
8845 .icon_color(color)
8846 .toggle_state(is_active)
8847 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8848 let quick_launch = match e {
8849 ClickEvent::Keyboard(_) => true,
8850 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8851 };
8852
8853 window.focus(&editor.focus_handle(cx));
8854 editor.toggle_code_actions(
8855 &ToggleCodeActions {
8856 deployed_from: Some(CodeActionSource::RunMenu(row)),
8857 quick_launch,
8858 },
8859 window,
8860 cx,
8861 );
8862 }))
8863 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8864 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8865 }))
8866 }
8867
8868 pub fn context_menu_visible(&self) -> bool {
8869 !self.edit_prediction_preview_is_active()
8870 && self
8871 .context_menu
8872 .borrow()
8873 .as_ref()
8874 .is_some_and(|menu| menu.visible())
8875 }
8876
8877 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8878 self.context_menu
8879 .borrow()
8880 .as_ref()
8881 .map(|menu| menu.origin())
8882 }
8883
8884 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8885 self.context_menu_options = Some(options);
8886 }
8887
8888 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8889 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8890
8891 fn render_edit_prediction_popover(
8892 &mut self,
8893 text_bounds: &Bounds<Pixels>,
8894 content_origin: gpui::Point<Pixels>,
8895 right_margin: Pixels,
8896 editor_snapshot: &EditorSnapshot,
8897 visible_row_range: Range<DisplayRow>,
8898 scroll_top: ScrollOffset,
8899 scroll_bottom: ScrollOffset,
8900 line_layouts: &[LineWithInvisibles],
8901 line_height: Pixels,
8902 scroll_position: gpui::Point<ScrollOffset>,
8903 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8904 newest_selection_head: Option<DisplayPoint>,
8905 editor_width: Pixels,
8906 style: &EditorStyle,
8907 window: &mut Window,
8908 cx: &mut App,
8909 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8910 if self.mode().is_minimap() {
8911 return None;
8912 }
8913 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8914
8915 if self.edit_prediction_visible_in_cursor_popover(true) {
8916 return None;
8917 }
8918
8919 match &active_edit_prediction.completion {
8920 EditPrediction::MoveWithin { target, .. } => {
8921 let target_display_point = target.to_display_point(editor_snapshot);
8922
8923 if self.edit_prediction_requires_modifier() {
8924 if !self.edit_prediction_preview_is_active() {
8925 return None;
8926 }
8927
8928 self.render_edit_prediction_modifier_jump_popover(
8929 text_bounds,
8930 content_origin,
8931 visible_row_range,
8932 line_layouts,
8933 line_height,
8934 scroll_pixel_position,
8935 newest_selection_head,
8936 target_display_point,
8937 window,
8938 cx,
8939 )
8940 } else {
8941 self.render_edit_prediction_eager_jump_popover(
8942 text_bounds,
8943 content_origin,
8944 editor_snapshot,
8945 visible_row_range,
8946 scroll_top,
8947 scroll_bottom,
8948 line_height,
8949 scroll_pixel_position,
8950 target_display_point,
8951 editor_width,
8952 window,
8953 cx,
8954 )
8955 }
8956 }
8957 EditPrediction::Edit {
8958 display_mode: EditDisplayMode::Inline,
8959 ..
8960 } => None,
8961 EditPrediction::Edit {
8962 display_mode: EditDisplayMode::TabAccept,
8963 edits,
8964 ..
8965 } => {
8966 let range = &edits.first()?.0;
8967 let target_display_point = range.end.to_display_point(editor_snapshot);
8968
8969 self.render_edit_prediction_end_of_line_popover(
8970 "Accept",
8971 editor_snapshot,
8972 visible_row_range,
8973 target_display_point,
8974 line_height,
8975 scroll_pixel_position,
8976 content_origin,
8977 editor_width,
8978 window,
8979 cx,
8980 )
8981 }
8982 EditPrediction::Edit {
8983 edits,
8984 edit_preview,
8985 display_mode: EditDisplayMode::DiffPopover,
8986 snapshot,
8987 } => self.render_edit_prediction_diff_popover(
8988 text_bounds,
8989 content_origin,
8990 right_margin,
8991 editor_snapshot,
8992 visible_row_range,
8993 line_layouts,
8994 line_height,
8995 scroll_position,
8996 scroll_pixel_position,
8997 newest_selection_head,
8998 editor_width,
8999 style,
9000 edits,
9001 edit_preview,
9002 snapshot,
9003 window,
9004 cx,
9005 ),
9006 EditPrediction::MoveOutside { snapshot, .. } => {
9007 let mut element = self
9008 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9009 .into_any();
9010
9011 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9012 let origin_x = text_bounds.size.width - size.width - px(30.);
9013 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9014 element.prepaint_at(origin, window, cx);
9015
9016 Some((element, origin))
9017 }
9018 }
9019 }
9020
9021 fn render_edit_prediction_modifier_jump_popover(
9022 &mut self,
9023 text_bounds: &Bounds<Pixels>,
9024 content_origin: gpui::Point<Pixels>,
9025 visible_row_range: Range<DisplayRow>,
9026 line_layouts: &[LineWithInvisibles],
9027 line_height: Pixels,
9028 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9029 newest_selection_head: Option<DisplayPoint>,
9030 target_display_point: DisplayPoint,
9031 window: &mut Window,
9032 cx: &mut App,
9033 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9034 let scrolled_content_origin =
9035 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9036
9037 const SCROLL_PADDING_Y: Pixels = px(12.);
9038
9039 if target_display_point.row() < visible_row_range.start {
9040 return self.render_edit_prediction_scroll_popover(
9041 |_| SCROLL_PADDING_Y,
9042 IconName::ArrowUp,
9043 visible_row_range,
9044 line_layouts,
9045 newest_selection_head,
9046 scrolled_content_origin,
9047 window,
9048 cx,
9049 );
9050 } else if target_display_point.row() >= visible_row_range.end {
9051 return self.render_edit_prediction_scroll_popover(
9052 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9053 IconName::ArrowDown,
9054 visible_row_range,
9055 line_layouts,
9056 newest_selection_head,
9057 scrolled_content_origin,
9058 window,
9059 cx,
9060 );
9061 }
9062
9063 const POLE_WIDTH: Pixels = px(2.);
9064
9065 let line_layout =
9066 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9067 let target_column = target_display_point.column() as usize;
9068
9069 let target_x = line_layout.x_for_index(target_column);
9070 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9071 - scroll_pixel_position.y;
9072
9073 let flag_on_right = target_x < text_bounds.size.width / 2.;
9074
9075 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9076 border_color.l += 0.001;
9077
9078 let mut element = v_flex()
9079 .items_end()
9080 .when(flag_on_right, |el| el.items_start())
9081 .child(if flag_on_right {
9082 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9083 .rounded_bl(px(0.))
9084 .rounded_tl(px(0.))
9085 .border_l_2()
9086 .border_color(border_color)
9087 } else {
9088 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9089 .rounded_br(px(0.))
9090 .rounded_tr(px(0.))
9091 .border_r_2()
9092 .border_color(border_color)
9093 })
9094 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9095 .into_any();
9096
9097 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9098
9099 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9100 - point(
9101 if flag_on_right {
9102 POLE_WIDTH
9103 } else {
9104 size.width - POLE_WIDTH
9105 },
9106 size.height - line_height,
9107 );
9108
9109 origin.x = origin.x.max(content_origin.x);
9110
9111 element.prepaint_at(origin, window, cx);
9112
9113 Some((element, origin))
9114 }
9115
9116 fn render_edit_prediction_scroll_popover(
9117 &mut self,
9118 to_y: impl Fn(Size<Pixels>) -> Pixels,
9119 scroll_icon: IconName,
9120 visible_row_range: Range<DisplayRow>,
9121 line_layouts: &[LineWithInvisibles],
9122 newest_selection_head: Option<DisplayPoint>,
9123 scrolled_content_origin: gpui::Point<Pixels>,
9124 window: &mut Window,
9125 cx: &mut App,
9126 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9127 let mut element = self
9128 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9129 .into_any();
9130
9131 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9132
9133 let cursor = newest_selection_head?;
9134 let cursor_row_layout =
9135 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9136 let cursor_column = cursor.column() as usize;
9137
9138 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9139
9140 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9141
9142 element.prepaint_at(origin, window, cx);
9143 Some((element, origin))
9144 }
9145
9146 fn render_edit_prediction_eager_jump_popover(
9147 &mut self,
9148 text_bounds: &Bounds<Pixels>,
9149 content_origin: gpui::Point<Pixels>,
9150 editor_snapshot: &EditorSnapshot,
9151 visible_row_range: Range<DisplayRow>,
9152 scroll_top: ScrollOffset,
9153 scroll_bottom: ScrollOffset,
9154 line_height: Pixels,
9155 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9156 target_display_point: DisplayPoint,
9157 editor_width: Pixels,
9158 window: &mut Window,
9159 cx: &mut App,
9160 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9161 if target_display_point.row().as_f64() < scroll_top {
9162 let mut element = self
9163 .render_edit_prediction_line_popover(
9164 "Jump to Edit",
9165 Some(IconName::ArrowUp),
9166 window,
9167 cx,
9168 )
9169 .into_any();
9170
9171 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9172 let offset = point(
9173 (text_bounds.size.width - size.width) / 2.,
9174 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9175 );
9176
9177 let origin = text_bounds.origin + offset;
9178 element.prepaint_at(origin, window, cx);
9179 Some((element, origin))
9180 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9181 let mut element = self
9182 .render_edit_prediction_line_popover(
9183 "Jump to Edit",
9184 Some(IconName::ArrowDown),
9185 window,
9186 cx,
9187 )
9188 .into_any();
9189
9190 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9191 let offset = point(
9192 (text_bounds.size.width - size.width) / 2.,
9193 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9194 );
9195
9196 let origin = text_bounds.origin + offset;
9197 element.prepaint_at(origin, window, cx);
9198 Some((element, origin))
9199 } else {
9200 self.render_edit_prediction_end_of_line_popover(
9201 "Jump to Edit",
9202 editor_snapshot,
9203 visible_row_range,
9204 target_display_point,
9205 line_height,
9206 scroll_pixel_position,
9207 content_origin,
9208 editor_width,
9209 window,
9210 cx,
9211 )
9212 }
9213 }
9214
9215 fn render_edit_prediction_end_of_line_popover(
9216 self: &mut Editor,
9217 label: &'static str,
9218 editor_snapshot: &EditorSnapshot,
9219 visible_row_range: Range<DisplayRow>,
9220 target_display_point: DisplayPoint,
9221 line_height: Pixels,
9222 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9223 content_origin: gpui::Point<Pixels>,
9224 editor_width: Pixels,
9225 window: &mut Window,
9226 cx: &mut App,
9227 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9228 let target_line_end = DisplayPoint::new(
9229 target_display_point.row(),
9230 editor_snapshot.line_len(target_display_point.row()),
9231 );
9232
9233 let mut element = self
9234 .render_edit_prediction_line_popover(label, None, window, cx)
9235 .into_any();
9236
9237 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9238
9239 let line_origin =
9240 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9241
9242 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9243 let mut origin = start_point
9244 + line_origin
9245 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9246 origin.x = origin.x.max(content_origin.x);
9247
9248 let max_x = content_origin.x + editor_width - size.width;
9249
9250 if origin.x > max_x {
9251 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9252
9253 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9254 origin.y += offset;
9255 IconName::ArrowUp
9256 } else {
9257 origin.y -= offset;
9258 IconName::ArrowDown
9259 };
9260
9261 element = self
9262 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9263 .into_any();
9264
9265 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9266
9267 origin.x = content_origin.x + editor_width - size.width - px(2.);
9268 }
9269
9270 element.prepaint_at(origin, window, cx);
9271 Some((element, origin))
9272 }
9273
9274 fn render_edit_prediction_diff_popover(
9275 self: &Editor,
9276 text_bounds: &Bounds<Pixels>,
9277 content_origin: gpui::Point<Pixels>,
9278 right_margin: Pixels,
9279 editor_snapshot: &EditorSnapshot,
9280 visible_row_range: Range<DisplayRow>,
9281 line_layouts: &[LineWithInvisibles],
9282 line_height: Pixels,
9283 scroll_position: gpui::Point<ScrollOffset>,
9284 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9285 newest_selection_head: Option<DisplayPoint>,
9286 editor_width: Pixels,
9287 style: &EditorStyle,
9288 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9289 edit_preview: &Option<language::EditPreview>,
9290 snapshot: &language::BufferSnapshot,
9291 window: &mut Window,
9292 cx: &mut App,
9293 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9294 let edit_start = edits
9295 .first()
9296 .unwrap()
9297 .0
9298 .start
9299 .to_display_point(editor_snapshot);
9300 let edit_end = edits
9301 .last()
9302 .unwrap()
9303 .0
9304 .end
9305 .to_display_point(editor_snapshot);
9306
9307 let is_visible = visible_row_range.contains(&edit_start.row())
9308 || visible_row_range.contains(&edit_end.row());
9309 if !is_visible {
9310 return None;
9311 }
9312
9313 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9314 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9315 } else {
9316 // Fallback for providers without edit_preview
9317 crate::edit_prediction_fallback_text(edits, cx)
9318 };
9319
9320 let styled_text = highlighted_edits.to_styled_text(&style.text);
9321 let line_count = highlighted_edits.text.lines().count();
9322
9323 const BORDER_WIDTH: Pixels = px(1.);
9324
9325 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9326 let has_keybind = keybind.is_some();
9327
9328 let mut element = h_flex()
9329 .items_start()
9330 .child(
9331 h_flex()
9332 .bg(cx.theme().colors().editor_background)
9333 .border(BORDER_WIDTH)
9334 .shadow_xs()
9335 .border_color(cx.theme().colors().border)
9336 .rounded_l_lg()
9337 .when(line_count > 1, |el| el.rounded_br_lg())
9338 .pr_1()
9339 .child(styled_text),
9340 )
9341 .child(
9342 h_flex()
9343 .h(line_height + BORDER_WIDTH * 2.)
9344 .px_1p5()
9345 .gap_1()
9346 // Workaround: For some reason, there's a gap if we don't do this
9347 .ml(-BORDER_WIDTH)
9348 .shadow(vec![gpui::BoxShadow {
9349 color: gpui::black().opacity(0.05),
9350 offset: point(px(1.), px(1.)),
9351 blur_radius: px(2.),
9352 spread_radius: px(0.),
9353 }])
9354 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9355 .border(BORDER_WIDTH)
9356 .border_color(cx.theme().colors().border)
9357 .rounded_r_lg()
9358 .id("edit_prediction_diff_popover_keybind")
9359 .when(!has_keybind, |el| {
9360 let status_colors = cx.theme().status();
9361
9362 el.bg(status_colors.error_background)
9363 .border_color(status_colors.error.opacity(0.6))
9364 .child(Icon::new(IconName::Info).color(Color::Error))
9365 .cursor_default()
9366 .hoverable_tooltip(move |_window, cx| {
9367 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9368 })
9369 })
9370 .children(keybind),
9371 )
9372 .into_any();
9373
9374 let longest_row =
9375 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9376 let longest_line_width = if visible_row_range.contains(&longest_row) {
9377 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9378 } else {
9379 layout_line(
9380 longest_row,
9381 editor_snapshot,
9382 style,
9383 editor_width,
9384 |_| false,
9385 window,
9386 cx,
9387 )
9388 .width
9389 };
9390
9391 let viewport_bounds =
9392 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9393 right: -right_margin,
9394 ..Default::default()
9395 });
9396
9397 let x_after_longest = Pixels::from(
9398 ScrollPixelOffset::from(
9399 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9400 ) - scroll_pixel_position.x,
9401 );
9402
9403 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9404
9405 // Fully visible if it can be displayed within the window (allow overlapping other
9406 // panes). However, this is only allowed if the popover starts within text_bounds.
9407 let can_position_to_the_right = x_after_longest < text_bounds.right()
9408 && x_after_longest + element_bounds.width < viewport_bounds.right();
9409
9410 let mut origin = if can_position_to_the_right {
9411 point(
9412 x_after_longest,
9413 text_bounds.origin.y
9414 + Pixels::from(
9415 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9416 - scroll_pixel_position.y,
9417 ),
9418 )
9419 } else {
9420 let cursor_row = newest_selection_head.map(|head| head.row());
9421 let above_edit = edit_start
9422 .row()
9423 .0
9424 .checked_sub(line_count as u32)
9425 .map(DisplayRow);
9426 let below_edit = Some(edit_end.row() + 1);
9427 let above_cursor =
9428 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9429 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9430
9431 // Place the edit popover adjacent to the edit if there is a location
9432 // available that is onscreen and does not obscure the cursor. Otherwise,
9433 // place it adjacent to the cursor.
9434 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9435 .into_iter()
9436 .flatten()
9437 .find(|&start_row| {
9438 let end_row = start_row + line_count as u32;
9439 visible_row_range.contains(&start_row)
9440 && visible_row_range.contains(&end_row)
9441 && cursor_row
9442 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9443 })?;
9444
9445 content_origin
9446 + point(
9447 Pixels::from(-scroll_pixel_position.x),
9448 Pixels::from(
9449 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9450 ),
9451 )
9452 };
9453
9454 origin.x -= BORDER_WIDTH;
9455
9456 window.defer_draw(element, origin, 1);
9457
9458 // Do not return an element, since it will already be drawn due to defer_draw.
9459 None
9460 }
9461
9462 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9463 px(30.)
9464 }
9465
9466 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9467 if self.read_only(cx) {
9468 cx.theme().players().read_only()
9469 } else {
9470 self.style.as_ref().unwrap().local_player
9471 }
9472 }
9473
9474 fn render_edit_prediction_accept_keybind(
9475 &self,
9476 window: &mut Window,
9477 cx: &mut App,
9478 ) -> Option<AnyElement> {
9479 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9480 let accept_keystroke = accept_binding.keystroke()?;
9481
9482 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9483
9484 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9485 Color::Accent
9486 } else {
9487 Color::Muted
9488 };
9489
9490 h_flex()
9491 .px_0p5()
9492 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9493 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9494 .text_size(TextSize::XSmall.rems(cx))
9495 .child(h_flex().children(ui::render_modifiers(
9496 accept_keystroke.modifiers(),
9497 PlatformStyle::platform(),
9498 Some(modifiers_color),
9499 Some(IconSize::XSmall.rems().into()),
9500 true,
9501 )))
9502 .when(is_platform_style_mac, |parent| {
9503 parent.child(accept_keystroke.key().to_string())
9504 })
9505 .when(!is_platform_style_mac, |parent| {
9506 parent.child(
9507 Key::new(
9508 util::capitalize(accept_keystroke.key()),
9509 Some(Color::Default),
9510 )
9511 .size(Some(IconSize::XSmall.rems().into())),
9512 )
9513 })
9514 .into_any()
9515 .into()
9516 }
9517
9518 fn render_edit_prediction_line_popover(
9519 &self,
9520 label: impl Into<SharedString>,
9521 icon: Option<IconName>,
9522 window: &mut Window,
9523 cx: &mut App,
9524 ) -> Stateful<Div> {
9525 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9526
9527 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9528 let has_keybind = keybind.is_some();
9529
9530 h_flex()
9531 .id("ep-line-popover")
9532 .py_0p5()
9533 .pl_1()
9534 .pr(padding_right)
9535 .gap_1()
9536 .rounded_md()
9537 .border_1()
9538 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9539 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9540 .shadow_xs()
9541 .when(!has_keybind, |el| {
9542 let status_colors = cx.theme().status();
9543
9544 el.bg(status_colors.error_background)
9545 .border_color(status_colors.error.opacity(0.6))
9546 .pl_2()
9547 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9548 .cursor_default()
9549 .hoverable_tooltip(move |_window, cx| {
9550 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9551 })
9552 })
9553 .children(keybind)
9554 .child(
9555 Label::new(label)
9556 .size(LabelSize::Small)
9557 .when(!has_keybind, |el| {
9558 el.color(cx.theme().status().error.into()).strikethrough()
9559 }),
9560 )
9561 .when(!has_keybind, |el| {
9562 el.child(
9563 h_flex().ml_1().child(
9564 Icon::new(IconName::Info)
9565 .size(IconSize::Small)
9566 .color(cx.theme().status().error.into()),
9567 ),
9568 )
9569 })
9570 .when_some(icon, |element, icon| {
9571 element.child(
9572 div()
9573 .mt(px(1.5))
9574 .child(Icon::new(icon).size(IconSize::Small)),
9575 )
9576 })
9577 }
9578
9579 fn render_edit_prediction_jump_outside_popover(
9580 &self,
9581 snapshot: &BufferSnapshot,
9582 window: &mut Window,
9583 cx: &mut App,
9584 ) -> Stateful<Div> {
9585 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9586 let has_keybind = keybind.is_some();
9587
9588 let file_name = snapshot
9589 .file()
9590 .map(|file| SharedString::new(file.file_name(cx)))
9591 .unwrap_or(SharedString::new_static("untitled"));
9592
9593 h_flex()
9594 .id("ep-jump-outside-popover")
9595 .py_1()
9596 .px_2()
9597 .gap_1()
9598 .rounded_md()
9599 .border_1()
9600 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9601 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9602 .shadow_xs()
9603 .when(!has_keybind, |el| {
9604 let status_colors = cx.theme().status();
9605
9606 el.bg(status_colors.error_background)
9607 .border_color(status_colors.error.opacity(0.6))
9608 .pl_2()
9609 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9610 .cursor_default()
9611 .hoverable_tooltip(move |_window, cx| {
9612 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9613 })
9614 })
9615 .children(keybind)
9616 .child(
9617 Label::new(file_name)
9618 .size(LabelSize::Small)
9619 .buffer_font(cx)
9620 .when(!has_keybind, |el| {
9621 el.color(cx.theme().status().error.into()).strikethrough()
9622 }),
9623 )
9624 .when(!has_keybind, |el| {
9625 el.child(
9626 h_flex().ml_1().child(
9627 Icon::new(IconName::Info)
9628 .size(IconSize::Small)
9629 .color(cx.theme().status().error.into()),
9630 ),
9631 )
9632 })
9633 .child(
9634 div()
9635 .mt(px(1.5))
9636 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9637 )
9638 }
9639
9640 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9641 let accent_color = cx.theme().colors().text_accent;
9642 let editor_bg_color = cx.theme().colors().editor_background;
9643 editor_bg_color.blend(accent_color.opacity(0.1))
9644 }
9645
9646 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9647 let accent_color = cx.theme().colors().text_accent;
9648 let editor_bg_color = cx.theme().colors().editor_background;
9649 editor_bg_color.blend(accent_color.opacity(0.6))
9650 }
9651 fn get_prediction_provider_icon_name(
9652 provider: &Option<RegisteredEditPredictionDelegate>,
9653 ) -> IconName {
9654 match provider {
9655 Some(provider) => match provider.provider.name() {
9656 "copilot" => IconName::Copilot,
9657 "supermaven" => IconName::Supermaven,
9658 _ => IconName::ZedPredict,
9659 },
9660 None => IconName::ZedPredict,
9661 }
9662 }
9663
9664 fn render_edit_prediction_cursor_popover(
9665 &self,
9666 min_width: Pixels,
9667 max_width: Pixels,
9668 cursor_point: Point,
9669 style: &EditorStyle,
9670 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9671 _window: &Window,
9672 cx: &mut Context<Editor>,
9673 ) -> Option<AnyElement> {
9674 let provider = self.edit_prediction_provider.as_ref()?;
9675 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9676
9677 let is_refreshing = provider.provider.is_refreshing(cx);
9678
9679 fn pending_completion_container(icon: IconName) -> Div {
9680 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9681 }
9682
9683 let completion = match &self.active_edit_prediction {
9684 Some(prediction) => {
9685 if !self.has_visible_completions_menu() {
9686 const RADIUS: Pixels = px(6.);
9687 const BORDER_WIDTH: Pixels = px(1.);
9688
9689 return Some(
9690 h_flex()
9691 .elevation_2(cx)
9692 .border(BORDER_WIDTH)
9693 .border_color(cx.theme().colors().border)
9694 .when(accept_keystroke.is_none(), |el| {
9695 el.border_color(cx.theme().status().error)
9696 })
9697 .rounded(RADIUS)
9698 .rounded_tl(px(0.))
9699 .overflow_hidden()
9700 .child(div().px_1p5().child(match &prediction.completion {
9701 EditPrediction::MoveWithin { target, snapshot } => {
9702 use text::ToPoint as _;
9703 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9704 {
9705 Icon::new(IconName::ZedPredictDown)
9706 } else {
9707 Icon::new(IconName::ZedPredictUp)
9708 }
9709 }
9710 EditPrediction::MoveOutside { .. } => {
9711 // TODO [zeta2] custom icon for external jump?
9712 Icon::new(provider_icon)
9713 }
9714 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9715 }))
9716 .child(
9717 h_flex()
9718 .gap_1()
9719 .py_1()
9720 .px_2()
9721 .rounded_r(RADIUS - BORDER_WIDTH)
9722 .border_l_1()
9723 .border_color(cx.theme().colors().border)
9724 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9725 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9726 el.child(
9727 Label::new("Hold")
9728 .size(LabelSize::Small)
9729 .when(accept_keystroke.is_none(), |el| {
9730 el.strikethrough()
9731 })
9732 .line_height_style(LineHeightStyle::UiLabel),
9733 )
9734 })
9735 .id("edit_prediction_cursor_popover_keybind")
9736 .when(accept_keystroke.is_none(), |el| {
9737 let status_colors = cx.theme().status();
9738
9739 el.bg(status_colors.error_background)
9740 .border_color(status_colors.error.opacity(0.6))
9741 .child(Icon::new(IconName::Info).color(Color::Error))
9742 .cursor_default()
9743 .hoverable_tooltip(move |_window, cx| {
9744 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9745 .into()
9746 })
9747 })
9748 .when_some(
9749 accept_keystroke.as_ref(),
9750 |el, accept_keystroke| {
9751 el.child(h_flex().children(ui::render_modifiers(
9752 accept_keystroke.modifiers(),
9753 PlatformStyle::platform(),
9754 Some(Color::Default),
9755 Some(IconSize::XSmall.rems().into()),
9756 false,
9757 )))
9758 },
9759 ),
9760 )
9761 .into_any(),
9762 );
9763 }
9764
9765 self.render_edit_prediction_cursor_popover_preview(
9766 prediction,
9767 cursor_point,
9768 style,
9769 cx,
9770 )?
9771 }
9772
9773 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9774 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9775 stale_completion,
9776 cursor_point,
9777 style,
9778 cx,
9779 )?,
9780
9781 None => pending_completion_container(provider_icon)
9782 .child(Label::new("...").size(LabelSize::Small)),
9783 },
9784
9785 None => pending_completion_container(provider_icon)
9786 .child(Label::new("...").size(LabelSize::Small)),
9787 };
9788
9789 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9790 completion
9791 .with_animation(
9792 "loading-completion",
9793 Animation::new(Duration::from_secs(2))
9794 .repeat()
9795 .with_easing(pulsating_between(0.4, 0.8)),
9796 |label, delta| label.opacity(delta),
9797 )
9798 .into_any_element()
9799 } else {
9800 completion.into_any_element()
9801 };
9802
9803 let has_completion = self.active_edit_prediction.is_some();
9804
9805 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9806 Some(
9807 h_flex()
9808 .min_w(min_width)
9809 .max_w(max_width)
9810 .flex_1()
9811 .elevation_2(cx)
9812 .border_color(cx.theme().colors().border)
9813 .child(
9814 div()
9815 .flex_1()
9816 .py_1()
9817 .px_2()
9818 .overflow_hidden()
9819 .child(completion),
9820 )
9821 .when_some(accept_keystroke, |el, accept_keystroke| {
9822 if !accept_keystroke.modifiers().modified() {
9823 return el;
9824 }
9825
9826 el.child(
9827 h_flex()
9828 .h_full()
9829 .border_l_1()
9830 .rounded_r_lg()
9831 .border_color(cx.theme().colors().border)
9832 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9833 .gap_1()
9834 .py_1()
9835 .px_2()
9836 .child(
9837 h_flex()
9838 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9839 .when(is_platform_style_mac, |parent| parent.gap_1())
9840 .child(h_flex().children(ui::render_modifiers(
9841 accept_keystroke.modifiers(),
9842 PlatformStyle::platform(),
9843 Some(if !has_completion {
9844 Color::Muted
9845 } else {
9846 Color::Default
9847 }),
9848 None,
9849 false,
9850 ))),
9851 )
9852 .child(Label::new("Preview").into_any_element())
9853 .opacity(if has_completion { 1.0 } else { 0.4 }),
9854 )
9855 })
9856 .into_any(),
9857 )
9858 }
9859
9860 fn render_edit_prediction_cursor_popover_preview(
9861 &self,
9862 completion: &EditPredictionState,
9863 cursor_point: Point,
9864 style: &EditorStyle,
9865 cx: &mut Context<Editor>,
9866 ) -> Option<Div> {
9867 use text::ToPoint as _;
9868
9869 fn render_relative_row_jump(
9870 prefix: impl Into<String>,
9871 current_row: u32,
9872 target_row: u32,
9873 ) -> Div {
9874 let (row_diff, arrow) = if target_row < current_row {
9875 (current_row - target_row, IconName::ArrowUp)
9876 } else {
9877 (target_row - current_row, IconName::ArrowDown)
9878 };
9879
9880 h_flex()
9881 .child(
9882 Label::new(format!("{}{}", prefix.into(), row_diff))
9883 .color(Color::Muted)
9884 .size(LabelSize::Small),
9885 )
9886 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9887 }
9888
9889 let supports_jump = self
9890 .edit_prediction_provider
9891 .as_ref()
9892 .map(|provider| provider.provider.supports_jump_to_edit())
9893 .unwrap_or(true);
9894
9895 match &completion.completion {
9896 EditPrediction::MoveWithin {
9897 target, snapshot, ..
9898 } => {
9899 if !supports_jump {
9900 return None;
9901 }
9902
9903 Some(
9904 h_flex()
9905 .px_2()
9906 .gap_2()
9907 .flex_1()
9908 .child(
9909 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9910 Icon::new(IconName::ZedPredictDown)
9911 } else {
9912 Icon::new(IconName::ZedPredictUp)
9913 },
9914 )
9915 .child(Label::new("Jump to Edit")),
9916 )
9917 }
9918 EditPrediction::MoveOutside { snapshot, .. } => {
9919 let file_name = snapshot
9920 .file()
9921 .map(|file| file.file_name(cx))
9922 .unwrap_or("untitled");
9923 Some(
9924 h_flex()
9925 .px_2()
9926 .gap_2()
9927 .flex_1()
9928 .child(Icon::new(IconName::ZedPredict))
9929 .child(Label::new(format!("Jump to {file_name}"))),
9930 )
9931 }
9932 EditPrediction::Edit {
9933 edits,
9934 edit_preview,
9935 snapshot,
9936 display_mode: _,
9937 } => {
9938 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9939
9940 let (highlighted_edits, has_more_lines) =
9941 if let Some(edit_preview) = edit_preview.as_ref() {
9942 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9943 .first_line_preview()
9944 } else {
9945 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9946 };
9947
9948 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9949 .with_default_highlights(&style.text, highlighted_edits.highlights);
9950
9951 let preview = h_flex()
9952 .gap_1()
9953 .min_w_16()
9954 .child(styled_text)
9955 .when(has_more_lines, |parent| parent.child("…"));
9956
9957 let left = if supports_jump && first_edit_row != cursor_point.row {
9958 render_relative_row_jump("", cursor_point.row, first_edit_row)
9959 .into_any_element()
9960 } else {
9961 let icon_name =
9962 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9963 Icon::new(icon_name).into_any_element()
9964 };
9965
9966 Some(
9967 h_flex()
9968 .h_full()
9969 .flex_1()
9970 .gap_2()
9971 .pr_1()
9972 .overflow_x_hidden()
9973 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9974 .child(left)
9975 .child(preview),
9976 )
9977 }
9978 }
9979 }
9980
9981 pub fn render_context_menu(
9982 &mut self,
9983 max_height_in_lines: u32,
9984 window: &mut Window,
9985 cx: &mut Context<Editor>,
9986 ) -> Option<AnyElement> {
9987 let menu = self.context_menu.borrow();
9988 let menu = menu.as_ref()?;
9989 if !menu.visible() {
9990 return None;
9991 };
9992 self.style
9993 .as_ref()
9994 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9995 }
9996
9997 fn render_context_menu_aside(
9998 &mut self,
9999 max_size: Size<Pixels>,
10000 window: &mut Window,
10001 cx: &mut Context<Editor>,
10002 ) -> Option<AnyElement> {
10003 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10004 if menu.visible() {
10005 menu.render_aside(max_size, window, cx)
10006 } else {
10007 None
10008 }
10009 })
10010 }
10011
10012 fn hide_context_menu(
10013 &mut self,
10014 window: &mut Window,
10015 cx: &mut Context<Self>,
10016 ) -> Option<CodeContextMenu> {
10017 cx.notify();
10018 self.completion_tasks.clear();
10019 let context_menu = self.context_menu.borrow_mut().take();
10020 self.stale_edit_prediction_in_menu.take();
10021 self.update_visible_edit_prediction(window, cx);
10022 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10023 && let Some(completion_provider) = &self.completion_provider
10024 {
10025 completion_provider.selection_changed(None, window, cx);
10026 }
10027 context_menu
10028 }
10029
10030 fn show_snippet_choices(
10031 &mut self,
10032 choices: &Vec<String>,
10033 selection: Range<Anchor>,
10034 cx: &mut Context<Self>,
10035 ) {
10036 let Some((_, buffer, _)) = self
10037 .buffer()
10038 .read(cx)
10039 .excerpt_containing(selection.start, cx)
10040 else {
10041 return;
10042 };
10043 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10044 else {
10045 return;
10046 };
10047 if buffer != end_buffer {
10048 log::error!("expected anchor range to have matching buffer IDs");
10049 return;
10050 }
10051
10052 let id = post_inc(&mut self.next_completion_id);
10053 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10054 let mut context_menu = self.context_menu.borrow_mut();
10055 let old_menu = context_menu.take();
10056 *context_menu = Some(CodeContextMenu::Completions(
10057 CompletionsMenu::new_snippet_choices(
10058 id,
10059 true,
10060 choices,
10061 selection,
10062 buffer,
10063 old_menu.map(|menu| menu.primary_scroll_handle()),
10064 snippet_sort_order,
10065 ),
10066 ));
10067 }
10068
10069 pub fn insert_snippet(
10070 &mut self,
10071 insertion_ranges: &[Range<MultiBufferOffset>],
10072 snippet: Snippet,
10073 window: &mut Window,
10074 cx: &mut Context<Self>,
10075 ) -> Result<()> {
10076 struct Tabstop<T> {
10077 is_end_tabstop: bool,
10078 ranges: Vec<Range<T>>,
10079 choices: Option<Vec<String>>,
10080 }
10081
10082 let tabstops = self.buffer.update(cx, |buffer, cx| {
10083 let snippet_text: Arc<str> = snippet.text.clone().into();
10084 let edits = insertion_ranges
10085 .iter()
10086 .cloned()
10087 .map(|range| (range, snippet_text.clone()));
10088 let autoindent_mode = AutoindentMode::Block {
10089 original_indent_columns: Vec::new(),
10090 };
10091 buffer.edit(edits, Some(autoindent_mode), cx);
10092
10093 let snapshot = &*buffer.read(cx);
10094 let snippet = &snippet;
10095 snippet
10096 .tabstops
10097 .iter()
10098 .map(|tabstop| {
10099 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10100 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10101 });
10102 let mut tabstop_ranges = tabstop
10103 .ranges
10104 .iter()
10105 .flat_map(|tabstop_range| {
10106 let mut delta = 0_isize;
10107 insertion_ranges.iter().map(move |insertion_range| {
10108 let insertion_start = insertion_range.start + delta;
10109 delta += snippet.text.len() as isize
10110 - (insertion_range.end - insertion_range.start) as isize;
10111
10112 let start =
10113 (insertion_start + tabstop_range.start).min(snapshot.len());
10114 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10115 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10116 })
10117 })
10118 .collect::<Vec<_>>();
10119 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10120
10121 Tabstop {
10122 is_end_tabstop,
10123 ranges: tabstop_ranges,
10124 choices: tabstop.choices.clone(),
10125 }
10126 })
10127 .collect::<Vec<_>>()
10128 });
10129 if let Some(tabstop) = tabstops.first() {
10130 self.change_selections(Default::default(), window, cx, |s| {
10131 // Reverse order so that the first range is the newest created selection.
10132 // Completions will use it and autoscroll will prioritize it.
10133 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10134 });
10135
10136 if let Some(choices) = &tabstop.choices
10137 && let Some(selection) = tabstop.ranges.first()
10138 {
10139 self.show_snippet_choices(choices, selection.clone(), cx)
10140 }
10141
10142 // If we're already at the last tabstop and it's at the end of the snippet,
10143 // we're done, we don't need to keep the state around.
10144 if !tabstop.is_end_tabstop {
10145 let choices = tabstops
10146 .iter()
10147 .map(|tabstop| tabstop.choices.clone())
10148 .collect();
10149
10150 let ranges = tabstops
10151 .into_iter()
10152 .map(|tabstop| tabstop.ranges)
10153 .collect::<Vec<_>>();
10154
10155 self.snippet_stack.push(SnippetState {
10156 active_index: 0,
10157 ranges,
10158 choices,
10159 });
10160 }
10161
10162 // Check whether the just-entered snippet ends with an auto-closable bracket.
10163 if self.autoclose_regions.is_empty() {
10164 let snapshot = self.buffer.read(cx).snapshot(cx);
10165 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10166 let selection_head = selection.head();
10167 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10168 continue;
10169 };
10170
10171 let mut bracket_pair = None;
10172 let max_lookup_length = scope
10173 .brackets()
10174 .map(|(pair, _)| {
10175 pair.start
10176 .as_str()
10177 .chars()
10178 .count()
10179 .max(pair.end.as_str().chars().count())
10180 })
10181 .max();
10182 if let Some(max_lookup_length) = max_lookup_length {
10183 let next_text = snapshot
10184 .chars_at(selection_head)
10185 .take(max_lookup_length)
10186 .collect::<String>();
10187 let prev_text = snapshot
10188 .reversed_chars_at(selection_head)
10189 .take(max_lookup_length)
10190 .collect::<String>();
10191
10192 for (pair, enabled) in scope.brackets() {
10193 if enabled
10194 && pair.close
10195 && prev_text.starts_with(pair.start.as_str())
10196 && next_text.starts_with(pair.end.as_str())
10197 {
10198 bracket_pair = Some(pair.clone());
10199 break;
10200 }
10201 }
10202 }
10203
10204 if let Some(pair) = bracket_pair {
10205 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10206 let autoclose_enabled =
10207 self.use_autoclose && snapshot_settings.use_autoclose;
10208 if autoclose_enabled {
10209 let start = snapshot.anchor_after(selection_head);
10210 let end = snapshot.anchor_after(selection_head);
10211 self.autoclose_regions.push(AutocloseRegion {
10212 selection_id: selection.id,
10213 range: start..end,
10214 pair,
10215 });
10216 }
10217 }
10218 }
10219 }
10220 }
10221 Ok(())
10222 }
10223
10224 pub fn move_to_next_snippet_tabstop(
10225 &mut self,
10226 window: &mut Window,
10227 cx: &mut Context<Self>,
10228 ) -> bool {
10229 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10230 }
10231
10232 pub fn move_to_prev_snippet_tabstop(
10233 &mut self,
10234 window: &mut Window,
10235 cx: &mut Context<Self>,
10236 ) -> bool {
10237 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10238 }
10239
10240 pub fn move_to_snippet_tabstop(
10241 &mut self,
10242 bias: Bias,
10243 window: &mut Window,
10244 cx: &mut Context<Self>,
10245 ) -> bool {
10246 if let Some(mut snippet) = self.snippet_stack.pop() {
10247 match bias {
10248 Bias::Left => {
10249 if snippet.active_index > 0 {
10250 snippet.active_index -= 1;
10251 } else {
10252 self.snippet_stack.push(snippet);
10253 return false;
10254 }
10255 }
10256 Bias::Right => {
10257 if snippet.active_index + 1 < snippet.ranges.len() {
10258 snippet.active_index += 1;
10259 } else {
10260 self.snippet_stack.push(snippet);
10261 return false;
10262 }
10263 }
10264 }
10265 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10266 self.change_selections(Default::default(), window, cx, |s| {
10267 // Reverse order so that the first range is the newest created selection.
10268 // Completions will use it and autoscroll will prioritize it.
10269 s.select_ranges(current_ranges.iter().rev().cloned())
10270 });
10271
10272 if let Some(choices) = &snippet.choices[snippet.active_index]
10273 && let Some(selection) = current_ranges.first()
10274 {
10275 self.show_snippet_choices(choices, selection.clone(), cx);
10276 }
10277
10278 // If snippet state is not at the last tabstop, push it back on the stack
10279 if snippet.active_index + 1 < snippet.ranges.len() {
10280 self.snippet_stack.push(snippet);
10281 }
10282 return true;
10283 }
10284 }
10285
10286 false
10287 }
10288
10289 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10290 self.transact(window, cx, |this, window, cx| {
10291 this.select_all(&SelectAll, window, cx);
10292 this.insert("", window, cx);
10293 });
10294 }
10295
10296 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10297 if self.read_only(cx) {
10298 return;
10299 }
10300 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10301 self.transact(window, cx, |this, window, cx| {
10302 this.select_autoclose_pair(window, cx);
10303
10304 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10305
10306 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10307 if !this.linked_edit_ranges.is_empty() {
10308 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10309 let snapshot = this.buffer.read(cx).snapshot(cx);
10310
10311 for selection in selections.iter() {
10312 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10313 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10314 if selection_start.buffer_id != selection_end.buffer_id {
10315 continue;
10316 }
10317 if let Some(ranges) =
10318 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10319 {
10320 for (buffer, entries) in ranges {
10321 linked_ranges.entry(buffer).or_default().extend(entries);
10322 }
10323 }
10324 }
10325 }
10326
10327 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10328 for selection in &mut selections {
10329 if selection.is_empty() {
10330 let old_head = selection.head();
10331 let mut new_head =
10332 movement::left(&display_map, old_head.to_display_point(&display_map))
10333 .to_point(&display_map);
10334 if let Some((buffer, line_buffer_range)) = display_map
10335 .buffer_snapshot()
10336 .buffer_line_for_row(MultiBufferRow(old_head.row))
10337 {
10338 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10339 let indent_len = match indent_size.kind {
10340 IndentKind::Space => {
10341 buffer.settings_at(line_buffer_range.start, cx).tab_size
10342 }
10343 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10344 };
10345 if old_head.column <= indent_size.len && old_head.column > 0 {
10346 let indent_len = indent_len.get();
10347 new_head = cmp::min(
10348 new_head,
10349 MultiBufferPoint::new(
10350 old_head.row,
10351 ((old_head.column - 1) / indent_len) * indent_len,
10352 ),
10353 );
10354 }
10355 }
10356
10357 selection.set_head(new_head, SelectionGoal::None);
10358 }
10359 }
10360
10361 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10362 this.insert("", window, cx);
10363 let empty_str: Arc<str> = Arc::from("");
10364 for (buffer, edits) in linked_ranges {
10365 let snapshot = buffer.read(cx).snapshot();
10366 use text::ToPoint as TP;
10367
10368 let edits = edits
10369 .into_iter()
10370 .map(|range| {
10371 let end_point = TP::to_point(&range.end, &snapshot);
10372 let mut start_point = TP::to_point(&range.start, &snapshot);
10373
10374 if end_point == start_point {
10375 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10376 .saturating_sub(1);
10377 start_point =
10378 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10379 };
10380
10381 (start_point..end_point, empty_str.clone())
10382 })
10383 .sorted_by_key(|(range, _)| range.start)
10384 .collect::<Vec<_>>();
10385 buffer.update(cx, |this, cx| {
10386 this.edit(edits, None, cx);
10387 })
10388 }
10389 this.refresh_edit_prediction(true, false, window, cx);
10390 refresh_linked_ranges(this, window, cx);
10391 });
10392 }
10393
10394 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10395 if self.read_only(cx) {
10396 return;
10397 }
10398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10399 self.transact(window, cx, |this, window, cx| {
10400 this.change_selections(Default::default(), window, cx, |s| {
10401 s.move_with(|map, selection| {
10402 if selection.is_empty() {
10403 let cursor = movement::right(map, selection.head());
10404 selection.end = cursor;
10405 selection.reversed = true;
10406 selection.goal = SelectionGoal::None;
10407 }
10408 })
10409 });
10410 this.insert("", window, cx);
10411 this.refresh_edit_prediction(true, false, window, cx);
10412 });
10413 }
10414
10415 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10416 if self.mode.is_single_line() {
10417 cx.propagate();
10418 return;
10419 }
10420
10421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10422 if self.move_to_prev_snippet_tabstop(window, cx) {
10423 return;
10424 }
10425 self.outdent(&Outdent, window, cx);
10426 }
10427
10428 pub fn next_snippet_tabstop(
10429 &mut self,
10430 _: &NextSnippetTabstop,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10435 cx.propagate();
10436 return;
10437 }
10438
10439 if self.move_to_next_snippet_tabstop(window, cx) {
10440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10441 return;
10442 }
10443 cx.propagate();
10444 }
10445
10446 pub fn previous_snippet_tabstop(
10447 &mut self,
10448 _: &PreviousSnippetTabstop,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10453 cx.propagate();
10454 return;
10455 }
10456
10457 if self.move_to_prev_snippet_tabstop(window, cx) {
10458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10459 return;
10460 }
10461 cx.propagate();
10462 }
10463
10464 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10465 if self.mode.is_single_line() {
10466 cx.propagate();
10467 return;
10468 }
10469
10470 if self.move_to_next_snippet_tabstop(window, cx) {
10471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10472 return;
10473 }
10474 if self.read_only(cx) {
10475 return;
10476 }
10477 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10478 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10479 let buffer = self.buffer.read(cx);
10480 let snapshot = buffer.snapshot(cx);
10481 let rows_iter = selections.iter().map(|s| s.head().row);
10482 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10483
10484 let has_some_cursor_in_whitespace = selections
10485 .iter()
10486 .filter(|selection| selection.is_empty())
10487 .any(|selection| {
10488 let cursor = selection.head();
10489 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10490 cursor.column < current_indent.len
10491 });
10492
10493 let mut edits = Vec::new();
10494 let mut prev_edited_row = 0;
10495 let mut row_delta = 0;
10496 for selection in &mut selections {
10497 if selection.start.row != prev_edited_row {
10498 row_delta = 0;
10499 }
10500 prev_edited_row = selection.end.row;
10501
10502 // If the selection is non-empty, then increase the indentation of the selected lines.
10503 if !selection.is_empty() {
10504 row_delta =
10505 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10506 continue;
10507 }
10508
10509 let cursor = selection.head();
10510 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10511 if let Some(suggested_indent) =
10512 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10513 {
10514 // Don't do anything if already at suggested indent
10515 // and there is any other cursor which is not
10516 if has_some_cursor_in_whitespace
10517 && cursor.column == current_indent.len
10518 && current_indent.len == suggested_indent.len
10519 {
10520 continue;
10521 }
10522
10523 // Adjust line and move cursor to suggested indent
10524 // if cursor is not at suggested indent
10525 if cursor.column < suggested_indent.len
10526 && cursor.column <= current_indent.len
10527 && current_indent.len <= suggested_indent.len
10528 {
10529 selection.start = Point::new(cursor.row, suggested_indent.len);
10530 selection.end = selection.start;
10531 if row_delta == 0 {
10532 edits.extend(Buffer::edit_for_indent_size_adjustment(
10533 cursor.row,
10534 current_indent,
10535 suggested_indent,
10536 ));
10537 row_delta = suggested_indent.len - current_indent.len;
10538 }
10539 continue;
10540 }
10541
10542 // If current indent is more than suggested indent
10543 // only move cursor to current indent and skip indent
10544 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10545 selection.start = Point::new(cursor.row, current_indent.len);
10546 selection.end = selection.start;
10547 continue;
10548 }
10549 }
10550
10551 // Otherwise, insert a hard or soft tab.
10552 let settings = buffer.language_settings_at(cursor, cx);
10553 let tab_size = if settings.hard_tabs {
10554 IndentSize::tab()
10555 } else {
10556 let tab_size = settings.tab_size.get();
10557 let indent_remainder = snapshot
10558 .text_for_range(Point::new(cursor.row, 0)..cursor)
10559 .flat_map(str::chars)
10560 .fold(row_delta % tab_size, |counter: u32, c| {
10561 if c == '\t' {
10562 0
10563 } else {
10564 (counter + 1) % tab_size
10565 }
10566 });
10567
10568 let chars_to_next_tab_stop = tab_size - indent_remainder;
10569 IndentSize::spaces(chars_to_next_tab_stop)
10570 };
10571 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10572 selection.end = selection.start;
10573 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10574 row_delta += tab_size.len;
10575 }
10576
10577 self.transact(window, cx, |this, window, cx| {
10578 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10579 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10580 this.refresh_edit_prediction(true, false, window, cx);
10581 });
10582 }
10583
10584 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10585 if self.read_only(cx) {
10586 return;
10587 }
10588 if self.mode.is_single_line() {
10589 cx.propagate();
10590 return;
10591 }
10592
10593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10594 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10595 let mut prev_edited_row = 0;
10596 let mut row_delta = 0;
10597 let mut edits = Vec::new();
10598 let buffer = self.buffer.read(cx);
10599 let snapshot = buffer.snapshot(cx);
10600 for selection in &mut selections {
10601 if selection.start.row != prev_edited_row {
10602 row_delta = 0;
10603 }
10604 prev_edited_row = selection.end.row;
10605
10606 row_delta =
10607 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10608 }
10609
10610 self.transact(window, cx, |this, window, cx| {
10611 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10612 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10613 });
10614 }
10615
10616 fn indent_selection(
10617 buffer: &MultiBuffer,
10618 snapshot: &MultiBufferSnapshot,
10619 selection: &mut Selection<Point>,
10620 edits: &mut Vec<(Range<Point>, String)>,
10621 delta_for_start_row: u32,
10622 cx: &App,
10623 ) -> u32 {
10624 let settings = buffer.language_settings_at(selection.start, cx);
10625 let tab_size = settings.tab_size.get();
10626 let indent_kind = if settings.hard_tabs {
10627 IndentKind::Tab
10628 } else {
10629 IndentKind::Space
10630 };
10631 let mut start_row = selection.start.row;
10632 let mut end_row = selection.end.row + 1;
10633
10634 // If a selection ends at the beginning of a line, don't indent
10635 // that last line.
10636 if selection.end.column == 0 && selection.end.row > selection.start.row {
10637 end_row -= 1;
10638 }
10639
10640 // Avoid re-indenting a row that has already been indented by a
10641 // previous selection, but still update this selection's column
10642 // to reflect that indentation.
10643 if delta_for_start_row > 0 {
10644 start_row += 1;
10645 selection.start.column += delta_for_start_row;
10646 if selection.end.row == selection.start.row {
10647 selection.end.column += delta_for_start_row;
10648 }
10649 }
10650
10651 let mut delta_for_end_row = 0;
10652 let has_multiple_rows = start_row + 1 != end_row;
10653 for row in start_row..end_row {
10654 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10655 let indent_delta = match (current_indent.kind, indent_kind) {
10656 (IndentKind::Space, IndentKind::Space) => {
10657 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10658 IndentSize::spaces(columns_to_next_tab_stop)
10659 }
10660 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10661 (_, IndentKind::Tab) => IndentSize::tab(),
10662 };
10663
10664 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10665 0
10666 } else {
10667 selection.start.column
10668 };
10669 let row_start = Point::new(row, start);
10670 edits.push((
10671 row_start..row_start,
10672 indent_delta.chars().collect::<String>(),
10673 ));
10674
10675 // Update this selection's endpoints to reflect the indentation.
10676 if row == selection.start.row {
10677 selection.start.column += indent_delta.len;
10678 }
10679 if row == selection.end.row {
10680 selection.end.column += indent_delta.len;
10681 delta_for_end_row = indent_delta.len;
10682 }
10683 }
10684
10685 if selection.start.row == selection.end.row {
10686 delta_for_start_row + delta_for_end_row
10687 } else {
10688 delta_for_end_row
10689 }
10690 }
10691
10692 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10693 if self.read_only(cx) {
10694 return;
10695 }
10696 if self.mode.is_single_line() {
10697 cx.propagate();
10698 return;
10699 }
10700
10701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10703 let selections = self.selections.all::<Point>(&display_map);
10704 let mut deletion_ranges = Vec::new();
10705 let mut last_outdent = None;
10706 {
10707 let buffer = self.buffer.read(cx);
10708 let snapshot = buffer.snapshot(cx);
10709 for selection in &selections {
10710 let settings = buffer.language_settings_at(selection.start, cx);
10711 let tab_size = settings.tab_size.get();
10712 let mut rows = selection.spanned_rows(false, &display_map);
10713
10714 // Avoid re-outdenting a row that has already been outdented by a
10715 // previous selection.
10716 if let Some(last_row) = last_outdent
10717 && last_row == rows.start
10718 {
10719 rows.start = rows.start.next_row();
10720 }
10721 let has_multiple_rows = rows.len() > 1;
10722 for row in rows.iter_rows() {
10723 let indent_size = snapshot.indent_size_for_line(row);
10724 if indent_size.len > 0 {
10725 let deletion_len = match indent_size.kind {
10726 IndentKind::Space => {
10727 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10728 if columns_to_prev_tab_stop == 0 {
10729 tab_size
10730 } else {
10731 columns_to_prev_tab_stop
10732 }
10733 }
10734 IndentKind::Tab => 1,
10735 };
10736 let start = if has_multiple_rows
10737 || deletion_len > selection.start.column
10738 || indent_size.len < selection.start.column
10739 {
10740 0
10741 } else {
10742 selection.start.column - deletion_len
10743 };
10744 deletion_ranges.push(
10745 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10746 );
10747 last_outdent = Some(row);
10748 }
10749 }
10750 }
10751 }
10752
10753 self.transact(window, cx, |this, window, cx| {
10754 this.buffer.update(cx, |buffer, cx| {
10755 let empty_str: Arc<str> = Arc::default();
10756 buffer.edit(
10757 deletion_ranges
10758 .into_iter()
10759 .map(|range| (range, empty_str.clone())),
10760 None,
10761 cx,
10762 );
10763 });
10764 let selections = this
10765 .selections
10766 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10767 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10768 });
10769 }
10770
10771 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10772 if self.read_only(cx) {
10773 return;
10774 }
10775 if self.mode.is_single_line() {
10776 cx.propagate();
10777 return;
10778 }
10779
10780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10781 let selections = self
10782 .selections
10783 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10784 .into_iter()
10785 .map(|s| s.range());
10786
10787 self.transact(window, cx, |this, window, cx| {
10788 this.buffer.update(cx, |buffer, cx| {
10789 buffer.autoindent_ranges(selections, cx);
10790 });
10791 let selections = this
10792 .selections
10793 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10794 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10795 });
10796 }
10797
10798 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10801 let selections = self.selections.all::<Point>(&display_map);
10802
10803 let mut new_cursors = Vec::new();
10804 let mut edit_ranges = Vec::new();
10805 let mut selections = selections.iter().peekable();
10806 while let Some(selection) = selections.next() {
10807 let mut rows = selection.spanned_rows(false, &display_map);
10808
10809 // Accumulate contiguous regions of rows that we want to delete.
10810 while let Some(next_selection) = selections.peek() {
10811 let next_rows = next_selection.spanned_rows(false, &display_map);
10812 if next_rows.start <= rows.end {
10813 rows.end = next_rows.end;
10814 selections.next().unwrap();
10815 } else {
10816 break;
10817 }
10818 }
10819
10820 let buffer = display_map.buffer_snapshot();
10821 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10822 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10823 // If there's a line after the range, delete the \n from the end of the row range
10824 (
10825 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10826 rows.end,
10827 )
10828 } else {
10829 // If there isn't a line after the range, delete the \n from the line before the
10830 // start of the row range
10831 edit_start = edit_start.saturating_sub_usize(1);
10832 (buffer.len(), rows.start.previous_row())
10833 };
10834
10835 let text_layout_details = self.text_layout_details(window);
10836 let x = display_map.x_for_display_point(
10837 selection.head().to_display_point(&display_map),
10838 &text_layout_details,
10839 );
10840 let row = Point::new(target_row.0, 0)
10841 .to_display_point(&display_map)
10842 .row();
10843 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10844
10845 new_cursors.push((
10846 selection.id,
10847 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10848 SelectionGoal::None,
10849 ));
10850 edit_ranges.push(edit_start..edit_end);
10851 }
10852
10853 self.transact(window, cx, |this, window, cx| {
10854 let buffer = this.buffer.update(cx, |buffer, cx| {
10855 let empty_str: Arc<str> = Arc::default();
10856 buffer.edit(
10857 edit_ranges
10858 .into_iter()
10859 .map(|range| (range, empty_str.clone())),
10860 None,
10861 cx,
10862 );
10863 buffer.snapshot(cx)
10864 });
10865 let new_selections = new_cursors
10866 .into_iter()
10867 .map(|(id, cursor, goal)| {
10868 let cursor = cursor.to_point(&buffer);
10869 Selection {
10870 id,
10871 start: cursor,
10872 end: cursor,
10873 reversed: false,
10874 goal,
10875 }
10876 })
10877 .collect();
10878
10879 this.change_selections(Default::default(), window, cx, |s| {
10880 s.select(new_selections);
10881 });
10882 });
10883 }
10884
10885 pub fn join_lines_impl(
10886 &mut self,
10887 insert_whitespace: bool,
10888 window: &mut Window,
10889 cx: &mut Context<Self>,
10890 ) {
10891 if self.read_only(cx) {
10892 return;
10893 }
10894 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10895 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10896 let start = MultiBufferRow(selection.start.row);
10897 // Treat single line selections as if they include the next line. Otherwise this action
10898 // would do nothing for single line selections individual cursors.
10899 let end = if selection.start.row == selection.end.row {
10900 MultiBufferRow(selection.start.row + 1)
10901 } else {
10902 MultiBufferRow(selection.end.row)
10903 };
10904
10905 if let Some(last_row_range) = row_ranges.last_mut()
10906 && start <= last_row_range.end
10907 {
10908 last_row_range.end = end;
10909 continue;
10910 }
10911 row_ranges.push(start..end);
10912 }
10913
10914 let snapshot = self.buffer.read(cx).snapshot(cx);
10915 let mut cursor_positions = Vec::new();
10916 for row_range in &row_ranges {
10917 let anchor = snapshot.anchor_before(Point::new(
10918 row_range.end.previous_row().0,
10919 snapshot.line_len(row_range.end.previous_row()),
10920 ));
10921 cursor_positions.push(anchor..anchor);
10922 }
10923
10924 self.transact(window, cx, |this, window, cx| {
10925 for row_range in row_ranges.into_iter().rev() {
10926 for row in row_range.iter_rows().rev() {
10927 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10928 let next_line_row = row.next_row();
10929 let indent = snapshot.indent_size_for_line(next_line_row);
10930 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10931
10932 let replace =
10933 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10934 " "
10935 } else {
10936 ""
10937 };
10938
10939 this.buffer.update(cx, |buffer, cx| {
10940 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10941 });
10942 }
10943 }
10944
10945 this.change_selections(Default::default(), window, cx, |s| {
10946 s.select_anchor_ranges(cursor_positions)
10947 });
10948 });
10949 }
10950
10951 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10953 self.join_lines_impl(true, window, cx);
10954 }
10955
10956 pub fn sort_lines_case_sensitive(
10957 &mut self,
10958 _: &SortLinesCaseSensitive,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10963 }
10964
10965 pub fn sort_lines_by_length(
10966 &mut self,
10967 _: &SortLinesByLength,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 ) {
10971 self.manipulate_immutable_lines(window, cx, |lines| {
10972 lines.sort_by_key(|&line| line.chars().count())
10973 })
10974 }
10975
10976 pub fn sort_lines_case_insensitive(
10977 &mut self,
10978 _: &SortLinesCaseInsensitive,
10979 window: &mut Window,
10980 cx: &mut Context<Self>,
10981 ) {
10982 self.manipulate_immutable_lines(window, cx, |lines| {
10983 lines.sort_by_key(|line| line.to_lowercase())
10984 })
10985 }
10986
10987 pub fn unique_lines_case_insensitive(
10988 &mut self,
10989 _: &UniqueLinesCaseInsensitive,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.manipulate_immutable_lines(window, cx, |lines| {
10994 let mut seen = HashSet::default();
10995 lines.retain(|line| seen.insert(line.to_lowercase()));
10996 })
10997 }
10998
10999 pub fn unique_lines_case_sensitive(
11000 &mut self,
11001 _: &UniqueLinesCaseSensitive,
11002 window: &mut Window,
11003 cx: &mut Context<Self>,
11004 ) {
11005 self.manipulate_immutable_lines(window, cx, |lines| {
11006 let mut seen = HashSet::default();
11007 lines.retain(|line| seen.insert(*line));
11008 })
11009 }
11010
11011 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11012 let snapshot = self.buffer.read(cx).snapshot(cx);
11013 for selection in self.selections.disjoint_anchors_arc().iter() {
11014 if snapshot
11015 .language_at(selection.start)
11016 .and_then(|lang| lang.config().wrap_characters.as_ref())
11017 .is_some()
11018 {
11019 return true;
11020 }
11021 }
11022 false
11023 }
11024
11025 fn wrap_selections_in_tag(
11026 &mut self,
11027 _: &WrapSelectionsInTag,
11028 window: &mut Window,
11029 cx: &mut Context<Self>,
11030 ) {
11031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11032
11033 let snapshot = self.buffer.read(cx).snapshot(cx);
11034
11035 let mut edits = Vec::new();
11036 let mut boundaries = Vec::new();
11037
11038 for selection in self
11039 .selections
11040 .all_adjusted(&self.display_snapshot(cx))
11041 .iter()
11042 {
11043 let Some(wrap_config) = snapshot
11044 .language_at(selection.start)
11045 .and_then(|lang| lang.config().wrap_characters.clone())
11046 else {
11047 continue;
11048 };
11049
11050 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11051 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11052
11053 let start_before = snapshot.anchor_before(selection.start);
11054 let end_after = snapshot.anchor_after(selection.end);
11055
11056 edits.push((start_before..start_before, open_tag));
11057 edits.push((end_after..end_after, close_tag));
11058
11059 boundaries.push((
11060 start_before,
11061 end_after,
11062 wrap_config.start_prefix.len(),
11063 wrap_config.end_suffix.len(),
11064 ));
11065 }
11066
11067 if edits.is_empty() {
11068 return;
11069 }
11070
11071 self.transact(window, cx, |this, window, cx| {
11072 let buffer = this.buffer.update(cx, |buffer, cx| {
11073 buffer.edit(edits, None, cx);
11074 buffer.snapshot(cx)
11075 });
11076
11077 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11078 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11079 boundaries.into_iter()
11080 {
11081 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11082 let close_offset = end_after
11083 .to_offset(&buffer)
11084 .saturating_sub_usize(end_suffix_len);
11085 new_selections.push(open_offset..open_offset);
11086 new_selections.push(close_offset..close_offset);
11087 }
11088
11089 this.change_selections(Default::default(), window, cx, |s| {
11090 s.select_ranges(new_selections);
11091 });
11092
11093 this.request_autoscroll(Autoscroll::fit(), cx);
11094 });
11095 }
11096
11097 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11098 let Some(project) = self.project.clone() else {
11099 return;
11100 };
11101 self.reload(project, window, cx)
11102 .detach_and_notify_err(window, cx);
11103 }
11104
11105 pub fn restore_file(
11106 &mut self,
11107 _: &::git::RestoreFile,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11112 let mut buffer_ids = HashSet::default();
11113 let snapshot = self.buffer().read(cx).snapshot(cx);
11114 for selection in self
11115 .selections
11116 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11117 {
11118 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11119 }
11120
11121 let buffer = self.buffer().read(cx);
11122 let ranges = buffer_ids
11123 .into_iter()
11124 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11125 .collect::<Vec<_>>();
11126
11127 self.restore_hunks_in_ranges(ranges, window, cx);
11128 }
11129
11130 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11132 let selections = self
11133 .selections
11134 .all(&self.display_snapshot(cx))
11135 .into_iter()
11136 .map(|s| s.range())
11137 .collect();
11138 self.restore_hunks_in_ranges(selections, window, cx);
11139 }
11140
11141 pub fn restore_hunks_in_ranges(
11142 &mut self,
11143 ranges: Vec<Range<Point>>,
11144 window: &mut Window,
11145 cx: &mut Context<Editor>,
11146 ) {
11147 let mut revert_changes = HashMap::default();
11148 let chunk_by = self
11149 .snapshot(window, cx)
11150 .hunks_for_ranges(ranges)
11151 .into_iter()
11152 .chunk_by(|hunk| hunk.buffer_id);
11153 for (buffer_id, hunks) in &chunk_by {
11154 let hunks = hunks.collect::<Vec<_>>();
11155 for hunk in &hunks {
11156 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11157 }
11158 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11159 }
11160 drop(chunk_by);
11161 if !revert_changes.is_empty() {
11162 self.transact(window, cx, |editor, window, cx| {
11163 editor.restore(revert_changes, window, cx);
11164 });
11165 }
11166 }
11167
11168 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11169 if let Some(status) = self
11170 .addons
11171 .iter()
11172 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11173 {
11174 return Some(status);
11175 }
11176 self.project
11177 .as_ref()?
11178 .read(cx)
11179 .status_for_buffer_id(buffer_id, cx)
11180 }
11181
11182 pub fn open_active_item_in_terminal(
11183 &mut self,
11184 _: &OpenInTerminal,
11185 window: &mut Window,
11186 cx: &mut Context<Self>,
11187 ) {
11188 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11189 let project_path = buffer.read(cx).project_path(cx)?;
11190 let project = self.project()?.read(cx);
11191 let entry = project.entry_for_path(&project_path, cx)?;
11192 let parent = match &entry.canonical_path {
11193 Some(canonical_path) => canonical_path.to_path_buf(),
11194 None => project.absolute_path(&project_path, cx)?,
11195 }
11196 .parent()?
11197 .to_path_buf();
11198 Some(parent)
11199 }) {
11200 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11201 }
11202 }
11203
11204 fn set_breakpoint_context_menu(
11205 &mut self,
11206 display_row: DisplayRow,
11207 position: Option<Anchor>,
11208 clicked_point: gpui::Point<Pixels>,
11209 window: &mut Window,
11210 cx: &mut Context<Self>,
11211 ) {
11212 let source = self
11213 .buffer
11214 .read(cx)
11215 .snapshot(cx)
11216 .anchor_before(Point::new(display_row.0, 0u32));
11217
11218 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11219
11220 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11221 self,
11222 source,
11223 clicked_point,
11224 context_menu,
11225 window,
11226 cx,
11227 );
11228 }
11229
11230 fn add_edit_breakpoint_block(
11231 &mut self,
11232 anchor: Anchor,
11233 breakpoint: &Breakpoint,
11234 edit_action: BreakpointPromptEditAction,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 let weak_editor = cx.weak_entity();
11239 let bp_prompt = cx.new(|cx| {
11240 BreakpointPromptEditor::new(
11241 weak_editor,
11242 anchor,
11243 breakpoint.clone(),
11244 edit_action,
11245 window,
11246 cx,
11247 )
11248 });
11249
11250 let height = bp_prompt.update(cx, |this, cx| {
11251 this.prompt
11252 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11253 });
11254 let cloned_prompt = bp_prompt.clone();
11255 let blocks = vec![BlockProperties {
11256 style: BlockStyle::Sticky,
11257 placement: BlockPlacement::Above(anchor),
11258 height: Some(height),
11259 render: Arc::new(move |cx| {
11260 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11261 cloned_prompt.clone().into_any_element()
11262 }),
11263 priority: 0,
11264 }];
11265
11266 let focus_handle = bp_prompt.focus_handle(cx);
11267 window.focus(&focus_handle);
11268
11269 let block_ids = self.insert_blocks(blocks, None, cx);
11270 bp_prompt.update(cx, |prompt, _| {
11271 prompt.add_block_ids(block_ids);
11272 });
11273 }
11274
11275 pub(crate) fn breakpoint_at_row(
11276 &self,
11277 row: u32,
11278 window: &mut Window,
11279 cx: &mut Context<Self>,
11280 ) -> Option<(Anchor, Breakpoint)> {
11281 let snapshot = self.snapshot(window, cx);
11282 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11283
11284 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11285 }
11286
11287 pub(crate) fn breakpoint_at_anchor(
11288 &self,
11289 breakpoint_position: Anchor,
11290 snapshot: &EditorSnapshot,
11291 cx: &mut Context<Self>,
11292 ) -> Option<(Anchor, Breakpoint)> {
11293 let buffer = self
11294 .buffer
11295 .read(cx)
11296 .buffer_for_anchor(breakpoint_position, cx)?;
11297
11298 let enclosing_excerpt = breakpoint_position.excerpt_id;
11299 let buffer_snapshot = buffer.read(cx).snapshot();
11300
11301 let row = buffer_snapshot
11302 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11303 .row;
11304
11305 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11306 let anchor_end = snapshot
11307 .buffer_snapshot()
11308 .anchor_after(Point::new(row, line_len));
11309
11310 self.breakpoint_store
11311 .as_ref()?
11312 .read_with(cx, |breakpoint_store, cx| {
11313 breakpoint_store
11314 .breakpoints(
11315 &buffer,
11316 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11317 &buffer_snapshot,
11318 cx,
11319 )
11320 .next()
11321 .and_then(|(bp, _)| {
11322 let breakpoint_row = buffer_snapshot
11323 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11324 .row;
11325
11326 if breakpoint_row == row {
11327 snapshot
11328 .buffer_snapshot()
11329 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11330 .map(|position| (position, bp.bp.clone()))
11331 } else {
11332 None
11333 }
11334 })
11335 })
11336 }
11337
11338 pub fn edit_log_breakpoint(
11339 &mut self,
11340 _: &EditLogBreakpoint,
11341 window: &mut Window,
11342 cx: &mut Context<Self>,
11343 ) {
11344 if self.breakpoint_store.is_none() {
11345 return;
11346 }
11347
11348 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11349 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11350 message: None,
11351 state: BreakpointState::Enabled,
11352 condition: None,
11353 hit_condition: None,
11354 });
11355
11356 self.add_edit_breakpoint_block(
11357 anchor,
11358 &breakpoint,
11359 BreakpointPromptEditAction::Log,
11360 window,
11361 cx,
11362 );
11363 }
11364 }
11365
11366 fn breakpoints_at_cursors(
11367 &self,
11368 window: &mut Window,
11369 cx: &mut Context<Self>,
11370 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11371 let snapshot = self.snapshot(window, cx);
11372 let cursors = self
11373 .selections
11374 .disjoint_anchors_arc()
11375 .iter()
11376 .map(|selection| {
11377 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11378
11379 let breakpoint_position = self
11380 .breakpoint_at_row(cursor_position.row, window, cx)
11381 .map(|bp| bp.0)
11382 .unwrap_or_else(|| {
11383 snapshot
11384 .display_snapshot
11385 .buffer_snapshot()
11386 .anchor_after(Point::new(cursor_position.row, 0))
11387 });
11388
11389 let breakpoint = self
11390 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11391 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11392
11393 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11394 })
11395 // 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.
11396 .collect::<HashMap<Anchor, _>>();
11397
11398 cursors.into_iter().collect()
11399 }
11400
11401 pub fn enable_breakpoint(
11402 &mut self,
11403 _: &crate::actions::EnableBreakpoint,
11404 window: &mut Window,
11405 cx: &mut Context<Self>,
11406 ) {
11407 if self.breakpoint_store.is_none() {
11408 return;
11409 }
11410
11411 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11412 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11413 continue;
11414 };
11415 self.edit_breakpoint_at_anchor(
11416 anchor,
11417 breakpoint,
11418 BreakpointEditAction::InvertState,
11419 cx,
11420 );
11421 }
11422 }
11423
11424 pub fn disable_breakpoint(
11425 &mut self,
11426 _: &crate::actions::DisableBreakpoint,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 if self.breakpoint_store.is_none() {
11431 return;
11432 }
11433
11434 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11435 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11436 continue;
11437 };
11438 self.edit_breakpoint_at_anchor(
11439 anchor,
11440 breakpoint,
11441 BreakpointEditAction::InvertState,
11442 cx,
11443 );
11444 }
11445 }
11446
11447 pub fn toggle_breakpoint(
11448 &mut self,
11449 _: &crate::actions::ToggleBreakpoint,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 if self.breakpoint_store.is_none() {
11454 return;
11455 }
11456
11457 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11458 if let Some(breakpoint) = breakpoint {
11459 self.edit_breakpoint_at_anchor(
11460 anchor,
11461 breakpoint,
11462 BreakpointEditAction::Toggle,
11463 cx,
11464 );
11465 } else {
11466 self.edit_breakpoint_at_anchor(
11467 anchor,
11468 Breakpoint::new_standard(),
11469 BreakpointEditAction::Toggle,
11470 cx,
11471 );
11472 }
11473 }
11474 }
11475
11476 pub fn edit_breakpoint_at_anchor(
11477 &mut self,
11478 breakpoint_position: Anchor,
11479 breakpoint: Breakpoint,
11480 edit_action: BreakpointEditAction,
11481 cx: &mut Context<Self>,
11482 ) {
11483 let Some(breakpoint_store) = &self.breakpoint_store else {
11484 return;
11485 };
11486
11487 let Some(buffer) = self
11488 .buffer
11489 .read(cx)
11490 .buffer_for_anchor(breakpoint_position, cx)
11491 else {
11492 return;
11493 };
11494
11495 breakpoint_store.update(cx, |breakpoint_store, cx| {
11496 breakpoint_store.toggle_breakpoint(
11497 buffer,
11498 BreakpointWithPosition {
11499 position: breakpoint_position.text_anchor,
11500 bp: breakpoint,
11501 },
11502 edit_action,
11503 cx,
11504 );
11505 });
11506
11507 cx.notify();
11508 }
11509
11510 #[cfg(any(test, feature = "test-support"))]
11511 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11512 self.breakpoint_store.clone()
11513 }
11514
11515 pub fn prepare_restore_change(
11516 &self,
11517 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11518 hunk: &MultiBufferDiffHunk,
11519 cx: &mut App,
11520 ) -> Option<()> {
11521 if hunk.is_created_file() {
11522 return None;
11523 }
11524 let buffer = self.buffer.read(cx);
11525 let diff = buffer.diff_for(hunk.buffer_id)?;
11526 let buffer = buffer.buffer(hunk.buffer_id)?;
11527 let buffer = buffer.read(cx);
11528 let original_text = diff
11529 .read(cx)
11530 .base_text()
11531 .as_rope()
11532 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11533 let buffer_snapshot = buffer.snapshot();
11534 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11535 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11536 probe
11537 .0
11538 .start
11539 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11540 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11541 }) {
11542 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11543 Some(())
11544 } else {
11545 None
11546 }
11547 }
11548
11549 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11550 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11551 }
11552
11553 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11554 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11555 }
11556
11557 pub fn rotate_selections_forward(
11558 &mut self,
11559 _: &RotateSelectionsForward,
11560 window: &mut Window,
11561 cx: &mut Context<Self>,
11562 ) {
11563 self.rotate_selections(window, cx, false)
11564 }
11565
11566 pub fn rotate_selections_backward(
11567 &mut self,
11568 _: &RotateSelectionsBackward,
11569 window: &mut Window,
11570 cx: &mut Context<Self>,
11571 ) {
11572 self.rotate_selections(window, cx, true)
11573 }
11574
11575 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11577 let display_snapshot = self.display_snapshot(cx);
11578 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11579
11580 if selections.len() < 2 {
11581 return;
11582 }
11583
11584 let (edits, new_selections) = {
11585 let buffer = self.buffer.read(cx).read(cx);
11586 let has_selections = selections.iter().any(|s| !s.is_empty());
11587 if has_selections {
11588 let mut selected_texts: Vec<String> = selections
11589 .iter()
11590 .map(|selection| {
11591 buffer
11592 .text_for_range(selection.start..selection.end)
11593 .collect()
11594 })
11595 .collect();
11596
11597 if reverse {
11598 selected_texts.rotate_left(1);
11599 } else {
11600 selected_texts.rotate_right(1);
11601 }
11602
11603 let mut offset_delta: i64 = 0;
11604 let mut new_selections = Vec::new();
11605 let edits: Vec<_> = selections
11606 .iter()
11607 .zip(selected_texts.iter())
11608 .map(|(selection, new_text)| {
11609 let old_len = (selection.end.0 - selection.start.0) as i64;
11610 let new_len = new_text.len() as i64;
11611 let adjusted_start =
11612 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11613 let adjusted_end =
11614 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11615
11616 new_selections.push(Selection {
11617 id: selection.id,
11618 start: adjusted_start,
11619 end: adjusted_end,
11620 reversed: selection.reversed,
11621 goal: selection.goal,
11622 });
11623
11624 offset_delta += new_len - old_len;
11625 (selection.start..selection.end, new_text.clone())
11626 })
11627 .collect();
11628 (edits, new_selections)
11629 } else {
11630 let mut all_rows: Vec<u32> = selections
11631 .iter()
11632 .map(|selection| buffer.offset_to_point(selection.start).row)
11633 .collect();
11634 all_rows.sort_unstable();
11635 all_rows.dedup();
11636
11637 if all_rows.len() < 2 {
11638 return;
11639 }
11640
11641 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11642 .iter()
11643 .map(|&row| {
11644 let start = Point::new(row, 0);
11645 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11646 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11647 })
11648 .collect();
11649
11650 let mut line_texts: Vec<String> = line_ranges
11651 .iter()
11652 .map(|range| buffer.text_for_range(range.clone()).collect())
11653 .collect();
11654
11655 if reverse {
11656 line_texts.rotate_left(1);
11657 } else {
11658 line_texts.rotate_right(1);
11659 }
11660
11661 let edits = line_ranges
11662 .iter()
11663 .zip(line_texts.iter())
11664 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11665 .collect();
11666
11667 let num_rows = all_rows.len();
11668 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11669 .iter()
11670 .enumerate()
11671 .map(|(i, &row)| (row, i))
11672 .collect();
11673
11674 // Compute new line start offsets after rotation (handles CRLF)
11675 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11676 let first_line_start = line_ranges[0].start.0;
11677 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11678 for text in line_texts.iter().take(num_rows - 1) {
11679 let prev_start = *new_line_starts.last().unwrap();
11680 new_line_starts.push(prev_start + text.len() + newline_len);
11681 }
11682
11683 let new_selections = selections
11684 .iter()
11685 .map(|selection| {
11686 let point = buffer.offset_to_point(selection.start);
11687 let old_index = row_to_index[&point.row];
11688 let new_index = if reverse {
11689 (old_index + num_rows - 1) % num_rows
11690 } else {
11691 (old_index + 1) % num_rows
11692 };
11693 let new_offset =
11694 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11695 Selection {
11696 id: selection.id,
11697 start: new_offset,
11698 end: new_offset,
11699 reversed: selection.reversed,
11700 goal: selection.goal,
11701 }
11702 })
11703 .collect();
11704
11705 (edits, new_selections)
11706 }
11707 };
11708
11709 self.transact(window, cx, |this, window, cx| {
11710 this.buffer.update(cx, |buffer, cx| {
11711 buffer.edit(edits, None, cx);
11712 });
11713 this.change_selections(Default::default(), window, cx, |s| {
11714 s.select(new_selections);
11715 });
11716 });
11717 }
11718
11719 fn manipulate_lines<M>(
11720 &mut self,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 mut manipulate: M,
11724 ) where
11725 M: FnMut(&str) -> LineManipulationResult,
11726 {
11727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11728
11729 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11730 let buffer = self.buffer.read(cx).snapshot(cx);
11731
11732 let mut edits = Vec::new();
11733
11734 let selections = self.selections.all::<Point>(&display_map);
11735 let mut selections = selections.iter().peekable();
11736 let mut contiguous_row_selections = Vec::new();
11737 let mut new_selections = Vec::new();
11738 let mut added_lines = 0;
11739 let mut removed_lines = 0;
11740
11741 while let Some(selection) = selections.next() {
11742 let (start_row, end_row) = consume_contiguous_rows(
11743 &mut contiguous_row_selections,
11744 selection,
11745 &display_map,
11746 &mut selections,
11747 );
11748
11749 let start_point = Point::new(start_row.0, 0);
11750 let end_point = Point::new(
11751 end_row.previous_row().0,
11752 buffer.line_len(end_row.previous_row()),
11753 );
11754 let text = buffer
11755 .text_for_range(start_point..end_point)
11756 .collect::<String>();
11757
11758 let LineManipulationResult {
11759 new_text,
11760 line_count_before,
11761 line_count_after,
11762 } = manipulate(&text);
11763
11764 edits.push((start_point..end_point, new_text));
11765
11766 // Selections must change based on added and removed line count
11767 let start_row =
11768 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11769 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11770 new_selections.push(Selection {
11771 id: selection.id,
11772 start: start_row,
11773 end: end_row,
11774 goal: SelectionGoal::None,
11775 reversed: selection.reversed,
11776 });
11777
11778 if line_count_after > line_count_before {
11779 added_lines += line_count_after - line_count_before;
11780 } else if line_count_before > line_count_after {
11781 removed_lines += line_count_before - line_count_after;
11782 }
11783 }
11784
11785 self.transact(window, cx, |this, window, cx| {
11786 let buffer = this.buffer.update(cx, |buffer, cx| {
11787 buffer.edit(edits, None, cx);
11788 buffer.snapshot(cx)
11789 });
11790
11791 // Recalculate offsets on newly edited buffer
11792 let new_selections = new_selections
11793 .iter()
11794 .map(|s| {
11795 let start_point = Point::new(s.start.0, 0);
11796 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11797 Selection {
11798 id: s.id,
11799 start: buffer.point_to_offset(start_point),
11800 end: buffer.point_to_offset(end_point),
11801 goal: s.goal,
11802 reversed: s.reversed,
11803 }
11804 })
11805 .collect();
11806
11807 this.change_selections(Default::default(), window, cx, |s| {
11808 s.select(new_selections);
11809 });
11810
11811 this.request_autoscroll(Autoscroll::fit(), cx);
11812 });
11813 }
11814
11815 fn manipulate_immutable_lines<Fn>(
11816 &mut self,
11817 window: &mut Window,
11818 cx: &mut Context<Self>,
11819 mut callback: Fn,
11820 ) where
11821 Fn: FnMut(&mut Vec<&str>),
11822 {
11823 self.manipulate_lines(window, cx, |text| {
11824 let mut lines: Vec<&str> = text.split('\n').collect();
11825 let line_count_before = lines.len();
11826
11827 callback(&mut lines);
11828
11829 LineManipulationResult {
11830 new_text: lines.join("\n"),
11831 line_count_before,
11832 line_count_after: lines.len(),
11833 }
11834 });
11835 }
11836
11837 fn manipulate_mutable_lines<Fn>(
11838 &mut self,
11839 window: &mut Window,
11840 cx: &mut Context<Self>,
11841 mut callback: Fn,
11842 ) where
11843 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11844 {
11845 self.manipulate_lines(window, cx, |text| {
11846 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11847 let line_count_before = lines.len();
11848
11849 callback(&mut lines);
11850
11851 LineManipulationResult {
11852 new_text: lines.join("\n"),
11853 line_count_before,
11854 line_count_after: lines.len(),
11855 }
11856 });
11857 }
11858
11859 pub fn convert_indentation_to_spaces(
11860 &mut self,
11861 _: &ConvertIndentationToSpaces,
11862 window: &mut Window,
11863 cx: &mut Context<Self>,
11864 ) {
11865 let settings = self.buffer.read(cx).language_settings(cx);
11866 let tab_size = settings.tab_size.get() as usize;
11867
11868 self.manipulate_mutable_lines(window, cx, |lines| {
11869 // Allocates a reasonably sized scratch buffer once for the whole loop
11870 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11871 // Avoids recomputing spaces that could be inserted many times
11872 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11873 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11874 .collect();
11875
11876 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11877 let mut chars = line.as_ref().chars();
11878 let mut col = 0;
11879 let mut changed = false;
11880
11881 for ch in chars.by_ref() {
11882 match ch {
11883 ' ' => {
11884 reindented_line.push(' ');
11885 col += 1;
11886 }
11887 '\t' => {
11888 // \t are converted to spaces depending on the current column
11889 let spaces_len = tab_size - (col % tab_size);
11890 reindented_line.extend(&space_cache[spaces_len - 1]);
11891 col += spaces_len;
11892 changed = true;
11893 }
11894 _ => {
11895 // If we dont append before break, the character is consumed
11896 reindented_line.push(ch);
11897 break;
11898 }
11899 }
11900 }
11901
11902 if !changed {
11903 reindented_line.clear();
11904 continue;
11905 }
11906 // Append the rest of the line and replace old reference with new one
11907 reindented_line.extend(chars);
11908 *line = Cow::Owned(reindented_line.clone());
11909 reindented_line.clear();
11910 }
11911 });
11912 }
11913
11914 pub fn convert_indentation_to_tabs(
11915 &mut self,
11916 _: &ConvertIndentationToTabs,
11917 window: &mut Window,
11918 cx: &mut Context<Self>,
11919 ) {
11920 let settings = self.buffer.read(cx).language_settings(cx);
11921 let tab_size = settings.tab_size.get() as usize;
11922
11923 self.manipulate_mutable_lines(window, cx, |lines| {
11924 // Allocates a reasonably sized buffer once for the whole loop
11925 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11926 // Avoids recomputing spaces that could be inserted many times
11927 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11928 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11929 .collect();
11930
11931 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11932 let mut chars = line.chars();
11933 let mut spaces_count = 0;
11934 let mut first_non_indent_char = None;
11935 let mut changed = false;
11936
11937 for ch in chars.by_ref() {
11938 match ch {
11939 ' ' => {
11940 // Keep track of spaces. Append \t when we reach tab_size
11941 spaces_count += 1;
11942 changed = true;
11943 if spaces_count == tab_size {
11944 reindented_line.push('\t');
11945 spaces_count = 0;
11946 }
11947 }
11948 '\t' => {
11949 reindented_line.push('\t');
11950 spaces_count = 0;
11951 }
11952 _ => {
11953 // Dont append it yet, we might have remaining spaces
11954 first_non_indent_char = Some(ch);
11955 break;
11956 }
11957 }
11958 }
11959
11960 if !changed {
11961 reindented_line.clear();
11962 continue;
11963 }
11964 // Remaining spaces that didn't make a full tab stop
11965 if spaces_count > 0 {
11966 reindented_line.extend(&space_cache[spaces_count - 1]);
11967 }
11968 // If we consume an extra character that was not indentation, add it back
11969 if let Some(extra_char) = first_non_indent_char {
11970 reindented_line.push(extra_char);
11971 }
11972 // Append the rest of the line and replace old reference with new one
11973 reindented_line.extend(chars);
11974 *line = Cow::Owned(reindented_line.clone());
11975 reindented_line.clear();
11976 }
11977 });
11978 }
11979
11980 pub fn convert_to_upper_case(
11981 &mut self,
11982 _: &ConvertToUpperCase,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) {
11986 self.manipulate_text(window, cx, |text| text.to_uppercase())
11987 }
11988
11989 pub fn convert_to_lower_case(
11990 &mut self,
11991 _: &ConvertToLowerCase,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.manipulate_text(window, cx, |text| text.to_lowercase())
11996 }
11997
11998 pub fn convert_to_title_case(
11999 &mut self,
12000 _: &ConvertToTitleCase,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.manipulate_text(window, cx, |text| {
12005 text.split('\n')
12006 .map(|line| line.to_case(Case::Title))
12007 .join("\n")
12008 })
12009 }
12010
12011 pub fn convert_to_snake_case(
12012 &mut self,
12013 _: &ConvertToSnakeCase,
12014 window: &mut Window,
12015 cx: &mut Context<Self>,
12016 ) {
12017 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12018 }
12019
12020 pub fn convert_to_kebab_case(
12021 &mut self,
12022 _: &ConvertToKebabCase,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12027 }
12028
12029 pub fn convert_to_upper_camel_case(
12030 &mut self,
12031 _: &ConvertToUpperCamelCase,
12032 window: &mut Window,
12033 cx: &mut Context<Self>,
12034 ) {
12035 self.manipulate_text(window, cx, |text| {
12036 text.split('\n')
12037 .map(|line| line.to_case(Case::UpperCamel))
12038 .join("\n")
12039 })
12040 }
12041
12042 pub fn convert_to_lower_camel_case(
12043 &mut self,
12044 _: &ConvertToLowerCamelCase,
12045 window: &mut Window,
12046 cx: &mut Context<Self>,
12047 ) {
12048 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12049 }
12050
12051 pub fn convert_to_opposite_case(
12052 &mut self,
12053 _: &ConvertToOppositeCase,
12054 window: &mut Window,
12055 cx: &mut Context<Self>,
12056 ) {
12057 self.manipulate_text(window, cx, |text| {
12058 text.chars()
12059 .fold(String::with_capacity(text.len()), |mut t, c| {
12060 if c.is_uppercase() {
12061 t.extend(c.to_lowercase());
12062 } else {
12063 t.extend(c.to_uppercase());
12064 }
12065 t
12066 })
12067 })
12068 }
12069
12070 pub fn convert_to_sentence_case(
12071 &mut self,
12072 _: &ConvertToSentenceCase,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12077 }
12078
12079 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12080 self.manipulate_text(window, cx, |text| {
12081 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12082 if has_upper_case_characters {
12083 text.to_lowercase()
12084 } else {
12085 text.to_uppercase()
12086 }
12087 })
12088 }
12089
12090 pub fn convert_to_rot13(
12091 &mut self,
12092 _: &ConvertToRot13,
12093 window: &mut Window,
12094 cx: &mut Context<Self>,
12095 ) {
12096 self.manipulate_text(window, cx, |text| {
12097 text.chars()
12098 .map(|c| match c {
12099 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12100 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12101 _ => c,
12102 })
12103 .collect()
12104 })
12105 }
12106
12107 pub fn convert_to_rot47(
12108 &mut self,
12109 _: &ConvertToRot47,
12110 window: &mut Window,
12111 cx: &mut Context<Self>,
12112 ) {
12113 self.manipulate_text(window, cx, |text| {
12114 text.chars()
12115 .map(|c| {
12116 let code_point = c as u32;
12117 if code_point >= 33 && code_point <= 126 {
12118 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12119 }
12120 c
12121 })
12122 .collect()
12123 })
12124 }
12125
12126 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12127 where
12128 Fn: FnMut(&str) -> String,
12129 {
12130 let buffer = self.buffer.read(cx).snapshot(cx);
12131
12132 let mut new_selections = Vec::new();
12133 let mut edits = Vec::new();
12134 let mut selection_adjustment = 0isize;
12135
12136 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12137 let selection_is_empty = selection.is_empty();
12138
12139 let (start, end) = if selection_is_empty {
12140 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12141 (word_range.start, word_range.end)
12142 } else {
12143 (
12144 buffer.point_to_offset(selection.start),
12145 buffer.point_to_offset(selection.end),
12146 )
12147 };
12148
12149 let text = buffer.text_for_range(start..end).collect::<String>();
12150 let old_length = text.len() as isize;
12151 let text = callback(&text);
12152
12153 new_selections.push(Selection {
12154 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12155 end: MultiBufferOffset(
12156 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12157 ),
12158 goal: SelectionGoal::None,
12159 id: selection.id,
12160 reversed: selection.reversed,
12161 });
12162
12163 selection_adjustment += old_length - text.len() as isize;
12164
12165 edits.push((start..end, text));
12166 }
12167
12168 self.transact(window, cx, |this, window, cx| {
12169 this.buffer.update(cx, |buffer, cx| {
12170 buffer.edit(edits, None, cx);
12171 });
12172
12173 this.change_selections(Default::default(), window, cx, |s| {
12174 s.select(new_selections);
12175 });
12176
12177 this.request_autoscroll(Autoscroll::fit(), cx);
12178 });
12179 }
12180
12181 pub fn move_selection_on_drop(
12182 &mut self,
12183 selection: &Selection<Anchor>,
12184 target: DisplayPoint,
12185 is_cut: bool,
12186 window: &mut Window,
12187 cx: &mut Context<Self>,
12188 ) {
12189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12190 let buffer = display_map.buffer_snapshot();
12191 let mut edits = Vec::new();
12192 let insert_point = display_map
12193 .clip_point(target, Bias::Left)
12194 .to_point(&display_map);
12195 let text = buffer
12196 .text_for_range(selection.start..selection.end)
12197 .collect::<String>();
12198 if is_cut {
12199 edits.push(((selection.start..selection.end), String::new()));
12200 }
12201 let insert_anchor = buffer.anchor_before(insert_point);
12202 edits.push(((insert_anchor..insert_anchor), text));
12203 let last_edit_start = insert_anchor.bias_left(buffer);
12204 let last_edit_end = insert_anchor.bias_right(buffer);
12205 self.transact(window, cx, |this, window, cx| {
12206 this.buffer.update(cx, |buffer, cx| {
12207 buffer.edit(edits, None, cx);
12208 });
12209 this.change_selections(Default::default(), window, cx, |s| {
12210 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12211 });
12212 });
12213 }
12214
12215 pub fn clear_selection_drag_state(&mut self) {
12216 self.selection_drag_state = SelectionDragState::None;
12217 }
12218
12219 pub fn duplicate(
12220 &mut self,
12221 upwards: bool,
12222 whole_lines: bool,
12223 window: &mut Window,
12224 cx: &mut Context<Self>,
12225 ) {
12226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12227
12228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12229 let buffer = display_map.buffer_snapshot();
12230 let selections = self.selections.all::<Point>(&display_map);
12231
12232 let mut edits = Vec::new();
12233 let mut selections_iter = selections.iter().peekable();
12234 while let Some(selection) = selections_iter.next() {
12235 let mut rows = selection.spanned_rows(false, &display_map);
12236 // duplicate line-wise
12237 if whole_lines || selection.start == selection.end {
12238 // Avoid duplicating the same lines twice.
12239 while let Some(next_selection) = selections_iter.peek() {
12240 let next_rows = next_selection.spanned_rows(false, &display_map);
12241 if next_rows.start < rows.end {
12242 rows.end = next_rows.end;
12243 selections_iter.next().unwrap();
12244 } else {
12245 break;
12246 }
12247 }
12248
12249 // Copy the text from the selected row region and splice it either at the start
12250 // or end of the region.
12251 let start = Point::new(rows.start.0, 0);
12252 let end = Point::new(
12253 rows.end.previous_row().0,
12254 buffer.line_len(rows.end.previous_row()),
12255 );
12256
12257 let mut text = buffer.text_for_range(start..end).collect::<String>();
12258
12259 let insert_location = if upwards {
12260 // When duplicating upward, we need to insert before the current line.
12261 // If we're on the last line and it doesn't end with a newline,
12262 // we need to add a newline before the duplicated content.
12263 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12264 && buffer.max_point().column > 0
12265 && !text.ends_with('\n');
12266
12267 if needs_leading_newline {
12268 text.insert(0, '\n');
12269 end
12270 } else {
12271 text.push('\n');
12272 Point::new(rows.start.0, 0)
12273 }
12274 } else {
12275 text.push('\n');
12276 start
12277 };
12278 edits.push((insert_location..insert_location, text));
12279 } else {
12280 // duplicate character-wise
12281 let start = selection.start;
12282 let end = selection.end;
12283 let text = buffer.text_for_range(start..end).collect::<String>();
12284 edits.push((selection.end..selection.end, text));
12285 }
12286 }
12287
12288 self.transact(window, cx, |this, window, cx| {
12289 this.buffer.update(cx, |buffer, cx| {
12290 buffer.edit(edits, None, cx);
12291 });
12292
12293 // When duplicating upward with whole lines, move the cursor to the duplicated line
12294 if upwards && whole_lines {
12295 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12296
12297 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12298 let mut new_ranges = Vec::new();
12299 let selections = s.all::<Point>(&display_map);
12300 let mut selections_iter = selections.iter().peekable();
12301
12302 while let Some(first_selection) = selections_iter.next() {
12303 // Group contiguous selections together to find the total row span
12304 let mut group_selections = vec![first_selection];
12305 let mut rows = first_selection.spanned_rows(false, &display_map);
12306
12307 while let Some(next_selection) = selections_iter.peek() {
12308 let next_rows = next_selection.spanned_rows(false, &display_map);
12309 if next_rows.start < rows.end {
12310 rows.end = next_rows.end;
12311 group_selections.push(selections_iter.next().unwrap());
12312 } else {
12313 break;
12314 }
12315 }
12316
12317 let row_count = rows.end.0 - rows.start.0;
12318
12319 // Move all selections in this group up by the total number of duplicated rows
12320 for selection in group_selections {
12321 let new_start = Point::new(
12322 selection.start.row.saturating_sub(row_count),
12323 selection.start.column,
12324 );
12325
12326 let new_end = Point::new(
12327 selection.end.row.saturating_sub(row_count),
12328 selection.end.column,
12329 );
12330
12331 new_ranges.push(new_start..new_end);
12332 }
12333 }
12334
12335 s.select_ranges(new_ranges);
12336 });
12337 }
12338
12339 this.request_autoscroll(Autoscroll::fit(), cx);
12340 });
12341 }
12342
12343 pub fn duplicate_line_up(
12344 &mut self,
12345 _: &DuplicateLineUp,
12346 window: &mut Window,
12347 cx: &mut Context<Self>,
12348 ) {
12349 self.duplicate(true, true, window, cx);
12350 }
12351
12352 pub fn duplicate_line_down(
12353 &mut self,
12354 _: &DuplicateLineDown,
12355 window: &mut Window,
12356 cx: &mut Context<Self>,
12357 ) {
12358 self.duplicate(false, true, window, cx);
12359 }
12360
12361 pub fn duplicate_selection(
12362 &mut self,
12363 _: &DuplicateSelection,
12364 window: &mut Window,
12365 cx: &mut Context<Self>,
12366 ) {
12367 self.duplicate(false, false, window, cx);
12368 }
12369
12370 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12372 if self.mode.is_single_line() {
12373 cx.propagate();
12374 return;
12375 }
12376
12377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12378 let buffer = self.buffer.read(cx).snapshot(cx);
12379
12380 let mut edits = Vec::new();
12381 let mut unfold_ranges = Vec::new();
12382 let mut refold_creases = Vec::new();
12383
12384 let selections = self.selections.all::<Point>(&display_map);
12385 let mut selections = selections.iter().peekable();
12386 let mut contiguous_row_selections = Vec::new();
12387 let mut new_selections = Vec::new();
12388
12389 while let Some(selection) = selections.next() {
12390 // Find all the selections that span a contiguous row range
12391 let (start_row, end_row) = consume_contiguous_rows(
12392 &mut contiguous_row_selections,
12393 selection,
12394 &display_map,
12395 &mut selections,
12396 );
12397
12398 // Move the text spanned by the row range to be before the line preceding the row range
12399 if start_row.0 > 0 {
12400 let range_to_move = Point::new(
12401 start_row.previous_row().0,
12402 buffer.line_len(start_row.previous_row()),
12403 )
12404 ..Point::new(
12405 end_row.previous_row().0,
12406 buffer.line_len(end_row.previous_row()),
12407 );
12408 let insertion_point = display_map
12409 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12410 .0;
12411
12412 // Don't move lines across excerpts
12413 if buffer
12414 .excerpt_containing(insertion_point..range_to_move.end)
12415 .is_some()
12416 {
12417 let text = buffer
12418 .text_for_range(range_to_move.clone())
12419 .flat_map(|s| s.chars())
12420 .skip(1)
12421 .chain(['\n'])
12422 .collect::<String>();
12423
12424 edits.push((
12425 buffer.anchor_after(range_to_move.start)
12426 ..buffer.anchor_before(range_to_move.end),
12427 String::new(),
12428 ));
12429 let insertion_anchor = buffer.anchor_after(insertion_point);
12430 edits.push((insertion_anchor..insertion_anchor, text));
12431
12432 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12433
12434 // Move selections up
12435 new_selections.extend(contiguous_row_selections.drain(..).map(
12436 |mut selection| {
12437 selection.start.row -= row_delta;
12438 selection.end.row -= row_delta;
12439 selection
12440 },
12441 ));
12442
12443 // Move folds up
12444 unfold_ranges.push(range_to_move.clone());
12445 for fold in display_map.folds_in_range(
12446 buffer.anchor_before(range_to_move.start)
12447 ..buffer.anchor_after(range_to_move.end),
12448 ) {
12449 let mut start = fold.range.start.to_point(&buffer);
12450 let mut end = fold.range.end.to_point(&buffer);
12451 start.row -= row_delta;
12452 end.row -= row_delta;
12453 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12454 }
12455 }
12456 }
12457
12458 // If we didn't move line(s), preserve the existing selections
12459 new_selections.append(&mut contiguous_row_selections);
12460 }
12461
12462 self.transact(window, cx, |this, window, cx| {
12463 this.unfold_ranges(&unfold_ranges, true, true, cx);
12464 this.buffer.update(cx, |buffer, cx| {
12465 for (range, text) in edits {
12466 buffer.edit([(range, text)], None, cx);
12467 }
12468 });
12469 this.fold_creases(refold_creases, true, window, cx);
12470 this.change_selections(Default::default(), window, cx, |s| {
12471 s.select(new_selections);
12472 })
12473 });
12474 }
12475
12476 pub fn move_line_down(
12477 &mut self,
12478 _: &MoveLineDown,
12479 window: &mut Window,
12480 cx: &mut Context<Self>,
12481 ) {
12482 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12483 if self.mode.is_single_line() {
12484 cx.propagate();
12485 return;
12486 }
12487
12488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12489 let buffer = self.buffer.read(cx).snapshot(cx);
12490
12491 let mut edits = Vec::new();
12492 let mut unfold_ranges = Vec::new();
12493 let mut refold_creases = Vec::new();
12494
12495 let selections = self.selections.all::<Point>(&display_map);
12496 let mut selections = selections.iter().peekable();
12497 let mut contiguous_row_selections = Vec::new();
12498 let mut new_selections = Vec::new();
12499
12500 while let Some(selection) = selections.next() {
12501 // Find all the selections that span a contiguous row range
12502 let (start_row, end_row) = consume_contiguous_rows(
12503 &mut contiguous_row_selections,
12504 selection,
12505 &display_map,
12506 &mut selections,
12507 );
12508
12509 // Move the text spanned by the row range to be after the last line of the row range
12510 if end_row.0 <= buffer.max_point().row {
12511 let range_to_move =
12512 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12513 let insertion_point = display_map
12514 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12515 .0;
12516
12517 // Don't move lines across excerpt boundaries
12518 if buffer
12519 .excerpt_containing(range_to_move.start..insertion_point)
12520 .is_some()
12521 {
12522 let mut text = String::from("\n");
12523 text.extend(buffer.text_for_range(range_to_move.clone()));
12524 text.pop(); // Drop trailing newline
12525 edits.push((
12526 buffer.anchor_after(range_to_move.start)
12527 ..buffer.anchor_before(range_to_move.end),
12528 String::new(),
12529 ));
12530 let insertion_anchor = buffer.anchor_after(insertion_point);
12531 edits.push((insertion_anchor..insertion_anchor, text));
12532
12533 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12534
12535 // Move selections down
12536 new_selections.extend(contiguous_row_selections.drain(..).map(
12537 |mut selection| {
12538 selection.start.row += row_delta;
12539 selection.end.row += row_delta;
12540 selection
12541 },
12542 ));
12543
12544 // Move folds down
12545 unfold_ranges.push(range_to_move.clone());
12546 for fold in display_map.folds_in_range(
12547 buffer.anchor_before(range_to_move.start)
12548 ..buffer.anchor_after(range_to_move.end),
12549 ) {
12550 let mut start = fold.range.start.to_point(&buffer);
12551 let mut end = fold.range.end.to_point(&buffer);
12552 start.row += row_delta;
12553 end.row += row_delta;
12554 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12555 }
12556 }
12557 }
12558
12559 // If we didn't move line(s), preserve the existing selections
12560 new_selections.append(&mut contiguous_row_selections);
12561 }
12562
12563 self.transact(window, cx, |this, window, cx| {
12564 this.unfold_ranges(&unfold_ranges, true, true, cx);
12565 this.buffer.update(cx, |buffer, cx| {
12566 for (range, text) in edits {
12567 buffer.edit([(range, text)], None, cx);
12568 }
12569 });
12570 this.fold_creases(refold_creases, true, window, cx);
12571 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12572 });
12573 }
12574
12575 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12577 let text_layout_details = &self.text_layout_details(window);
12578 self.transact(window, cx, |this, window, cx| {
12579 let edits = this.change_selections(Default::default(), window, cx, |s| {
12580 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12581 s.move_with(|display_map, selection| {
12582 if !selection.is_empty() {
12583 return;
12584 }
12585
12586 let mut head = selection.head();
12587 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12588 if head.column() == display_map.line_len(head.row()) {
12589 transpose_offset = display_map
12590 .buffer_snapshot()
12591 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12592 }
12593
12594 if transpose_offset == MultiBufferOffset(0) {
12595 return;
12596 }
12597
12598 *head.column_mut() += 1;
12599 head = display_map.clip_point(head, Bias::Right);
12600 let goal = SelectionGoal::HorizontalPosition(
12601 display_map
12602 .x_for_display_point(head, text_layout_details)
12603 .into(),
12604 );
12605 selection.collapse_to(head, goal);
12606
12607 let transpose_start = display_map
12608 .buffer_snapshot()
12609 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12610 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12611 let transpose_end = display_map
12612 .buffer_snapshot()
12613 .clip_offset(transpose_offset + 1usize, Bias::Right);
12614 if let Some(ch) = display_map
12615 .buffer_snapshot()
12616 .chars_at(transpose_start)
12617 .next()
12618 {
12619 edits.push((transpose_start..transpose_offset, String::new()));
12620 edits.push((transpose_end..transpose_end, ch.to_string()));
12621 }
12622 }
12623 });
12624 edits
12625 });
12626 this.buffer
12627 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12628 let selections = this
12629 .selections
12630 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12631 this.change_selections(Default::default(), window, cx, |s| {
12632 s.select(selections);
12633 });
12634 });
12635 }
12636
12637 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12639 if self.mode.is_single_line() {
12640 cx.propagate();
12641 return;
12642 }
12643
12644 self.rewrap_impl(RewrapOptions::default(), cx)
12645 }
12646
12647 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12648 let buffer = self.buffer.read(cx).snapshot(cx);
12649 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12650
12651 #[derive(Clone, Debug, PartialEq)]
12652 enum CommentFormat {
12653 /// single line comment, with prefix for line
12654 Line(String),
12655 /// single line within a block comment, with prefix for line
12656 BlockLine(String),
12657 /// a single line of a block comment that includes the initial delimiter
12658 BlockCommentWithStart(BlockCommentConfig),
12659 /// a single line of a block comment that includes the ending delimiter
12660 BlockCommentWithEnd(BlockCommentConfig),
12661 }
12662
12663 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12664 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12665 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12666 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12667 .peekable();
12668
12669 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12670 row
12671 } else {
12672 return Vec::new();
12673 };
12674
12675 let language_settings = buffer.language_settings_at(selection.head(), cx);
12676 let language_scope = buffer.language_scope_at(selection.head());
12677
12678 let indent_and_prefix_for_row =
12679 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12680 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12681 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12682 &language_scope
12683 {
12684 let indent_end = Point::new(row, indent.len);
12685 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12686 let line_text_after_indent = buffer
12687 .text_for_range(indent_end..line_end)
12688 .collect::<String>();
12689
12690 let is_within_comment_override = buffer
12691 .language_scope_at(indent_end)
12692 .is_some_and(|scope| scope.override_name() == Some("comment"));
12693 let comment_delimiters = if is_within_comment_override {
12694 // we are within a comment syntax node, but we don't
12695 // yet know what kind of comment: block, doc or line
12696 match (
12697 language_scope.documentation_comment(),
12698 language_scope.block_comment(),
12699 ) {
12700 (Some(config), _) | (_, Some(config))
12701 if buffer.contains_str_at(indent_end, &config.start) =>
12702 {
12703 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12704 }
12705 (Some(config), _) | (_, Some(config))
12706 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12707 {
12708 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12709 }
12710 (Some(config), _) | (_, Some(config))
12711 if buffer.contains_str_at(indent_end, &config.prefix) =>
12712 {
12713 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12714 }
12715 (_, _) => language_scope
12716 .line_comment_prefixes()
12717 .iter()
12718 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12719 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12720 }
12721 } else {
12722 // we not in an overridden comment node, but we may
12723 // be within a non-overridden line comment node
12724 language_scope
12725 .line_comment_prefixes()
12726 .iter()
12727 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12728 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12729 };
12730
12731 let rewrap_prefix = language_scope
12732 .rewrap_prefixes()
12733 .iter()
12734 .find_map(|prefix_regex| {
12735 prefix_regex.find(&line_text_after_indent).map(|mat| {
12736 if mat.start() == 0 {
12737 Some(mat.as_str().to_string())
12738 } else {
12739 None
12740 }
12741 })
12742 })
12743 .flatten();
12744 (comment_delimiters, rewrap_prefix)
12745 } else {
12746 (None, None)
12747 };
12748 (indent, comment_prefix, rewrap_prefix)
12749 };
12750
12751 let mut ranges = Vec::new();
12752 let from_empty_selection = selection.is_empty();
12753
12754 let mut current_range_start = first_row;
12755 let mut prev_row = first_row;
12756 let (
12757 mut current_range_indent,
12758 mut current_range_comment_delimiters,
12759 mut current_range_rewrap_prefix,
12760 ) = indent_and_prefix_for_row(first_row);
12761
12762 for row in non_blank_rows_iter.skip(1) {
12763 let has_paragraph_break = row > prev_row + 1;
12764
12765 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12766 indent_and_prefix_for_row(row);
12767
12768 let has_indent_change = row_indent != current_range_indent;
12769 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12770
12771 let has_boundary_change = has_comment_change
12772 || row_rewrap_prefix.is_some()
12773 || (has_indent_change && current_range_comment_delimiters.is_some());
12774
12775 if has_paragraph_break || has_boundary_change {
12776 ranges.push((
12777 language_settings.clone(),
12778 Point::new(current_range_start, 0)
12779 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12780 current_range_indent,
12781 current_range_comment_delimiters.clone(),
12782 current_range_rewrap_prefix.clone(),
12783 from_empty_selection,
12784 ));
12785 current_range_start = row;
12786 current_range_indent = row_indent;
12787 current_range_comment_delimiters = row_comment_delimiters;
12788 current_range_rewrap_prefix = row_rewrap_prefix;
12789 }
12790 prev_row = row;
12791 }
12792
12793 ranges.push((
12794 language_settings.clone(),
12795 Point::new(current_range_start, 0)
12796 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12797 current_range_indent,
12798 current_range_comment_delimiters,
12799 current_range_rewrap_prefix,
12800 from_empty_selection,
12801 ));
12802
12803 ranges
12804 });
12805
12806 let mut edits = Vec::new();
12807 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12808
12809 for (
12810 language_settings,
12811 wrap_range,
12812 mut indent_size,
12813 comment_prefix,
12814 rewrap_prefix,
12815 from_empty_selection,
12816 ) in wrap_ranges
12817 {
12818 let mut start_row = wrap_range.start.row;
12819 let mut end_row = wrap_range.end.row;
12820
12821 // Skip selections that overlap with a range that has already been rewrapped.
12822 let selection_range = start_row..end_row;
12823 if rewrapped_row_ranges
12824 .iter()
12825 .any(|range| range.overlaps(&selection_range))
12826 {
12827 continue;
12828 }
12829
12830 let tab_size = language_settings.tab_size;
12831
12832 let (line_prefix, inside_comment) = match &comment_prefix {
12833 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12834 (Some(prefix.as_str()), true)
12835 }
12836 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12837 (Some(prefix.as_ref()), true)
12838 }
12839 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12840 start: _,
12841 end: _,
12842 prefix,
12843 tab_size,
12844 })) => {
12845 indent_size.len += tab_size;
12846 (Some(prefix.as_ref()), true)
12847 }
12848 None => (None, false),
12849 };
12850 let indent_prefix = indent_size.chars().collect::<String>();
12851 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12852
12853 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12854 RewrapBehavior::InComments => inside_comment,
12855 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12856 RewrapBehavior::Anywhere => true,
12857 };
12858
12859 let should_rewrap = options.override_language_settings
12860 || allow_rewrap_based_on_language
12861 || self.hard_wrap.is_some();
12862 if !should_rewrap {
12863 continue;
12864 }
12865
12866 if from_empty_selection {
12867 'expand_upwards: while start_row > 0 {
12868 let prev_row = start_row - 1;
12869 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12870 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12871 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12872 {
12873 start_row = prev_row;
12874 } else {
12875 break 'expand_upwards;
12876 }
12877 }
12878
12879 'expand_downwards: while end_row < buffer.max_point().row {
12880 let next_row = end_row + 1;
12881 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12882 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12883 && !buffer.is_line_blank(MultiBufferRow(next_row))
12884 {
12885 end_row = next_row;
12886 } else {
12887 break 'expand_downwards;
12888 }
12889 }
12890 }
12891
12892 let start = Point::new(start_row, 0);
12893 let start_offset = ToOffset::to_offset(&start, &buffer);
12894 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12895 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12896 let mut first_line_delimiter = None;
12897 let mut last_line_delimiter = None;
12898 let Some(lines_without_prefixes) = selection_text
12899 .lines()
12900 .enumerate()
12901 .map(|(ix, line)| {
12902 let line_trimmed = line.trim_start();
12903 if rewrap_prefix.is_some() && ix > 0 {
12904 Ok(line_trimmed)
12905 } else if let Some(
12906 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12907 start,
12908 prefix,
12909 end,
12910 tab_size,
12911 })
12912 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12913 start,
12914 prefix,
12915 end,
12916 tab_size,
12917 }),
12918 ) = &comment_prefix
12919 {
12920 let line_trimmed = line_trimmed
12921 .strip_prefix(start.as_ref())
12922 .map(|s| {
12923 let mut indent_size = indent_size;
12924 indent_size.len -= tab_size;
12925 let indent_prefix: String = indent_size.chars().collect();
12926 first_line_delimiter = Some((indent_prefix, start));
12927 s.trim_start()
12928 })
12929 .unwrap_or(line_trimmed);
12930 let line_trimmed = line_trimmed
12931 .strip_suffix(end.as_ref())
12932 .map(|s| {
12933 last_line_delimiter = Some(end);
12934 s.trim_end()
12935 })
12936 .unwrap_or(line_trimmed);
12937 let line_trimmed = line_trimmed
12938 .strip_prefix(prefix.as_ref())
12939 .unwrap_or(line_trimmed);
12940 Ok(line_trimmed)
12941 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12942 line_trimmed.strip_prefix(prefix).with_context(|| {
12943 format!("line did not start with prefix {prefix:?}: {line:?}")
12944 })
12945 } else {
12946 line_trimmed
12947 .strip_prefix(&line_prefix.trim_start())
12948 .with_context(|| {
12949 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12950 })
12951 }
12952 })
12953 .collect::<Result<Vec<_>, _>>()
12954 .log_err()
12955 else {
12956 continue;
12957 };
12958
12959 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12960 buffer
12961 .language_settings_at(Point::new(start_row, 0), cx)
12962 .preferred_line_length as usize
12963 });
12964
12965 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12966 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12967 } else {
12968 line_prefix.clone()
12969 };
12970
12971 let wrapped_text = {
12972 let mut wrapped_text = wrap_with_prefix(
12973 line_prefix,
12974 subsequent_lines_prefix,
12975 lines_without_prefixes.join("\n"),
12976 wrap_column,
12977 tab_size,
12978 options.preserve_existing_whitespace,
12979 );
12980
12981 if let Some((indent, delimiter)) = first_line_delimiter {
12982 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12983 }
12984 if let Some(last_line) = last_line_delimiter {
12985 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12986 }
12987
12988 wrapped_text
12989 };
12990
12991 // TODO: should always use char-based diff while still supporting cursor behavior that
12992 // matches vim.
12993 let mut diff_options = DiffOptions::default();
12994 if options.override_language_settings {
12995 diff_options.max_word_diff_len = 0;
12996 diff_options.max_word_diff_line_count = 0;
12997 } else {
12998 diff_options.max_word_diff_len = usize::MAX;
12999 diff_options.max_word_diff_line_count = usize::MAX;
13000 }
13001
13002 for (old_range, new_text) in
13003 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13004 {
13005 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13006 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13007 edits.push((edit_start..edit_end, new_text));
13008 }
13009
13010 rewrapped_row_ranges.push(start_row..=end_row);
13011 }
13012
13013 self.buffer
13014 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13015 }
13016
13017 pub fn cut_common(
13018 &mut self,
13019 cut_no_selection_line: bool,
13020 window: &mut Window,
13021 cx: &mut Context<Self>,
13022 ) -> ClipboardItem {
13023 let mut text = String::new();
13024 let buffer = self.buffer.read(cx).snapshot(cx);
13025 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13026 let mut clipboard_selections = Vec::with_capacity(selections.len());
13027 {
13028 let max_point = buffer.max_point();
13029 let mut is_first = true;
13030 let mut prev_selection_was_entire_line = false;
13031 for selection in &mut selections {
13032 let is_entire_line =
13033 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13034 if is_entire_line {
13035 selection.start = Point::new(selection.start.row, 0);
13036 if !selection.is_empty() && selection.end.column == 0 {
13037 selection.end = cmp::min(max_point, selection.end);
13038 } else {
13039 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13040 }
13041 selection.goal = SelectionGoal::None;
13042 }
13043 if is_first {
13044 is_first = false;
13045 } else if !prev_selection_was_entire_line {
13046 text += "\n";
13047 }
13048 prev_selection_was_entire_line = is_entire_line;
13049 let mut len = 0;
13050 for chunk in buffer.text_for_range(selection.start..selection.end) {
13051 text.push_str(chunk);
13052 len += chunk.len();
13053 }
13054
13055 clipboard_selections.push(ClipboardSelection::for_buffer(
13056 len,
13057 is_entire_line,
13058 selection.range(),
13059 &buffer,
13060 self.project.as_ref(),
13061 cx,
13062 ));
13063 }
13064 }
13065
13066 self.transact(window, cx, |this, window, cx| {
13067 this.change_selections(Default::default(), window, cx, |s| {
13068 s.select(selections);
13069 });
13070 this.insert("", window, cx);
13071 });
13072 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13073 }
13074
13075 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13077 let item = self.cut_common(true, window, cx);
13078 cx.write_to_clipboard(item);
13079 }
13080
13081 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13083 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13084 s.move_with(|snapshot, sel| {
13085 if sel.is_empty() {
13086 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13087 }
13088 if sel.is_empty() {
13089 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13090 }
13091 });
13092 });
13093 let item = self.cut_common(false, window, cx);
13094 cx.set_global(KillRing(item))
13095 }
13096
13097 pub fn kill_ring_yank(
13098 &mut self,
13099 _: &KillRingYank,
13100 window: &mut Window,
13101 cx: &mut Context<Self>,
13102 ) {
13103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13104 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13105 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13106 (kill_ring.text().to_string(), kill_ring.metadata_json())
13107 } else {
13108 return;
13109 }
13110 } else {
13111 return;
13112 };
13113 self.do_paste(&text, metadata, false, window, cx);
13114 }
13115
13116 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13117 self.do_copy(true, cx);
13118 }
13119
13120 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13121 self.do_copy(false, cx);
13122 }
13123
13124 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13125 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13126 let buffer = self.buffer.read(cx).read(cx);
13127 let mut text = String::new();
13128
13129 let mut clipboard_selections = Vec::with_capacity(selections.len());
13130 {
13131 let max_point = buffer.max_point();
13132 let mut is_first = true;
13133 let mut prev_selection_was_entire_line = false;
13134 for selection in &selections {
13135 let mut start = selection.start;
13136 let mut end = selection.end;
13137 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13138 let mut add_trailing_newline = false;
13139 if is_entire_line {
13140 start = Point::new(start.row, 0);
13141 let next_line_start = Point::new(end.row + 1, 0);
13142 if next_line_start <= max_point {
13143 end = next_line_start;
13144 } else {
13145 // We're on the last line without a trailing newline.
13146 // Copy to the end of the line and add a newline afterwards.
13147 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13148 add_trailing_newline = true;
13149 }
13150 }
13151
13152 let mut trimmed_selections = Vec::new();
13153 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13154 let row = MultiBufferRow(start.row);
13155 let first_indent = buffer.indent_size_for_line(row);
13156 if first_indent.len == 0 || start.column > first_indent.len {
13157 trimmed_selections.push(start..end);
13158 } else {
13159 trimmed_selections.push(
13160 Point::new(row.0, first_indent.len)
13161 ..Point::new(row.0, buffer.line_len(row)),
13162 );
13163 for row in start.row + 1..=end.row {
13164 let mut line_len = buffer.line_len(MultiBufferRow(row));
13165 if row == end.row {
13166 line_len = end.column;
13167 }
13168 if line_len == 0 {
13169 trimmed_selections
13170 .push(Point::new(row, 0)..Point::new(row, line_len));
13171 continue;
13172 }
13173 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13174 if row_indent_size.len >= first_indent.len {
13175 trimmed_selections.push(
13176 Point::new(row, first_indent.len)..Point::new(row, line_len),
13177 );
13178 } else {
13179 trimmed_selections.clear();
13180 trimmed_selections.push(start..end);
13181 break;
13182 }
13183 }
13184 }
13185 } else {
13186 trimmed_selections.push(start..end);
13187 }
13188
13189 for trimmed_range in trimmed_selections {
13190 if is_first {
13191 is_first = false;
13192 } else if !prev_selection_was_entire_line {
13193 text += "\n";
13194 }
13195 prev_selection_was_entire_line = is_entire_line;
13196 let mut len = 0;
13197 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13198 text.push_str(chunk);
13199 len += chunk.len();
13200 }
13201 if add_trailing_newline {
13202 text.push('\n');
13203 len += 1;
13204 }
13205 clipboard_selections.push(ClipboardSelection::for_buffer(
13206 len,
13207 is_entire_line,
13208 trimmed_range,
13209 &buffer,
13210 self.project.as_ref(),
13211 cx,
13212 ));
13213 }
13214 }
13215 }
13216
13217 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13218 text,
13219 clipboard_selections,
13220 ));
13221 }
13222
13223 pub fn do_paste(
13224 &mut self,
13225 text: &String,
13226 clipboard_selections: Option<Vec<ClipboardSelection>>,
13227 handle_entire_lines: bool,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 if self.read_only(cx) {
13232 return;
13233 }
13234
13235 let clipboard_text = Cow::Borrowed(text.as_str());
13236
13237 self.transact(window, cx, |this, window, cx| {
13238 let had_active_edit_prediction = this.has_active_edit_prediction();
13239 let display_map = this.display_snapshot(cx);
13240 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13241 let cursor_offset = this
13242 .selections
13243 .last::<MultiBufferOffset>(&display_map)
13244 .head();
13245
13246 if let Some(mut clipboard_selections) = clipboard_selections {
13247 let all_selections_were_entire_line =
13248 clipboard_selections.iter().all(|s| s.is_entire_line);
13249 let first_selection_indent_column =
13250 clipboard_selections.first().map(|s| s.first_line_indent);
13251 if clipboard_selections.len() != old_selections.len() {
13252 clipboard_selections.drain(..);
13253 }
13254 let mut auto_indent_on_paste = true;
13255
13256 this.buffer.update(cx, |buffer, cx| {
13257 let snapshot = buffer.read(cx);
13258 auto_indent_on_paste = snapshot
13259 .language_settings_at(cursor_offset, cx)
13260 .auto_indent_on_paste;
13261
13262 let mut start_offset = 0;
13263 let mut edits = Vec::new();
13264 let mut original_indent_columns = Vec::new();
13265 for (ix, selection) in old_selections.iter().enumerate() {
13266 let to_insert;
13267 let entire_line;
13268 let original_indent_column;
13269 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13270 let end_offset = start_offset + clipboard_selection.len;
13271 to_insert = &clipboard_text[start_offset..end_offset];
13272 entire_line = clipboard_selection.is_entire_line;
13273 start_offset = if entire_line {
13274 end_offset
13275 } else {
13276 end_offset + 1
13277 };
13278 original_indent_column = Some(clipboard_selection.first_line_indent);
13279 } else {
13280 to_insert = &*clipboard_text;
13281 entire_line = all_selections_were_entire_line;
13282 original_indent_column = first_selection_indent_column
13283 }
13284
13285 let (range, to_insert) =
13286 if selection.is_empty() && handle_entire_lines && entire_line {
13287 // If the corresponding selection was empty when this slice of the
13288 // clipboard text was written, then the entire line containing the
13289 // selection was copied. If this selection is also currently empty,
13290 // then paste the line before the current line of the buffer.
13291 let column = selection.start.to_point(&snapshot).column as usize;
13292 let line_start = selection.start - column;
13293 (line_start..line_start, Cow::Borrowed(to_insert))
13294 } else {
13295 let language = snapshot.language_at(selection.head());
13296 let range = selection.range();
13297 if let Some(language) = language
13298 && language.name() == "Markdown".into()
13299 {
13300 edit_for_markdown_paste(
13301 &snapshot,
13302 range,
13303 to_insert,
13304 url::Url::parse(to_insert).ok(),
13305 )
13306 } else {
13307 (range, Cow::Borrowed(to_insert))
13308 }
13309 };
13310
13311 edits.push((range, to_insert));
13312 original_indent_columns.push(original_indent_column);
13313 }
13314 drop(snapshot);
13315
13316 buffer.edit(
13317 edits,
13318 if auto_indent_on_paste {
13319 Some(AutoindentMode::Block {
13320 original_indent_columns,
13321 })
13322 } else {
13323 None
13324 },
13325 cx,
13326 );
13327 });
13328
13329 let selections = this
13330 .selections
13331 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13332 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13333 } else {
13334 let url = url::Url::parse(&clipboard_text).ok();
13335
13336 let auto_indent_mode = if !clipboard_text.is_empty() {
13337 Some(AutoindentMode::Block {
13338 original_indent_columns: Vec::new(),
13339 })
13340 } else {
13341 None
13342 };
13343
13344 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13345 let snapshot = buffer.snapshot(cx);
13346
13347 let anchors = old_selections
13348 .iter()
13349 .map(|s| {
13350 let anchor = snapshot.anchor_after(s.head());
13351 s.map(|_| anchor)
13352 })
13353 .collect::<Vec<_>>();
13354
13355 let mut edits = Vec::new();
13356
13357 for selection in old_selections.iter() {
13358 let language = snapshot.language_at(selection.head());
13359 let range = selection.range();
13360
13361 let (edit_range, edit_text) = if let Some(language) = language
13362 && language.name() == "Markdown".into()
13363 {
13364 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13365 } else {
13366 (range, clipboard_text.clone())
13367 };
13368
13369 edits.push((edit_range, edit_text));
13370 }
13371
13372 drop(snapshot);
13373 buffer.edit(edits, auto_indent_mode, cx);
13374
13375 anchors
13376 });
13377
13378 this.change_selections(Default::default(), window, cx, |s| {
13379 s.select_anchors(selection_anchors);
13380 });
13381 }
13382
13383 // 🤔 | .. | show_in_menu |
13384 // | .. | true true
13385 // | had_edit_prediction | false true
13386
13387 let trigger_in_words =
13388 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13389
13390 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13391 });
13392 }
13393
13394 pub fn diff_clipboard_with_selection(
13395 &mut self,
13396 _: &DiffClipboardWithSelection,
13397 window: &mut Window,
13398 cx: &mut Context<Self>,
13399 ) {
13400 let selections = self
13401 .selections
13402 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13403
13404 if selections.is_empty() {
13405 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13406 return;
13407 };
13408
13409 let clipboard_text = match cx.read_from_clipboard() {
13410 Some(item) => match item.entries().first() {
13411 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13412 _ => None,
13413 },
13414 None => None,
13415 };
13416
13417 let Some(clipboard_text) = clipboard_text else {
13418 log::warn!("Clipboard doesn't contain text.");
13419 return;
13420 };
13421
13422 window.dispatch_action(
13423 Box::new(DiffClipboardWithSelectionData {
13424 clipboard_text,
13425 editor: cx.entity(),
13426 }),
13427 cx,
13428 );
13429 }
13430
13431 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13433 if let Some(item) = cx.read_from_clipboard() {
13434 let entries = item.entries();
13435
13436 match entries.first() {
13437 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13438 // of all the pasted entries.
13439 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13440 .do_paste(
13441 clipboard_string.text(),
13442 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13443 true,
13444 window,
13445 cx,
13446 ),
13447 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13448 }
13449 }
13450 }
13451
13452 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13453 if self.read_only(cx) {
13454 return;
13455 }
13456
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13458
13459 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13460 if let Some((selections, _)) =
13461 self.selection_history.transaction(transaction_id).cloned()
13462 {
13463 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13464 s.select_anchors(selections.to_vec());
13465 });
13466 } else {
13467 log::error!(
13468 "No entry in selection_history found for undo. \
13469 This may correspond to a bug where undo does not update the selection. \
13470 If this is occurring, please add details to \
13471 https://github.com/zed-industries/zed/issues/22692"
13472 );
13473 }
13474 self.request_autoscroll(Autoscroll::fit(), cx);
13475 self.unmark_text(window, cx);
13476 self.refresh_edit_prediction(true, false, window, cx);
13477 cx.emit(EditorEvent::Edited { transaction_id });
13478 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13479 }
13480 }
13481
13482 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13483 if self.read_only(cx) {
13484 return;
13485 }
13486
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13488
13489 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13490 if let Some((_, Some(selections))) =
13491 self.selection_history.transaction(transaction_id).cloned()
13492 {
13493 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13494 s.select_anchors(selections.to_vec());
13495 });
13496 } else {
13497 log::error!(
13498 "No entry in selection_history found for redo. \
13499 This may correspond to a bug where undo does not update the selection. \
13500 If this is occurring, please add details to \
13501 https://github.com/zed-industries/zed/issues/22692"
13502 );
13503 }
13504 self.request_autoscroll(Autoscroll::fit(), cx);
13505 self.unmark_text(window, cx);
13506 self.refresh_edit_prediction(true, false, window, cx);
13507 cx.emit(EditorEvent::Edited { transaction_id });
13508 }
13509 }
13510
13511 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13512 self.buffer
13513 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13514 }
13515
13516 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13517 self.buffer
13518 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13519 }
13520
13521 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13523 self.change_selections(Default::default(), window, cx, |s| {
13524 s.move_with(|map, selection| {
13525 let cursor = if selection.is_empty() {
13526 movement::left(map, selection.start)
13527 } else {
13528 selection.start
13529 };
13530 selection.collapse_to(cursor, SelectionGoal::None);
13531 });
13532 })
13533 }
13534
13535 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13537 self.change_selections(Default::default(), window, cx, |s| {
13538 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13539 })
13540 }
13541
13542 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13544 self.change_selections(Default::default(), window, cx, |s| {
13545 s.move_with(|map, selection| {
13546 let cursor = if selection.is_empty() {
13547 movement::right(map, selection.end)
13548 } else {
13549 selection.end
13550 };
13551 selection.collapse_to(cursor, SelectionGoal::None)
13552 });
13553 })
13554 }
13555
13556 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13558 self.change_selections(Default::default(), window, cx, |s| {
13559 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13560 });
13561 }
13562
13563 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13564 if self.take_rename(true, window, cx).is_some() {
13565 return;
13566 }
13567
13568 if self.mode.is_single_line() {
13569 cx.propagate();
13570 return;
13571 }
13572
13573 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13574
13575 let text_layout_details = &self.text_layout_details(window);
13576 let selection_count = self.selections.count();
13577 let first_selection = self.selections.first_anchor();
13578
13579 self.change_selections(Default::default(), window, cx, |s| {
13580 s.move_with(|map, selection| {
13581 if !selection.is_empty() {
13582 selection.goal = SelectionGoal::None;
13583 }
13584 let (cursor, goal) = movement::up(
13585 map,
13586 selection.start,
13587 selection.goal,
13588 false,
13589 text_layout_details,
13590 );
13591 selection.collapse_to(cursor, goal);
13592 });
13593 });
13594
13595 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13596 {
13597 cx.propagate();
13598 }
13599 }
13600
13601 pub fn move_up_by_lines(
13602 &mut self,
13603 action: &MoveUpByLines,
13604 window: &mut Window,
13605 cx: &mut Context<Self>,
13606 ) {
13607 if self.take_rename(true, window, cx).is_some() {
13608 return;
13609 }
13610
13611 if self.mode.is_single_line() {
13612 cx.propagate();
13613 return;
13614 }
13615
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13617
13618 let text_layout_details = &self.text_layout_details(window);
13619
13620 self.change_selections(Default::default(), window, cx, |s| {
13621 s.move_with(|map, selection| {
13622 if !selection.is_empty() {
13623 selection.goal = SelectionGoal::None;
13624 }
13625 let (cursor, goal) = movement::up_by_rows(
13626 map,
13627 selection.start,
13628 action.lines,
13629 selection.goal,
13630 false,
13631 text_layout_details,
13632 );
13633 selection.collapse_to(cursor, goal);
13634 });
13635 })
13636 }
13637
13638 pub fn move_down_by_lines(
13639 &mut self,
13640 action: &MoveDownByLines,
13641 window: &mut Window,
13642 cx: &mut Context<Self>,
13643 ) {
13644 if self.take_rename(true, window, cx).is_some() {
13645 return;
13646 }
13647
13648 if self.mode.is_single_line() {
13649 cx.propagate();
13650 return;
13651 }
13652
13653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13654
13655 let text_layout_details = &self.text_layout_details(window);
13656
13657 self.change_selections(Default::default(), window, cx, |s| {
13658 s.move_with(|map, selection| {
13659 if !selection.is_empty() {
13660 selection.goal = SelectionGoal::None;
13661 }
13662 let (cursor, goal) = movement::down_by_rows(
13663 map,
13664 selection.start,
13665 action.lines,
13666 selection.goal,
13667 false,
13668 text_layout_details,
13669 );
13670 selection.collapse_to(cursor, goal);
13671 });
13672 })
13673 }
13674
13675 pub fn select_down_by_lines(
13676 &mut self,
13677 action: &SelectDownByLines,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13682 let text_layout_details = &self.text_layout_details(window);
13683 self.change_selections(Default::default(), window, cx, |s| {
13684 s.move_heads_with(|map, head, goal| {
13685 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13686 })
13687 })
13688 }
13689
13690 pub fn select_up_by_lines(
13691 &mut self,
13692 action: &SelectUpByLines,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13697 let text_layout_details = &self.text_layout_details(window);
13698 self.change_selections(Default::default(), window, cx, |s| {
13699 s.move_heads_with(|map, head, goal| {
13700 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13701 })
13702 })
13703 }
13704
13705 pub fn select_page_up(
13706 &mut self,
13707 _: &SelectPageUp,
13708 window: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 let Some(row_count) = self.visible_row_count() else {
13712 return;
13713 };
13714
13715 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13716
13717 let text_layout_details = &self.text_layout_details(window);
13718
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_heads_with(|map, head, goal| {
13721 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13722 })
13723 })
13724 }
13725
13726 pub fn move_page_up(
13727 &mut self,
13728 action: &MovePageUp,
13729 window: &mut Window,
13730 cx: &mut Context<Self>,
13731 ) {
13732 if self.take_rename(true, window, cx).is_some() {
13733 return;
13734 }
13735
13736 if self
13737 .context_menu
13738 .borrow_mut()
13739 .as_mut()
13740 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13741 .unwrap_or(false)
13742 {
13743 return;
13744 }
13745
13746 if matches!(self.mode, EditorMode::SingleLine) {
13747 cx.propagate();
13748 return;
13749 }
13750
13751 let Some(row_count) = self.visible_row_count() else {
13752 return;
13753 };
13754
13755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13756
13757 let effects = if action.center_cursor {
13758 SelectionEffects::scroll(Autoscroll::center())
13759 } else {
13760 SelectionEffects::default()
13761 };
13762
13763 let text_layout_details = &self.text_layout_details(window);
13764
13765 self.change_selections(effects, window, cx, |s| {
13766 s.move_with(|map, selection| {
13767 if !selection.is_empty() {
13768 selection.goal = SelectionGoal::None;
13769 }
13770 let (cursor, goal) = movement::up_by_rows(
13771 map,
13772 selection.end,
13773 row_count,
13774 selection.goal,
13775 false,
13776 text_layout_details,
13777 );
13778 selection.collapse_to(cursor, goal);
13779 });
13780 });
13781 }
13782
13783 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13784 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13785 let text_layout_details = &self.text_layout_details(window);
13786 self.change_selections(Default::default(), window, cx, |s| {
13787 s.move_heads_with(|map, head, goal| {
13788 movement::up(map, head, goal, false, text_layout_details)
13789 })
13790 })
13791 }
13792
13793 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13794 self.take_rename(true, window, cx);
13795
13796 if self.mode.is_single_line() {
13797 cx.propagate();
13798 return;
13799 }
13800
13801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13802
13803 let text_layout_details = &self.text_layout_details(window);
13804 let selection_count = self.selections.count();
13805 let first_selection = self.selections.first_anchor();
13806
13807 self.change_selections(Default::default(), window, cx, |s| {
13808 s.move_with(|map, selection| {
13809 if !selection.is_empty() {
13810 selection.goal = SelectionGoal::None;
13811 }
13812 let (cursor, goal) = movement::down(
13813 map,
13814 selection.end,
13815 selection.goal,
13816 false,
13817 text_layout_details,
13818 );
13819 selection.collapse_to(cursor, goal);
13820 });
13821 });
13822
13823 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13824 {
13825 cx.propagate();
13826 }
13827 }
13828
13829 pub fn select_page_down(
13830 &mut self,
13831 _: &SelectPageDown,
13832 window: &mut Window,
13833 cx: &mut Context<Self>,
13834 ) {
13835 let Some(row_count) = self.visible_row_count() else {
13836 return;
13837 };
13838
13839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13840
13841 let text_layout_details = &self.text_layout_details(window);
13842
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.move_heads_with(|map, head, goal| {
13845 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13846 })
13847 })
13848 }
13849
13850 pub fn move_page_down(
13851 &mut self,
13852 action: &MovePageDown,
13853 window: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) {
13856 if self.take_rename(true, window, cx).is_some() {
13857 return;
13858 }
13859
13860 if self
13861 .context_menu
13862 .borrow_mut()
13863 .as_mut()
13864 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13865 .unwrap_or(false)
13866 {
13867 return;
13868 }
13869
13870 if matches!(self.mode, EditorMode::SingleLine) {
13871 cx.propagate();
13872 return;
13873 }
13874
13875 let Some(row_count) = self.visible_row_count() else {
13876 return;
13877 };
13878
13879 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13880
13881 let effects = if action.center_cursor {
13882 SelectionEffects::scroll(Autoscroll::center())
13883 } else {
13884 SelectionEffects::default()
13885 };
13886
13887 let text_layout_details = &self.text_layout_details(window);
13888 self.change_selections(effects, window, cx, |s| {
13889 s.move_with(|map, selection| {
13890 if !selection.is_empty() {
13891 selection.goal = SelectionGoal::None;
13892 }
13893 let (cursor, goal) = movement::down_by_rows(
13894 map,
13895 selection.end,
13896 row_count,
13897 selection.goal,
13898 false,
13899 text_layout_details,
13900 );
13901 selection.collapse_to(cursor, goal);
13902 });
13903 });
13904 }
13905
13906 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13908 let text_layout_details = &self.text_layout_details(window);
13909 self.change_selections(Default::default(), window, cx, |s| {
13910 s.move_heads_with(|map, head, goal| {
13911 movement::down(map, head, goal, false, text_layout_details)
13912 })
13913 });
13914 }
13915
13916 pub fn context_menu_first(
13917 &mut self,
13918 _: &ContextMenuFirst,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13923 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13924 }
13925 }
13926
13927 pub fn context_menu_prev(
13928 &mut self,
13929 _: &ContextMenuPrevious,
13930 window: &mut Window,
13931 cx: &mut Context<Self>,
13932 ) {
13933 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13934 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13935 }
13936 }
13937
13938 pub fn context_menu_next(
13939 &mut self,
13940 _: &ContextMenuNext,
13941 window: &mut Window,
13942 cx: &mut Context<Self>,
13943 ) {
13944 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13945 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13946 }
13947 }
13948
13949 pub fn context_menu_last(
13950 &mut self,
13951 _: &ContextMenuLast,
13952 window: &mut Window,
13953 cx: &mut Context<Self>,
13954 ) {
13955 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13956 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13957 }
13958 }
13959
13960 pub fn signature_help_prev(
13961 &mut self,
13962 _: &SignatureHelpPrevious,
13963 _: &mut Window,
13964 cx: &mut Context<Self>,
13965 ) {
13966 if let Some(popover) = self.signature_help_state.popover_mut() {
13967 if popover.current_signature == 0 {
13968 popover.current_signature = popover.signatures.len() - 1;
13969 } else {
13970 popover.current_signature -= 1;
13971 }
13972 cx.notify();
13973 }
13974 }
13975
13976 pub fn signature_help_next(
13977 &mut self,
13978 _: &SignatureHelpNext,
13979 _: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 if let Some(popover) = self.signature_help_state.popover_mut() {
13983 if popover.current_signature + 1 == popover.signatures.len() {
13984 popover.current_signature = 0;
13985 } else {
13986 popover.current_signature += 1;
13987 }
13988 cx.notify();
13989 }
13990 }
13991
13992 pub fn move_to_previous_word_start(
13993 &mut self,
13994 _: &MoveToPreviousWordStart,
13995 window: &mut Window,
13996 cx: &mut Context<Self>,
13997 ) {
13998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13999 self.change_selections(Default::default(), window, cx, |s| {
14000 s.move_cursors_with(|map, head, _| {
14001 (
14002 movement::previous_word_start(map, head),
14003 SelectionGoal::None,
14004 )
14005 });
14006 })
14007 }
14008
14009 pub fn move_to_previous_subword_start(
14010 &mut self,
14011 _: &MoveToPreviousSubwordStart,
14012 window: &mut Window,
14013 cx: &mut Context<Self>,
14014 ) {
14015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14016 self.change_selections(Default::default(), window, cx, |s| {
14017 s.move_cursors_with(|map, head, _| {
14018 (
14019 movement::previous_subword_start(map, head),
14020 SelectionGoal::None,
14021 )
14022 });
14023 })
14024 }
14025
14026 pub fn select_to_previous_word_start(
14027 &mut self,
14028 _: &SelectToPreviousWordStart,
14029 window: &mut Window,
14030 cx: &mut Context<Self>,
14031 ) {
14032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14033 self.change_selections(Default::default(), window, cx, |s| {
14034 s.move_heads_with(|map, head, _| {
14035 (
14036 movement::previous_word_start(map, head),
14037 SelectionGoal::None,
14038 )
14039 });
14040 })
14041 }
14042
14043 pub fn select_to_previous_subword_start(
14044 &mut self,
14045 _: &SelectToPreviousSubwordStart,
14046 window: &mut Window,
14047 cx: &mut Context<Self>,
14048 ) {
14049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14050 self.change_selections(Default::default(), window, cx, |s| {
14051 s.move_heads_with(|map, head, _| {
14052 (
14053 movement::previous_subword_start(map, head),
14054 SelectionGoal::None,
14055 )
14056 });
14057 })
14058 }
14059
14060 pub fn delete_to_previous_word_start(
14061 &mut self,
14062 action: &DeleteToPreviousWordStart,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14067 self.transact(window, cx, |this, window, cx| {
14068 this.select_autoclose_pair(window, cx);
14069 this.change_selections(Default::default(), window, cx, |s| {
14070 s.move_with(|map, selection| {
14071 if selection.is_empty() {
14072 let mut cursor = if action.ignore_newlines {
14073 movement::previous_word_start(map, selection.head())
14074 } else {
14075 movement::previous_word_start_or_newline(map, selection.head())
14076 };
14077 cursor = movement::adjust_greedy_deletion(
14078 map,
14079 selection.head(),
14080 cursor,
14081 action.ignore_brackets,
14082 );
14083 selection.set_head(cursor, SelectionGoal::None);
14084 }
14085 });
14086 });
14087 this.insert("", window, cx);
14088 });
14089 }
14090
14091 pub fn delete_to_previous_subword_start(
14092 &mut self,
14093 _: &DeleteToPreviousSubwordStart,
14094 window: &mut Window,
14095 cx: &mut Context<Self>,
14096 ) {
14097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14098 self.transact(window, cx, |this, window, cx| {
14099 this.select_autoclose_pair(window, cx);
14100 this.change_selections(Default::default(), window, cx, |s| {
14101 s.move_with(|map, selection| {
14102 if selection.is_empty() {
14103 let mut cursor = movement::previous_subword_start(map, selection.head());
14104 cursor =
14105 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14106 selection.set_head(cursor, SelectionGoal::None);
14107 }
14108 });
14109 });
14110 this.insert("", window, cx);
14111 });
14112 }
14113
14114 pub fn move_to_next_word_end(
14115 &mut self,
14116 _: &MoveToNextWordEnd,
14117 window: &mut Window,
14118 cx: &mut Context<Self>,
14119 ) {
14120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14121 self.change_selections(Default::default(), window, cx, |s| {
14122 s.move_cursors_with(|map, head, _| {
14123 (movement::next_word_end(map, head), SelectionGoal::None)
14124 });
14125 })
14126 }
14127
14128 pub fn move_to_next_subword_end(
14129 &mut self,
14130 _: &MoveToNextSubwordEnd,
14131 window: &mut Window,
14132 cx: &mut Context<Self>,
14133 ) {
14134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14135 self.change_selections(Default::default(), window, cx, |s| {
14136 s.move_cursors_with(|map, head, _| {
14137 (movement::next_subword_end(map, head), SelectionGoal::None)
14138 });
14139 })
14140 }
14141
14142 pub fn select_to_next_word_end(
14143 &mut self,
14144 _: &SelectToNextWordEnd,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) {
14148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14149 self.change_selections(Default::default(), window, cx, |s| {
14150 s.move_heads_with(|map, head, _| {
14151 (movement::next_word_end(map, head), SelectionGoal::None)
14152 });
14153 })
14154 }
14155
14156 pub fn select_to_next_subword_end(
14157 &mut self,
14158 _: &SelectToNextSubwordEnd,
14159 window: &mut Window,
14160 cx: &mut Context<Self>,
14161 ) {
14162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14163 self.change_selections(Default::default(), window, cx, |s| {
14164 s.move_heads_with(|map, head, _| {
14165 (movement::next_subword_end(map, head), SelectionGoal::None)
14166 });
14167 })
14168 }
14169
14170 pub fn delete_to_next_word_end(
14171 &mut self,
14172 action: &DeleteToNextWordEnd,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) {
14176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14177 self.transact(window, cx, |this, window, cx| {
14178 this.change_selections(Default::default(), window, cx, |s| {
14179 s.move_with(|map, selection| {
14180 if selection.is_empty() {
14181 let mut cursor = if action.ignore_newlines {
14182 movement::next_word_end(map, selection.head())
14183 } else {
14184 movement::next_word_end_or_newline(map, selection.head())
14185 };
14186 cursor = movement::adjust_greedy_deletion(
14187 map,
14188 selection.head(),
14189 cursor,
14190 action.ignore_brackets,
14191 );
14192 selection.set_head(cursor, SelectionGoal::None);
14193 }
14194 });
14195 });
14196 this.insert("", window, cx);
14197 });
14198 }
14199
14200 pub fn delete_to_next_subword_end(
14201 &mut self,
14202 _: &DeleteToNextSubwordEnd,
14203 window: &mut Window,
14204 cx: &mut Context<Self>,
14205 ) {
14206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14207 self.transact(window, cx, |this, window, cx| {
14208 this.change_selections(Default::default(), window, cx, |s| {
14209 s.move_with(|map, selection| {
14210 if selection.is_empty() {
14211 let mut cursor = movement::next_subword_end(map, selection.head());
14212 cursor =
14213 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14214 selection.set_head(cursor, SelectionGoal::None);
14215 }
14216 });
14217 });
14218 this.insert("", window, cx);
14219 });
14220 }
14221
14222 pub fn move_to_beginning_of_line(
14223 &mut self,
14224 action: &MoveToBeginningOfLine,
14225 window: &mut Window,
14226 cx: &mut Context<Self>,
14227 ) {
14228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14229 self.change_selections(Default::default(), window, cx, |s| {
14230 s.move_cursors_with(|map, head, _| {
14231 (
14232 movement::indented_line_beginning(
14233 map,
14234 head,
14235 action.stop_at_soft_wraps,
14236 action.stop_at_indent,
14237 ),
14238 SelectionGoal::None,
14239 )
14240 });
14241 })
14242 }
14243
14244 pub fn select_to_beginning_of_line(
14245 &mut self,
14246 action: &SelectToBeginningOfLine,
14247 window: &mut Window,
14248 cx: &mut Context<Self>,
14249 ) {
14250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14251 self.change_selections(Default::default(), window, cx, |s| {
14252 s.move_heads_with(|map, head, _| {
14253 (
14254 movement::indented_line_beginning(
14255 map,
14256 head,
14257 action.stop_at_soft_wraps,
14258 action.stop_at_indent,
14259 ),
14260 SelectionGoal::None,
14261 )
14262 });
14263 });
14264 }
14265
14266 pub fn delete_to_beginning_of_line(
14267 &mut self,
14268 action: &DeleteToBeginningOfLine,
14269 window: &mut Window,
14270 cx: &mut Context<Self>,
14271 ) {
14272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14273 self.transact(window, cx, |this, window, cx| {
14274 this.change_selections(Default::default(), window, cx, |s| {
14275 s.move_with(|_, selection| {
14276 selection.reversed = true;
14277 });
14278 });
14279
14280 this.select_to_beginning_of_line(
14281 &SelectToBeginningOfLine {
14282 stop_at_soft_wraps: false,
14283 stop_at_indent: action.stop_at_indent,
14284 },
14285 window,
14286 cx,
14287 );
14288 this.backspace(&Backspace, window, cx);
14289 });
14290 }
14291
14292 pub fn move_to_end_of_line(
14293 &mut self,
14294 action: &MoveToEndOfLine,
14295 window: &mut Window,
14296 cx: &mut Context<Self>,
14297 ) {
14298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14299 self.change_selections(Default::default(), window, cx, |s| {
14300 s.move_cursors_with(|map, head, _| {
14301 (
14302 movement::line_end(map, head, action.stop_at_soft_wraps),
14303 SelectionGoal::None,
14304 )
14305 });
14306 })
14307 }
14308
14309 pub fn select_to_end_of_line(
14310 &mut self,
14311 action: &SelectToEndOfLine,
14312 window: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) {
14315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14316 self.change_selections(Default::default(), window, cx, |s| {
14317 s.move_heads_with(|map, head, _| {
14318 (
14319 movement::line_end(map, head, action.stop_at_soft_wraps),
14320 SelectionGoal::None,
14321 )
14322 });
14323 })
14324 }
14325
14326 pub fn delete_to_end_of_line(
14327 &mut self,
14328 _: &DeleteToEndOfLine,
14329 window: &mut Window,
14330 cx: &mut Context<Self>,
14331 ) {
14332 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14333 self.transact(window, cx, |this, window, cx| {
14334 this.select_to_end_of_line(
14335 &SelectToEndOfLine {
14336 stop_at_soft_wraps: false,
14337 },
14338 window,
14339 cx,
14340 );
14341 this.delete(&Delete, window, cx);
14342 });
14343 }
14344
14345 pub fn cut_to_end_of_line(
14346 &mut self,
14347 action: &CutToEndOfLine,
14348 window: &mut Window,
14349 cx: &mut Context<Self>,
14350 ) {
14351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14352 self.transact(window, cx, |this, window, cx| {
14353 this.select_to_end_of_line(
14354 &SelectToEndOfLine {
14355 stop_at_soft_wraps: false,
14356 },
14357 window,
14358 cx,
14359 );
14360 if !action.stop_at_newlines {
14361 this.change_selections(Default::default(), window, cx, |s| {
14362 s.move_with(|_, sel| {
14363 if sel.is_empty() {
14364 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14365 }
14366 });
14367 });
14368 }
14369 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14370 let item = this.cut_common(false, window, cx);
14371 cx.write_to_clipboard(item);
14372 });
14373 }
14374
14375 pub fn move_to_start_of_paragraph(
14376 &mut self,
14377 _: &MoveToStartOfParagraph,
14378 window: &mut Window,
14379 cx: &mut Context<Self>,
14380 ) {
14381 if matches!(self.mode, EditorMode::SingleLine) {
14382 cx.propagate();
14383 return;
14384 }
14385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14386 self.change_selections(Default::default(), window, cx, |s| {
14387 s.move_with(|map, selection| {
14388 selection.collapse_to(
14389 movement::start_of_paragraph(map, selection.head(), 1),
14390 SelectionGoal::None,
14391 )
14392 });
14393 })
14394 }
14395
14396 pub fn move_to_end_of_paragraph(
14397 &mut self,
14398 _: &MoveToEndOfParagraph,
14399 window: &mut Window,
14400 cx: &mut Context<Self>,
14401 ) {
14402 if matches!(self.mode, EditorMode::SingleLine) {
14403 cx.propagate();
14404 return;
14405 }
14406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14407 self.change_selections(Default::default(), window, cx, |s| {
14408 s.move_with(|map, selection| {
14409 selection.collapse_to(
14410 movement::end_of_paragraph(map, selection.head(), 1),
14411 SelectionGoal::None,
14412 )
14413 });
14414 })
14415 }
14416
14417 pub fn select_to_start_of_paragraph(
14418 &mut self,
14419 _: &SelectToStartOfParagraph,
14420 window: &mut Window,
14421 cx: &mut Context<Self>,
14422 ) {
14423 if matches!(self.mode, EditorMode::SingleLine) {
14424 cx.propagate();
14425 return;
14426 }
14427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14428 self.change_selections(Default::default(), window, cx, |s| {
14429 s.move_heads_with(|map, head, _| {
14430 (
14431 movement::start_of_paragraph(map, head, 1),
14432 SelectionGoal::None,
14433 )
14434 });
14435 })
14436 }
14437
14438 pub fn select_to_end_of_paragraph(
14439 &mut self,
14440 _: &SelectToEndOfParagraph,
14441 window: &mut Window,
14442 cx: &mut Context<Self>,
14443 ) {
14444 if matches!(self.mode, EditorMode::SingleLine) {
14445 cx.propagate();
14446 return;
14447 }
14448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14449 self.change_selections(Default::default(), window, cx, |s| {
14450 s.move_heads_with(|map, head, _| {
14451 (
14452 movement::end_of_paragraph(map, head, 1),
14453 SelectionGoal::None,
14454 )
14455 });
14456 })
14457 }
14458
14459 pub fn move_to_start_of_excerpt(
14460 &mut self,
14461 _: &MoveToStartOfExcerpt,
14462 window: &mut Window,
14463 cx: &mut Context<Self>,
14464 ) {
14465 if matches!(self.mode, EditorMode::SingleLine) {
14466 cx.propagate();
14467 return;
14468 }
14469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14470 self.change_selections(Default::default(), window, cx, |s| {
14471 s.move_with(|map, selection| {
14472 selection.collapse_to(
14473 movement::start_of_excerpt(
14474 map,
14475 selection.head(),
14476 workspace::searchable::Direction::Prev,
14477 ),
14478 SelectionGoal::None,
14479 )
14480 });
14481 })
14482 }
14483
14484 pub fn move_to_start_of_next_excerpt(
14485 &mut self,
14486 _: &MoveToStartOfNextExcerpt,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) {
14490 if matches!(self.mode, EditorMode::SingleLine) {
14491 cx.propagate();
14492 return;
14493 }
14494
14495 self.change_selections(Default::default(), window, cx, |s| {
14496 s.move_with(|map, selection| {
14497 selection.collapse_to(
14498 movement::start_of_excerpt(
14499 map,
14500 selection.head(),
14501 workspace::searchable::Direction::Next,
14502 ),
14503 SelectionGoal::None,
14504 )
14505 });
14506 })
14507 }
14508
14509 pub fn move_to_end_of_excerpt(
14510 &mut self,
14511 _: &MoveToEndOfExcerpt,
14512 window: &mut Window,
14513 cx: &mut Context<Self>,
14514 ) {
14515 if matches!(self.mode, EditorMode::SingleLine) {
14516 cx.propagate();
14517 return;
14518 }
14519 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14520 self.change_selections(Default::default(), window, cx, |s| {
14521 s.move_with(|map, selection| {
14522 selection.collapse_to(
14523 movement::end_of_excerpt(
14524 map,
14525 selection.head(),
14526 workspace::searchable::Direction::Next,
14527 ),
14528 SelectionGoal::None,
14529 )
14530 });
14531 })
14532 }
14533
14534 pub fn move_to_end_of_previous_excerpt(
14535 &mut self,
14536 _: &MoveToEndOfPreviousExcerpt,
14537 window: &mut Window,
14538 cx: &mut Context<Self>,
14539 ) {
14540 if matches!(self.mode, EditorMode::SingleLine) {
14541 cx.propagate();
14542 return;
14543 }
14544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14545 self.change_selections(Default::default(), window, cx, |s| {
14546 s.move_with(|map, selection| {
14547 selection.collapse_to(
14548 movement::end_of_excerpt(
14549 map,
14550 selection.head(),
14551 workspace::searchable::Direction::Prev,
14552 ),
14553 SelectionGoal::None,
14554 )
14555 });
14556 })
14557 }
14558
14559 pub fn select_to_start_of_excerpt(
14560 &mut self,
14561 _: &SelectToStartOfExcerpt,
14562 window: &mut Window,
14563 cx: &mut Context<Self>,
14564 ) {
14565 if matches!(self.mode, EditorMode::SingleLine) {
14566 cx.propagate();
14567 return;
14568 }
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570 self.change_selections(Default::default(), window, cx, |s| {
14571 s.move_heads_with(|map, head, _| {
14572 (
14573 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14574 SelectionGoal::None,
14575 )
14576 });
14577 })
14578 }
14579
14580 pub fn select_to_start_of_next_excerpt(
14581 &mut self,
14582 _: &SelectToStartOfNextExcerpt,
14583 window: &mut Window,
14584 cx: &mut Context<Self>,
14585 ) {
14586 if matches!(self.mode, EditorMode::SingleLine) {
14587 cx.propagate();
14588 return;
14589 }
14590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14591 self.change_selections(Default::default(), window, cx, |s| {
14592 s.move_heads_with(|map, head, _| {
14593 (
14594 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14595 SelectionGoal::None,
14596 )
14597 });
14598 })
14599 }
14600
14601 pub fn select_to_end_of_excerpt(
14602 &mut self,
14603 _: &SelectToEndOfExcerpt,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 ) {
14607 if matches!(self.mode, EditorMode::SingleLine) {
14608 cx.propagate();
14609 return;
14610 }
14611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14612 self.change_selections(Default::default(), window, cx, |s| {
14613 s.move_heads_with(|map, head, _| {
14614 (
14615 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14616 SelectionGoal::None,
14617 )
14618 });
14619 })
14620 }
14621
14622 pub fn select_to_end_of_previous_excerpt(
14623 &mut self,
14624 _: &SelectToEndOfPreviousExcerpt,
14625 window: &mut Window,
14626 cx: &mut Context<Self>,
14627 ) {
14628 if matches!(self.mode, EditorMode::SingleLine) {
14629 cx.propagate();
14630 return;
14631 }
14632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14633 self.change_selections(Default::default(), window, cx, |s| {
14634 s.move_heads_with(|map, head, _| {
14635 (
14636 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14637 SelectionGoal::None,
14638 )
14639 });
14640 })
14641 }
14642
14643 pub fn move_to_beginning(
14644 &mut self,
14645 _: &MoveToBeginning,
14646 window: &mut Window,
14647 cx: &mut Context<Self>,
14648 ) {
14649 if matches!(self.mode, EditorMode::SingleLine) {
14650 cx.propagate();
14651 return;
14652 }
14653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14654 self.change_selections(Default::default(), window, cx, |s| {
14655 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14656 });
14657 }
14658
14659 pub fn select_to_beginning(
14660 &mut self,
14661 _: &SelectToBeginning,
14662 window: &mut Window,
14663 cx: &mut Context<Self>,
14664 ) {
14665 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14666 selection.set_head(Point::zero(), SelectionGoal::None);
14667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14668 self.change_selections(Default::default(), window, cx, |s| {
14669 s.select(vec![selection]);
14670 });
14671 }
14672
14673 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14674 if matches!(self.mode, EditorMode::SingleLine) {
14675 cx.propagate();
14676 return;
14677 }
14678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14679 let cursor = self.buffer.read(cx).read(cx).len();
14680 self.change_selections(Default::default(), window, cx, |s| {
14681 s.select_ranges(vec![cursor..cursor])
14682 });
14683 }
14684
14685 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14686 self.nav_history = nav_history;
14687 }
14688
14689 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14690 self.nav_history.as_ref()
14691 }
14692
14693 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14694 self.push_to_nav_history(
14695 self.selections.newest_anchor().head(),
14696 None,
14697 false,
14698 true,
14699 cx,
14700 );
14701 }
14702
14703 fn push_to_nav_history(
14704 &mut self,
14705 cursor_anchor: Anchor,
14706 new_position: Option<Point>,
14707 is_deactivate: bool,
14708 always: bool,
14709 cx: &mut Context<Self>,
14710 ) {
14711 if let Some(nav_history) = self.nav_history.as_mut() {
14712 let buffer = self.buffer.read(cx).read(cx);
14713 let cursor_position = cursor_anchor.to_point(&buffer);
14714 let scroll_state = self.scroll_manager.anchor();
14715 let scroll_top_row = scroll_state.top_row(&buffer);
14716 drop(buffer);
14717
14718 if let Some(new_position) = new_position {
14719 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14720 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14721 return;
14722 }
14723 }
14724
14725 nav_history.push(
14726 Some(NavigationData {
14727 cursor_anchor,
14728 cursor_position,
14729 scroll_anchor: scroll_state,
14730 scroll_top_row,
14731 }),
14732 cx,
14733 );
14734 cx.emit(EditorEvent::PushedToNavHistory {
14735 anchor: cursor_anchor,
14736 is_deactivate,
14737 })
14738 }
14739 }
14740
14741 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14743 let buffer = self.buffer.read(cx).snapshot(cx);
14744 let mut selection = self
14745 .selections
14746 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14747 selection.set_head(buffer.len(), SelectionGoal::None);
14748 self.change_selections(Default::default(), window, cx, |s| {
14749 s.select(vec![selection]);
14750 });
14751 }
14752
14753 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14755 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14756 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14757 });
14758 }
14759
14760 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14762 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14763 let mut selections = self.selections.all::<Point>(&display_map);
14764 let max_point = display_map.buffer_snapshot().max_point();
14765 for selection in &mut selections {
14766 let rows = selection.spanned_rows(true, &display_map);
14767 selection.start = Point::new(rows.start.0, 0);
14768 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14769 selection.reversed = false;
14770 }
14771 self.change_selections(Default::default(), window, cx, |s| {
14772 s.select(selections);
14773 });
14774 }
14775
14776 pub fn split_selection_into_lines(
14777 &mut self,
14778 action: &SplitSelectionIntoLines,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 let selections = self
14783 .selections
14784 .all::<Point>(&self.display_snapshot(cx))
14785 .into_iter()
14786 .map(|selection| selection.start..selection.end)
14787 .collect::<Vec<_>>();
14788 self.unfold_ranges(&selections, true, true, cx);
14789
14790 let mut new_selection_ranges = Vec::new();
14791 {
14792 let buffer = self.buffer.read(cx).read(cx);
14793 for selection in selections {
14794 for row in selection.start.row..selection.end.row {
14795 let line_start = Point::new(row, 0);
14796 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14797
14798 if action.keep_selections {
14799 // Keep the selection range for each line
14800 let selection_start = if row == selection.start.row {
14801 selection.start
14802 } else {
14803 line_start
14804 };
14805 new_selection_ranges.push(selection_start..line_end);
14806 } else {
14807 // Collapse to cursor at end of line
14808 new_selection_ranges.push(line_end..line_end);
14809 }
14810 }
14811
14812 let is_multiline_selection = selection.start.row != selection.end.row;
14813 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14814 // so this action feels more ergonomic when paired with other selection operations
14815 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14816 if !should_skip_last {
14817 if action.keep_selections {
14818 if is_multiline_selection {
14819 let line_start = Point::new(selection.end.row, 0);
14820 new_selection_ranges.push(line_start..selection.end);
14821 } else {
14822 new_selection_ranges.push(selection.start..selection.end);
14823 }
14824 } else {
14825 new_selection_ranges.push(selection.end..selection.end);
14826 }
14827 }
14828 }
14829 }
14830 self.change_selections(Default::default(), window, cx, |s| {
14831 s.select_ranges(new_selection_ranges);
14832 });
14833 }
14834
14835 pub fn add_selection_above(
14836 &mut self,
14837 action: &AddSelectionAbove,
14838 window: &mut Window,
14839 cx: &mut Context<Self>,
14840 ) {
14841 self.add_selection(true, action.skip_soft_wrap, window, cx);
14842 }
14843
14844 pub fn add_selection_below(
14845 &mut self,
14846 action: &AddSelectionBelow,
14847 window: &mut Window,
14848 cx: &mut Context<Self>,
14849 ) {
14850 self.add_selection(false, action.skip_soft_wrap, window, cx);
14851 }
14852
14853 fn add_selection(
14854 &mut self,
14855 above: bool,
14856 skip_soft_wrap: bool,
14857 window: &mut Window,
14858 cx: &mut Context<Self>,
14859 ) {
14860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14861
14862 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14863 let all_selections = self.selections.all::<Point>(&display_map);
14864 let text_layout_details = self.text_layout_details(window);
14865
14866 let (mut columnar_selections, new_selections_to_columnarize) = {
14867 if let Some(state) = self.add_selections_state.as_ref() {
14868 let columnar_selection_ids: HashSet<_> = state
14869 .groups
14870 .iter()
14871 .flat_map(|group| group.stack.iter())
14872 .copied()
14873 .collect();
14874
14875 all_selections
14876 .into_iter()
14877 .partition(|s| columnar_selection_ids.contains(&s.id))
14878 } else {
14879 (Vec::new(), all_selections)
14880 }
14881 };
14882
14883 let mut state = self
14884 .add_selections_state
14885 .take()
14886 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14887
14888 for selection in new_selections_to_columnarize {
14889 let range = selection.display_range(&display_map).sorted();
14890 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14891 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14892 let positions = start_x.min(end_x)..start_x.max(end_x);
14893 let mut stack = Vec::new();
14894 for row in range.start.row().0..=range.end.row().0 {
14895 if let Some(selection) = self.selections.build_columnar_selection(
14896 &display_map,
14897 DisplayRow(row),
14898 &positions,
14899 selection.reversed,
14900 &text_layout_details,
14901 ) {
14902 stack.push(selection.id);
14903 columnar_selections.push(selection);
14904 }
14905 }
14906 if !stack.is_empty() {
14907 if above {
14908 stack.reverse();
14909 }
14910 state.groups.push(AddSelectionsGroup { above, stack });
14911 }
14912 }
14913
14914 let mut final_selections = Vec::new();
14915 let end_row = if above {
14916 DisplayRow(0)
14917 } else {
14918 display_map.max_point().row()
14919 };
14920
14921 let mut last_added_item_per_group = HashMap::default();
14922 for group in state.groups.iter_mut() {
14923 if let Some(last_id) = group.stack.last() {
14924 last_added_item_per_group.insert(*last_id, group);
14925 }
14926 }
14927
14928 for selection in columnar_selections {
14929 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14930 if above == group.above {
14931 let range = selection.display_range(&display_map).sorted();
14932 debug_assert_eq!(range.start.row(), range.end.row());
14933 let mut row = range.start.row();
14934 let positions =
14935 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14936 Pixels::from(start)..Pixels::from(end)
14937 } else {
14938 let start_x =
14939 display_map.x_for_display_point(range.start, &text_layout_details);
14940 let end_x =
14941 display_map.x_for_display_point(range.end, &text_layout_details);
14942 start_x.min(end_x)..start_x.max(end_x)
14943 };
14944
14945 let mut maybe_new_selection = None;
14946 let direction = if above { -1 } else { 1 };
14947
14948 while row != end_row {
14949 if skip_soft_wrap {
14950 row = display_map
14951 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14952 .row();
14953 } else if above {
14954 row.0 -= 1;
14955 } else {
14956 row.0 += 1;
14957 }
14958
14959 if let Some(new_selection) = self.selections.build_columnar_selection(
14960 &display_map,
14961 row,
14962 &positions,
14963 selection.reversed,
14964 &text_layout_details,
14965 ) {
14966 maybe_new_selection = Some(new_selection);
14967 break;
14968 }
14969 }
14970
14971 if let Some(new_selection) = maybe_new_selection {
14972 group.stack.push(new_selection.id);
14973 if above {
14974 final_selections.push(new_selection);
14975 final_selections.push(selection);
14976 } else {
14977 final_selections.push(selection);
14978 final_selections.push(new_selection);
14979 }
14980 } else {
14981 final_selections.push(selection);
14982 }
14983 } else {
14984 group.stack.pop();
14985 }
14986 } else {
14987 final_selections.push(selection);
14988 }
14989 }
14990
14991 self.change_selections(Default::default(), window, cx, |s| {
14992 s.select(final_selections);
14993 });
14994
14995 let final_selection_ids: HashSet<_> = self
14996 .selections
14997 .all::<Point>(&display_map)
14998 .iter()
14999 .map(|s| s.id)
15000 .collect();
15001 state.groups.retain_mut(|group| {
15002 // selections might get merged above so we remove invalid items from stacks
15003 group.stack.retain(|id| final_selection_ids.contains(id));
15004
15005 // single selection in stack can be treated as initial state
15006 group.stack.len() > 1
15007 });
15008
15009 if !state.groups.is_empty() {
15010 self.add_selections_state = Some(state);
15011 }
15012 }
15013
15014 pub fn insert_snippet_at_selections(
15015 &mut self,
15016 action: &InsertSnippet,
15017 window: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) {
15020 self.try_insert_snippet_at_selections(action, window, cx)
15021 .log_err();
15022 }
15023
15024 fn try_insert_snippet_at_selections(
15025 &mut self,
15026 action: &InsertSnippet,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) -> Result<()> {
15030 let insertion_ranges = self
15031 .selections
15032 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15033 .into_iter()
15034 .map(|selection| selection.range())
15035 .collect_vec();
15036
15037 let snippet = if let Some(snippet_body) = &action.snippet {
15038 if action.language.is_none() && action.name.is_none() {
15039 Snippet::parse(snippet_body)?
15040 } else {
15041 bail!("`snippet` is mutually exclusive with `language` and `name`")
15042 }
15043 } else if let Some(name) = &action.name {
15044 let project = self.project().context("no project")?;
15045 let snippet_store = project.read(cx).snippets().read(cx);
15046 let snippet = snippet_store
15047 .snippets_for(action.language.clone(), cx)
15048 .into_iter()
15049 .find(|snippet| snippet.name == *name)
15050 .context("snippet not found")?;
15051 Snippet::parse(&snippet.body)?
15052 } else {
15053 // todo(andrew): open modal to select snippet
15054 bail!("`name` or `snippet` is required")
15055 };
15056
15057 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15058 }
15059
15060 fn select_match_ranges(
15061 &mut self,
15062 range: Range<MultiBufferOffset>,
15063 reversed: bool,
15064 replace_newest: bool,
15065 auto_scroll: Option<Autoscroll>,
15066 window: &mut Window,
15067 cx: &mut Context<Editor>,
15068 ) {
15069 self.unfold_ranges(
15070 std::slice::from_ref(&range),
15071 false,
15072 auto_scroll.is_some(),
15073 cx,
15074 );
15075 let effects = if let Some(scroll) = auto_scroll {
15076 SelectionEffects::scroll(scroll)
15077 } else {
15078 SelectionEffects::no_scroll()
15079 };
15080 self.change_selections(effects, window, cx, |s| {
15081 if replace_newest {
15082 s.delete(s.newest_anchor().id);
15083 }
15084 if reversed {
15085 s.insert_range(range.end..range.start);
15086 } else {
15087 s.insert_range(range);
15088 }
15089 });
15090 }
15091
15092 pub fn select_next_match_internal(
15093 &mut self,
15094 display_map: &DisplaySnapshot,
15095 replace_newest: bool,
15096 autoscroll: Option<Autoscroll>,
15097 window: &mut Window,
15098 cx: &mut Context<Self>,
15099 ) -> Result<()> {
15100 let buffer = display_map.buffer_snapshot();
15101 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15102 if let Some(mut select_next_state) = self.select_next_state.take() {
15103 let query = &select_next_state.query;
15104 if !select_next_state.done {
15105 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15106 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15107 let mut next_selected_range = None;
15108
15109 let bytes_after_last_selection =
15110 buffer.bytes_in_range(last_selection.end..buffer.len());
15111 let bytes_before_first_selection =
15112 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15113 let query_matches = query
15114 .stream_find_iter(bytes_after_last_selection)
15115 .map(|result| (last_selection.end, result))
15116 .chain(
15117 query
15118 .stream_find_iter(bytes_before_first_selection)
15119 .map(|result| (MultiBufferOffset(0), result)),
15120 );
15121
15122 for (start_offset, query_match) in query_matches {
15123 let query_match = query_match.unwrap(); // can only fail due to I/O
15124 let offset_range =
15125 start_offset + query_match.start()..start_offset + query_match.end();
15126
15127 if !select_next_state.wordwise
15128 || (!buffer.is_inside_word(offset_range.start, None)
15129 && !buffer.is_inside_word(offset_range.end, None))
15130 {
15131 let idx = selections
15132 .partition_point(|selection| selection.end <= offset_range.start);
15133 let overlaps = selections
15134 .get(idx)
15135 .map_or(false, |selection| selection.start < offset_range.end);
15136
15137 if !overlaps {
15138 next_selected_range = Some(offset_range);
15139 break;
15140 }
15141 }
15142 }
15143
15144 if let Some(next_selected_range) = next_selected_range {
15145 self.select_match_ranges(
15146 next_selected_range,
15147 last_selection.reversed,
15148 replace_newest,
15149 autoscroll,
15150 window,
15151 cx,
15152 );
15153 } else {
15154 select_next_state.done = true;
15155 }
15156 }
15157
15158 self.select_next_state = Some(select_next_state);
15159 } else {
15160 let mut only_carets = true;
15161 let mut same_text_selected = true;
15162 let mut selected_text = None;
15163
15164 let mut selections_iter = selections.iter().peekable();
15165 while let Some(selection) = selections_iter.next() {
15166 if selection.start != selection.end {
15167 only_carets = false;
15168 }
15169
15170 if same_text_selected {
15171 if selected_text.is_none() {
15172 selected_text =
15173 Some(buffer.text_for_range(selection.range()).collect::<String>());
15174 }
15175
15176 if let Some(next_selection) = selections_iter.peek() {
15177 if next_selection.len() == selection.len() {
15178 let next_selected_text = buffer
15179 .text_for_range(next_selection.range())
15180 .collect::<String>();
15181 if Some(next_selected_text) != selected_text {
15182 same_text_selected = false;
15183 selected_text = None;
15184 }
15185 } else {
15186 same_text_selected = false;
15187 selected_text = None;
15188 }
15189 }
15190 }
15191 }
15192
15193 if only_carets {
15194 for selection in &mut selections {
15195 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15196 selection.start = word_range.start;
15197 selection.end = word_range.end;
15198 selection.goal = SelectionGoal::None;
15199 selection.reversed = false;
15200 self.select_match_ranges(
15201 selection.start..selection.end,
15202 selection.reversed,
15203 replace_newest,
15204 autoscroll,
15205 window,
15206 cx,
15207 );
15208 }
15209
15210 if selections.len() == 1 {
15211 let selection = selections
15212 .last()
15213 .expect("ensured that there's only one selection");
15214 let query = buffer
15215 .text_for_range(selection.start..selection.end)
15216 .collect::<String>();
15217 let is_empty = query.is_empty();
15218 let select_state = SelectNextState {
15219 query: self.build_query(&[query], cx)?,
15220 wordwise: true,
15221 done: is_empty,
15222 };
15223 self.select_next_state = Some(select_state);
15224 } else {
15225 self.select_next_state = None;
15226 }
15227 } else if let Some(selected_text) = selected_text {
15228 self.select_next_state = Some(SelectNextState {
15229 query: self.build_query(&[selected_text], cx)?,
15230 wordwise: false,
15231 done: false,
15232 });
15233 self.select_next_match_internal(
15234 display_map,
15235 replace_newest,
15236 autoscroll,
15237 window,
15238 cx,
15239 )?;
15240 }
15241 }
15242 Ok(())
15243 }
15244
15245 pub fn select_all_matches(
15246 &mut self,
15247 _action: &SelectAllMatches,
15248 window: &mut Window,
15249 cx: &mut Context<Self>,
15250 ) -> Result<()> {
15251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15252
15253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15254
15255 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15256 let Some(select_next_state) = self.select_next_state.as_mut() else {
15257 return Ok(());
15258 };
15259 if select_next_state.done {
15260 return Ok(());
15261 }
15262
15263 let mut new_selections = Vec::new();
15264
15265 let reversed = self
15266 .selections
15267 .oldest::<MultiBufferOffset>(&display_map)
15268 .reversed;
15269 let buffer = display_map.buffer_snapshot();
15270 let query_matches = select_next_state
15271 .query
15272 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15273
15274 for query_match in query_matches.into_iter() {
15275 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15276 let offset_range = if reversed {
15277 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15278 } else {
15279 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15280 };
15281
15282 if !select_next_state.wordwise
15283 || (!buffer.is_inside_word(offset_range.start, None)
15284 && !buffer.is_inside_word(offset_range.end, None))
15285 {
15286 new_selections.push(offset_range.start..offset_range.end);
15287 }
15288 }
15289
15290 select_next_state.done = true;
15291
15292 if new_selections.is_empty() {
15293 log::error!("bug: new_selections is empty in select_all_matches");
15294 return Ok(());
15295 }
15296
15297 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15298 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15299 selections.select_ranges(new_selections)
15300 });
15301
15302 Ok(())
15303 }
15304
15305 pub fn select_next(
15306 &mut self,
15307 action: &SelectNext,
15308 window: &mut Window,
15309 cx: &mut Context<Self>,
15310 ) -> Result<()> {
15311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15313 self.select_next_match_internal(
15314 &display_map,
15315 action.replace_newest,
15316 Some(Autoscroll::newest()),
15317 window,
15318 cx,
15319 )?;
15320 Ok(())
15321 }
15322
15323 pub fn select_previous(
15324 &mut self,
15325 action: &SelectPrevious,
15326 window: &mut Window,
15327 cx: &mut Context<Self>,
15328 ) -> Result<()> {
15329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15331 let buffer = display_map.buffer_snapshot();
15332 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15333 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15334 let query = &select_prev_state.query;
15335 if !select_prev_state.done {
15336 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15337 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15338 let mut next_selected_range = None;
15339 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15340 let bytes_before_last_selection =
15341 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15342 let bytes_after_first_selection =
15343 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15344 let query_matches = query
15345 .stream_find_iter(bytes_before_last_selection)
15346 .map(|result| (last_selection.start, result))
15347 .chain(
15348 query
15349 .stream_find_iter(bytes_after_first_selection)
15350 .map(|result| (buffer.len(), result)),
15351 );
15352 for (end_offset, query_match) in query_matches {
15353 let query_match = query_match.unwrap(); // can only fail due to I/O
15354 let offset_range =
15355 end_offset - query_match.end()..end_offset - query_match.start();
15356
15357 if !select_prev_state.wordwise
15358 || (!buffer.is_inside_word(offset_range.start, None)
15359 && !buffer.is_inside_word(offset_range.end, None))
15360 {
15361 next_selected_range = Some(offset_range);
15362 break;
15363 }
15364 }
15365
15366 if let Some(next_selected_range) = next_selected_range {
15367 self.select_match_ranges(
15368 next_selected_range,
15369 last_selection.reversed,
15370 action.replace_newest,
15371 Some(Autoscroll::newest()),
15372 window,
15373 cx,
15374 );
15375 } else {
15376 select_prev_state.done = true;
15377 }
15378 }
15379
15380 self.select_prev_state = Some(select_prev_state);
15381 } else {
15382 let mut only_carets = true;
15383 let mut same_text_selected = true;
15384 let mut selected_text = None;
15385
15386 let mut selections_iter = selections.iter().peekable();
15387 while let Some(selection) = selections_iter.next() {
15388 if selection.start != selection.end {
15389 only_carets = false;
15390 }
15391
15392 if same_text_selected {
15393 if selected_text.is_none() {
15394 selected_text =
15395 Some(buffer.text_for_range(selection.range()).collect::<String>());
15396 }
15397
15398 if let Some(next_selection) = selections_iter.peek() {
15399 if next_selection.len() == selection.len() {
15400 let next_selected_text = buffer
15401 .text_for_range(next_selection.range())
15402 .collect::<String>();
15403 if Some(next_selected_text) != selected_text {
15404 same_text_selected = false;
15405 selected_text = None;
15406 }
15407 } else {
15408 same_text_selected = false;
15409 selected_text = None;
15410 }
15411 }
15412 }
15413 }
15414
15415 if only_carets {
15416 for selection in &mut selections {
15417 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15418 selection.start = word_range.start;
15419 selection.end = word_range.end;
15420 selection.goal = SelectionGoal::None;
15421 selection.reversed = false;
15422 self.select_match_ranges(
15423 selection.start..selection.end,
15424 selection.reversed,
15425 action.replace_newest,
15426 Some(Autoscroll::newest()),
15427 window,
15428 cx,
15429 );
15430 }
15431 if selections.len() == 1 {
15432 let selection = selections
15433 .last()
15434 .expect("ensured that there's only one selection");
15435 let query = buffer
15436 .text_for_range(selection.start..selection.end)
15437 .collect::<String>();
15438 let is_empty = query.is_empty();
15439 let select_state = SelectNextState {
15440 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15441 wordwise: true,
15442 done: is_empty,
15443 };
15444 self.select_prev_state = Some(select_state);
15445 } else {
15446 self.select_prev_state = None;
15447 }
15448 } else if let Some(selected_text) = selected_text {
15449 self.select_prev_state = Some(SelectNextState {
15450 query: self
15451 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15452 wordwise: false,
15453 done: false,
15454 });
15455 self.select_previous(action, window, cx)?;
15456 }
15457 }
15458 Ok(())
15459 }
15460
15461 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15462 /// setting the case sensitivity based on the global
15463 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15464 /// editor's settings.
15465 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15466 where
15467 I: IntoIterator<Item = P>,
15468 P: AsRef<[u8]>,
15469 {
15470 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15471 || EditorSettings::get_global(cx).search.case_sensitive,
15472 |value| value,
15473 );
15474
15475 let mut builder = AhoCorasickBuilder::new();
15476 builder.ascii_case_insensitive(!case_sensitive);
15477 builder.build(patterns)
15478 }
15479
15480 pub fn find_next_match(
15481 &mut self,
15482 _: &FindNextMatch,
15483 window: &mut Window,
15484 cx: &mut Context<Self>,
15485 ) -> Result<()> {
15486 let selections = self.selections.disjoint_anchors_arc();
15487 match selections.first() {
15488 Some(first) if selections.len() >= 2 => {
15489 self.change_selections(Default::default(), window, cx, |s| {
15490 s.select_ranges([first.range()]);
15491 });
15492 }
15493 _ => self.select_next(
15494 &SelectNext {
15495 replace_newest: true,
15496 },
15497 window,
15498 cx,
15499 )?,
15500 }
15501 Ok(())
15502 }
15503
15504 pub fn find_previous_match(
15505 &mut self,
15506 _: &FindPreviousMatch,
15507 window: &mut Window,
15508 cx: &mut Context<Self>,
15509 ) -> Result<()> {
15510 let selections = self.selections.disjoint_anchors_arc();
15511 match selections.last() {
15512 Some(last) if selections.len() >= 2 => {
15513 self.change_selections(Default::default(), window, cx, |s| {
15514 s.select_ranges([last.range()]);
15515 });
15516 }
15517 _ => self.select_previous(
15518 &SelectPrevious {
15519 replace_newest: true,
15520 },
15521 window,
15522 cx,
15523 )?,
15524 }
15525 Ok(())
15526 }
15527
15528 pub fn toggle_comments(
15529 &mut self,
15530 action: &ToggleComments,
15531 window: &mut Window,
15532 cx: &mut Context<Self>,
15533 ) {
15534 if self.read_only(cx) {
15535 return;
15536 }
15537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15538 let text_layout_details = &self.text_layout_details(window);
15539 self.transact(window, cx, |this, window, cx| {
15540 let mut selections = this
15541 .selections
15542 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15543 let mut edits = Vec::new();
15544 let mut selection_edit_ranges = Vec::new();
15545 let mut last_toggled_row = None;
15546 let snapshot = this.buffer.read(cx).read(cx);
15547 let empty_str: Arc<str> = Arc::default();
15548 let mut suffixes_inserted = Vec::new();
15549 let ignore_indent = action.ignore_indent;
15550
15551 fn comment_prefix_range(
15552 snapshot: &MultiBufferSnapshot,
15553 row: MultiBufferRow,
15554 comment_prefix: &str,
15555 comment_prefix_whitespace: &str,
15556 ignore_indent: bool,
15557 ) -> Range<Point> {
15558 let indent_size = if ignore_indent {
15559 0
15560 } else {
15561 snapshot.indent_size_for_line(row).len
15562 };
15563
15564 let start = Point::new(row.0, indent_size);
15565
15566 let mut line_bytes = snapshot
15567 .bytes_in_range(start..snapshot.max_point())
15568 .flatten()
15569 .copied();
15570
15571 // If this line currently begins with the line comment prefix, then record
15572 // the range containing the prefix.
15573 if line_bytes
15574 .by_ref()
15575 .take(comment_prefix.len())
15576 .eq(comment_prefix.bytes())
15577 {
15578 // Include any whitespace that matches the comment prefix.
15579 let matching_whitespace_len = line_bytes
15580 .zip(comment_prefix_whitespace.bytes())
15581 .take_while(|(a, b)| a == b)
15582 .count() as u32;
15583 let end = Point::new(
15584 start.row,
15585 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15586 );
15587 start..end
15588 } else {
15589 start..start
15590 }
15591 }
15592
15593 fn comment_suffix_range(
15594 snapshot: &MultiBufferSnapshot,
15595 row: MultiBufferRow,
15596 comment_suffix: &str,
15597 comment_suffix_has_leading_space: bool,
15598 ) -> Range<Point> {
15599 let end = Point::new(row.0, snapshot.line_len(row));
15600 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15601
15602 let mut line_end_bytes = snapshot
15603 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15604 .flatten()
15605 .copied();
15606
15607 let leading_space_len = if suffix_start_column > 0
15608 && line_end_bytes.next() == Some(b' ')
15609 && comment_suffix_has_leading_space
15610 {
15611 1
15612 } else {
15613 0
15614 };
15615
15616 // If this line currently begins with the line comment prefix, then record
15617 // the range containing the prefix.
15618 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15619 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15620 start..end
15621 } else {
15622 end..end
15623 }
15624 }
15625
15626 // TODO: Handle selections that cross excerpts
15627 for selection in &mut selections {
15628 let start_column = snapshot
15629 .indent_size_for_line(MultiBufferRow(selection.start.row))
15630 .len;
15631 let language = if let Some(language) =
15632 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15633 {
15634 language
15635 } else {
15636 continue;
15637 };
15638
15639 selection_edit_ranges.clear();
15640
15641 // If multiple selections contain a given row, avoid processing that
15642 // row more than once.
15643 let mut start_row = MultiBufferRow(selection.start.row);
15644 if last_toggled_row == Some(start_row) {
15645 start_row = start_row.next_row();
15646 }
15647 let end_row =
15648 if selection.end.row > selection.start.row && selection.end.column == 0 {
15649 MultiBufferRow(selection.end.row - 1)
15650 } else {
15651 MultiBufferRow(selection.end.row)
15652 };
15653 last_toggled_row = Some(end_row);
15654
15655 if start_row > end_row {
15656 continue;
15657 }
15658
15659 // If the language has line comments, toggle those.
15660 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15661
15662 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15663 if ignore_indent {
15664 full_comment_prefixes = full_comment_prefixes
15665 .into_iter()
15666 .map(|s| Arc::from(s.trim_end()))
15667 .collect();
15668 }
15669
15670 if !full_comment_prefixes.is_empty() {
15671 let first_prefix = full_comment_prefixes
15672 .first()
15673 .expect("prefixes is non-empty");
15674 let prefix_trimmed_lengths = full_comment_prefixes
15675 .iter()
15676 .map(|p| p.trim_end_matches(' ').len())
15677 .collect::<SmallVec<[usize; 4]>>();
15678
15679 let mut all_selection_lines_are_comments = true;
15680
15681 for row in start_row.0..=end_row.0 {
15682 let row = MultiBufferRow(row);
15683 if start_row < end_row && snapshot.is_line_blank(row) {
15684 continue;
15685 }
15686
15687 let prefix_range = full_comment_prefixes
15688 .iter()
15689 .zip(prefix_trimmed_lengths.iter().copied())
15690 .map(|(prefix, trimmed_prefix_len)| {
15691 comment_prefix_range(
15692 snapshot.deref(),
15693 row,
15694 &prefix[..trimmed_prefix_len],
15695 &prefix[trimmed_prefix_len..],
15696 ignore_indent,
15697 )
15698 })
15699 .max_by_key(|range| range.end.column - range.start.column)
15700 .expect("prefixes is non-empty");
15701
15702 if prefix_range.is_empty() {
15703 all_selection_lines_are_comments = false;
15704 }
15705
15706 selection_edit_ranges.push(prefix_range);
15707 }
15708
15709 if all_selection_lines_are_comments {
15710 edits.extend(
15711 selection_edit_ranges
15712 .iter()
15713 .cloned()
15714 .map(|range| (range, empty_str.clone())),
15715 );
15716 } else {
15717 let min_column = selection_edit_ranges
15718 .iter()
15719 .map(|range| range.start.column)
15720 .min()
15721 .unwrap_or(0);
15722 edits.extend(selection_edit_ranges.iter().map(|range| {
15723 let position = Point::new(range.start.row, min_column);
15724 (position..position, first_prefix.clone())
15725 }));
15726 }
15727 } else if let Some(BlockCommentConfig {
15728 start: full_comment_prefix,
15729 end: comment_suffix,
15730 ..
15731 }) = language.block_comment()
15732 {
15733 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15734 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15735 let prefix_range = comment_prefix_range(
15736 snapshot.deref(),
15737 start_row,
15738 comment_prefix,
15739 comment_prefix_whitespace,
15740 ignore_indent,
15741 );
15742 let suffix_range = comment_suffix_range(
15743 snapshot.deref(),
15744 end_row,
15745 comment_suffix.trim_start_matches(' '),
15746 comment_suffix.starts_with(' '),
15747 );
15748
15749 if prefix_range.is_empty() || suffix_range.is_empty() {
15750 edits.push((
15751 prefix_range.start..prefix_range.start,
15752 full_comment_prefix.clone(),
15753 ));
15754 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15755 suffixes_inserted.push((end_row, comment_suffix.len()));
15756 } else {
15757 edits.push((prefix_range, empty_str.clone()));
15758 edits.push((suffix_range, empty_str.clone()));
15759 }
15760 } else {
15761 continue;
15762 }
15763 }
15764
15765 drop(snapshot);
15766 this.buffer.update(cx, |buffer, cx| {
15767 buffer.edit(edits, None, cx);
15768 });
15769
15770 // Adjust selections so that they end before any comment suffixes that
15771 // were inserted.
15772 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15773 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15774 let snapshot = this.buffer.read(cx).read(cx);
15775 for selection in &mut selections {
15776 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15777 match row.cmp(&MultiBufferRow(selection.end.row)) {
15778 Ordering::Less => {
15779 suffixes_inserted.next();
15780 continue;
15781 }
15782 Ordering::Greater => break,
15783 Ordering::Equal => {
15784 if selection.end.column == snapshot.line_len(row) {
15785 if selection.is_empty() {
15786 selection.start.column -= suffix_len as u32;
15787 }
15788 selection.end.column -= suffix_len as u32;
15789 }
15790 break;
15791 }
15792 }
15793 }
15794 }
15795
15796 drop(snapshot);
15797 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15798
15799 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15800 let selections_on_single_row = selections.windows(2).all(|selections| {
15801 selections[0].start.row == selections[1].start.row
15802 && selections[0].end.row == selections[1].end.row
15803 && selections[0].start.row == selections[0].end.row
15804 });
15805 let selections_selecting = selections
15806 .iter()
15807 .any(|selection| selection.start != selection.end);
15808 let advance_downwards = action.advance_downwards
15809 && selections_on_single_row
15810 && !selections_selecting
15811 && !matches!(this.mode, EditorMode::SingleLine);
15812
15813 if advance_downwards {
15814 let snapshot = this.buffer.read(cx).snapshot(cx);
15815
15816 this.change_selections(Default::default(), window, cx, |s| {
15817 s.move_cursors_with(|display_snapshot, display_point, _| {
15818 let mut point = display_point.to_point(display_snapshot);
15819 point.row += 1;
15820 point = snapshot.clip_point(point, Bias::Left);
15821 let display_point = point.to_display_point(display_snapshot);
15822 let goal = SelectionGoal::HorizontalPosition(
15823 display_snapshot
15824 .x_for_display_point(display_point, text_layout_details)
15825 .into(),
15826 );
15827 (display_point, goal)
15828 })
15829 });
15830 }
15831 });
15832 }
15833
15834 pub fn select_enclosing_symbol(
15835 &mut self,
15836 _: &SelectEnclosingSymbol,
15837 window: &mut Window,
15838 cx: &mut Context<Self>,
15839 ) {
15840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15841
15842 let buffer = self.buffer.read(cx).snapshot(cx);
15843 let old_selections = self
15844 .selections
15845 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15846 .into_boxed_slice();
15847
15848 fn update_selection(
15849 selection: &Selection<MultiBufferOffset>,
15850 buffer_snap: &MultiBufferSnapshot,
15851 ) -> Option<Selection<MultiBufferOffset>> {
15852 let cursor = selection.head();
15853 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15854 for symbol in symbols.iter().rev() {
15855 let start = symbol.range.start.to_offset(buffer_snap);
15856 let end = symbol.range.end.to_offset(buffer_snap);
15857 let new_range = start..end;
15858 if start < selection.start || end > selection.end {
15859 return Some(Selection {
15860 id: selection.id,
15861 start: new_range.start,
15862 end: new_range.end,
15863 goal: SelectionGoal::None,
15864 reversed: selection.reversed,
15865 });
15866 }
15867 }
15868 None
15869 }
15870
15871 let mut selected_larger_symbol = false;
15872 let new_selections = old_selections
15873 .iter()
15874 .map(|selection| match update_selection(selection, &buffer) {
15875 Some(new_selection) => {
15876 if new_selection.range() != selection.range() {
15877 selected_larger_symbol = true;
15878 }
15879 new_selection
15880 }
15881 None => selection.clone(),
15882 })
15883 .collect::<Vec<_>>();
15884
15885 if selected_larger_symbol {
15886 self.change_selections(Default::default(), window, cx, |s| {
15887 s.select(new_selections);
15888 });
15889 }
15890 }
15891
15892 pub fn select_larger_syntax_node(
15893 &mut self,
15894 _: &SelectLargerSyntaxNode,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) {
15898 let Some(visible_row_count) = self.visible_row_count() else {
15899 return;
15900 };
15901 let old_selections: Box<[_]> = self
15902 .selections
15903 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15904 .into();
15905 if old_selections.is_empty() {
15906 return;
15907 }
15908
15909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15910
15911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15912 let buffer = self.buffer.read(cx).snapshot(cx);
15913
15914 let mut selected_larger_node = false;
15915 let mut new_selections = old_selections
15916 .iter()
15917 .map(|selection| {
15918 let old_range = selection.start..selection.end;
15919
15920 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15921 // manually select word at selection
15922 if ["string_content", "inline"].contains(&node.kind()) {
15923 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15924 // ignore if word is already selected
15925 if !word_range.is_empty() && old_range != word_range {
15926 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15927 // only select word if start and end point belongs to same word
15928 if word_range == last_word_range {
15929 selected_larger_node = true;
15930 return Selection {
15931 id: selection.id,
15932 start: word_range.start,
15933 end: word_range.end,
15934 goal: SelectionGoal::None,
15935 reversed: selection.reversed,
15936 };
15937 }
15938 }
15939 }
15940 }
15941
15942 let mut new_range = old_range.clone();
15943 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15944 new_range = range;
15945 if !node.is_named() {
15946 continue;
15947 }
15948 if !display_map.intersects_fold(new_range.start)
15949 && !display_map.intersects_fold(new_range.end)
15950 {
15951 break;
15952 }
15953 }
15954
15955 selected_larger_node |= new_range != old_range;
15956 Selection {
15957 id: selection.id,
15958 start: new_range.start,
15959 end: new_range.end,
15960 goal: SelectionGoal::None,
15961 reversed: selection.reversed,
15962 }
15963 })
15964 .collect::<Vec<_>>();
15965
15966 if !selected_larger_node {
15967 return; // don't put this call in the history
15968 }
15969
15970 // scroll based on transformation done to the last selection created by the user
15971 let (last_old, last_new) = old_selections
15972 .last()
15973 .zip(new_selections.last().cloned())
15974 .expect("old_selections isn't empty");
15975
15976 // revert selection
15977 let is_selection_reversed = {
15978 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15979 new_selections.last_mut().expect("checked above").reversed =
15980 should_newest_selection_be_reversed;
15981 should_newest_selection_be_reversed
15982 };
15983
15984 if selected_larger_node {
15985 self.select_syntax_node_history.disable_clearing = true;
15986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15987 s.select(new_selections.clone());
15988 });
15989 self.select_syntax_node_history.disable_clearing = false;
15990 }
15991
15992 let start_row = last_new.start.to_display_point(&display_map).row().0;
15993 let end_row = last_new.end.to_display_point(&display_map).row().0;
15994 let selection_height = end_row - start_row + 1;
15995 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15996
15997 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15998 let scroll_behavior = if fits_on_the_screen {
15999 self.request_autoscroll(Autoscroll::fit(), cx);
16000 SelectSyntaxNodeScrollBehavior::FitSelection
16001 } else if is_selection_reversed {
16002 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16003 SelectSyntaxNodeScrollBehavior::CursorTop
16004 } else {
16005 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16006 SelectSyntaxNodeScrollBehavior::CursorBottom
16007 };
16008
16009 self.select_syntax_node_history.push((
16010 old_selections,
16011 scroll_behavior,
16012 is_selection_reversed,
16013 ));
16014 }
16015
16016 pub fn select_smaller_syntax_node(
16017 &mut self,
16018 _: &SelectSmallerSyntaxNode,
16019 window: &mut Window,
16020 cx: &mut Context<Self>,
16021 ) {
16022 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16023
16024 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16025 self.select_syntax_node_history.pop()
16026 {
16027 if let Some(selection) = selections.last_mut() {
16028 selection.reversed = is_selection_reversed;
16029 }
16030
16031 self.select_syntax_node_history.disable_clearing = true;
16032 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16033 s.select(selections.to_vec());
16034 });
16035 self.select_syntax_node_history.disable_clearing = false;
16036
16037 match scroll_behavior {
16038 SelectSyntaxNodeScrollBehavior::CursorTop => {
16039 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16040 }
16041 SelectSyntaxNodeScrollBehavior::FitSelection => {
16042 self.request_autoscroll(Autoscroll::fit(), cx);
16043 }
16044 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16045 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16046 }
16047 }
16048 }
16049 }
16050
16051 pub fn unwrap_syntax_node(
16052 &mut self,
16053 _: &UnwrapSyntaxNode,
16054 window: &mut Window,
16055 cx: &mut Context<Self>,
16056 ) {
16057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16058
16059 let buffer = self.buffer.read(cx).snapshot(cx);
16060 let selections = self
16061 .selections
16062 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16063 .into_iter()
16064 // subtracting the offset requires sorting
16065 .sorted_by_key(|i| i.start);
16066
16067 let full_edits = selections
16068 .into_iter()
16069 .filter_map(|selection| {
16070 let child = if selection.is_empty()
16071 && let Some((_, ancestor_range)) =
16072 buffer.syntax_ancestor(selection.start..selection.end)
16073 {
16074 ancestor_range
16075 } else {
16076 selection.range()
16077 };
16078
16079 let mut parent = child.clone();
16080 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16081 parent = ancestor_range;
16082 if parent.start < child.start || parent.end > child.end {
16083 break;
16084 }
16085 }
16086
16087 if parent == child {
16088 return None;
16089 }
16090 let text = buffer.text_for_range(child).collect::<String>();
16091 Some((selection.id, parent, text))
16092 })
16093 .collect::<Vec<_>>();
16094 if full_edits.is_empty() {
16095 return;
16096 }
16097
16098 self.transact(window, cx, |this, window, cx| {
16099 this.buffer.update(cx, |buffer, cx| {
16100 buffer.edit(
16101 full_edits
16102 .iter()
16103 .map(|(_, p, t)| (p.clone(), t.clone()))
16104 .collect::<Vec<_>>(),
16105 None,
16106 cx,
16107 );
16108 });
16109 this.change_selections(Default::default(), window, cx, |s| {
16110 let mut offset = 0;
16111 let mut selections = vec![];
16112 for (id, parent, text) in full_edits {
16113 let start = parent.start - offset;
16114 offset += (parent.end - parent.start) - text.len();
16115 selections.push(Selection {
16116 id,
16117 start,
16118 end: start + text.len(),
16119 reversed: false,
16120 goal: Default::default(),
16121 });
16122 }
16123 s.select(selections);
16124 });
16125 });
16126 }
16127
16128 pub fn select_next_syntax_node(
16129 &mut self,
16130 _: &SelectNextSyntaxNode,
16131 window: &mut Window,
16132 cx: &mut Context<Self>,
16133 ) {
16134 let old_selections: Box<[_]> = self
16135 .selections
16136 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16137 .into();
16138 if old_selections.is_empty() {
16139 return;
16140 }
16141
16142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16143
16144 let buffer = self.buffer.read(cx).snapshot(cx);
16145 let mut selected_sibling = false;
16146
16147 let new_selections = old_selections
16148 .iter()
16149 .map(|selection| {
16150 let old_range = selection.start..selection.end;
16151
16152 let old_range =
16153 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16154 let excerpt = buffer.excerpt_containing(old_range.clone());
16155
16156 if let Some(mut excerpt) = excerpt
16157 && let Some(node) = excerpt
16158 .buffer()
16159 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16160 {
16161 let new_range = excerpt.map_range_from_buffer(
16162 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16163 );
16164 selected_sibling = true;
16165 Selection {
16166 id: selection.id,
16167 start: new_range.start,
16168 end: new_range.end,
16169 goal: SelectionGoal::None,
16170 reversed: selection.reversed,
16171 }
16172 } else {
16173 selection.clone()
16174 }
16175 })
16176 .collect::<Vec<_>>();
16177
16178 if selected_sibling {
16179 self.change_selections(
16180 SelectionEffects::scroll(Autoscroll::fit()),
16181 window,
16182 cx,
16183 |s| {
16184 s.select(new_selections);
16185 },
16186 );
16187 }
16188 }
16189
16190 pub fn select_prev_syntax_node(
16191 &mut self,
16192 _: &SelectPreviousSyntaxNode,
16193 window: &mut Window,
16194 cx: &mut Context<Self>,
16195 ) {
16196 let old_selections: Box<[_]> = self
16197 .selections
16198 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16199 .into();
16200 if old_selections.is_empty() {
16201 return;
16202 }
16203
16204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16205
16206 let buffer = self.buffer.read(cx).snapshot(cx);
16207 let mut selected_sibling = false;
16208
16209 let new_selections = old_selections
16210 .iter()
16211 .map(|selection| {
16212 let old_range = selection.start..selection.end;
16213 let old_range =
16214 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16215 let excerpt = buffer.excerpt_containing(old_range.clone());
16216
16217 if let Some(mut excerpt) = excerpt
16218 && let Some(node) = excerpt
16219 .buffer()
16220 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16221 {
16222 let new_range = excerpt.map_range_from_buffer(
16223 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16224 );
16225 selected_sibling = true;
16226 Selection {
16227 id: selection.id,
16228 start: new_range.start,
16229 end: new_range.end,
16230 goal: SelectionGoal::None,
16231 reversed: selection.reversed,
16232 }
16233 } else {
16234 selection.clone()
16235 }
16236 })
16237 .collect::<Vec<_>>();
16238
16239 if selected_sibling {
16240 self.change_selections(
16241 SelectionEffects::scroll(Autoscroll::fit()),
16242 window,
16243 cx,
16244 |s| {
16245 s.select(new_selections);
16246 },
16247 );
16248 }
16249 }
16250
16251 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16252 if !EditorSettings::get_global(cx).gutter.runnables {
16253 self.clear_tasks();
16254 return Task::ready(());
16255 }
16256 let project = self.project().map(Entity::downgrade);
16257 let task_sources = self.lsp_task_sources(cx);
16258 let multi_buffer = self.buffer.downgrade();
16259 cx.spawn_in(window, async move |editor, cx| {
16260 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16261 let Some(project) = project.and_then(|p| p.upgrade()) else {
16262 return;
16263 };
16264 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16265 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16266 }) else {
16267 return;
16268 };
16269
16270 let hide_runnables = project
16271 .update(cx, |project, _| project.is_via_collab())
16272 .unwrap_or(true);
16273 if hide_runnables {
16274 return;
16275 }
16276 let new_rows =
16277 cx.background_spawn({
16278 let snapshot = display_snapshot.clone();
16279 async move {
16280 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16281 }
16282 })
16283 .await;
16284 let Ok(lsp_tasks) =
16285 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16286 else {
16287 return;
16288 };
16289 let lsp_tasks = lsp_tasks.await;
16290
16291 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16292 lsp_tasks
16293 .into_iter()
16294 .flat_map(|(kind, tasks)| {
16295 tasks.into_iter().filter_map(move |(location, task)| {
16296 Some((kind.clone(), location?, task))
16297 })
16298 })
16299 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16300 let buffer = location.target.buffer;
16301 let buffer_snapshot = buffer.read(cx).snapshot();
16302 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16303 |(excerpt_id, snapshot, _)| {
16304 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16305 display_snapshot
16306 .buffer_snapshot()
16307 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16308 } else {
16309 None
16310 }
16311 },
16312 );
16313 if let Some(offset) = offset {
16314 let task_buffer_range =
16315 location.target.range.to_point(&buffer_snapshot);
16316 let context_buffer_range =
16317 task_buffer_range.to_offset(&buffer_snapshot);
16318 let context_range = BufferOffset(context_buffer_range.start)
16319 ..BufferOffset(context_buffer_range.end);
16320
16321 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16322 .or_insert_with(|| RunnableTasks {
16323 templates: Vec::new(),
16324 offset,
16325 column: task_buffer_range.start.column,
16326 extra_variables: HashMap::default(),
16327 context_range,
16328 })
16329 .templates
16330 .push((kind, task.original_task().clone()));
16331 }
16332
16333 acc
16334 })
16335 }) else {
16336 return;
16337 };
16338
16339 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16340 buffer.language_settings(cx).tasks.prefer_lsp
16341 }) else {
16342 return;
16343 };
16344
16345 let rows = Self::runnable_rows(
16346 project,
16347 display_snapshot,
16348 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16349 new_rows,
16350 cx.clone(),
16351 )
16352 .await;
16353 editor
16354 .update(cx, |editor, _| {
16355 editor.clear_tasks();
16356 for (key, mut value) in rows {
16357 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16358 value.templates.extend(lsp_tasks.templates);
16359 }
16360
16361 editor.insert_tasks(key, value);
16362 }
16363 for (key, value) in lsp_tasks_by_rows {
16364 editor.insert_tasks(key, value);
16365 }
16366 })
16367 .ok();
16368 })
16369 }
16370 fn fetch_runnable_ranges(
16371 snapshot: &DisplaySnapshot,
16372 range: Range<Anchor>,
16373 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16374 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16375 }
16376
16377 fn runnable_rows(
16378 project: Entity<Project>,
16379 snapshot: DisplaySnapshot,
16380 prefer_lsp: bool,
16381 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16382 cx: AsyncWindowContext,
16383 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16384 cx.spawn(async move |cx| {
16385 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16386 for (run_range, mut runnable) in runnable_ranges {
16387 let Some(tasks) = cx
16388 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16389 .ok()
16390 else {
16391 continue;
16392 };
16393 let mut tasks = tasks.await;
16394
16395 if prefer_lsp {
16396 tasks.retain(|(task_kind, _)| {
16397 !matches!(task_kind, TaskSourceKind::Language { .. })
16398 });
16399 }
16400 if tasks.is_empty() {
16401 continue;
16402 }
16403
16404 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16405 let Some(row) = snapshot
16406 .buffer_snapshot()
16407 .buffer_line_for_row(MultiBufferRow(point.row))
16408 .map(|(_, range)| range.start.row)
16409 else {
16410 continue;
16411 };
16412
16413 let context_range =
16414 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16415 runnable_rows.push((
16416 (runnable.buffer_id, row),
16417 RunnableTasks {
16418 templates: tasks,
16419 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16420 context_range,
16421 column: point.column,
16422 extra_variables: runnable.extra_captures,
16423 },
16424 ));
16425 }
16426 runnable_rows
16427 })
16428 }
16429
16430 fn templates_with_tags(
16431 project: &Entity<Project>,
16432 runnable: &mut Runnable,
16433 cx: &mut App,
16434 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16435 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16436 let (worktree_id, file) = project
16437 .buffer_for_id(runnable.buffer, cx)
16438 .and_then(|buffer| buffer.read(cx).file())
16439 .map(|file| (file.worktree_id(cx), file.clone()))
16440 .unzip();
16441
16442 (
16443 project.task_store().read(cx).task_inventory().cloned(),
16444 worktree_id,
16445 file,
16446 )
16447 });
16448
16449 let tags = mem::take(&mut runnable.tags);
16450 let language = runnable.language.clone();
16451 cx.spawn(async move |cx| {
16452 let mut templates_with_tags = Vec::new();
16453 if let Some(inventory) = inventory {
16454 for RunnableTag(tag) in tags {
16455 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16456 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16457 }) else {
16458 return templates_with_tags;
16459 };
16460 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16461 move |(_, template)| {
16462 template.tags.iter().any(|source_tag| source_tag == &tag)
16463 },
16464 ));
16465 }
16466 }
16467 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16468
16469 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16470 // Strongest source wins; if we have worktree tag binding, prefer that to
16471 // global and language bindings;
16472 // if we have a global binding, prefer that to language binding.
16473 let first_mismatch = templates_with_tags
16474 .iter()
16475 .position(|(tag_source, _)| tag_source != leading_tag_source);
16476 if let Some(index) = first_mismatch {
16477 templates_with_tags.truncate(index);
16478 }
16479 }
16480
16481 templates_with_tags
16482 })
16483 }
16484
16485 pub fn move_to_enclosing_bracket(
16486 &mut self,
16487 _: &MoveToEnclosingBracket,
16488 window: &mut Window,
16489 cx: &mut Context<Self>,
16490 ) {
16491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16492 self.change_selections(Default::default(), window, cx, |s| {
16493 s.move_offsets_with(|snapshot, selection| {
16494 let Some(enclosing_bracket_ranges) =
16495 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16496 else {
16497 return;
16498 };
16499
16500 let mut best_length = usize::MAX;
16501 let mut best_inside = false;
16502 let mut best_in_bracket_range = false;
16503 let mut best_destination = None;
16504 for (open, close) in enclosing_bracket_ranges {
16505 let close = close.to_inclusive();
16506 let length = *close.end() - open.start;
16507 let inside = selection.start >= open.end && selection.end <= *close.start();
16508 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16509 || close.contains(&selection.head());
16510
16511 // If best is next to a bracket and current isn't, skip
16512 if !in_bracket_range && best_in_bracket_range {
16513 continue;
16514 }
16515
16516 // Prefer smaller lengths unless best is inside and current isn't
16517 if length > best_length && (best_inside || !inside) {
16518 continue;
16519 }
16520
16521 best_length = length;
16522 best_inside = inside;
16523 best_in_bracket_range = in_bracket_range;
16524 best_destination = Some(
16525 if close.contains(&selection.start) && close.contains(&selection.end) {
16526 if inside { open.end } else { open.start }
16527 } else if inside {
16528 *close.start()
16529 } else {
16530 *close.end()
16531 },
16532 );
16533 }
16534
16535 if let Some(destination) = best_destination {
16536 selection.collapse_to(destination, SelectionGoal::None);
16537 }
16538 })
16539 });
16540 }
16541
16542 pub fn undo_selection(
16543 &mut self,
16544 _: &UndoSelection,
16545 window: &mut Window,
16546 cx: &mut Context<Self>,
16547 ) {
16548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16549 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16550 self.selection_history.mode = SelectionHistoryMode::Undoing;
16551 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16552 this.end_selection(window, cx);
16553 this.change_selections(
16554 SelectionEffects::scroll(Autoscroll::newest()),
16555 window,
16556 cx,
16557 |s| s.select_anchors(entry.selections.to_vec()),
16558 );
16559 });
16560 self.selection_history.mode = SelectionHistoryMode::Normal;
16561
16562 self.select_next_state = entry.select_next_state;
16563 self.select_prev_state = entry.select_prev_state;
16564 self.add_selections_state = entry.add_selections_state;
16565 }
16566 }
16567
16568 pub fn redo_selection(
16569 &mut self,
16570 _: &RedoSelection,
16571 window: &mut Window,
16572 cx: &mut Context<Self>,
16573 ) {
16574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16575 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16576 self.selection_history.mode = SelectionHistoryMode::Redoing;
16577 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16578 this.end_selection(window, cx);
16579 this.change_selections(
16580 SelectionEffects::scroll(Autoscroll::newest()),
16581 window,
16582 cx,
16583 |s| s.select_anchors(entry.selections.to_vec()),
16584 );
16585 });
16586 self.selection_history.mode = SelectionHistoryMode::Normal;
16587
16588 self.select_next_state = entry.select_next_state;
16589 self.select_prev_state = entry.select_prev_state;
16590 self.add_selections_state = entry.add_selections_state;
16591 }
16592 }
16593
16594 pub fn expand_excerpts(
16595 &mut self,
16596 action: &ExpandExcerpts,
16597 _: &mut Window,
16598 cx: &mut Context<Self>,
16599 ) {
16600 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16601 }
16602
16603 pub fn expand_excerpts_down(
16604 &mut self,
16605 action: &ExpandExcerptsDown,
16606 _: &mut Window,
16607 cx: &mut Context<Self>,
16608 ) {
16609 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16610 }
16611
16612 pub fn expand_excerpts_up(
16613 &mut self,
16614 action: &ExpandExcerptsUp,
16615 _: &mut Window,
16616 cx: &mut Context<Self>,
16617 ) {
16618 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16619 }
16620
16621 pub fn expand_excerpts_for_direction(
16622 &mut self,
16623 lines: u32,
16624 direction: ExpandExcerptDirection,
16625
16626 cx: &mut Context<Self>,
16627 ) {
16628 let selections = self.selections.disjoint_anchors_arc();
16629
16630 let lines = if lines == 0 {
16631 EditorSettings::get_global(cx).expand_excerpt_lines
16632 } else {
16633 lines
16634 };
16635
16636 self.buffer.update(cx, |buffer, cx| {
16637 let snapshot = buffer.snapshot(cx);
16638 let mut excerpt_ids = selections
16639 .iter()
16640 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16641 .collect::<Vec<_>>();
16642 excerpt_ids.sort();
16643 excerpt_ids.dedup();
16644 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16645 })
16646 }
16647
16648 pub fn expand_excerpt(
16649 &mut self,
16650 excerpt: ExcerptId,
16651 direction: ExpandExcerptDirection,
16652 window: &mut Window,
16653 cx: &mut Context<Self>,
16654 ) {
16655 let current_scroll_position = self.scroll_position(cx);
16656 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16657 let mut scroll = None;
16658
16659 if direction == ExpandExcerptDirection::Down {
16660 let multi_buffer = self.buffer.read(cx);
16661 let snapshot = multi_buffer.snapshot(cx);
16662 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16663 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16664 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16665 {
16666 let buffer_snapshot = buffer.read(cx).snapshot();
16667 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16668 let last_row = buffer_snapshot.max_point().row;
16669 let lines_below = last_row.saturating_sub(excerpt_end_row);
16670 if lines_below >= lines_to_expand {
16671 scroll = Some(
16672 current_scroll_position
16673 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16674 );
16675 }
16676 }
16677 }
16678 if direction == ExpandExcerptDirection::Up
16679 && self
16680 .buffer
16681 .read(cx)
16682 .snapshot(cx)
16683 .excerpt_before(excerpt)
16684 .is_none()
16685 {
16686 scroll = Some(current_scroll_position);
16687 }
16688
16689 self.buffer.update(cx, |buffer, cx| {
16690 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16691 });
16692
16693 if let Some(new_scroll_position) = scroll {
16694 self.set_scroll_position(new_scroll_position, window, cx);
16695 }
16696 }
16697
16698 pub fn go_to_singleton_buffer_point(
16699 &mut self,
16700 point: Point,
16701 window: &mut Window,
16702 cx: &mut Context<Self>,
16703 ) {
16704 self.go_to_singleton_buffer_range(point..point, window, cx);
16705 }
16706
16707 pub fn go_to_singleton_buffer_range(
16708 &mut self,
16709 range: Range<Point>,
16710 window: &mut Window,
16711 cx: &mut Context<Self>,
16712 ) {
16713 let multibuffer = self.buffer().read(cx);
16714 let Some(buffer) = multibuffer.as_singleton() else {
16715 return;
16716 };
16717 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16718 return;
16719 };
16720 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16721 return;
16722 };
16723 self.change_selections(
16724 SelectionEffects::default().nav_history(true),
16725 window,
16726 cx,
16727 |s| s.select_anchor_ranges([start..end]),
16728 );
16729 }
16730
16731 pub fn go_to_diagnostic(
16732 &mut self,
16733 action: &GoToDiagnostic,
16734 window: &mut Window,
16735 cx: &mut Context<Self>,
16736 ) {
16737 if !self.diagnostics_enabled() {
16738 return;
16739 }
16740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16741 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16742 }
16743
16744 pub fn go_to_prev_diagnostic(
16745 &mut self,
16746 action: &GoToPreviousDiagnostic,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 if !self.diagnostics_enabled() {
16751 return;
16752 }
16753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16754 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16755 }
16756
16757 pub fn go_to_diagnostic_impl(
16758 &mut self,
16759 direction: Direction,
16760 severity: GoToDiagnosticSeverityFilter,
16761 window: &mut Window,
16762 cx: &mut Context<Self>,
16763 ) {
16764 let buffer = self.buffer.read(cx).snapshot(cx);
16765 let selection = self
16766 .selections
16767 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16768
16769 let mut active_group_id = None;
16770 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16771 && active_group.active_range.start.to_offset(&buffer) == selection.start
16772 {
16773 active_group_id = Some(active_group.group_id);
16774 }
16775
16776 fn filtered<'a>(
16777 severity: GoToDiagnosticSeverityFilter,
16778 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16779 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16780 diagnostics
16781 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16782 .filter(|entry| entry.range.start != entry.range.end)
16783 .filter(|entry| !entry.diagnostic.is_unnecessary)
16784 }
16785
16786 let before = filtered(
16787 severity,
16788 buffer
16789 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16790 .filter(|entry| entry.range.start <= selection.start),
16791 );
16792 let after = filtered(
16793 severity,
16794 buffer
16795 .diagnostics_in_range(selection.start..buffer.len())
16796 .filter(|entry| entry.range.start >= selection.start),
16797 );
16798
16799 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16800 if direction == Direction::Prev {
16801 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16802 {
16803 for diagnostic in prev_diagnostics.into_iter().rev() {
16804 if diagnostic.range.start != selection.start
16805 || active_group_id
16806 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16807 {
16808 found = Some(diagnostic);
16809 break 'outer;
16810 }
16811 }
16812 }
16813 } else {
16814 for diagnostic in after.chain(before) {
16815 if diagnostic.range.start != selection.start
16816 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16817 {
16818 found = Some(diagnostic);
16819 break;
16820 }
16821 }
16822 }
16823 let Some(next_diagnostic) = found else {
16824 return;
16825 };
16826
16827 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16828 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16829 return;
16830 };
16831 let snapshot = self.snapshot(window, cx);
16832 if snapshot.intersects_fold(next_diagnostic.range.start) {
16833 self.unfold_ranges(
16834 std::slice::from_ref(&next_diagnostic.range),
16835 true,
16836 false,
16837 cx,
16838 );
16839 }
16840 self.change_selections(Default::default(), window, cx, |s| {
16841 s.select_ranges(vec![
16842 next_diagnostic.range.start..next_diagnostic.range.start,
16843 ])
16844 });
16845 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16846 self.refresh_edit_prediction(false, true, window, cx);
16847 }
16848
16849 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16851 let snapshot = self.snapshot(window, cx);
16852 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16853 self.go_to_hunk_before_or_after_position(
16854 &snapshot,
16855 selection.head(),
16856 Direction::Next,
16857 window,
16858 cx,
16859 );
16860 }
16861
16862 pub fn go_to_hunk_before_or_after_position(
16863 &mut self,
16864 snapshot: &EditorSnapshot,
16865 position: Point,
16866 direction: Direction,
16867 window: &mut Window,
16868 cx: &mut Context<Editor>,
16869 ) {
16870 let row = if direction == Direction::Next {
16871 self.hunk_after_position(snapshot, position)
16872 .map(|hunk| hunk.row_range.start)
16873 } else {
16874 self.hunk_before_position(snapshot, position)
16875 };
16876
16877 if let Some(row) = row {
16878 let destination = Point::new(row.0, 0);
16879 let autoscroll = Autoscroll::center();
16880
16881 self.unfold_ranges(&[destination..destination], false, false, cx);
16882 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16883 s.select_ranges([destination..destination]);
16884 });
16885 }
16886 }
16887
16888 fn hunk_after_position(
16889 &mut self,
16890 snapshot: &EditorSnapshot,
16891 position: Point,
16892 ) -> Option<MultiBufferDiffHunk> {
16893 snapshot
16894 .buffer_snapshot()
16895 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16896 .find(|hunk| hunk.row_range.start.0 > position.row)
16897 .or_else(|| {
16898 snapshot
16899 .buffer_snapshot()
16900 .diff_hunks_in_range(Point::zero()..position)
16901 .find(|hunk| hunk.row_range.end.0 < position.row)
16902 })
16903 }
16904
16905 fn go_to_prev_hunk(
16906 &mut self,
16907 _: &GoToPreviousHunk,
16908 window: &mut Window,
16909 cx: &mut Context<Self>,
16910 ) {
16911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16912 let snapshot = self.snapshot(window, cx);
16913 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16914 self.go_to_hunk_before_or_after_position(
16915 &snapshot,
16916 selection.head(),
16917 Direction::Prev,
16918 window,
16919 cx,
16920 );
16921 }
16922
16923 fn hunk_before_position(
16924 &mut self,
16925 snapshot: &EditorSnapshot,
16926 position: Point,
16927 ) -> Option<MultiBufferRow> {
16928 snapshot
16929 .buffer_snapshot()
16930 .diff_hunk_before(position)
16931 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16932 }
16933
16934 fn go_to_next_change(
16935 &mut self,
16936 _: &GoToNextChange,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 ) {
16940 if let Some(selections) = self
16941 .change_list
16942 .next_change(1, Direction::Next)
16943 .map(|s| s.to_vec())
16944 {
16945 self.change_selections(Default::default(), window, cx, |s| {
16946 let map = s.display_snapshot();
16947 s.select_display_ranges(selections.iter().map(|a| {
16948 let point = a.to_display_point(&map);
16949 point..point
16950 }))
16951 })
16952 }
16953 }
16954
16955 fn go_to_previous_change(
16956 &mut self,
16957 _: &GoToPreviousChange,
16958 window: &mut Window,
16959 cx: &mut Context<Self>,
16960 ) {
16961 if let Some(selections) = self
16962 .change_list
16963 .next_change(1, Direction::Prev)
16964 .map(|s| s.to_vec())
16965 {
16966 self.change_selections(Default::default(), window, cx, |s| {
16967 let map = s.display_snapshot();
16968 s.select_display_ranges(selections.iter().map(|a| {
16969 let point = a.to_display_point(&map);
16970 point..point
16971 }))
16972 })
16973 }
16974 }
16975
16976 pub fn go_to_next_document_highlight(
16977 &mut self,
16978 _: &GoToNextDocumentHighlight,
16979 window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16983 }
16984
16985 pub fn go_to_prev_document_highlight(
16986 &mut self,
16987 _: &GoToPreviousDocumentHighlight,
16988 window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16992 }
16993
16994 pub fn go_to_document_highlight_before_or_after_position(
16995 &mut self,
16996 direction: Direction,
16997 window: &mut Window,
16998 cx: &mut Context<Editor>,
16999 ) {
17000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17001 let snapshot = self.snapshot(window, cx);
17002 let buffer = &snapshot.buffer_snapshot();
17003 let position = self
17004 .selections
17005 .newest::<Point>(&snapshot.display_snapshot)
17006 .head();
17007 let anchor_position = buffer.anchor_after(position);
17008
17009 // Get all document highlights (both read and write)
17010 let mut all_highlights = Vec::new();
17011
17012 if let Some((_, read_highlights)) = self
17013 .background_highlights
17014 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17015 {
17016 all_highlights.extend(read_highlights.iter());
17017 }
17018
17019 if let Some((_, write_highlights)) = self
17020 .background_highlights
17021 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17022 {
17023 all_highlights.extend(write_highlights.iter());
17024 }
17025
17026 if all_highlights.is_empty() {
17027 return;
17028 }
17029
17030 // Sort highlights by position
17031 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17032
17033 let target_highlight = match direction {
17034 Direction::Next => {
17035 // Find the first highlight after the current position
17036 all_highlights
17037 .iter()
17038 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17039 }
17040 Direction::Prev => {
17041 // Find the last highlight before the current position
17042 all_highlights
17043 .iter()
17044 .rev()
17045 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17046 }
17047 };
17048
17049 if let Some(highlight) = target_highlight {
17050 let destination = highlight.start.to_point(buffer);
17051 let autoscroll = Autoscroll::center();
17052
17053 self.unfold_ranges(&[destination..destination], false, false, cx);
17054 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17055 s.select_ranges([destination..destination]);
17056 });
17057 }
17058 }
17059
17060 fn go_to_line<T: 'static>(
17061 &mut self,
17062 position: Anchor,
17063 highlight_color: Option<Hsla>,
17064 window: &mut Window,
17065 cx: &mut Context<Self>,
17066 ) {
17067 let snapshot = self.snapshot(window, cx).display_snapshot;
17068 let position = position.to_point(&snapshot.buffer_snapshot());
17069 let start = snapshot
17070 .buffer_snapshot()
17071 .clip_point(Point::new(position.row, 0), Bias::Left);
17072 let end = start + Point::new(1, 0);
17073 let start = snapshot.buffer_snapshot().anchor_before(start);
17074 let end = snapshot.buffer_snapshot().anchor_before(end);
17075
17076 self.highlight_rows::<T>(
17077 start..end,
17078 highlight_color
17079 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17080 Default::default(),
17081 cx,
17082 );
17083
17084 if self.buffer.read(cx).is_singleton() {
17085 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17086 }
17087 }
17088
17089 pub fn go_to_definition(
17090 &mut self,
17091 _: &GoToDefinition,
17092 window: &mut Window,
17093 cx: &mut Context<Self>,
17094 ) -> Task<Result<Navigated>> {
17095 let definition =
17096 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17097 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17098 cx.spawn_in(window, async move |editor, cx| {
17099 if definition.await? == Navigated::Yes {
17100 return Ok(Navigated::Yes);
17101 }
17102 match fallback_strategy {
17103 GoToDefinitionFallback::None => Ok(Navigated::No),
17104 GoToDefinitionFallback::FindAllReferences => {
17105 match editor.update_in(cx, |editor, window, cx| {
17106 editor.find_all_references(&FindAllReferences::default(), window, cx)
17107 })? {
17108 Some(references) => references.await,
17109 None => Ok(Navigated::No),
17110 }
17111 }
17112 }
17113 })
17114 }
17115
17116 pub fn go_to_declaration(
17117 &mut self,
17118 _: &GoToDeclaration,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) -> Task<Result<Navigated>> {
17122 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17123 }
17124
17125 pub fn go_to_declaration_split(
17126 &mut self,
17127 _: &GoToDeclaration,
17128 window: &mut Window,
17129 cx: &mut Context<Self>,
17130 ) -> Task<Result<Navigated>> {
17131 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17132 }
17133
17134 pub fn go_to_implementation(
17135 &mut self,
17136 _: &GoToImplementation,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) -> Task<Result<Navigated>> {
17140 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17141 }
17142
17143 pub fn go_to_implementation_split(
17144 &mut self,
17145 _: &GoToImplementationSplit,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) -> Task<Result<Navigated>> {
17149 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17150 }
17151
17152 pub fn go_to_type_definition(
17153 &mut self,
17154 _: &GoToTypeDefinition,
17155 window: &mut Window,
17156 cx: &mut Context<Self>,
17157 ) -> Task<Result<Navigated>> {
17158 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17159 }
17160
17161 pub fn go_to_definition_split(
17162 &mut self,
17163 _: &GoToDefinitionSplit,
17164 window: &mut Window,
17165 cx: &mut Context<Self>,
17166 ) -> Task<Result<Navigated>> {
17167 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17168 }
17169
17170 pub fn go_to_type_definition_split(
17171 &mut self,
17172 _: &GoToTypeDefinitionSplit,
17173 window: &mut Window,
17174 cx: &mut Context<Self>,
17175 ) -> Task<Result<Navigated>> {
17176 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17177 }
17178
17179 fn go_to_definition_of_kind(
17180 &mut self,
17181 kind: GotoDefinitionKind,
17182 split: bool,
17183 window: &mut Window,
17184 cx: &mut Context<Self>,
17185 ) -> Task<Result<Navigated>> {
17186 let Some(provider) = self.semantics_provider.clone() else {
17187 return Task::ready(Ok(Navigated::No));
17188 };
17189 let head = self
17190 .selections
17191 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17192 .head();
17193 let buffer = self.buffer.read(cx);
17194 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17195 return Task::ready(Ok(Navigated::No));
17196 };
17197 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17198 return Task::ready(Ok(Navigated::No));
17199 };
17200
17201 cx.spawn_in(window, async move |editor, cx| {
17202 let Some(definitions) = definitions.await? else {
17203 return Ok(Navigated::No);
17204 };
17205 let navigated = editor
17206 .update_in(cx, |editor, window, cx| {
17207 editor.navigate_to_hover_links(
17208 Some(kind),
17209 definitions
17210 .into_iter()
17211 .filter(|location| {
17212 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17213 })
17214 .map(HoverLink::Text)
17215 .collect::<Vec<_>>(),
17216 split,
17217 window,
17218 cx,
17219 )
17220 })?
17221 .await?;
17222 anyhow::Ok(navigated)
17223 })
17224 }
17225
17226 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17227 let selection = self.selections.newest_anchor();
17228 let head = selection.head();
17229 let tail = selection.tail();
17230
17231 let Some((buffer, start_position)) =
17232 self.buffer.read(cx).text_anchor_for_position(head, cx)
17233 else {
17234 return;
17235 };
17236
17237 let end_position = if head != tail {
17238 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17239 return;
17240 };
17241 Some(pos)
17242 } else {
17243 None
17244 };
17245
17246 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17247 let url = if let Some(end_pos) = end_position {
17248 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17249 } else {
17250 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17251 };
17252
17253 if let Some(url) = url {
17254 cx.update(|window, cx| {
17255 if parse_zed_link(&url, cx).is_some() {
17256 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17257 } else {
17258 cx.open_url(&url);
17259 }
17260 })?;
17261 }
17262
17263 anyhow::Ok(())
17264 });
17265
17266 url_finder.detach();
17267 }
17268
17269 pub fn open_selected_filename(
17270 &mut self,
17271 _: &OpenSelectedFilename,
17272 window: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) {
17275 let Some(workspace) = self.workspace() else {
17276 return;
17277 };
17278
17279 let position = self.selections.newest_anchor().head();
17280
17281 let Some((buffer, buffer_position)) =
17282 self.buffer.read(cx).text_anchor_for_position(position, cx)
17283 else {
17284 return;
17285 };
17286
17287 let project = self.project.clone();
17288
17289 cx.spawn_in(window, async move |_, cx| {
17290 let result = find_file(&buffer, project, buffer_position, cx).await;
17291
17292 if let Some((_, path)) = result {
17293 workspace
17294 .update_in(cx, |workspace, window, cx| {
17295 workspace.open_resolved_path(path, window, cx)
17296 })?
17297 .await?;
17298 }
17299 anyhow::Ok(())
17300 })
17301 .detach();
17302 }
17303
17304 pub(crate) fn navigate_to_hover_links(
17305 &mut self,
17306 kind: Option<GotoDefinitionKind>,
17307 definitions: Vec<HoverLink>,
17308 split: bool,
17309 window: &mut Window,
17310 cx: &mut Context<Editor>,
17311 ) -> Task<Result<Navigated>> {
17312 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17313 let mut first_url_or_file = None;
17314 let definitions: Vec<_> = definitions
17315 .into_iter()
17316 .filter_map(|def| match def {
17317 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17318 HoverLink::InlayHint(lsp_location, server_id) => {
17319 let computation =
17320 self.compute_target_location(lsp_location, server_id, window, cx);
17321 Some(cx.background_spawn(computation))
17322 }
17323 HoverLink::Url(url) => {
17324 first_url_or_file = Some(Either::Left(url));
17325 None
17326 }
17327 HoverLink::File(path) => {
17328 first_url_or_file = Some(Either::Right(path));
17329 None
17330 }
17331 })
17332 .collect();
17333
17334 let workspace = self.workspace();
17335
17336 cx.spawn_in(window, async move |editor, cx| {
17337 let locations: Vec<Location> = future::join_all(definitions)
17338 .await
17339 .into_iter()
17340 .filter_map(|location| location.transpose())
17341 .collect::<Result<_>>()
17342 .context("location tasks")?;
17343 let mut locations = cx.update(|_, cx| {
17344 locations
17345 .into_iter()
17346 .map(|location| {
17347 let buffer = location.buffer.read(cx);
17348 (location.buffer, location.range.to_point(buffer))
17349 })
17350 .into_group_map()
17351 })?;
17352 let mut num_locations = 0;
17353 for ranges in locations.values_mut() {
17354 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17355 ranges.dedup();
17356 num_locations += ranges.len();
17357 }
17358
17359 if num_locations > 1 {
17360 let tab_kind = match kind {
17361 Some(GotoDefinitionKind::Implementation) => "Implementations",
17362 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17363 Some(GotoDefinitionKind::Declaration) => "Declarations",
17364 Some(GotoDefinitionKind::Type) => "Types",
17365 };
17366 let title = editor
17367 .update_in(cx, |_, _, cx| {
17368 let target = locations
17369 .iter()
17370 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17371 .map(|(buffer, location)| {
17372 buffer
17373 .read(cx)
17374 .text_for_range(location.clone())
17375 .collect::<String>()
17376 })
17377 .filter(|text| !text.contains('\n'))
17378 .unique()
17379 .take(3)
17380 .join(", ");
17381 if target.is_empty() {
17382 tab_kind.to_owned()
17383 } else {
17384 format!("{tab_kind} for {target}")
17385 }
17386 })
17387 .context("buffer title")?;
17388
17389 let Some(workspace) = workspace else {
17390 return Ok(Navigated::No);
17391 };
17392
17393 let opened = workspace
17394 .update_in(cx, |workspace, window, cx| {
17395 let allow_preview = PreviewTabsSettings::get_global(cx)
17396 .enable_preview_multibuffer_from_code_navigation;
17397 Self::open_locations_in_multibuffer(
17398 workspace,
17399 locations,
17400 title,
17401 split,
17402 allow_preview,
17403 MultibufferSelectionMode::First,
17404 window,
17405 cx,
17406 )
17407 })
17408 .is_ok();
17409
17410 anyhow::Ok(Navigated::from_bool(opened))
17411 } else if num_locations == 0 {
17412 // If there is one url or file, open it directly
17413 match first_url_or_file {
17414 Some(Either::Left(url)) => {
17415 cx.update(|_, cx| cx.open_url(&url))?;
17416 Ok(Navigated::Yes)
17417 }
17418 Some(Either::Right(path)) => {
17419 // TODO(andrew): respect preview tab settings
17420 // `enable_keep_preview_on_code_navigation` and
17421 // `enable_preview_file_from_code_navigation`
17422 let Some(workspace) = workspace else {
17423 return Ok(Navigated::No);
17424 };
17425 workspace
17426 .update_in(cx, |workspace, window, cx| {
17427 workspace.open_resolved_path(path, window, cx)
17428 })?
17429 .await?;
17430 Ok(Navigated::Yes)
17431 }
17432 None => Ok(Navigated::No),
17433 }
17434 } else {
17435 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17436 let target_range = target_ranges.first().unwrap().clone();
17437
17438 editor.update_in(cx, |editor, window, cx| {
17439 let range = target_range.to_point(target_buffer.read(cx));
17440 let range = editor.range_for_match(&range);
17441 let range = collapse_multiline_range(range);
17442
17443 if !split
17444 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17445 {
17446 editor.go_to_singleton_buffer_range(range, window, cx);
17447 } else {
17448 let Some(workspace) = workspace else {
17449 return Navigated::No;
17450 };
17451 let pane = workspace.read(cx).active_pane().clone();
17452 window.defer(cx, move |window, cx| {
17453 let target_editor: Entity<Self> =
17454 workspace.update(cx, |workspace, cx| {
17455 let pane = if split {
17456 workspace.adjacent_pane(window, cx)
17457 } else {
17458 workspace.active_pane().clone()
17459 };
17460
17461 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17462 let keep_old_preview = preview_tabs_settings
17463 .enable_keep_preview_on_code_navigation;
17464 let allow_new_preview = preview_tabs_settings
17465 .enable_preview_file_from_code_navigation;
17466
17467 workspace.open_project_item(
17468 pane,
17469 target_buffer.clone(),
17470 true,
17471 true,
17472 keep_old_preview,
17473 allow_new_preview,
17474 window,
17475 cx,
17476 )
17477 });
17478 target_editor.update(cx, |target_editor, cx| {
17479 // When selecting a definition in a different buffer, disable the nav history
17480 // to avoid creating a history entry at the previous cursor location.
17481 pane.update(cx, |pane, _| pane.disable_history());
17482 target_editor.go_to_singleton_buffer_range(range, window, cx);
17483 pane.update(cx, |pane, _| pane.enable_history());
17484 });
17485 });
17486 }
17487 Navigated::Yes
17488 })
17489 }
17490 })
17491 }
17492
17493 fn compute_target_location(
17494 &self,
17495 lsp_location: lsp::Location,
17496 server_id: LanguageServerId,
17497 window: &mut Window,
17498 cx: &mut Context<Self>,
17499 ) -> Task<anyhow::Result<Option<Location>>> {
17500 let Some(project) = self.project.clone() else {
17501 return Task::ready(Ok(None));
17502 };
17503
17504 cx.spawn_in(window, async move |editor, cx| {
17505 let location_task = editor.update(cx, |_, cx| {
17506 project.update(cx, |project, cx| {
17507 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17508 })
17509 })?;
17510 let location = Some({
17511 let target_buffer_handle = location_task.await.context("open local buffer")?;
17512 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17513 let target_start = target_buffer
17514 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17515 let target_end = target_buffer
17516 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17517 target_buffer.anchor_after(target_start)
17518 ..target_buffer.anchor_before(target_end)
17519 })?;
17520 Location {
17521 buffer: target_buffer_handle,
17522 range,
17523 }
17524 });
17525 Ok(location)
17526 })
17527 }
17528
17529 fn go_to_next_reference(
17530 &mut self,
17531 _: &GoToNextReference,
17532 window: &mut Window,
17533 cx: &mut Context<Self>,
17534 ) {
17535 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17536 if let Some(task) = task {
17537 task.detach();
17538 };
17539 }
17540
17541 fn go_to_prev_reference(
17542 &mut self,
17543 _: &GoToPreviousReference,
17544 window: &mut Window,
17545 cx: &mut Context<Self>,
17546 ) {
17547 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17548 if let Some(task) = task {
17549 task.detach();
17550 };
17551 }
17552
17553 pub fn go_to_reference_before_or_after_position(
17554 &mut self,
17555 direction: Direction,
17556 count: usize,
17557 window: &mut Window,
17558 cx: &mut Context<Self>,
17559 ) -> Option<Task<Result<()>>> {
17560 let selection = self.selections.newest_anchor();
17561 let head = selection.head();
17562
17563 let multi_buffer = self.buffer.read(cx);
17564
17565 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17566 let workspace = self.workspace()?;
17567 let project = workspace.read(cx).project().clone();
17568 let references =
17569 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17570 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17571 let Some(locations) = references.await? else {
17572 return Ok(());
17573 };
17574
17575 if locations.is_empty() {
17576 // totally normal - the cursor may be on something which is not
17577 // a symbol (e.g. a keyword)
17578 log::info!("no references found under cursor");
17579 return Ok(());
17580 }
17581
17582 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17583
17584 let (locations, current_location_index) =
17585 multi_buffer.update(cx, |multi_buffer, cx| {
17586 let mut locations = locations
17587 .into_iter()
17588 .filter_map(|loc| {
17589 let start = multi_buffer.buffer_anchor_to_anchor(
17590 &loc.buffer,
17591 loc.range.start,
17592 cx,
17593 )?;
17594 let end = multi_buffer.buffer_anchor_to_anchor(
17595 &loc.buffer,
17596 loc.range.end,
17597 cx,
17598 )?;
17599 Some(start..end)
17600 })
17601 .collect::<Vec<_>>();
17602
17603 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17604 // There is an O(n) implementation, but given this list will be
17605 // small (usually <100 items), the extra O(log(n)) factor isn't
17606 // worth the (surprisingly large amount of) extra complexity.
17607 locations
17608 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17609
17610 let head_offset = head.to_offset(&multi_buffer_snapshot);
17611
17612 let current_location_index = locations.iter().position(|loc| {
17613 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17614 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17615 });
17616
17617 (locations, current_location_index)
17618 })?;
17619
17620 let Some(current_location_index) = current_location_index else {
17621 // This indicates something has gone wrong, because we already
17622 // handle the "no references" case above
17623 log::error!(
17624 "failed to find current reference under cursor. Total references: {}",
17625 locations.len()
17626 );
17627 return Ok(());
17628 };
17629
17630 let destination_location_index = match direction {
17631 Direction::Next => (current_location_index + count) % locations.len(),
17632 Direction::Prev => {
17633 (current_location_index + locations.len() - count % locations.len())
17634 % locations.len()
17635 }
17636 };
17637
17638 // TODO(cameron): is this needed?
17639 // the thinking is to avoid "jumping to the current location" (avoid
17640 // polluting "jumplist" in vim terms)
17641 if current_location_index == destination_location_index {
17642 return Ok(());
17643 }
17644
17645 let Range { start, end } = locations[destination_location_index];
17646
17647 editor.update_in(cx, |editor, window, cx| {
17648 let effects = SelectionEffects::default();
17649
17650 editor.unfold_ranges(&[start..end], false, false, cx);
17651 editor.change_selections(effects, window, cx, |s| {
17652 s.select_ranges([start..start]);
17653 });
17654 })?;
17655
17656 Ok(())
17657 }))
17658 }
17659
17660 pub fn find_all_references(
17661 &mut self,
17662 action: &FindAllReferences,
17663 window: &mut Window,
17664 cx: &mut Context<Self>,
17665 ) -> Option<Task<Result<Navigated>>> {
17666 let always_open_multibuffer = action.always_open_multibuffer;
17667 let selection = self.selections.newest_anchor();
17668 let multi_buffer = self.buffer.read(cx);
17669 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17670 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17671 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17672 let head = selection_offset.head();
17673
17674 let head_anchor = multi_buffer_snapshot.anchor_at(
17675 head,
17676 if head < selection_offset.tail() {
17677 Bias::Right
17678 } else {
17679 Bias::Left
17680 },
17681 );
17682
17683 match self
17684 .find_all_references_task_sources
17685 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17686 {
17687 Ok(_) => {
17688 log::info!(
17689 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17690 );
17691 return None;
17692 }
17693 Err(i) => {
17694 self.find_all_references_task_sources.insert(i, head_anchor);
17695 }
17696 }
17697
17698 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17699 let workspace = self.workspace()?;
17700 let project = workspace.read(cx).project().clone();
17701 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17702 Some(cx.spawn_in(window, async move |editor, cx| {
17703 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17704 if let Ok(i) = editor
17705 .find_all_references_task_sources
17706 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17707 {
17708 editor.find_all_references_task_sources.remove(i);
17709 }
17710 });
17711
17712 let Some(locations) = references.await? else {
17713 return anyhow::Ok(Navigated::No);
17714 };
17715 let mut locations = cx.update(|_, cx| {
17716 locations
17717 .into_iter()
17718 .map(|location| {
17719 let buffer = location.buffer.read(cx);
17720 (location.buffer, location.range.to_point(buffer))
17721 })
17722 // if special-casing the single-match case, remove ranges
17723 // that intersect current selection
17724 .filter(|(location_buffer, location)| {
17725 if always_open_multibuffer || &buffer != location_buffer {
17726 return true;
17727 }
17728
17729 !location.contains_inclusive(&selection_point.range())
17730 })
17731 .into_group_map()
17732 })?;
17733 if locations.is_empty() {
17734 return anyhow::Ok(Navigated::No);
17735 }
17736 for ranges in locations.values_mut() {
17737 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17738 ranges.dedup();
17739 }
17740 let mut num_locations = 0;
17741 for ranges in locations.values_mut() {
17742 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17743 ranges.dedup();
17744 num_locations += ranges.len();
17745 }
17746
17747 if num_locations == 1 && !always_open_multibuffer {
17748 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17749 let target_range = target_ranges.first().unwrap().clone();
17750
17751 return editor.update_in(cx, |editor, window, cx| {
17752 let range = target_range.to_point(target_buffer.read(cx));
17753 let range = editor.range_for_match(&range);
17754 let range = range.start..range.start;
17755
17756 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17757 editor.go_to_singleton_buffer_range(range, window, cx);
17758 } else {
17759 let pane = workspace.read(cx).active_pane().clone();
17760 window.defer(cx, move |window, cx| {
17761 let target_editor: Entity<Self> =
17762 workspace.update(cx, |workspace, cx| {
17763 let pane = workspace.active_pane().clone();
17764
17765 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17766 let keep_old_preview = preview_tabs_settings
17767 .enable_keep_preview_on_code_navigation;
17768 let allow_new_preview = preview_tabs_settings
17769 .enable_preview_file_from_code_navigation;
17770
17771 workspace.open_project_item(
17772 pane,
17773 target_buffer.clone(),
17774 true,
17775 true,
17776 keep_old_preview,
17777 allow_new_preview,
17778 window,
17779 cx,
17780 )
17781 });
17782 target_editor.update(cx, |target_editor, cx| {
17783 // When selecting a definition in a different buffer, disable the nav history
17784 // to avoid creating a history entry at the previous cursor location.
17785 pane.update(cx, |pane, _| pane.disable_history());
17786 target_editor.go_to_singleton_buffer_range(range, window, cx);
17787 pane.update(cx, |pane, _| pane.enable_history());
17788 });
17789 });
17790 }
17791 Navigated::No
17792 });
17793 }
17794
17795 workspace.update_in(cx, |workspace, window, cx| {
17796 let target = locations
17797 .iter()
17798 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17799 .map(|(buffer, location)| {
17800 buffer
17801 .read(cx)
17802 .text_for_range(location.clone())
17803 .collect::<String>()
17804 })
17805 .filter(|text| !text.contains('\n'))
17806 .unique()
17807 .take(3)
17808 .join(", ");
17809 let title = if target.is_empty() {
17810 "References".to_owned()
17811 } else {
17812 format!("References to {target}")
17813 };
17814 let allow_preview = PreviewTabsSettings::get_global(cx)
17815 .enable_preview_multibuffer_from_code_navigation;
17816 Self::open_locations_in_multibuffer(
17817 workspace,
17818 locations,
17819 title,
17820 false,
17821 allow_preview,
17822 MultibufferSelectionMode::First,
17823 window,
17824 cx,
17825 );
17826 Navigated::Yes
17827 })
17828 }))
17829 }
17830
17831 /// Opens a multibuffer with the given project locations in it.
17832 pub fn open_locations_in_multibuffer(
17833 workspace: &mut Workspace,
17834 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17835 title: String,
17836 split: bool,
17837 allow_preview: bool,
17838 multibuffer_selection_mode: MultibufferSelectionMode,
17839 window: &mut Window,
17840 cx: &mut Context<Workspace>,
17841 ) {
17842 if locations.is_empty() {
17843 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17844 return;
17845 }
17846
17847 let capability = workspace.project().read(cx).capability();
17848 let mut ranges = <Vec<Range<Anchor>>>::new();
17849
17850 // a key to find existing multibuffer editors with the same set of locations
17851 // to prevent us from opening more and more multibuffer tabs for searches and the like
17852 let mut key = (title.clone(), vec![]);
17853 let excerpt_buffer = cx.new(|cx| {
17854 let key = &mut key.1;
17855 let mut multibuffer = MultiBuffer::new(capability);
17856 for (buffer, mut ranges_for_buffer) in locations {
17857 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17858 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17859 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17860 PathKey::for_buffer(&buffer, cx),
17861 buffer.clone(),
17862 ranges_for_buffer,
17863 multibuffer_context_lines(cx),
17864 cx,
17865 );
17866 ranges.extend(new_ranges)
17867 }
17868
17869 multibuffer.with_title(title)
17870 });
17871 let existing = workspace.active_pane().update(cx, |pane, cx| {
17872 pane.items()
17873 .filter_map(|item| item.downcast::<Editor>())
17874 .find(|editor| {
17875 editor
17876 .read(cx)
17877 .lookup_key
17878 .as_ref()
17879 .and_then(|it| {
17880 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17881 })
17882 .is_some_and(|it| *it == key)
17883 })
17884 });
17885 let was_existing = existing.is_some();
17886 let editor = existing.unwrap_or_else(|| {
17887 cx.new(|cx| {
17888 let mut editor = Editor::for_multibuffer(
17889 excerpt_buffer,
17890 Some(workspace.project().clone()),
17891 window,
17892 cx,
17893 );
17894 editor.lookup_key = Some(Box::new(key));
17895 editor
17896 })
17897 });
17898 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17899 MultibufferSelectionMode::First => {
17900 if let Some(first_range) = ranges.first() {
17901 editor.change_selections(
17902 SelectionEffects::no_scroll(),
17903 window,
17904 cx,
17905 |selections| {
17906 selections.clear_disjoint();
17907 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17908 },
17909 );
17910 }
17911 editor.highlight_background::<Self>(
17912 &ranges,
17913 |_, theme| theme.colors().editor_highlighted_line_background,
17914 cx,
17915 );
17916 }
17917 MultibufferSelectionMode::All => {
17918 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17919 selections.clear_disjoint();
17920 selections.select_anchor_ranges(ranges);
17921 });
17922 }
17923 });
17924
17925 let item = Box::new(editor);
17926
17927 let pane = if split {
17928 workspace.adjacent_pane(window, cx)
17929 } else {
17930 workspace.active_pane().clone()
17931 };
17932 let activate_pane = split;
17933
17934 let mut destination_index = None;
17935 pane.update(cx, |pane, cx| {
17936 if allow_preview && !was_existing {
17937 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17938 }
17939 if was_existing && !allow_preview {
17940 pane.unpreview_item_if_preview(item.item_id());
17941 }
17942 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17943 });
17944 }
17945
17946 pub fn rename(
17947 &mut self,
17948 _: &Rename,
17949 window: &mut Window,
17950 cx: &mut Context<Self>,
17951 ) -> Option<Task<Result<()>>> {
17952 use language::ToOffset as _;
17953
17954 let provider = self.semantics_provider.clone()?;
17955 let selection = self.selections.newest_anchor().clone();
17956 let (cursor_buffer, cursor_buffer_position) = self
17957 .buffer
17958 .read(cx)
17959 .text_anchor_for_position(selection.head(), cx)?;
17960 let (tail_buffer, cursor_buffer_position_end) = self
17961 .buffer
17962 .read(cx)
17963 .text_anchor_for_position(selection.tail(), cx)?;
17964 if tail_buffer != cursor_buffer {
17965 return None;
17966 }
17967
17968 let snapshot = cursor_buffer.read(cx).snapshot();
17969 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17970 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17971 let prepare_rename = provider
17972 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17973 .unwrap_or_else(|| Task::ready(Ok(None)));
17974 drop(snapshot);
17975
17976 Some(cx.spawn_in(window, async move |this, cx| {
17977 let rename_range = if let Some(range) = prepare_rename.await? {
17978 Some(range)
17979 } else {
17980 this.update(cx, |this, cx| {
17981 let buffer = this.buffer.read(cx).snapshot(cx);
17982 let mut buffer_highlights = this
17983 .document_highlights_for_position(selection.head(), &buffer)
17984 .filter(|highlight| {
17985 highlight.start.excerpt_id == selection.head().excerpt_id
17986 && highlight.end.excerpt_id == selection.head().excerpt_id
17987 });
17988 buffer_highlights
17989 .next()
17990 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17991 })?
17992 };
17993 if let Some(rename_range) = rename_range {
17994 this.update_in(cx, |this, window, cx| {
17995 let snapshot = cursor_buffer.read(cx).snapshot();
17996 let rename_buffer_range = rename_range.to_offset(&snapshot);
17997 let cursor_offset_in_rename_range =
17998 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17999 let cursor_offset_in_rename_range_end =
18000 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18001
18002 this.take_rename(false, window, cx);
18003 let buffer = this.buffer.read(cx).read(cx);
18004 let cursor_offset = selection.head().to_offset(&buffer);
18005 let rename_start =
18006 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18007 let rename_end = rename_start + rename_buffer_range.len();
18008 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18009 let mut old_highlight_id = None;
18010 let old_name: Arc<str> = buffer
18011 .chunks(rename_start..rename_end, true)
18012 .map(|chunk| {
18013 if old_highlight_id.is_none() {
18014 old_highlight_id = chunk.syntax_highlight_id;
18015 }
18016 chunk.text
18017 })
18018 .collect::<String>()
18019 .into();
18020
18021 drop(buffer);
18022
18023 // Position the selection in the rename editor so that it matches the current selection.
18024 this.show_local_selections = false;
18025 let rename_editor = cx.new(|cx| {
18026 let mut editor = Editor::single_line(window, cx);
18027 editor.buffer.update(cx, |buffer, cx| {
18028 buffer.edit(
18029 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18030 None,
18031 cx,
18032 )
18033 });
18034 let cursor_offset_in_rename_range =
18035 MultiBufferOffset(cursor_offset_in_rename_range);
18036 let cursor_offset_in_rename_range_end =
18037 MultiBufferOffset(cursor_offset_in_rename_range_end);
18038 let rename_selection_range = match cursor_offset_in_rename_range
18039 .cmp(&cursor_offset_in_rename_range_end)
18040 {
18041 Ordering::Equal => {
18042 editor.select_all(&SelectAll, window, cx);
18043 return editor;
18044 }
18045 Ordering::Less => {
18046 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18047 }
18048 Ordering::Greater => {
18049 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18050 }
18051 };
18052 if rename_selection_range.end.0 > old_name.len() {
18053 editor.select_all(&SelectAll, window, cx);
18054 } else {
18055 editor.change_selections(Default::default(), window, cx, |s| {
18056 s.select_ranges([rename_selection_range]);
18057 });
18058 }
18059 editor
18060 });
18061 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18062 if e == &EditorEvent::Focused {
18063 cx.emit(EditorEvent::FocusedIn)
18064 }
18065 })
18066 .detach();
18067
18068 let write_highlights =
18069 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18070 let read_highlights =
18071 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18072 let ranges = write_highlights
18073 .iter()
18074 .flat_map(|(_, ranges)| ranges.iter())
18075 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18076 .cloned()
18077 .collect();
18078
18079 this.highlight_text::<Rename>(
18080 ranges,
18081 HighlightStyle {
18082 fade_out: Some(0.6),
18083 ..Default::default()
18084 },
18085 cx,
18086 );
18087 let rename_focus_handle = rename_editor.focus_handle(cx);
18088 window.focus(&rename_focus_handle);
18089 let block_id = this.insert_blocks(
18090 [BlockProperties {
18091 style: BlockStyle::Flex,
18092 placement: BlockPlacement::Below(range.start),
18093 height: Some(1),
18094 render: Arc::new({
18095 let rename_editor = rename_editor.clone();
18096 move |cx: &mut BlockContext| {
18097 let mut text_style = cx.editor_style.text.clone();
18098 if let Some(highlight_style) = old_highlight_id
18099 .and_then(|h| h.style(&cx.editor_style.syntax))
18100 {
18101 text_style = text_style.highlight(highlight_style);
18102 }
18103 div()
18104 .block_mouse_except_scroll()
18105 .pl(cx.anchor_x)
18106 .child(EditorElement::new(
18107 &rename_editor,
18108 EditorStyle {
18109 background: cx.theme().system().transparent,
18110 local_player: cx.editor_style.local_player,
18111 text: text_style,
18112 scrollbar_width: cx.editor_style.scrollbar_width,
18113 syntax: cx.editor_style.syntax.clone(),
18114 status: cx.editor_style.status.clone(),
18115 inlay_hints_style: HighlightStyle {
18116 font_weight: Some(FontWeight::BOLD),
18117 ..make_inlay_hints_style(cx.app)
18118 },
18119 edit_prediction_styles: make_suggestion_styles(
18120 cx.app,
18121 ),
18122 ..EditorStyle::default()
18123 },
18124 ))
18125 .into_any_element()
18126 }
18127 }),
18128 priority: 0,
18129 }],
18130 Some(Autoscroll::fit()),
18131 cx,
18132 )[0];
18133 this.pending_rename = Some(RenameState {
18134 range,
18135 old_name,
18136 editor: rename_editor,
18137 block_id,
18138 });
18139 })?;
18140 }
18141
18142 Ok(())
18143 }))
18144 }
18145
18146 pub fn confirm_rename(
18147 &mut self,
18148 _: &ConfirmRename,
18149 window: &mut Window,
18150 cx: &mut Context<Self>,
18151 ) -> Option<Task<Result<()>>> {
18152 let rename = self.take_rename(false, window, cx)?;
18153 let workspace = self.workspace()?.downgrade();
18154 let (buffer, start) = self
18155 .buffer
18156 .read(cx)
18157 .text_anchor_for_position(rename.range.start, cx)?;
18158 let (end_buffer, _) = self
18159 .buffer
18160 .read(cx)
18161 .text_anchor_for_position(rename.range.end, cx)?;
18162 if buffer != end_buffer {
18163 return None;
18164 }
18165
18166 let old_name = rename.old_name;
18167 let new_name = rename.editor.read(cx).text(cx);
18168
18169 let rename = self.semantics_provider.as_ref()?.perform_rename(
18170 &buffer,
18171 start,
18172 new_name.clone(),
18173 cx,
18174 )?;
18175
18176 Some(cx.spawn_in(window, async move |editor, cx| {
18177 let project_transaction = rename.await?;
18178 Self::open_project_transaction(
18179 &editor,
18180 workspace,
18181 project_transaction,
18182 format!("Rename: {} → {}", old_name, new_name),
18183 cx,
18184 )
18185 .await?;
18186
18187 editor.update(cx, |editor, cx| {
18188 editor.refresh_document_highlights(cx);
18189 })?;
18190 Ok(())
18191 }))
18192 }
18193
18194 fn take_rename(
18195 &mut self,
18196 moving_cursor: bool,
18197 window: &mut Window,
18198 cx: &mut Context<Self>,
18199 ) -> Option<RenameState> {
18200 let rename = self.pending_rename.take()?;
18201 if rename.editor.focus_handle(cx).is_focused(window) {
18202 window.focus(&self.focus_handle);
18203 }
18204
18205 self.remove_blocks(
18206 [rename.block_id].into_iter().collect(),
18207 Some(Autoscroll::fit()),
18208 cx,
18209 );
18210 self.clear_highlights::<Rename>(cx);
18211 self.show_local_selections = true;
18212
18213 if moving_cursor {
18214 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18215 editor
18216 .selections
18217 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18218 .head()
18219 });
18220
18221 // Update the selection to match the position of the selection inside
18222 // the rename editor.
18223 let snapshot = self.buffer.read(cx).read(cx);
18224 let rename_range = rename.range.to_offset(&snapshot);
18225 let cursor_in_editor = snapshot
18226 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18227 .min(rename_range.end);
18228 drop(snapshot);
18229
18230 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18231 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18232 });
18233 } else {
18234 self.refresh_document_highlights(cx);
18235 }
18236
18237 Some(rename)
18238 }
18239
18240 pub fn pending_rename(&self) -> Option<&RenameState> {
18241 self.pending_rename.as_ref()
18242 }
18243
18244 fn format(
18245 &mut self,
18246 _: &Format,
18247 window: &mut Window,
18248 cx: &mut Context<Self>,
18249 ) -> Option<Task<Result<()>>> {
18250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18251
18252 let project = match &self.project {
18253 Some(project) => project.clone(),
18254 None => return None,
18255 };
18256
18257 Some(self.perform_format(
18258 project,
18259 FormatTrigger::Manual,
18260 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18261 window,
18262 cx,
18263 ))
18264 }
18265
18266 fn format_selections(
18267 &mut self,
18268 _: &FormatSelections,
18269 window: &mut Window,
18270 cx: &mut Context<Self>,
18271 ) -> Option<Task<Result<()>>> {
18272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18273
18274 let project = match &self.project {
18275 Some(project) => project.clone(),
18276 None => return None,
18277 };
18278
18279 let ranges = self
18280 .selections
18281 .all_adjusted(&self.display_snapshot(cx))
18282 .into_iter()
18283 .map(|selection| selection.range())
18284 .collect_vec();
18285
18286 Some(self.perform_format(
18287 project,
18288 FormatTrigger::Manual,
18289 FormatTarget::Ranges(ranges),
18290 window,
18291 cx,
18292 ))
18293 }
18294
18295 fn perform_format(
18296 &mut self,
18297 project: Entity<Project>,
18298 trigger: FormatTrigger,
18299 target: FormatTarget,
18300 window: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) -> Task<Result<()>> {
18303 let buffer = self.buffer.clone();
18304 let (buffers, target) = match target {
18305 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18306 FormatTarget::Ranges(selection_ranges) => {
18307 let multi_buffer = buffer.read(cx);
18308 let snapshot = multi_buffer.read(cx);
18309 let mut buffers = HashSet::default();
18310 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18311 BTreeMap::new();
18312 for selection_range in selection_ranges {
18313 for (buffer, buffer_range, _) in
18314 snapshot.range_to_buffer_ranges(selection_range)
18315 {
18316 let buffer_id = buffer.remote_id();
18317 let start = buffer.anchor_before(buffer_range.start);
18318 let end = buffer.anchor_after(buffer_range.end);
18319 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18320 buffer_id_to_ranges
18321 .entry(buffer_id)
18322 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18323 .or_insert_with(|| vec![start..end]);
18324 }
18325 }
18326 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18327 }
18328 };
18329
18330 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18331 let selections_prev = transaction_id_prev
18332 .and_then(|transaction_id_prev| {
18333 // default to selections as they were after the last edit, if we have them,
18334 // instead of how they are now.
18335 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18336 // will take you back to where you made the last edit, instead of staying where you scrolled
18337 self.selection_history
18338 .transaction(transaction_id_prev)
18339 .map(|t| t.0.clone())
18340 })
18341 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18342
18343 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18344 let format = project.update(cx, |project, cx| {
18345 project.format(buffers, target, true, trigger, cx)
18346 });
18347
18348 cx.spawn_in(window, async move |editor, cx| {
18349 let transaction = futures::select_biased! {
18350 transaction = format.log_err().fuse() => transaction,
18351 () = timeout => {
18352 log::warn!("timed out waiting for formatting");
18353 None
18354 }
18355 };
18356
18357 buffer
18358 .update(cx, |buffer, cx| {
18359 if let Some(transaction) = transaction
18360 && !buffer.is_singleton()
18361 {
18362 buffer.push_transaction(&transaction.0, cx);
18363 }
18364 cx.notify();
18365 })
18366 .ok();
18367
18368 if let Some(transaction_id_now) =
18369 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18370 {
18371 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18372 if has_new_transaction {
18373 _ = editor.update(cx, |editor, _| {
18374 editor
18375 .selection_history
18376 .insert_transaction(transaction_id_now, selections_prev);
18377 });
18378 }
18379 }
18380
18381 Ok(())
18382 })
18383 }
18384
18385 fn organize_imports(
18386 &mut self,
18387 _: &OrganizeImports,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) -> Option<Task<Result<()>>> {
18391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18392 let project = match &self.project {
18393 Some(project) => project.clone(),
18394 None => return None,
18395 };
18396 Some(self.perform_code_action_kind(
18397 project,
18398 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18399 window,
18400 cx,
18401 ))
18402 }
18403
18404 fn perform_code_action_kind(
18405 &mut self,
18406 project: Entity<Project>,
18407 kind: CodeActionKind,
18408 window: &mut Window,
18409 cx: &mut Context<Self>,
18410 ) -> Task<Result<()>> {
18411 let buffer = self.buffer.clone();
18412 let buffers = buffer.read(cx).all_buffers();
18413 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18414 let apply_action = project.update(cx, |project, cx| {
18415 project.apply_code_action_kind(buffers, kind, true, cx)
18416 });
18417 cx.spawn_in(window, async move |_, cx| {
18418 let transaction = futures::select_biased! {
18419 () = timeout => {
18420 log::warn!("timed out waiting for executing code action");
18421 None
18422 }
18423 transaction = apply_action.log_err().fuse() => transaction,
18424 };
18425 buffer
18426 .update(cx, |buffer, cx| {
18427 // check if we need this
18428 if let Some(transaction) = transaction
18429 && !buffer.is_singleton()
18430 {
18431 buffer.push_transaction(&transaction.0, cx);
18432 }
18433 cx.notify();
18434 })
18435 .ok();
18436 Ok(())
18437 })
18438 }
18439
18440 pub fn restart_language_server(
18441 &mut self,
18442 _: &RestartLanguageServer,
18443 _: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) {
18446 if let Some(project) = self.project.clone() {
18447 self.buffer.update(cx, |multi_buffer, cx| {
18448 project.update(cx, |project, cx| {
18449 project.restart_language_servers_for_buffers(
18450 multi_buffer.all_buffers().into_iter().collect(),
18451 HashSet::default(),
18452 cx,
18453 );
18454 });
18455 })
18456 }
18457 }
18458
18459 pub fn stop_language_server(
18460 &mut self,
18461 _: &StopLanguageServer,
18462 _: &mut Window,
18463 cx: &mut Context<Self>,
18464 ) {
18465 if let Some(project) = self.project.clone() {
18466 self.buffer.update(cx, |multi_buffer, cx| {
18467 project.update(cx, |project, cx| {
18468 project.stop_language_servers_for_buffers(
18469 multi_buffer.all_buffers().into_iter().collect(),
18470 HashSet::default(),
18471 cx,
18472 );
18473 });
18474 });
18475 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18476 }
18477 }
18478
18479 fn cancel_language_server_work(
18480 workspace: &mut Workspace,
18481 _: &actions::CancelLanguageServerWork,
18482 _: &mut Window,
18483 cx: &mut Context<Workspace>,
18484 ) {
18485 let project = workspace.project();
18486 let buffers = workspace
18487 .active_item(cx)
18488 .and_then(|item| item.act_as::<Editor>(cx))
18489 .map_or(HashSet::default(), |editor| {
18490 editor.read(cx).buffer.read(cx).all_buffers()
18491 });
18492 project.update(cx, |project, cx| {
18493 project.cancel_language_server_work_for_buffers(buffers, cx);
18494 });
18495 }
18496
18497 fn show_character_palette(
18498 &mut self,
18499 _: &ShowCharacterPalette,
18500 window: &mut Window,
18501 _: &mut Context<Self>,
18502 ) {
18503 window.show_character_palette();
18504 }
18505
18506 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18507 if !self.diagnostics_enabled() {
18508 return;
18509 }
18510
18511 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18512 let buffer = self.buffer.read(cx).snapshot(cx);
18513 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18514 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18515 let is_valid = buffer
18516 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18517 .any(|entry| {
18518 entry.diagnostic.is_primary
18519 && !entry.range.is_empty()
18520 && entry.range.start == primary_range_start
18521 && entry.diagnostic.message == active_diagnostics.active_message
18522 });
18523
18524 if !is_valid {
18525 self.dismiss_diagnostics(cx);
18526 }
18527 }
18528 }
18529
18530 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18531 match &self.active_diagnostics {
18532 ActiveDiagnostic::Group(group) => Some(group),
18533 _ => None,
18534 }
18535 }
18536
18537 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18538 if !self.diagnostics_enabled() {
18539 return;
18540 }
18541 self.dismiss_diagnostics(cx);
18542 self.active_diagnostics = ActiveDiagnostic::All;
18543 }
18544
18545 fn activate_diagnostics(
18546 &mut self,
18547 buffer_id: BufferId,
18548 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18549 window: &mut Window,
18550 cx: &mut Context<Self>,
18551 ) {
18552 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18553 return;
18554 }
18555 self.dismiss_diagnostics(cx);
18556 let snapshot = self.snapshot(window, cx);
18557 let buffer = self.buffer.read(cx).snapshot(cx);
18558 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18559 return;
18560 };
18561
18562 let diagnostic_group = buffer
18563 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18564 .collect::<Vec<_>>();
18565
18566 let language_registry = self
18567 .project()
18568 .map(|project| project.read(cx).languages().clone());
18569
18570 let blocks = renderer.render_group(
18571 diagnostic_group,
18572 buffer_id,
18573 snapshot,
18574 cx.weak_entity(),
18575 language_registry,
18576 cx,
18577 );
18578
18579 let blocks = self.display_map.update(cx, |display_map, cx| {
18580 display_map.insert_blocks(blocks, cx).into_iter().collect()
18581 });
18582 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18583 active_range: buffer.anchor_before(diagnostic.range.start)
18584 ..buffer.anchor_after(diagnostic.range.end),
18585 active_message: diagnostic.diagnostic.message.clone(),
18586 group_id: diagnostic.diagnostic.group_id,
18587 blocks,
18588 });
18589 cx.notify();
18590 }
18591
18592 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18593 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18594 return;
18595 };
18596
18597 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18598 if let ActiveDiagnostic::Group(group) = prev {
18599 self.display_map.update(cx, |display_map, cx| {
18600 display_map.remove_blocks(group.blocks, cx);
18601 });
18602 cx.notify();
18603 }
18604 }
18605
18606 /// Disable inline diagnostics rendering for this editor.
18607 pub fn disable_inline_diagnostics(&mut self) {
18608 self.inline_diagnostics_enabled = false;
18609 self.inline_diagnostics_update = Task::ready(());
18610 self.inline_diagnostics.clear();
18611 }
18612
18613 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18614 self.diagnostics_enabled = false;
18615 self.dismiss_diagnostics(cx);
18616 self.inline_diagnostics_update = Task::ready(());
18617 self.inline_diagnostics.clear();
18618 }
18619
18620 pub fn disable_word_completions(&mut self) {
18621 self.word_completions_enabled = false;
18622 }
18623
18624 pub fn diagnostics_enabled(&self) -> bool {
18625 self.diagnostics_enabled && self.mode.is_full()
18626 }
18627
18628 pub fn inline_diagnostics_enabled(&self) -> bool {
18629 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18630 }
18631
18632 pub fn show_inline_diagnostics(&self) -> bool {
18633 self.show_inline_diagnostics
18634 }
18635
18636 pub fn toggle_inline_diagnostics(
18637 &mut self,
18638 _: &ToggleInlineDiagnostics,
18639 window: &mut Window,
18640 cx: &mut Context<Editor>,
18641 ) {
18642 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18643 self.refresh_inline_diagnostics(false, window, cx);
18644 }
18645
18646 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18647 self.diagnostics_max_severity = severity;
18648 self.display_map.update(cx, |display_map, _| {
18649 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18650 });
18651 }
18652
18653 pub fn toggle_diagnostics(
18654 &mut self,
18655 _: &ToggleDiagnostics,
18656 window: &mut Window,
18657 cx: &mut Context<Editor>,
18658 ) {
18659 if !self.diagnostics_enabled() {
18660 return;
18661 }
18662
18663 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18664 EditorSettings::get_global(cx)
18665 .diagnostics_max_severity
18666 .filter(|severity| severity != &DiagnosticSeverity::Off)
18667 .unwrap_or(DiagnosticSeverity::Hint)
18668 } else {
18669 DiagnosticSeverity::Off
18670 };
18671 self.set_max_diagnostics_severity(new_severity, cx);
18672 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18673 self.active_diagnostics = ActiveDiagnostic::None;
18674 self.inline_diagnostics_update = Task::ready(());
18675 self.inline_diagnostics.clear();
18676 } else {
18677 self.refresh_inline_diagnostics(false, window, cx);
18678 }
18679
18680 cx.notify();
18681 }
18682
18683 pub fn toggle_minimap(
18684 &mut self,
18685 _: &ToggleMinimap,
18686 window: &mut Window,
18687 cx: &mut Context<Editor>,
18688 ) {
18689 if self.supports_minimap(cx) {
18690 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18691 }
18692 }
18693
18694 fn refresh_inline_diagnostics(
18695 &mut self,
18696 debounce: bool,
18697 window: &mut Window,
18698 cx: &mut Context<Self>,
18699 ) {
18700 let max_severity = ProjectSettings::get_global(cx)
18701 .diagnostics
18702 .inline
18703 .max_severity
18704 .unwrap_or(self.diagnostics_max_severity);
18705
18706 if !self.inline_diagnostics_enabled()
18707 || !self.diagnostics_enabled()
18708 || !self.show_inline_diagnostics
18709 || max_severity == DiagnosticSeverity::Off
18710 {
18711 self.inline_diagnostics_update = Task::ready(());
18712 self.inline_diagnostics.clear();
18713 return;
18714 }
18715
18716 let debounce_ms = ProjectSettings::get_global(cx)
18717 .diagnostics
18718 .inline
18719 .update_debounce_ms;
18720 let debounce = if debounce && debounce_ms > 0 {
18721 Some(Duration::from_millis(debounce_ms))
18722 } else {
18723 None
18724 };
18725 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18726 if let Some(debounce) = debounce {
18727 cx.background_executor().timer(debounce).await;
18728 }
18729 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18730 editor
18731 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18732 .ok()
18733 }) else {
18734 return;
18735 };
18736
18737 let new_inline_diagnostics = cx
18738 .background_spawn(async move {
18739 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18740 for diagnostic_entry in
18741 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18742 {
18743 let message = diagnostic_entry
18744 .diagnostic
18745 .message
18746 .split_once('\n')
18747 .map(|(line, _)| line)
18748 .map(SharedString::new)
18749 .unwrap_or_else(|| {
18750 SharedString::new(&*diagnostic_entry.diagnostic.message)
18751 });
18752 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18753 let (Ok(i) | Err(i)) = inline_diagnostics
18754 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18755 inline_diagnostics.insert(
18756 i,
18757 (
18758 start_anchor,
18759 InlineDiagnostic {
18760 message,
18761 group_id: diagnostic_entry.diagnostic.group_id,
18762 start: diagnostic_entry.range.start.to_point(&snapshot),
18763 is_primary: diagnostic_entry.diagnostic.is_primary,
18764 severity: diagnostic_entry.diagnostic.severity,
18765 },
18766 ),
18767 );
18768 }
18769 inline_diagnostics
18770 })
18771 .await;
18772
18773 editor
18774 .update(cx, |editor, cx| {
18775 editor.inline_diagnostics = new_inline_diagnostics;
18776 cx.notify();
18777 })
18778 .ok();
18779 });
18780 }
18781
18782 fn pull_diagnostics(
18783 &mut self,
18784 buffer_id: Option<BufferId>,
18785 window: &Window,
18786 cx: &mut Context<Self>,
18787 ) -> Option<()> {
18788 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18789 return None;
18790 }
18791 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18792 .diagnostics
18793 .lsp_pull_diagnostics;
18794 if !pull_diagnostics_settings.enabled {
18795 return None;
18796 }
18797 let project = self.project()?.downgrade();
18798
18799 let mut edited_buffer_ids = HashSet::default();
18800 let mut edited_worktree_ids = HashSet::default();
18801 let edited_buffers = match buffer_id {
18802 Some(buffer_id) => {
18803 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18804 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18805 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18806 edited_worktree_ids.insert(worktree_id);
18807 vec![buffer]
18808 }
18809 None => self
18810 .buffer()
18811 .read(cx)
18812 .all_buffers()
18813 .into_iter()
18814 .filter(|buffer| {
18815 let buffer = buffer.read(cx);
18816 match buffer.file().map(|f| f.worktree_id(cx)) {
18817 Some(worktree_id) => {
18818 edited_buffer_ids.insert(buffer.remote_id());
18819 edited_worktree_ids.insert(worktree_id);
18820 true
18821 }
18822 None => false,
18823 }
18824 })
18825 .collect::<Vec<_>>(),
18826 };
18827
18828 if edited_buffers.is_empty() {
18829 self.pull_diagnostics_task = Task::ready(());
18830 self.pull_diagnostics_background_task = Task::ready(());
18831 return None;
18832 }
18833
18834 let mut already_used_buffers = HashSet::default();
18835 let related_open_buffers = self
18836 .workspace
18837 .as_ref()
18838 .and_then(|(workspace, _)| workspace.upgrade())
18839 .into_iter()
18840 .flat_map(|workspace| workspace.read(cx).panes())
18841 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18842 .filter(|editor| editor != &cx.entity())
18843 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18844 .filter(|buffer| {
18845 let buffer = buffer.read(cx);
18846 let buffer_id = buffer.remote_id();
18847 if already_used_buffers.insert(buffer_id) {
18848 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18849 return !edited_buffer_ids.contains(&buffer_id)
18850 && !edited_worktree_ids.contains(&worktree_id);
18851 }
18852 }
18853 false
18854 })
18855 .collect::<Vec<_>>();
18856
18857 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18858 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18859 if buffers.is_empty() {
18860 return Task::ready(());
18861 }
18862 let project_weak = project.clone();
18863 cx.spawn_in(window, async move |_, cx| {
18864 cx.background_executor().timer(delay).await;
18865
18866 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18867 buffers
18868 .into_iter()
18869 .filter_map(|buffer| {
18870 project_weak
18871 .update(cx, |project, cx| {
18872 project.lsp_store().update(cx, |lsp_store, cx| {
18873 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18874 })
18875 })
18876 .ok()
18877 })
18878 .collect::<FuturesUnordered<_>>()
18879 }) else {
18880 return;
18881 };
18882
18883 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18884 if let Err(e) = pull_task {
18885 log::error!("Failed to update project diagnostics: {e:#}");
18886 }
18887 }
18888 })
18889 };
18890
18891 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18892 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18893
18894 Some(())
18895 }
18896
18897 pub fn set_selections_from_remote(
18898 &mut self,
18899 selections: Vec<Selection<Anchor>>,
18900 pending_selection: Option<Selection<Anchor>>,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 let old_cursor_position = self.selections.newest_anchor().head();
18905 self.selections
18906 .change_with(&self.display_snapshot(cx), |s| {
18907 s.select_anchors(selections);
18908 if let Some(pending_selection) = pending_selection {
18909 s.set_pending(pending_selection, SelectMode::Character);
18910 } else {
18911 s.clear_pending();
18912 }
18913 });
18914 self.selections_did_change(
18915 false,
18916 &old_cursor_position,
18917 SelectionEffects::default(),
18918 window,
18919 cx,
18920 );
18921 }
18922
18923 pub fn transact(
18924 &mut self,
18925 window: &mut Window,
18926 cx: &mut Context<Self>,
18927 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18928 ) -> Option<TransactionId> {
18929 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18930 this.start_transaction_at(Instant::now(), window, cx);
18931 update(this, window, cx);
18932 this.end_transaction_at(Instant::now(), cx)
18933 })
18934 }
18935
18936 pub fn start_transaction_at(
18937 &mut self,
18938 now: Instant,
18939 window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) -> Option<TransactionId> {
18942 self.end_selection(window, cx);
18943 if let Some(tx_id) = self
18944 .buffer
18945 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18946 {
18947 self.selection_history
18948 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18949 cx.emit(EditorEvent::TransactionBegun {
18950 transaction_id: tx_id,
18951 });
18952 Some(tx_id)
18953 } else {
18954 None
18955 }
18956 }
18957
18958 pub fn end_transaction_at(
18959 &mut self,
18960 now: Instant,
18961 cx: &mut Context<Self>,
18962 ) -> Option<TransactionId> {
18963 if let Some(transaction_id) = self
18964 .buffer
18965 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18966 {
18967 if let Some((_, end_selections)) =
18968 self.selection_history.transaction_mut(transaction_id)
18969 {
18970 *end_selections = Some(self.selections.disjoint_anchors_arc());
18971 } else {
18972 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18973 }
18974
18975 cx.emit(EditorEvent::Edited { transaction_id });
18976 Some(transaction_id)
18977 } else {
18978 None
18979 }
18980 }
18981
18982 pub fn modify_transaction_selection_history(
18983 &mut self,
18984 transaction_id: TransactionId,
18985 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18986 ) -> bool {
18987 self.selection_history
18988 .transaction_mut(transaction_id)
18989 .map(modify)
18990 .is_some()
18991 }
18992
18993 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18994 if self.selection_mark_mode {
18995 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18996 s.move_with(|_, sel| {
18997 sel.collapse_to(sel.head(), SelectionGoal::None);
18998 });
18999 })
19000 }
19001 self.selection_mark_mode = true;
19002 cx.notify();
19003 }
19004
19005 pub fn swap_selection_ends(
19006 &mut self,
19007 _: &actions::SwapSelectionEnds,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19012 s.move_with(|_, sel| {
19013 if sel.start != sel.end {
19014 sel.reversed = !sel.reversed
19015 }
19016 });
19017 });
19018 self.request_autoscroll(Autoscroll::newest(), cx);
19019 cx.notify();
19020 }
19021
19022 pub fn toggle_focus(
19023 workspace: &mut Workspace,
19024 _: &actions::ToggleFocus,
19025 window: &mut Window,
19026 cx: &mut Context<Workspace>,
19027 ) {
19028 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19029 return;
19030 };
19031 workspace.activate_item(&item, true, true, window, cx);
19032 }
19033
19034 pub fn toggle_fold(
19035 &mut self,
19036 _: &actions::ToggleFold,
19037 window: &mut Window,
19038 cx: &mut Context<Self>,
19039 ) {
19040 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19042 let selection = self.selections.newest::<Point>(&display_map);
19043
19044 let range = if selection.is_empty() {
19045 let point = selection.head().to_display_point(&display_map);
19046 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19047 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19048 .to_point(&display_map);
19049 start..end
19050 } else {
19051 selection.range()
19052 };
19053 if display_map.folds_in_range(range).next().is_some() {
19054 self.unfold_lines(&Default::default(), window, cx)
19055 } else {
19056 self.fold(&Default::default(), window, cx)
19057 }
19058 } else {
19059 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19060 let buffer_ids: HashSet<_> = self
19061 .selections
19062 .disjoint_anchor_ranges()
19063 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19064 .collect();
19065
19066 let should_unfold = buffer_ids
19067 .iter()
19068 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19069
19070 for buffer_id in buffer_ids {
19071 if should_unfold {
19072 self.unfold_buffer(buffer_id, cx);
19073 } else {
19074 self.fold_buffer(buffer_id, cx);
19075 }
19076 }
19077 }
19078 }
19079
19080 pub fn toggle_fold_recursive(
19081 &mut self,
19082 _: &actions::ToggleFoldRecursive,
19083 window: &mut Window,
19084 cx: &mut Context<Self>,
19085 ) {
19086 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19087
19088 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19089 let range = if selection.is_empty() {
19090 let point = selection.head().to_display_point(&display_map);
19091 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19092 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19093 .to_point(&display_map);
19094 start..end
19095 } else {
19096 selection.range()
19097 };
19098 if display_map.folds_in_range(range).next().is_some() {
19099 self.unfold_recursive(&Default::default(), window, cx)
19100 } else {
19101 self.fold_recursive(&Default::default(), window, cx)
19102 }
19103 }
19104
19105 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19106 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19107 let mut to_fold = Vec::new();
19108 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19109 let selections = self.selections.all_adjusted(&display_map);
19110
19111 for selection in selections {
19112 let range = selection.range().sorted();
19113 let buffer_start_row = range.start.row;
19114
19115 if range.start.row != range.end.row {
19116 let mut found = false;
19117 let mut row = range.start.row;
19118 while row <= range.end.row {
19119 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19120 {
19121 found = true;
19122 row = crease.range().end.row + 1;
19123 to_fold.push(crease);
19124 } else {
19125 row += 1
19126 }
19127 }
19128 if found {
19129 continue;
19130 }
19131 }
19132
19133 for row in (0..=range.start.row).rev() {
19134 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19135 && crease.range().end.row >= buffer_start_row
19136 {
19137 to_fold.push(crease);
19138 if row <= range.start.row {
19139 break;
19140 }
19141 }
19142 }
19143 }
19144
19145 self.fold_creases(to_fold, true, window, cx);
19146 } else {
19147 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19148 let buffer_ids = self
19149 .selections
19150 .disjoint_anchor_ranges()
19151 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19152 .collect::<HashSet<_>>();
19153 for buffer_id in buffer_ids {
19154 self.fold_buffer(buffer_id, cx);
19155 }
19156 }
19157 }
19158
19159 pub fn toggle_fold_all(
19160 &mut self,
19161 _: &actions::ToggleFoldAll,
19162 window: &mut Window,
19163 cx: &mut Context<Self>,
19164 ) {
19165 if self.buffer.read(cx).is_singleton() {
19166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19167 let has_folds = display_map
19168 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19169 .next()
19170 .is_some();
19171
19172 if has_folds {
19173 self.unfold_all(&actions::UnfoldAll, window, cx);
19174 } else {
19175 self.fold_all(&actions::FoldAll, window, cx);
19176 }
19177 } else {
19178 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19179 let should_unfold = buffer_ids
19180 .iter()
19181 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19182
19183 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19184 editor
19185 .update_in(cx, |editor, _, cx| {
19186 for buffer_id in buffer_ids {
19187 if should_unfold {
19188 editor.unfold_buffer(buffer_id, cx);
19189 } else {
19190 editor.fold_buffer(buffer_id, cx);
19191 }
19192 }
19193 })
19194 .ok();
19195 });
19196 }
19197 }
19198
19199 fn fold_at_level(
19200 &mut self,
19201 fold_at: &FoldAtLevel,
19202 window: &mut Window,
19203 cx: &mut Context<Self>,
19204 ) {
19205 if !self.buffer.read(cx).is_singleton() {
19206 return;
19207 }
19208
19209 let fold_at_level = fold_at.0;
19210 let snapshot = self.buffer.read(cx).snapshot(cx);
19211 let mut to_fold = Vec::new();
19212 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19213
19214 let row_ranges_to_keep: Vec<Range<u32>> = self
19215 .selections
19216 .all::<Point>(&self.display_snapshot(cx))
19217 .into_iter()
19218 .map(|sel| sel.start.row..sel.end.row)
19219 .collect();
19220
19221 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19222 while start_row < end_row {
19223 match self
19224 .snapshot(window, cx)
19225 .crease_for_buffer_row(MultiBufferRow(start_row))
19226 {
19227 Some(crease) => {
19228 let nested_start_row = crease.range().start.row + 1;
19229 let nested_end_row = crease.range().end.row;
19230
19231 if current_level < fold_at_level {
19232 stack.push((nested_start_row, nested_end_row, current_level + 1));
19233 } else if current_level == fold_at_level {
19234 // Fold iff there is no selection completely contained within the fold region
19235 if !row_ranges_to_keep.iter().any(|selection| {
19236 selection.end >= nested_start_row
19237 && selection.start <= nested_end_row
19238 }) {
19239 to_fold.push(crease);
19240 }
19241 }
19242
19243 start_row = nested_end_row + 1;
19244 }
19245 None => start_row += 1,
19246 }
19247 }
19248 }
19249
19250 self.fold_creases(to_fold, true, window, cx);
19251 }
19252
19253 pub fn fold_at_level_1(
19254 &mut self,
19255 _: &actions::FoldAtLevel1,
19256 window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19260 }
19261
19262 pub fn fold_at_level_2(
19263 &mut self,
19264 _: &actions::FoldAtLevel2,
19265 window: &mut Window,
19266 cx: &mut Context<Self>,
19267 ) {
19268 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19269 }
19270
19271 pub fn fold_at_level_3(
19272 &mut self,
19273 _: &actions::FoldAtLevel3,
19274 window: &mut Window,
19275 cx: &mut Context<Self>,
19276 ) {
19277 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19278 }
19279
19280 pub fn fold_at_level_4(
19281 &mut self,
19282 _: &actions::FoldAtLevel4,
19283 window: &mut Window,
19284 cx: &mut Context<Self>,
19285 ) {
19286 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19287 }
19288
19289 pub fn fold_at_level_5(
19290 &mut self,
19291 _: &actions::FoldAtLevel5,
19292 window: &mut Window,
19293 cx: &mut Context<Self>,
19294 ) {
19295 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19296 }
19297
19298 pub fn fold_at_level_6(
19299 &mut self,
19300 _: &actions::FoldAtLevel6,
19301 window: &mut Window,
19302 cx: &mut Context<Self>,
19303 ) {
19304 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19305 }
19306
19307 pub fn fold_at_level_7(
19308 &mut self,
19309 _: &actions::FoldAtLevel7,
19310 window: &mut Window,
19311 cx: &mut Context<Self>,
19312 ) {
19313 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19314 }
19315
19316 pub fn fold_at_level_8(
19317 &mut self,
19318 _: &actions::FoldAtLevel8,
19319 window: &mut Window,
19320 cx: &mut Context<Self>,
19321 ) {
19322 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19323 }
19324
19325 pub fn fold_at_level_9(
19326 &mut self,
19327 _: &actions::FoldAtLevel9,
19328 window: &mut Window,
19329 cx: &mut Context<Self>,
19330 ) {
19331 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19332 }
19333
19334 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19335 if self.buffer.read(cx).is_singleton() {
19336 let mut fold_ranges = Vec::new();
19337 let snapshot = self.buffer.read(cx).snapshot(cx);
19338
19339 for row in 0..snapshot.max_row().0 {
19340 if let Some(foldable_range) = self
19341 .snapshot(window, cx)
19342 .crease_for_buffer_row(MultiBufferRow(row))
19343 {
19344 fold_ranges.push(foldable_range);
19345 }
19346 }
19347
19348 self.fold_creases(fold_ranges, true, window, cx);
19349 } else {
19350 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19351 editor
19352 .update_in(cx, |editor, _, cx| {
19353 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19354 editor.fold_buffer(buffer_id, cx);
19355 }
19356 })
19357 .ok();
19358 });
19359 }
19360 }
19361
19362 pub fn fold_function_bodies(
19363 &mut self,
19364 _: &actions::FoldFunctionBodies,
19365 window: &mut Window,
19366 cx: &mut Context<Self>,
19367 ) {
19368 let snapshot = self.buffer.read(cx).snapshot(cx);
19369
19370 let ranges = snapshot
19371 .text_object_ranges(
19372 MultiBufferOffset(0)..snapshot.len(),
19373 TreeSitterOptions::default(),
19374 )
19375 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19376 .collect::<Vec<_>>();
19377
19378 let creases = ranges
19379 .into_iter()
19380 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19381 .collect();
19382
19383 self.fold_creases(creases, true, window, cx);
19384 }
19385
19386 pub fn fold_recursive(
19387 &mut self,
19388 _: &actions::FoldRecursive,
19389 window: &mut Window,
19390 cx: &mut Context<Self>,
19391 ) {
19392 let mut to_fold = Vec::new();
19393 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19394 let selections = self.selections.all_adjusted(&display_map);
19395
19396 for selection in selections {
19397 let range = selection.range().sorted();
19398 let buffer_start_row = range.start.row;
19399
19400 if range.start.row != range.end.row {
19401 let mut found = false;
19402 for row in range.start.row..=range.end.row {
19403 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19404 found = true;
19405 to_fold.push(crease);
19406 }
19407 }
19408 if found {
19409 continue;
19410 }
19411 }
19412
19413 for row in (0..=range.start.row).rev() {
19414 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19415 if crease.range().end.row >= buffer_start_row {
19416 to_fold.push(crease);
19417 } else {
19418 break;
19419 }
19420 }
19421 }
19422 }
19423
19424 self.fold_creases(to_fold, true, window, cx);
19425 }
19426
19427 pub fn fold_at(
19428 &mut self,
19429 buffer_row: MultiBufferRow,
19430 window: &mut Window,
19431 cx: &mut Context<Self>,
19432 ) {
19433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19434
19435 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19436 let autoscroll = self
19437 .selections
19438 .all::<Point>(&display_map)
19439 .iter()
19440 .any(|selection| crease.range().overlaps(&selection.range()));
19441
19442 self.fold_creases(vec![crease], autoscroll, window, cx);
19443 }
19444 }
19445
19446 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19447 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19449 let buffer = display_map.buffer_snapshot();
19450 let selections = self.selections.all::<Point>(&display_map);
19451 let ranges = selections
19452 .iter()
19453 .map(|s| {
19454 let range = s.display_range(&display_map).sorted();
19455 let mut start = range.start.to_point(&display_map);
19456 let mut end = range.end.to_point(&display_map);
19457 start.column = 0;
19458 end.column = buffer.line_len(MultiBufferRow(end.row));
19459 start..end
19460 })
19461 .collect::<Vec<_>>();
19462
19463 self.unfold_ranges(&ranges, true, true, cx);
19464 } else {
19465 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19466 let buffer_ids = self
19467 .selections
19468 .disjoint_anchor_ranges()
19469 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19470 .collect::<HashSet<_>>();
19471 for buffer_id in buffer_ids {
19472 self.unfold_buffer(buffer_id, cx);
19473 }
19474 }
19475 }
19476
19477 pub fn unfold_recursive(
19478 &mut self,
19479 _: &UnfoldRecursive,
19480 _window: &mut Window,
19481 cx: &mut Context<Self>,
19482 ) {
19483 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19484 let selections = self.selections.all::<Point>(&display_map);
19485 let ranges = selections
19486 .iter()
19487 .map(|s| {
19488 let mut range = s.display_range(&display_map).sorted();
19489 *range.start.column_mut() = 0;
19490 *range.end.column_mut() = display_map.line_len(range.end.row());
19491 let start = range.start.to_point(&display_map);
19492 let end = range.end.to_point(&display_map);
19493 start..end
19494 })
19495 .collect::<Vec<_>>();
19496
19497 self.unfold_ranges(&ranges, true, true, cx);
19498 }
19499
19500 pub fn unfold_at(
19501 &mut self,
19502 buffer_row: MultiBufferRow,
19503 _window: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19507
19508 let intersection_range = Point::new(buffer_row.0, 0)
19509 ..Point::new(
19510 buffer_row.0,
19511 display_map.buffer_snapshot().line_len(buffer_row),
19512 );
19513
19514 let autoscroll = self
19515 .selections
19516 .all::<Point>(&display_map)
19517 .iter()
19518 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19519
19520 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19521 }
19522
19523 pub fn unfold_all(
19524 &mut self,
19525 _: &actions::UnfoldAll,
19526 _window: &mut Window,
19527 cx: &mut Context<Self>,
19528 ) {
19529 if self.buffer.read(cx).is_singleton() {
19530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19531 self.unfold_ranges(
19532 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19533 true,
19534 true,
19535 cx,
19536 );
19537 } else {
19538 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19539 editor
19540 .update(cx, |editor, cx| {
19541 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19542 editor.unfold_buffer(buffer_id, cx);
19543 }
19544 })
19545 .ok();
19546 });
19547 }
19548 }
19549
19550 pub fn fold_selected_ranges(
19551 &mut self,
19552 _: &FoldSelectedRanges,
19553 window: &mut Window,
19554 cx: &mut Context<Self>,
19555 ) {
19556 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19557 let selections = self.selections.all_adjusted(&display_map);
19558 let ranges = selections
19559 .into_iter()
19560 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19561 .collect::<Vec<_>>();
19562 self.fold_creases(ranges, true, window, cx);
19563 }
19564
19565 pub fn fold_ranges<T: ToOffset + Clone>(
19566 &mut self,
19567 ranges: Vec<Range<T>>,
19568 auto_scroll: bool,
19569 window: &mut Window,
19570 cx: &mut Context<Self>,
19571 ) {
19572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19573 let ranges = ranges
19574 .into_iter()
19575 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19576 .collect::<Vec<_>>();
19577 self.fold_creases(ranges, auto_scroll, window, cx);
19578 }
19579
19580 pub fn fold_creases<T: ToOffset + Clone>(
19581 &mut self,
19582 creases: Vec<Crease<T>>,
19583 auto_scroll: bool,
19584 _window: &mut Window,
19585 cx: &mut Context<Self>,
19586 ) {
19587 if creases.is_empty() {
19588 return;
19589 }
19590
19591 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19592
19593 if auto_scroll {
19594 self.request_autoscroll(Autoscroll::fit(), cx);
19595 }
19596
19597 cx.notify();
19598
19599 self.scrollbar_marker_state.dirty = true;
19600 self.folds_did_change(cx);
19601 }
19602
19603 /// Removes any folds whose ranges intersect any of the given ranges.
19604 pub fn unfold_ranges<T: ToOffset + Clone>(
19605 &mut self,
19606 ranges: &[Range<T>],
19607 inclusive: bool,
19608 auto_scroll: bool,
19609 cx: &mut Context<Self>,
19610 ) {
19611 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19612 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19613 });
19614 self.folds_did_change(cx);
19615 }
19616
19617 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19618 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19619 return;
19620 }
19621
19622 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19623 self.display_map.update(cx, |display_map, cx| {
19624 display_map.fold_buffers([buffer_id], cx)
19625 });
19626
19627 let snapshot = self.display_snapshot(cx);
19628 self.selections.change_with(&snapshot, |selections| {
19629 selections.remove_selections_from_buffer(buffer_id);
19630 });
19631
19632 cx.emit(EditorEvent::BufferFoldToggled {
19633 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19634 folded: true,
19635 });
19636 cx.notify();
19637 }
19638
19639 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19640 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19641 return;
19642 }
19643 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19644 self.display_map.update(cx, |display_map, cx| {
19645 display_map.unfold_buffers([buffer_id], cx);
19646 });
19647 cx.emit(EditorEvent::BufferFoldToggled {
19648 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19649 folded: false,
19650 });
19651 cx.notify();
19652 }
19653
19654 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19655 self.display_map.read(cx).is_buffer_folded(buffer)
19656 }
19657
19658 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19659 self.display_map.read(cx).folded_buffers()
19660 }
19661
19662 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19663 self.display_map.update(cx, |display_map, cx| {
19664 display_map.disable_header_for_buffer(buffer_id, cx);
19665 });
19666 cx.notify();
19667 }
19668
19669 /// Removes any folds with the given ranges.
19670 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19671 &mut self,
19672 ranges: &[Range<T>],
19673 type_id: TypeId,
19674 auto_scroll: bool,
19675 cx: &mut Context<Self>,
19676 ) {
19677 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19678 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19679 });
19680 self.folds_did_change(cx);
19681 }
19682
19683 fn remove_folds_with<T: ToOffset + Clone>(
19684 &mut self,
19685 ranges: &[Range<T>],
19686 auto_scroll: bool,
19687 cx: &mut Context<Self>,
19688 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19689 ) {
19690 if ranges.is_empty() {
19691 return;
19692 }
19693
19694 let mut buffers_affected = HashSet::default();
19695 let multi_buffer = self.buffer().read(cx);
19696 for range in ranges {
19697 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19698 buffers_affected.insert(buffer.read(cx).remote_id());
19699 };
19700 }
19701
19702 self.display_map.update(cx, update);
19703
19704 if auto_scroll {
19705 self.request_autoscroll(Autoscroll::fit(), cx);
19706 }
19707
19708 cx.notify();
19709 self.scrollbar_marker_state.dirty = true;
19710 self.active_indent_guides_state.dirty = true;
19711 }
19712
19713 pub fn update_renderer_widths(
19714 &mut self,
19715 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19716 cx: &mut Context<Self>,
19717 ) -> bool {
19718 self.display_map
19719 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19720 }
19721
19722 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19723 self.display_map.read(cx).fold_placeholder.clone()
19724 }
19725
19726 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19727 self.use_base_text_line_numbers = show;
19728 }
19729
19730 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19731 self.buffer.update(cx, |buffer, cx| {
19732 buffer.set_all_diff_hunks_expanded(cx);
19733 });
19734 }
19735
19736 pub fn expand_all_diff_hunks(
19737 &mut self,
19738 _: &ExpandAllDiffHunks,
19739 _window: &mut Window,
19740 cx: &mut Context<Self>,
19741 ) {
19742 self.buffer.update(cx, |buffer, cx| {
19743 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19744 });
19745 }
19746
19747 pub fn collapse_all_diff_hunks(
19748 &mut self,
19749 _: &CollapseAllDiffHunks,
19750 _window: &mut Window,
19751 cx: &mut Context<Self>,
19752 ) {
19753 self.buffer.update(cx, |buffer, cx| {
19754 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19755 });
19756 }
19757
19758 pub fn toggle_selected_diff_hunks(
19759 &mut self,
19760 _: &ToggleSelectedDiffHunks,
19761 _window: &mut Window,
19762 cx: &mut Context<Self>,
19763 ) {
19764 let ranges: Vec<_> = self
19765 .selections
19766 .disjoint_anchors()
19767 .iter()
19768 .map(|s| s.range())
19769 .collect();
19770 self.toggle_diff_hunks_in_ranges(ranges, cx);
19771 }
19772
19773 pub fn diff_hunks_in_ranges<'a>(
19774 &'a self,
19775 ranges: &'a [Range<Anchor>],
19776 buffer: &'a MultiBufferSnapshot,
19777 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19778 ranges.iter().flat_map(move |range| {
19779 let end_excerpt_id = range.end.excerpt_id;
19780 let range = range.to_point(buffer);
19781 let mut peek_end = range.end;
19782 if range.end.row < buffer.max_row().0 {
19783 peek_end = Point::new(range.end.row + 1, 0);
19784 }
19785 buffer
19786 .diff_hunks_in_range(range.start..peek_end)
19787 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19788 })
19789 }
19790
19791 pub fn has_stageable_diff_hunks_in_ranges(
19792 &self,
19793 ranges: &[Range<Anchor>],
19794 snapshot: &MultiBufferSnapshot,
19795 ) -> bool {
19796 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19797 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19798 }
19799
19800 pub fn toggle_staged_selected_diff_hunks(
19801 &mut self,
19802 _: &::git::ToggleStaged,
19803 _: &mut Window,
19804 cx: &mut Context<Self>,
19805 ) {
19806 let snapshot = self.buffer.read(cx).snapshot(cx);
19807 let ranges: Vec<_> = self
19808 .selections
19809 .disjoint_anchors()
19810 .iter()
19811 .map(|s| s.range())
19812 .collect();
19813 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19814 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19815 }
19816
19817 pub fn set_render_diff_hunk_controls(
19818 &mut self,
19819 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19820 cx: &mut Context<Self>,
19821 ) {
19822 self.render_diff_hunk_controls = render_diff_hunk_controls;
19823 cx.notify();
19824 }
19825
19826 pub fn stage_and_next(
19827 &mut self,
19828 _: &::git::StageAndNext,
19829 window: &mut Window,
19830 cx: &mut Context<Self>,
19831 ) {
19832 self.do_stage_or_unstage_and_next(true, window, cx);
19833 }
19834
19835 pub fn unstage_and_next(
19836 &mut self,
19837 _: &::git::UnstageAndNext,
19838 window: &mut Window,
19839 cx: &mut Context<Self>,
19840 ) {
19841 self.do_stage_or_unstage_and_next(false, window, cx);
19842 }
19843
19844 pub fn stage_or_unstage_diff_hunks(
19845 &mut self,
19846 stage: bool,
19847 ranges: Vec<Range<Anchor>>,
19848 cx: &mut Context<Self>,
19849 ) {
19850 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19851 cx.spawn(async move |this, cx| {
19852 task.await?;
19853 this.update(cx, |this, cx| {
19854 let snapshot = this.buffer.read(cx).snapshot(cx);
19855 let chunk_by = this
19856 .diff_hunks_in_ranges(&ranges, &snapshot)
19857 .chunk_by(|hunk| hunk.buffer_id);
19858 for (buffer_id, hunks) in &chunk_by {
19859 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19860 }
19861 })
19862 })
19863 .detach_and_log_err(cx);
19864 }
19865
19866 fn save_buffers_for_ranges_if_needed(
19867 &mut self,
19868 ranges: &[Range<Anchor>],
19869 cx: &mut Context<Editor>,
19870 ) -> Task<Result<()>> {
19871 let multibuffer = self.buffer.read(cx);
19872 let snapshot = multibuffer.read(cx);
19873 let buffer_ids: HashSet<_> = ranges
19874 .iter()
19875 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19876 .collect();
19877 drop(snapshot);
19878
19879 let mut buffers = HashSet::default();
19880 for buffer_id in buffer_ids {
19881 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19882 let buffer = buffer_entity.read(cx);
19883 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19884 {
19885 buffers.insert(buffer_entity);
19886 }
19887 }
19888 }
19889
19890 if let Some(project) = &self.project {
19891 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19892 } else {
19893 Task::ready(Ok(()))
19894 }
19895 }
19896
19897 fn do_stage_or_unstage_and_next(
19898 &mut self,
19899 stage: bool,
19900 window: &mut Window,
19901 cx: &mut Context<Self>,
19902 ) {
19903 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19904
19905 if ranges.iter().any(|range| range.start != range.end) {
19906 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19907 return;
19908 }
19909
19910 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19911 let snapshot = self.snapshot(window, cx);
19912 let position = self
19913 .selections
19914 .newest::<Point>(&snapshot.display_snapshot)
19915 .head();
19916 let mut row = snapshot
19917 .buffer_snapshot()
19918 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19919 .find(|hunk| hunk.row_range.start.0 > position.row)
19920 .map(|hunk| hunk.row_range.start);
19921
19922 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19923 // Outside of the project diff editor, wrap around to the beginning.
19924 if !all_diff_hunks_expanded {
19925 row = row.or_else(|| {
19926 snapshot
19927 .buffer_snapshot()
19928 .diff_hunks_in_range(Point::zero()..position)
19929 .find(|hunk| hunk.row_range.end.0 < position.row)
19930 .map(|hunk| hunk.row_range.start)
19931 });
19932 }
19933
19934 if let Some(row) = row {
19935 let destination = Point::new(row.0, 0);
19936 let autoscroll = Autoscroll::center();
19937
19938 self.unfold_ranges(&[destination..destination], false, false, cx);
19939 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19940 s.select_ranges([destination..destination]);
19941 });
19942 }
19943 }
19944
19945 fn do_stage_or_unstage(
19946 &self,
19947 stage: bool,
19948 buffer_id: BufferId,
19949 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19950 cx: &mut App,
19951 ) -> Option<()> {
19952 let project = self.project()?;
19953 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19954 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19955 let buffer_snapshot = buffer.read(cx).snapshot();
19956 let file_exists = buffer_snapshot
19957 .file()
19958 .is_some_and(|file| file.disk_state().exists());
19959 diff.update(cx, |diff, cx| {
19960 diff.stage_or_unstage_hunks(
19961 stage,
19962 &hunks
19963 .map(|hunk| buffer_diff::DiffHunk {
19964 buffer_range: hunk.buffer_range,
19965 // We don't need to pass in word diffs here because they're only used for rendering and
19966 // this function changes internal state
19967 base_word_diffs: Vec::default(),
19968 buffer_word_diffs: Vec::default(),
19969 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19970 ..hunk.diff_base_byte_range.end.0,
19971 secondary_status: hunk.secondary_status,
19972 range: Point::zero()..Point::zero(), // unused
19973 })
19974 .collect::<Vec<_>>(),
19975 &buffer_snapshot,
19976 file_exists,
19977 cx,
19978 )
19979 });
19980 None
19981 }
19982
19983 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19984 let ranges: Vec<_> = self
19985 .selections
19986 .disjoint_anchors()
19987 .iter()
19988 .map(|s| s.range())
19989 .collect();
19990 self.buffer
19991 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19992 }
19993
19994 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19995 self.buffer.update(cx, |buffer, cx| {
19996 let ranges = vec![Anchor::min()..Anchor::max()];
19997 if !buffer.all_diff_hunks_expanded()
19998 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19999 {
20000 buffer.collapse_diff_hunks(ranges, cx);
20001 true
20002 } else {
20003 false
20004 }
20005 })
20006 }
20007
20008 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20009 if self.buffer.read(cx).all_diff_hunks_expanded() {
20010 return true;
20011 }
20012 let ranges = vec![Anchor::min()..Anchor::max()];
20013 self.buffer
20014 .read(cx)
20015 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20016 }
20017
20018 fn toggle_diff_hunks_in_ranges(
20019 &mut self,
20020 ranges: Vec<Range<Anchor>>,
20021 cx: &mut Context<Editor>,
20022 ) {
20023 self.buffer.update(cx, |buffer, cx| {
20024 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20025 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20026 })
20027 }
20028
20029 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20030 self.buffer.update(cx, |buffer, cx| {
20031 let snapshot = buffer.snapshot(cx);
20032 let excerpt_id = range.end.excerpt_id;
20033 let point_range = range.to_point(&snapshot);
20034 let expand = !buffer.single_hunk_is_expanded(range, cx);
20035 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20036 })
20037 }
20038
20039 pub(crate) fn apply_all_diff_hunks(
20040 &mut self,
20041 _: &ApplyAllDiffHunks,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20046
20047 let buffers = self.buffer.read(cx).all_buffers();
20048 for branch_buffer in buffers {
20049 branch_buffer.update(cx, |branch_buffer, cx| {
20050 branch_buffer.merge_into_base(Vec::new(), cx);
20051 });
20052 }
20053
20054 if let Some(project) = self.project.clone() {
20055 self.save(
20056 SaveOptions {
20057 format: true,
20058 autosave: false,
20059 },
20060 project,
20061 window,
20062 cx,
20063 )
20064 .detach_and_log_err(cx);
20065 }
20066 }
20067
20068 pub(crate) fn apply_selected_diff_hunks(
20069 &mut self,
20070 _: &ApplyDiffHunk,
20071 window: &mut Window,
20072 cx: &mut Context<Self>,
20073 ) {
20074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20075 let snapshot = self.snapshot(window, cx);
20076 let hunks = snapshot.hunks_for_ranges(
20077 self.selections
20078 .all(&snapshot.display_snapshot)
20079 .into_iter()
20080 .map(|selection| selection.range()),
20081 );
20082 let mut ranges_by_buffer = HashMap::default();
20083 self.transact(window, cx, |editor, _window, cx| {
20084 for hunk in hunks {
20085 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20086 ranges_by_buffer
20087 .entry(buffer.clone())
20088 .or_insert_with(Vec::new)
20089 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20090 }
20091 }
20092
20093 for (buffer, ranges) in ranges_by_buffer {
20094 buffer.update(cx, |buffer, cx| {
20095 buffer.merge_into_base(ranges, cx);
20096 });
20097 }
20098 });
20099
20100 if let Some(project) = self.project.clone() {
20101 self.save(
20102 SaveOptions {
20103 format: true,
20104 autosave: false,
20105 },
20106 project,
20107 window,
20108 cx,
20109 )
20110 .detach_and_log_err(cx);
20111 }
20112 }
20113
20114 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20115 if hovered != self.gutter_hovered {
20116 self.gutter_hovered = hovered;
20117 cx.notify();
20118 }
20119 }
20120
20121 pub fn insert_blocks(
20122 &mut self,
20123 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20124 autoscroll: Option<Autoscroll>,
20125 cx: &mut Context<Self>,
20126 ) -> Vec<CustomBlockId> {
20127 let blocks = self
20128 .display_map
20129 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20130 if let Some(autoscroll) = autoscroll {
20131 self.request_autoscroll(autoscroll, cx);
20132 }
20133 cx.notify();
20134 blocks
20135 }
20136
20137 pub fn resize_blocks(
20138 &mut self,
20139 heights: HashMap<CustomBlockId, u32>,
20140 autoscroll: Option<Autoscroll>,
20141 cx: &mut Context<Self>,
20142 ) {
20143 self.display_map
20144 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20145 if let Some(autoscroll) = autoscroll {
20146 self.request_autoscroll(autoscroll, cx);
20147 }
20148 cx.notify();
20149 }
20150
20151 pub fn replace_blocks(
20152 &mut self,
20153 renderers: HashMap<CustomBlockId, RenderBlock>,
20154 autoscroll: Option<Autoscroll>,
20155 cx: &mut Context<Self>,
20156 ) {
20157 self.display_map
20158 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20159 if let Some(autoscroll) = autoscroll {
20160 self.request_autoscroll(autoscroll, cx);
20161 }
20162 cx.notify();
20163 }
20164
20165 pub fn remove_blocks(
20166 &mut self,
20167 block_ids: HashSet<CustomBlockId>,
20168 autoscroll: Option<Autoscroll>,
20169 cx: &mut Context<Self>,
20170 ) {
20171 self.display_map.update(cx, |display_map, cx| {
20172 display_map.remove_blocks(block_ids, cx)
20173 });
20174 if let Some(autoscroll) = autoscroll {
20175 self.request_autoscroll(autoscroll, cx);
20176 }
20177 cx.notify();
20178 }
20179
20180 pub fn row_for_block(
20181 &self,
20182 block_id: CustomBlockId,
20183 cx: &mut Context<Self>,
20184 ) -> Option<DisplayRow> {
20185 self.display_map
20186 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20187 }
20188
20189 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20190 self.focused_block = Some(focused_block);
20191 }
20192
20193 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20194 self.focused_block.take()
20195 }
20196
20197 pub fn insert_creases(
20198 &mut self,
20199 creases: impl IntoIterator<Item = Crease<Anchor>>,
20200 cx: &mut Context<Self>,
20201 ) -> Vec<CreaseId> {
20202 self.display_map
20203 .update(cx, |map, cx| map.insert_creases(creases, cx))
20204 }
20205
20206 pub fn remove_creases(
20207 &mut self,
20208 ids: impl IntoIterator<Item = CreaseId>,
20209 cx: &mut Context<Self>,
20210 ) -> Vec<(CreaseId, Range<Anchor>)> {
20211 self.display_map
20212 .update(cx, |map, cx| map.remove_creases(ids, cx))
20213 }
20214
20215 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20216 self.display_map
20217 .update(cx, |map, cx| map.snapshot(cx))
20218 .longest_row()
20219 }
20220
20221 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20222 self.display_map
20223 .update(cx, |map, cx| map.snapshot(cx))
20224 .max_point()
20225 }
20226
20227 pub fn text(&self, cx: &App) -> String {
20228 self.buffer.read(cx).read(cx).text()
20229 }
20230
20231 pub fn is_empty(&self, cx: &App) -> bool {
20232 self.buffer.read(cx).read(cx).is_empty()
20233 }
20234
20235 pub fn text_option(&self, cx: &App) -> Option<String> {
20236 let text = self.text(cx);
20237 let text = text.trim();
20238
20239 if text.is_empty() {
20240 return None;
20241 }
20242
20243 Some(text.to_string())
20244 }
20245
20246 pub fn set_text(
20247 &mut self,
20248 text: impl Into<Arc<str>>,
20249 window: &mut Window,
20250 cx: &mut Context<Self>,
20251 ) {
20252 self.transact(window, cx, |this, _, cx| {
20253 this.buffer
20254 .read(cx)
20255 .as_singleton()
20256 .expect("you can only call set_text on editors for singleton buffers")
20257 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20258 });
20259 }
20260
20261 pub fn display_text(&self, cx: &mut App) -> String {
20262 self.display_map
20263 .update(cx, |map, cx| map.snapshot(cx))
20264 .text()
20265 }
20266
20267 fn create_minimap(
20268 &self,
20269 minimap_settings: MinimapSettings,
20270 window: &mut Window,
20271 cx: &mut Context<Self>,
20272 ) -> Option<Entity<Self>> {
20273 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20274 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20275 }
20276
20277 fn initialize_new_minimap(
20278 &self,
20279 minimap_settings: MinimapSettings,
20280 window: &mut Window,
20281 cx: &mut Context<Self>,
20282 ) -> Entity<Self> {
20283 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20284
20285 let mut minimap = Editor::new_internal(
20286 EditorMode::Minimap {
20287 parent: cx.weak_entity(),
20288 },
20289 self.buffer.clone(),
20290 None,
20291 Some(self.display_map.clone()),
20292 window,
20293 cx,
20294 );
20295 minimap.scroll_manager.clone_state(&self.scroll_manager);
20296 minimap.set_text_style_refinement(TextStyleRefinement {
20297 font_size: Some(MINIMAP_FONT_SIZE),
20298 font_weight: Some(MINIMAP_FONT_WEIGHT),
20299 ..Default::default()
20300 });
20301 minimap.update_minimap_configuration(minimap_settings, cx);
20302 cx.new(|_| minimap)
20303 }
20304
20305 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20306 let current_line_highlight = minimap_settings
20307 .current_line_highlight
20308 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20309 self.set_current_line_highlight(Some(current_line_highlight));
20310 }
20311
20312 pub fn minimap(&self) -> Option<&Entity<Self>> {
20313 self.minimap
20314 .as_ref()
20315 .filter(|_| self.minimap_visibility.visible())
20316 }
20317
20318 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20319 let mut wrap_guides = smallvec![];
20320
20321 if self.show_wrap_guides == Some(false) {
20322 return wrap_guides;
20323 }
20324
20325 let settings = self.buffer.read(cx).language_settings(cx);
20326 if settings.show_wrap_guides {
20327 match self.soft_wrap_mode(cx) {
20328 SoftWrap::Column(soft_wrap) => {
20329 wrap_guides.push((soft_wrap as usize, true));
20330 }
20331 SoftWrap::Bounded(soft_wrap) => {
20332 wrap_guides.push((soft_wrap as usize, true));
20333 }
20334 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20335 }
20336 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20337 }
20338
20339 wrap_guides
20340 }
20341
20342 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20343 let settings = self.buffer.read(cx).language_settings(cx);
20344 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20345 match mode {
20346 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20347 SoftWrap::None
20348 }
20349 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20350 language_settings::SoftWrap::PreferredLineLength => {
20351 SoftWrap::Column(settings.preferred_line_length)
20352 }
20353 language_settings::SoftWrap::Bounded => {
20354 SoftWrap::Bounded(settings.preferred_line_length)
20355 }
20356 }
20357 }
20358
20359 pub fn set_soft_wrap_mode(
20360 &mut self,
20361 mode: language_settings::SoftWrap,
20362
20363 cx: &mut Context<Self>,
20364 ) {
20365 self.soft_wrap_mode_override = Some(mode);
20366 cx.notify();
20367 }
20368
20369 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20370 self.hard_wrap = hard_wrap;
20371 cx.notify();
20372 }
20373
20374 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20375 self.text_style_refinement = Some(style);
20376 }
20377
20378 /// called by the Element so we know what style we were most recently rendered with.
20379 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20380 // We intentionally do not inform the display map about the minimap style
20381 // so that wrapping is not recalculated and stays consistent for the editor
20382 // and its linked minimap.
20383 if !self.mode.is_minimap() {
20384 let font = style.text.font();
20385 let font_size = style.text.font_size.to_pixels(window.rem_size());
20386 let display_map = self
20387 .placeholder_display_map
20388 .as_ref()
20389 .filter(|_| self.is_empty(cx))
20390 .unwrap_or(&self.display_map);
20391
20392 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20393 }
20394 self.style = Some(style);
20395 }
20396
20397 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20398 if self.style.is_none() {
20399 self.style = Some(self.create_style(cx));
20400 }
20401 self.style.as_ref().unwrap()
20402 }
20403
20404 // Called by the element. This method is not designed to be called outside of the editor
20405 // element's layout code because it does not notify when rewrapping is computed synchronously.
20406 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20407 if self.is_empty(cx) {
20408 self.placeholder_display_map
20409 .as_ref()
20410 .map_or(false, |display_map| {
20411 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20412 })
20413 } else {
20414 self.display_map
20415 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20416 }
20417 }
20418
20419 pub fn set_soft_wrap(&mut self) {
20420 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20421 }
20422
20423 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20424 if self.soft_wrap_mode_override.is_some() {
20425 self.soft_wrap_mode_override.take();
20426 } else {
20427 let soft_wrap = match self.soft_wrap_mode(cx) {
20428 SoftWrap::GitDiff => return,
20429 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20430 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20431 language_settings::SoftWrap::None
20432 }
20433 };
20434 self.soft_wrap_mode_override = Some(soft_wrap);
20435 }
20436 cx.notify();
20437 }
20438
20439 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20440 let Some(workspace) = self.workspace() else {
20441 return;
20442 };
20443 let fs = workspace.read(cx).app_state().fs.clone();
20444 let current_show = TabBarSettings::get_global(cx).show;
20445 update_settings_file(fs, cx, move |setting, _| {
20446 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20447 });
20448 }
20449
20450 pub fn toggle_indent_guides(
20451 &mut self,
20452 _: &ToggleIndentGuides,
20453 _: &mut Window,
20454 cx: &mut Context<Self>,
20455 ) {
20456 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20457 self.buffer
20458 .read(cx)
20459 .language_settings(cx)
20460 .indent_guides
20461 .enabled
20462 });
20463 self.show_indent_guides = Some(!currently_enabled);
20464 cx.notify();
20465 }
20466
20467 fn should_show_indent_guides(&self) -> Option<bool> {
20468 self.show_indent_guides
20469 }
20470
20471 pub fn disable_indent_guides_for_buffer(
20472 &mut self,
20473 buffer_id: BufferId,
20474 cx: &mut Context<Self>,
20475 ) {
20476 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20477 cx.notify();
20478 }
20479
20480 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20481 self.buffers_with_disabled_indent_guides
20482 .contains(&buffer_id)
20483 }
20484
20485 pub fn toggle_line_numbers(
20486 &mut self,
20487 _: &ToggleLineNumbers,
20488 _: &mut Window,
20489 cx: &mut Context<Self>,
20490 ) {
20491 let mut editor_settings = EditorSettings::get_global(cx).clone();
20492 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20493 EditorSettings::override_global(editor_settings, cx);
20494 }
20495
20496 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20497 if let Some(show_line_numbers) = self.show_line_numbers {
20498 return show_line_numbers;
20499 }
20500 EditorSettings::get_global(cx).gutter.line_numbers
20501 }
20502
20503 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20504 match (
20505 self.use_relative_line_numbers,
20506 EditorSettings::get_global(cx).relative_line_numbers,
20507 ) {
20508 (None, setting) => setting,
20509 (Some(false), _) => RelativeLineNumbers::Disabled,
20510 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20511 (Some(true), _) => RelativeLineNumbers::Enabled,
20512 }
20513 }
20514
20515 pub fn toggle_relative_line_numbers(
20516 &mut self,
20517 _: &ToggleRelativeLineNumbers,
20518 _: &mut Window,
20519 cx: &mut Context<Self>,
20520 ) {
20521 let is_relative = self.relative_line_numbers(cx);
20522 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20523 }
20524
20525 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20526 self.use_relative_line_numbers = is_relative;
20527 cx.notify();
20528 }
20529
20530 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20531 self.show_gutter = show_gutter;
20532 cx.notify();
20533 }
20534
20535 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20536 self.show_scrollbars = ScrollbarAxes {
20537 horizontal: show,
20538 vertical: show,
20539 };
20540 cx.notify();
20541 }
20542
20543 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20544 self.show_scrollbars.vertical = show;
20545 cx.notify();
20546 }
20547
20548 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20549 self.show_scrollbars.horizontal = show;
20550 cx.notify();
20551 }
20552
20553 pub fn set_minimap_visibility(
20554 &mut self,
20555 minimap_visibility: MinimapVisibility,
20556 window: &mut Window,
20557 cx: &mut Context<Self>,
20558 ) {
20559 if self.minimap_visibility != minimap_visibility {
20560 if minimap_visibility.visible() && self.minimap.is_none() {
20561 let minimap_settings = EditorSettings::get_global(cx).minimap;
20562 self.minimap =
20563 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20564 }
20565 self.minimap_visibility = minimap_visibility;
20566 cx.notify();
20567 }
20568 }
20569
20570 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20571 self.set_show_scrollbars(false, cx);
20572 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20573 }
20574
20575 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20576 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20577 }
20578
20579 /// Normally the text in full mode and auto height editors is padded on the
20580 /// left side by roughly half a character width for improved hit testing.
20581 ///
20582 /// Use this method to disable this for cases where this is not wanted (e.g.
20583 /// if you want to align the editor text with some other text above or below)
20584 /// or if you want to add this padding to single-line editors.
20585 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20586 self.offset_content = offset_content;
20587 cx.notify();
20588 }
20589
20590 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20591 self.show_line_numbers = Some(show_line_numbers);
20592 cx.notify();
20593 }
20594
20595 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20596 self.disable_expand_excerpt_buttons = true;
20597 cx.notify();
20598 }
20599
20600 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20601 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20602 cx.notify();
20603 }
20604
20605 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20606 self.show_code_actions = Some(show_code_actions);
20607 cx.notify();
20608 }
20609
20610 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20611 self.show_runnables = Some(show_runnables);
20612 cx.notify();
20613 }
20614
20615 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20616 self.show_breakpoints = Some(show_breakpoints);
20617 cx.notify();
20618 }
20619
20620 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20621 if self.display_map.read(cx).masked != masked {
20622 self.display_map.update(cx, |map, _| map.masked = masked);
20623 }
20624 cx.notify()
20625 }
20626
20627 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20628 self.show_wrap_guides = Some(show_wrap_guides);
20629 cx.notify();
20630 }
20631
20632 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20633 self.show_indent_guides = Some(show_indent_guides);
20634 cx.notify();
20635 }
20636
20637 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20638 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20639 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20640 && let Some(dir) = file.abs_path(cx).parent()
20641 {
20642 return Some(dir.to_owned());
20643 }
20644 }
20645
20646 None
20647 }
20648
20649 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20650 self.active_excerpt(cx)?
20651 .1
20652 .read(cx)
20653 .file()
20654 .and_then(|f| f.as_local())
20655 }
20656
20657 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20658 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20659 let buffer = buffer.read(cx);
20660 if let Some(project_path) = buffer.project_path(cx) {
20661 let project = self.project()?.read(cx);
20662 project.absolute_path(&project_path, cx)
20663 } else {
20664 buffer
20665 .file()
20666 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20667 }
20668 })
20669 }
20670
20671 pub fn reveal_in_finder(
20672 &mut self,
20673 _: &RevealInFileManager,
20674 _window: &mut Window,
20675 cx: &mut Context<Self>,
20676 ) {
20677 if let Some(target) = self.target_file(cx) {
20678 cx.reveal_path(&target.abs_path(cx));
20679 }
20680 }
20681
20682 pub fn copy_path(
20683 &mut self,
20684 _: &zed_actions::workspace::CopyPath,
20685 _window: &mut Window,
20686 cx: &mut Context<Self>,
20687 ) {
20688 if let Some(path) = self.target_file_abs_path(cx)
20689 && let Some(path) = path.to_str()
20690 {
20691 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20692 } else {
20693 cx.propagate();
20694 }
20695 }
20696
20697 pub fn copy_relative_path(
20698 &mut self,
20699 _: &zed_actions::workspace::CopyRelativePath,
20700 _window: &mut Window,
20701 cx: &mut Context<Self>,
20702 ) {
20703 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20704 let project = self.project()?.read(cx);
20705 let path = buffer.read(cx).file()?.path();
20706 let path = path.display(project.path_style(cx));
20707 Some(path)
20708 }) {
20709 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20710 } else {
20711 cx.propagate();
20712 }
20713 }
20714
20715 /// Returns the project path for the editor's buffer, if any buffer is
20716 /// opened in the editor.
20717 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20718 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20719 buffer.read(cx).project_path(cx)
20720 } else {
20721 None
20722 }
20723 }
20724
20725 // Returns true if the editor handled a go-to-line request
20726 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20727 maybe!({
20728 let breakpoint_store = self.breakpoint_store.as_ref()?;
20729
20730 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20731 else {
20732 self.clear_row_highlights::<ActiveDebugLine>();
20733 return None;
20734 };
20735
20736 let position = active_stack_frame.position;
20737 let buffer_id = position.buffer_id?;
20738 let snapshot = self
20739 .project
20740 .as_ref()?
20741 .read(cx)
20742 .buffer_for_id(buffer_id, cx)?
20743 .read(cx)
20744 .snapshot();
20745
20746 let mut handled = false;
20747 for (id, ExcerptRange { context, .. }) in
20748 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20749 {
20750 if context.start.cmp(&position, &snapshot).is_ge()
20751 || context.end.cmp(&position, &snapshot).is_lt()
20752 {
20753 continue;
20754 }
20755 let snapshot = self.buffer.read(cx).snapshot(cx);
20756 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20757
20758 handled = true;
20759 self.clear_row_highlights::<ActiveDebugLine>();
20760
20761 self.go_to_line::<ActiveDebugLine>(
20762 multibuffer_anchor,
20763 Some(cx.theme().colors().editor_debugger_active_line_background),
20764 window,
20765 cx,
20766 );
20767
20768 cx.notify();
20769 }
20770
20771 handled.then_some(())
20772 })
20773 .is_some()
20774 }
20775
20776 pub fn copy_file_name_without_extension(
20777 &mut self,
20778 _: &CopyFileNameWithoutExtension,
20779 _: &mut Window,
20780 cx: &mut Context<Self>,
20781 ) {
20782 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20783 let file = buffer.read(cx).file()?;
20784 file.path().file_stem()
20785 }) {
20786 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20787 }
20788 }
20789
20790 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20791 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20792 let file = buffer.read(cx).file()?;
20793 Some(file.file_name(cx))
20794 }) {
20795 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20796 }
20797 }
20798
20799 pub fn toggle_git_blame(
20800 &mut self,
20801 _: &::git::Blame,
20802 window: &mut Window,
20803 cx: &mut Context<Self>,
20804 ) {
20805 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20806
20807 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20808 self.start_git_blame(true, window, cx);
20809 }
20810
20811 cx.notify();
20812 }
20813
20814 pub fn toggle_git_blame_inline(
20815 &mut self,
20816 _: &ToggleGitBlameInline,
20817 window: &mut Window,
20818 cx: &mut Context<Self>,
20819 ) {
20820 self.toggle_git_blame_inline_internal(true, window, cx);
20821 cx.notify();
20822 }
20823
20824 pub fn open_git_blame_commit(
20825 &mut self,
20826 _: &OpenGitBlameCommit,
20827 window: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 self.open_git_blame_commit_internal(window, cx);
20831 }
20832
20833 fn open_git_blame_commit_internal(
20834 &mut self,
20835 window: &mut Window,
20836 cx: &mut Context<Self>,
20837 ) -> Option<()> {
20838 let blame = self.blame.as_ref()?;
20839 let snapshot = self.snapshot(window, cx);
20840 let cursor = self
20841 .selections
20842 .newest::<Point>(&snapshot.display_snapshot)
20843 .head();
20844 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20845 let (_, blame_entry) = blame
20846 .update(cx, |blame, cx| {
20847 blame
20848 .blame_for_rows(
20849 &[RowInfo {
20850 buffer_id: Some(buffer.remote_id()),
20851 buffer_row: Some(point.row),
20852 ..Default::default()
20853 }],
20854 cx,
20855 )
20856 .next()
20857 })
20858 .flatten()?;
20859 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20860 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20861 let workspace = self.workspace()?.downgrade();
20862 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20863 None
20864 }
20865
20866 pub fn git_blame_inline_enabled(&self) -> bool {
20867 self.git_blame_inline_enabled
20868 }
20869
20870 pub fn toggle_selection_menu(
20871 &mut self,
20872 _: &ToggleSelectionMenu,
20873 _: &mut Window,
20874 cx: &mut Context<Self>,
20875 ) {
20876 self.show_selection_menu = self
20877 .show_selection_menu
20878 .map(|show_selections_menu| !show_selections_menu)
20879 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20880
20881 cx.notify();
20882 }
20883
20884 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20885 self.show_selection_menu
20886 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20887 }
20888
20889 fn start_git_blame(
20890 &mut self,
20891 user_triggered: bool,
20892 window: &mut Window,
20893 cx: &mut Context<Self>,
20894 ) {
20895 if let Some(project) = self.project() {
20896 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20897 && buffer.read(cx).file().is_none()
20898 {
20899 return;
20900 }
20901
20902 let focused = self.focus_handle(cx).contains_focused(window, cx);
20903
20904 let project = project.clone();
20905 let blame = cx
20906 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20907 self.blame_subscription =
20908 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20909 self.blame = Some(blame);
20910 }
20911 }
20912
20913 fn toggle_git_blame_inline_internal(
20914 &mut self,
20915 user_triggered: bool,
20916 window: &mut Window,
20917 cx: &mut Context<Self>,
20918 ) {
20919 if self.git_blame_inline_enabled {
20920 self.git_blame_inline_enabled = false;
20921 self.show_git_blame_inline = false;
20922 self.show_git_blame_inline_delay_task.take();
20923 } else {
20924 self.git_blame_inline_enabled = true;
20925 self.start_git_blame_inline(user_triggered, window, cx);
20926 }
20927
20928 cx.notify();
20929 }
20930
20931 fn start_git_blame_inline(
20932 &mut self,
20933 user_triggered: bool,
20934 window: &mut Window,
20935 cx: &mut Context<Self>,
20936 ) {
20937 self.start_git_blame(user_triggered, window, cx);
20938
20939 if ProjectSettings::get_global(cx)
20940 .git
20941 .inline_blame_delay()
20942 .is_some()
20943 {
20944 self.start_inline_blame_timer(window, cx);
20945 } else {
20946 self.show_git_blame_inline = true
20947 }
20948 }
20949
20950 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20951 self.blame.as_ref()
20952 }
20953
20954 pub fn show_git_blame_gutter(&self) -> bool {
20955 self.show_git_blame_gutter
20956 }
20957
20958 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20959 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20960 }
20961
20962 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20963 self.show_git_blame_inline
20964 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20965 && !self.newest_selection_head_on_empty_line(cx)
20966 && self.has_blame_entries(cx)
20967 }
20968
20969 fn has_blame_entries(&self, cx: &App) -> bool {
20970 self.blame()
20971 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20972 }
20973
20974 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20975 let cursor_anchor = self.selections.newest_anchor().head();
20976
20977 let snapshot = self.buffer.read(cx).snapshot(cx);
20978 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20979
20980 snapshot.line_len(buffer_row) == 0
20981 }
20982
20983 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20984 let buffer_and_selection = maybe!({
20985 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20986 let selection_range = selection.range();
20987
20988 let multi_buffer = self.buffer().read(cx);
20989 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20990 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20991
20992 let (buffer, range, _) = if selection.reversed {
20993 buffer_ranges.first()
20994 } else {
20995 buffer_ranges.last()
20996 }?;
20997
20998 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20999 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21000
21001 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21002 let selection = start_row_in_buffer..end_row_in_buffer;
21003
21004 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21005 };
21006
21007 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21008
21009 Some((
21010 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21011 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
21012 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
21013 ))
21014 });
21015
21016 let Some((buffer, selection)) = buffer_and_selection else {
21017 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21018 };
21019
21020 let Some(project) = self.project() else {
21021 return Task::ready(Err(anyhow!("editor does not have project")));
21022 };
21023
21024 project.update(cx, |project, cx| {
21025 project.get_permalink_to_line(&buffer, selection, cx)
21026 })
21027 }
21028
21029 pub fn copy_permalink_to_line(
21030 &mut self,
21031 _: &CopyPermalinkToLine,
21032 window: &mut Window,
21033 cx: &mut Context<Self>,
21034 ) {
21035 let permalink_task = self.get_permalink_to_line(cx);
21036 let workspace = self.workspace();
21037
21038 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21039 Ok(permalink) => {
21040 cx.update(|_, cx| {
21041 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21042 })
21043 .ok();
21044 }
21045 Err(err) => {
21046 let message = format!("Failed to copy permalink: {err}");
21047
21048 anyhow::Result::<()>::Err(err).log_err();
21049
21050 if let Some(workspace) = workspace {
21051 workspace
21052 .update_in(cx, |workspace, _, cx| {
21053 struct CopyPermalinkToLine;
21054
21055 workspace.show_toast(
21056 Toast::new(
21057 NotificationId::unique::<CopyPermalinkToLine>(),
21058 message,
21059 ),
21060 cx,
21061 )
21062 })
21063 .ok();
21064 }
21065 }
21066 })
21067 .detach();
21068 }
21069
21070 pub fn copy_file_location(
21071 &mut self,
21072 _: &CopyFileLocation,
21073 _: &mut Window,
21074 cx: &mut Context<Self>,
21075 ) {
21076 let selection = self
21077 .selections
21078 .newest::<Point>(&self.display_snapshot(cx))
21079 .start
21080 .row
21081 + 1;
21082 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21083 let project = self.project()?.read(cx);
21084 let file = buffer.read(cx).file()?;
21085 let path = file.path().display(project.path_style(cx));
21086
21087 Some(format!("{path}:{selection}"))
21088 }) {
21089 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21090 }
21091 }
21092
21093 pub fn open_permalink_to_line(
21094 &mut self,
21095 _: &OpenPermalinkToLine,
21096 window: &mut Window,
21097 cx: &mut Context<Self>,
21098 ) {
21099 let permalink_task = self.get_permalink_to_line(cx);
21100 let workspace = self.workspace();
21101
21102 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21103 Ok(permalink) => {
21104 cx.update(|_, cx| {
21105 cx.open_url(permalink.as_ref());
21106 })
21107 .ok();
21108 }
21109 Err(err) => {
21110 let message = format!("Failed to open permalink: {err}");
21111
21112 anyhow::Result::<()>::Err(err).log_err();
21113
21114 if let Some(workspace) = workspace {
21115 workspace
21116 .update(cx, |workspace, cx| {
21117 struct OpenPermalinkToLine;
21118
21119 workspace.show_toast(
21120 Toast::new(
21121 NotificationId::unique::<OpenPermalinkToLine>(),
21122 message,
21123 ),
21124 cx,
21125 )
21126 })
21127 .ok();
21128 }
21129 }
21130 })
21131 .detach();
21132 }
21133
21134 pub fn insert_uuid_v4(
21135 &mut self,
21136 _: &InsertUuidV4,
21137 window: &mut Window,
21138 cx: &mut Context<Self>,
21139 ) {
21140 self.insert_uuid(UuidVersion::V4, window, cx);
21141 }
21142
21143 pub fn insert_uuid_v7(
21144 &mut self,
21145 _: &InsertUuidV7,
21146 window: &mut Window,
21147 cx: &mut Context<Self>,
21148 ) {
21149 self.insert_uuid(UuidVersion::V7, window, cx);
21150 }
21151
21152 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21153 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21154 self.transact(window, cx, |this, window, cx| {
21155 let edits = this
21156 .selections
21157 .all::<Point>(&this.display_snapshot(cx))
21158 .into_iter()
21159 .map(|selection| {
21160 let uuid = match version {
21161 UuidVersion::V4 => uuid::Uuid::new_v4(),
21162 UuidVersion::V7 => uuid::Uuid::now_v7(),
21163 };
21164
21165 (selection.range(), uuid.to_string())
21166 });
21167 this.edit(edits, cx);
21168 this.refresh_edit_prediction(true, false, window, cx);
21169 });
21170 }
21171
21172 pub fn open_selections_in_multibuffer(
21173 &mut self,
21174 _: &OpenSelectionsInMultibuffer,
21175 window: &mut Window,
21176 cx: &mut Context<Self>,
21177 ) {
21178 let multibuffer = self.buffer.read(cx);
21179
21180 let Some(buffer) = multibuffer.as_singleton() else {
21181 return;
21182 };
21183
21184 let Some(workspace) = self.workspace() else {
21185 return;
21186 };
21187
21188 let title = multibuffer.title(cx).to_string();
21189
21190 let locations = self
21191 .selections
21192 .all_anchors(&self.display_snapshot(cx))
21193 .iter()
21194 .map(|selection| {
21195 (
21196 buffer.clone(),
21197 (selection.start.text_anchor..selection.end.text_anchor)
21198 .to_point(buffer.read(cx)),
21199 )
21200 })
21201 .into_group_map();
21202
21203 cx.spawn_in(window, async move |_, cx| {
21204 workspace.update_in(cx, |workspace, window, cx| {
21205 Self::open_locations_in_multibuffer(
21206 workspace,
21207 locations,
21208 format!("Selections for '{title}'"),
21209 false,
21210 false,
21211 MultibufferSelectionMode::All,
21212 window,
21213 cx,
21214 );
21215 })
21216 })
21217 .detach();
21218 }
21219
21220 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21221 /// last highlight added will be used.
21222 ///
21223 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21224 pub fn highlight_rows<T: 'static>(
21225 &mut self,
21226 range: Range<Anchor>,
21227 color: Hsla,
21228 options: RowHighlightOptions,
21229 cx: &mut Context<Self>,
21230 ) {
21231 let snapshot = self.buffer().read(cx).snapshot(cx);
21232 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21233 let ix = row_highlights.binary_search_by(|highlight| {
21234 Ordering::Equal
21235 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21236 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21237 });
21238
21239 if let Err(mut ix) = ix {
21240 let index = post_inc(&mut self.highlight_order);
21241
21242 // If this range intersects with the preceding highlight, then merge it with
21243 // the preceding highlight. Otherwise insert a new highlight.
21244 let mut merged = false;
21245 if ix > 0 {
21246 let prev_highlight = &mut row_highlights[ix - 1];
21247 if prev_highlight
21248 .range
21249 .end
21250 .cmp(&range.start, &snapshot)
21251 .is_ge()
21252 {
21253 ix -= 1;
21254 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21255 prev_highlight.range.end = range.end;
21256 }
21257 merged = true;
21258 prev_highlight.index = index;
21259 prev_highlight.color = color;
21260 prev_highlight.options = options;
21261 }
21262 }
21263
21264 if !merged {
21265 row_highlights.insert(
21266 ix,
21267 RowHighlight {
21268 range,
21269 index,
21270 color,
21271 options,
21272 type_id: TypeId::of::<T>(),
21273 },
21274 );
21275 }
21276
21277 // If any of the following highlights intersect with this one, merge them.
21278 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21279 let highlight = &row_highlights[ix];
21280 if next_highlight
21281 .range
21282 .start
21283 .cmp(&highlight.range.end, &snapshot)
21284 .is_le()
21285 {
21286 if next_highlight
21287 .range
21288 .end
21289 .cmp(&highlight.range.end, &snapshot)
21290 .is_gt()
21291 {
21292 row_highlights[ix].range.end = next_highlight.range.end;
21293 }
21294 row_highlights.remove(ix + 1);
21295 } else {
21296 break;
21297 }
21298 }
21299 }
21300 }
21301
21302 /// Remove any highlighted row ranges of the given type that intersect the
21303 /// given ranges.
21304 pub fn remove_highlighted_rows<T: 'static>(
21305 &mut self,
21306 ranges_to_remove: Vec<Range<Anchor>>,
21307 cx: &mut Context<Self>,
21308 ) {
21309 let snapshot = self.buffer().read(cx).snapshot(cx);
21310 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21311 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21312 row_highlights.retain(|highlight| {
21313 while let Some(range_to_remove) = ranges_to_remove.peek() {
21314 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21315 Ordering::Less | Ordering::Equal => {
21316 ranges_to_remove.next();
21317 }
21318 Ordering::Greater => {
21319 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21320 Ordering::Less | Ordering::Equal => {
21321 return false;
21322 }
21323 Ordering::Greater => break,
21324 }
21325 }
21326 }
21327 }
21328
21329 true
21330 })
21331 }
21332
21333 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21334 pub fn clear_row_highlights<T: 'static>(&mut self) {
21335 self.highlighted_rows.remove(&TypeId::of::<T>());
21336 }
21337
21338 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21339 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21340 self.highlighted_rows
21341 .get(&TypeId::of::<T>())
21342 .map_or(&[] as &[_], |vec| vec.as_slice())
21343 .iter()
21344 .map(|highlight| (highlight.range.clone(), highlight.color))
21345 }
21346
21347 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21348 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21349 /// Allows to ignore certain kinds of highlights.
21350 pub fn highlighted_display_rows(
21351 &self,
21352 window: &mut Window,
21353 cx: &mut App,
21354 ) -> BTreeMap<DisplayRow, LineHighlight> {
21355 let snapshot = self.snapshot(window, cx);
21356 let mut used_highlight_orders = HashMap::default();
21357 self.highlighted_rows
21358 .iter()
21359 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21360 .fold(
21361 BTreeMap::<DisplayRow, LineHighlight>::new(),
21362 |mut unique_rows, highlight| {
21363 let start = highlight.range.start.to_display_point(&snapshot);
21364 let end = highlight.range.end.to_display_point(&snapshot);
21365 let start_row = start.row().0;
21366 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21367 {
21368 end.row().0.saturating_sub(1)
21369 } else {
21370 end.row().0
21371 };
21372 for row in start_row..=end_row {
21373 let used_index =
21374 used_highlight_orders.entry(row).or_insert(highlight.index);
21375 if highlight.index >= *used_index {
21376 *used_index = highlight.index;
21377 unique_rows.insert(
21378 DisplayRow(row),
21379 LineHighlight {
21380 include_gutter: highlight.options.include_gutter,
21381 border: None,
21382 background: highlight.color.into(),
21383 type_id: Some(highlight.type_id),
21384 },
21385 );
21386 }
21387 }
21388 unique_rows
21389 },
21390 )
21391 }
21392
21393 pub fn highlighted_display_row_for_autoscroll(
21394 &self,
21395 snapshot: &DisplaySnapshot,
21396 ) -> Option<DisplayRow> {
21397 self.highlighted_rows
21398 .values()
21399 .flat_map(|highlighted_rows| highlighted_rows.iter())
21400 .filter_map(|highlight| {
21401 if highlight.options.autoscroll {
21402 Some(highlight.range.start.to_display_point(snapshot).row())
21403 } else {
21404 None
21405 }
21406 })
21407 .min()
21408 }
21409
21410 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21411 self.highlight_background::<SearchWithinRange>(
21412 ranges,
21413 |_, colors| colors.colors().editor_document_highlight_read_background,
21414 cx,
21415 )
21416 }
21417
21418 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21419 self.breadcrumb_header = Some(new_header);
21420 }
21421
21422 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21423 self.clear_background_highlights::<SearchWithinRange>(cx);
21424 }
21425
21426 pub fn highlight_background<T: 'static>(
21427 &mut self,
21428 ranges: &[Range<Anchor>],
21429 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21430 cx: &mut Context<Self>,
21431 ) {
21432 self.background_highlights.insert(
21433 HighlightKey::Type(TypeId::of::<T>()),
21434 (Arc::new(color_fetcher), Arc::from(ranges)),
21435 );
21436 self.scrollbar_marker_state.dirty = true;
21437 cx.notify();
21438 }
21439
21440 pub fn highlight_background_key<T: 'static>(
21441 &mut self,
21442 key: usize,
21443 ranges: &[Range<Anchor>],
21444 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21445 cx: &mut Context<Self>,
21446 ) {
21447 self.background_highlights.insert(
21448 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21449 (Arc::new(color_fetcher), Arc::from(ranges)),
21450 );
21451 self.scrollbar_marker_state.dirty = true;
21452 cx.notify();
21453 }
21454
21455 pub fn clear_background_highlights<T: 'static>(
21456 &mut self,
21457 cx: &mut Context<Self>,
21458 ) -> Option<BackgroundHighlight> {
21459 let text_highlights = self
21460 .background_highlights
21461 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21462 if !text_highlights.1.is_empty() {
21463 self.scrollbar_marker_state.dirty = true;
21464 cx.notify();
21465 }
21466 Some(text_highlights)
21467 }
21468
21469 pub fn highlight_gutter<T: 'static>(
21470 &mut self,
21471 ranges: impl Into<Vec<Range<Anchor>>>,
21472 color_fetcher: fn(&App) -> Hsla,
21473 cx: &mut Context<Self>,
21474 ) {
21475 self.gutter_highlights
21476 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21477 cx.notify();
21478 }
21479
21480 pub fn clear_gutter_highlights<T: 'static>(
21481 &mut self,
21482 cx: &mut Context<Self>,
21483 ) -> Option<GutterHighlight> {
21484 cx.notify();
21485 self.gutter_highlights.remove(&TypeId::of::<T>())
21486 }
21487
21488 pub fn insert_gutter_highlight<T: 'static>(
21489 &mut self,
21490 range: Range<Anchor>,
21491 color_fetcher: fn(&App) -> Hsla,
21492 cx: &mut Context<Self>,
21493 ) {
21494 let snapshot = self.buffer().read(cx).snapshot(cx);
21495 let mut highlights = self
21496 .gutter_highlights
21497 .remove(&TypeId::of::<T>())
21498 .map(|(_, highlights)| highlights)
21499 .unwrap_or_default();
21500 let ix = highlights.binary_search_by(|highlight| {
21501 Ordering::Equal
21502 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21503 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21504 });
21505 if let Err(ix) = ix {
21506 highlights.insert(ix, range);
21507 }
21508 self.gutter_highlights
21509 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21510 }
21511
21512 pub fn remove_gutter_highlights<T: 'static>(
21513 &mut self,
21514 ranges_to_remove: Vec<Range<Anchor>>,
21515 cx: &mut Context<Self>,
21516 ) {
21517 let snapshot = self.buffer().read(cx).snapshot(cx);
21518 let Some((color_fetcher, mut gutter_highlights)) =
21519 self.gutter_highlights.remove(&TypeId::of::<T>())
21520 else {
21521 return;
21522 };
21523 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21524 gutter_highlights.retain(|highlight| {
21525 while let Some(range_to_remove) = ranges_to_remove.peek() {
21526 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21527 Ordering::Less | Ordering::Equal => {
21528 ranges_to_remove.next();
21529 }
21530 Ordering::Greater => {
21531 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21532 Ordering::Less | Ordering::Equal => {
21533 return false;
21534 }
21535 Ordering::Greater => break,
21536 }
21537 }
21538 }
21539 }
21540
21541 true
21542 });
21543 self.gutter_highlights
21544 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21545 }
21546
21547 #[cfg(feature = "test-support")]
21548 pub fn all_text_highlights(
21549 &self,
21550 window: &mut Window,
21551 cx: &mut Context<Self>,
21552 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21553 let snapshot = self.snapshot(window, cx);
21554 self.display_map.update(cx, |display_map, _| {
21555 display_map
21556 .all_text_highlights()
21557 .map(|highlight| {
21558 let (style, ranges) = highlight.as_ref();
21559 (
21560 *style,
21561 ranges
21562 .iter()
21563 .map(|range| range.clone().to_display_points(&snapshot))
21564 .collect(),
21565 )
21566 })
21567 .collect()
21568 })
21569 }
21570
21571 #[cfg(feature = "test-support")]
21572 pub fn all_text_background_highlights(
21573 &self,
21574 window: &mut Window,
21575 cx: &mut Context<Self>,
21576 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21577 let snapshot = self.snapshot(window, cx);
21578 let buffer = &snapshot.buffer_snapshot();
21579 let start = buffer.anchor_before(MultiBufferOffset(0));
21580 let end = buffer.anchor_after(buffer.len());
21581 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21582 }
21583
21584 #[cfg(any(test, feature = "test-support"))]
21585 pub fn sorted_background_highlights_in_range(
21586 &self,
21587 search_range: Range<Anchor>,
21588 display_snapshot: &DisplaySnapshot,
21589 theme: &Theme,
21590 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21591 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21592 res.sort_by(|a, b| {
21593 a.0.start
21594 .cmp(&b.0.start)
21595 .then_with(|| a.0.end.cmp(&b.0.end))
21596 .then_with(|| a.1.cmp(&b.1))
21597 });
21598 res
21599 }
21600
21601 #[cfg(feature = "test-support")]
21602 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21603 let snapshot = self.buffer().read(cx).snapshot(cx);
21604
21605 let highlights = self
21606 .background_highlights
21607 .get(&HighlightKey::Type(TypeId::of::<
21608 items::BufferSearchHighlights,
21609 >()));
21610
21611 if let Some((_color, ranges)) = highlights {
21612 ranges
21613 .iter()
21614 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21615 .collect_vec()
21616 } else {
21617 vec![]
21618 }
21619 }
21620
21621 fn document_highlights_for_position<'a>(
21622 &'a self,
21623 position: Anchor,
21624 buffer: &'a MultiBufferSnapshot,
21625 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21626 let read_highlights = self
21627 .background_highlights
21628 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21629 .map(|h| &h.1);
21630 let write_highlights = self
21631 .background_highlights
21632 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21633 .map(|h| &h.1);
21634 let left_position = position.bias_left(buffer);
21635 let right_position = position.bias_right(buffer);
21636 read_highlights
21637 .into_iter()
21638 .chain(write_highlights)
21639 .flat_map(move |ranges| {
21640 let start_ix = match ranges.binary_search_by(|probe| {
21641 let cmp = probe.end.cmp(&left_position, buffer);
21642 if cmp.is_ge() {
21643 Ordering::Greater
21644 } else {
21645 Ordering::Less
21646 }
21647 }) {
21648 Ok(i) | Err(i) => i,
21649 };
21650
21651 ranges[start_ix..]
21652 .iter()
21653 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21654 })
21655 }
21656
21657 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21658 self.background_highlights
21659 .get(&HighlightKey::Type(TypeId::of::<T>()))
21660 .is_some_and(|(_, highlights)| !highlights.is_empty())
21661 }
21662
21663 /// Returns all background highlights for a given range.
21664 ///
21665 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21666 pub fn background_highlights_in_range(
21667 &self,
21668 search_range: Range<Anchor>,
21669 display_snapshot: &DisplaySnapshot,
21670 theme: &Theme,
21671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21672 let mut results = Vec::new();
21673 for (color_fetcher, ranges) in self.background_highlights.values() {
21674 let start_ix = match ranges.binary_search_by(|probe| {
21675 let cmp = probe
21676 .end
21677 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21678 if cmp.is_gt() {
21679 Ordering::Greater
21680 } else {
21681 Ordering::Less
21682 }
21683 }) {
21684 Ok(i) | Err(i) => i,
21685 };
21686 for (index, range) in ranges[start_ix..].iter().enumerate() {
21687 if range
21688 .start
21689 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21690 .is_ge()
21691 {
21692 break;
21693 }
21694
21695 let color = color_fetcher(&(start_ix + index), theme);
21696 let start = range.start.to_display_point(display_snapshot);
21697 let end = range.end.to_display_point(display_snapshot);
21698 results.push((start..end, color))
21699 }
21700 }
21701 results
21702 }
21703
21704 pub fn gutter_highlights_in_range(
21705 &self,
21706 search_range: Range<Anchor>,
21707 display_snapshot: &DisplaySnapshot,
21708 cx: &App,
21709 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21710 let mut results = Vec::new();
21711 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21712 let color = color_fetcher(cx);
21713 let start_ix = match ranges.binary_search_by(|probe| {
21714 let cmp = probe
21715 .end
21716 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21717 if cmp.is_gt() {
21718 Ordering::Greater
21719 } else {
21720 Ordering::Less
21721 }
21722 }) {
21723 Ok(i) | Err(i) => i,
21724 };
21725 for range in &ranges[start_ix..] {
21726 if range
21727 .start
21728 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21729 .is_ge()
21730 {
21731 break;
21732 }
21733
21734 let start = range.start.to_display_point(display_snapshot);
21735 let end = range.end.to_display_point(display_snapshot);
21736 results.push((start..end, color))
21737 }
21738 }
21739 results
21740 }
21741
21742 /// Get the text ranges corresponding to the redaction query
21743 pub fn redacted_ranges(
21744 &self,
21745 search_range: Range<Anchor>,
21746 display_snapshot: &DisplaySnapshot,
21747 cx: &App,
21748 ) -> Vec<Range<DisplayPoint>> {
21749 display_snapshot
21750 .buffer_snapshot()
21751 .redacted_ranges(search_range, |file| {
21752 if let Some(file) = file {
21753 file.is_private()
21754 && EditorSettings::get(
21755 Some(SettingsLocation {
21756 worktree_id: file.worktree_id(cx),
21757 path: file.path().as_ref(),
21758 }),
21759 cx,
21760 )
21761 .redact_private_values
21762 } else {
21763 false
21764 }
21765 })
21766 .map(|range| {
21767 range.start.to_display_point(display_snapshot)
21768 ..range.end.to_display_point(display_snapshot)
21769 })
21770 .collect()
21771 }
21772
21773 pub fn highlight_text_key<T: 'static>(
21774 &mut self,
21775 key: usize,
21776 ranges: Vec<Range<Anchor>>,
21777 style: HighlightStyle,
21778 merge: bool,
21779 cx: &mut Context<Self>,
21780 ) {
21781 self.display_map.update(cx, |map, cx| {
21782 map.highlight_text(
21783 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21784 ranges,
21785 style,
21786 merge,
21787 cx,
21788 );
21789 });
21790 cx.notify();
21791 }
21792
21793 pub fn highlight_text<T: 'static>(
21794 &mut self,
21795 ranges: Vec<Range<Anchor>>,
21796 style: HighlightStyle,
21797 cx: &mut Context<Self>,
21798 ) {
21799 self.display_map.update(cx, |map, cx| {
21800 map.highlight_text(
21801 HighlightKey::Type(TypeId::of::<T>()),
21802 ranges,
21803 style,
21804 false,
21805 cx,
21806 )
21807 });
21808 cx.notify();
21809 }
21810
21811 pub fn text_highlights<'a, T: 'static>(
21812 &'a self,
21813 cx: &'a App,
21814 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21815 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21816 }
21817
21818 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21819 let cleared = self
21820 .display_map
21821 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21822 if cleared {
21823 cx.notify();
21824 }
21825 }
21826
21827 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21828 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21829 && self.focus_handle.is_focused(window)
21830 }
21831
21832 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21833 self.show_cursor_when_unfocused = is_enabled;
21834 cx.notify();
21835 }
21836
21837 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21838 cx.notify();
21839 }
21840
21841 fn on_debug_session_event(
21842 &mut self,
21843 _session: Entity<Session>,
21844 event: &SessionEvent,
21845 cx: &mut Context<Self>,
21846 ) {
21847 if let SessionEvent::InvalidateInlineValue = event {
21848 self.refresh_inline_values(cx);
21849 }
21850 }
21851
21852 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21853 let Some(project) = self.project.clone() else {
21854 return;
21855 };
21856
21857 if !self.inline_value_cache.enabled {
21858 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21859 self.splice_inlays(&inlays, Vec::new(), cx);
21860 return;
21861 }
21862
21863 let current_execution_position = self
21864 .highlighted_rows
21865 .get(&TypeId::of::<ActiveDebugLine>())
21866 .and_then(|lines| lines.last().map(|line| line.range.end));
21867
21868 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21869 let inline_values = editor
21870 .update(cx, |editor, cx| {
21871 let Some(current_execution_position) = current_execution_position else {
21872 return Some(Task::ready(Ok(Vec::new())));
21873 };
21874
21875 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21876 let snapshot = buffer.snapshot(cx);
21877
21878 let excerpt = snapshot.excerpt_containing(
21879 current_execution_position..current_execution_position,
21880 )?;
21881
21882 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21883 })?;
21884
21885 let range =
21886 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21887
21888 project.inline_values(buffer, range, cx)
21889 })
21890 .ok()
21891 .flatten()?
21892 .await
21893 .context("refreshing debugger inlays")
21894 .log_err()?;
21895
21896 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21897
21898 for (buffer_id, inline_value) in inline_values
21899 .into_iter()
21900 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21901 {
21902 buffer_inline_values
21903 .entry(buffer_id)
21904 .or_default()
21905 .push(inline_value);
21906 }
21907
21908 editor
21909 .update(cx, |editor, cx| {
21910 let snapshot = editor.buffer.read(cx).snapshot(cx);
21911 let mut new_inlays = Vec::default();
21912
21913 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21914 let buffer_id = buffer_snapshot.remote_id();
21915 buffer_inline_values
21916 .get(&buffer_id)
21917 .into_iter()
21918 .flatten()
21919 .for_each(|hint| {
21920 let inlay = Inlay::debugger(
21921 post_inc(&mut editor.next_inlay_id),
21922 Anchor::in_buffer(excerpt_id, hint.position),
21923 hint.text(),
21924 );
21925 if !inlay.text().chars().contains(&'\n') {
21926 new_inlays.push(inlay);
21927 }
21928 });
21929 }
21930
21931 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21932 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21933
21934 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21935 })
21936 .ok()?;
21937 Some(())
21938 });
21939 }
21940
21941 fn on_buffer_event(
21942 &mut self,
21943 multibuffer: &Entity<MultiBuffer>,
21944 event: &multi_buffer::Event,
21945 window: &mut Window,
21946 cx: &mut Context<Self>,
21947 ) {
21948 match event {
21949 multi_buffer::Event::Edited { edited_buffer } => {
21950 self.scrollbar_marker_state.dirty = true;
21951 self.active_indent_guides_state.dirty = true;
21952 self.refresh_active_diagnostics(cx);
21953 self.refresh_code_actions(window, cx);
21954 self.refresh_single_line_folds(window, cx);
21955 self.refresh_matching_bracket_highlights(window, cx);
21956 if self.has_active_edit_prediction() {
21957 self.update_visible_edit_prediction(window, cx);
21958 }
21959
21960 if let Some(buffer) = edited_buffer {
21961 if buffer.read(cx).file().is_none() {
21962 cx.emit(EditorEvent::TitleChanged);
21963 }
21964
21965 if self.project.is_some() {
21966 let buffer_id = buffer.read(cx).remote_id();
21967 self.register_buffer(buffer_id, cx);
21968 self.update_lsp_data(Some(buffer_id), window, cx);
21969 self.refresh_inlay_hints(
21970 InlayHintRefreshReason::BufferEdited(buffer_id),
21971 cx,
21972 );
21973 }
21974 }
21975
21976 cx.emit(EditorEvent::BufferEdited);
21977 cx.emit(SearchEvent::MatchesInvalidated);
21978
21979 let Some(project) = &self.project else { return };
21980 let (telemetry, is_via_ssh) = {
21981 let project = project.read(cx);
21982 let telemetry = project.client().telemetry().clone();
21983 let is_via_ssh = project.is_via_remote_server();
21984 (telemetry, is_via_ssh)
21985 };
21986 telemetry.log_edit_event("editor", is_via_ssh);
21987 }
21988 multi_buffer::Event::ExcerptsAdded {
21989 buffer,
21990 predecessor,
21991 excerpts,
21992 } => {
21993 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21994 let buffer_id = buffer.read(cx).remote_id();
21995 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21996 && let Some(project) = &self.project
21997 {
21998 update_uncommitted_diff_for_buffer(
21999 cx.entity(),
22000 project,
22001 [buffer.clone()],
22002 self.buffer.clone(),
22003 cx,
22004 )
22005 .detach();
22006 }
22007 self.update_lsp_data(Some(buffer_id), window, cx);
22008 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22009 self.colorize_brackets(false, cx);
22010 cx.emit(EditorEvent::ExcerptsAdded {
22011 buffer: buffer.clone(),
22012 predecessor: *predecessor,
22013 excerpts: excerpts.clone(),
22014 });
22015 }
22016 multi_buffer::Event::ExcerptsRemoved {
22017 ids,
22018 removed_buffer_ids,
22019 } => {
22020 if let Some(inlay_hints) = &mut self.inlay_hints {
22021 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22022 }
22023 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22024 for buffer_id in removed_buffer_ids {
22025 self.registered_buffers.remove(buffer_id);
22026 }
22027 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22028 cx.emit(EditorEvent::ExcerptsRemoved {
22029 ids: ids.clone(),
22030 removed_buffer_ids: removed_buffer_ids.clone(),
22031 });
22032 }
22033 multi_buffer::Event::ExcerptsEdited {
22034 excerpt_ids,
22035 buffer_ids,
22036 } => {
22037 self.display_map.update(cx, |map, cx| {
22038 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22039 });
22040 cx.emit(EditorEvent::ExcerptsEdited {
22041 ids: excerpt_ids.clone(),
22042 });
22043 }
22044 multi_buffer::Event::ExcerptsExpanded { ids } => {
22045 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22046 self.refresh_document_highlights(cx);
22047 for id in ids {
22048 self.fetched_tree_sitter_chunks.remove(id);
22049 }
22050 self.colorize_brackets(false, cx);
22051 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22052 }
22053 multi_buffer::Event::Reparsed(buffer_id) => {
22054 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22055 self.refresh_selected_text_highlights(true, window, cx);
22056 self.colorize_brackets(true, cx);
22057 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22058
22059 cx.emit(EditorEvent::Reparsed(*buffer_id));
22060 }
22061 multi_buffer::Event::DiffHunksToggled => {
22062 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22063 }
22064 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22065 if !is_fresh_language {
22066 self.registered_buffers.remove(&buffer_id);
22067 }
22068 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22069 cx.emit(EditorEvent::Reparsed(*buffer_id));
22070 cx.notify();
22071 }
22072 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22073 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22074 multi_buffer::Event::FileHandleChanged
22075 | multi_buffer::Event::Reloaded
22076 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22077 multi_buffer::Event::DiagnosticsUpdated => {
22078 self.update_diagnostics_state(window, cx);
22079 }
22080 _ => {}
22081 };
22082 }
22083
22084 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22085 if !self.diagnostics_enabled() {
22086 return;
22087 }
22088 self.refresh_active_diagnostics(cx);
22089 self.refresh_inline_diagnostics(true, window, cx);
22090 self.scrollbar_marker_state.dirty = true;
22091 cx.notify();
22092 }
22093
22094 pub fn start_temporary_diff_override(&mut self) {
22095 self.load_diff_task.take();
22096 self.temporary_diff_override = true;
22097 }
22098
22099 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22100 self.temporary_diff_override = false;
22101 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22102 self.buffer.update(cx, |buffer, cx| {
22103 buffer.set_all_diff_hunks_collapsed(cx);
22104 });
22105
22106 if let Some(project) = self.project.clone() {
22107 self.load_diff_task = Some(
22108 update_uncommitted_diff_for_buffer(
22109 cx.entity(),
22110 &project,
22111 self.buffer.read(cx).all_buffers(),
22112 self.buffer.clone(),
22113 cx,
22114 )
22115 .shared(),
22116 );
22117 }
22118 }
22119
22120 fn on_display_map_changed(
22121 &mut self,
22122 _: Entity<DisplayMap>,
22123 _: &mut Window,
22124 cx: &mut Context<Self>,
22125 ) {
22126 cx.notify();
22127 }
22128
22129 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22130 if !self.mode.is_full() {
22131 return None;
22132 }
22133
22134 let theme_settings = theme::ThemeSettings::get_global(cx);
22135 let theme = cx.theme();
22136 let accent_colors = theme.accents().clone();
22137
22138 let accent_overrides = theme_settings
22139 .theme_overrides
22140 .get(theme.name.as_ref())
22141 .map(|theme_style| &theme_style.accents)
22142 .into_iter()
22143 .flatten()
22144 .chain(
22145 theme_settings
22146 .experimental_theme_overrides
22147 .as_ref()
22148 .map(|overrides| &overrides.accents)
22149 .into_iter()
22150 .flatten(),
22151 )
22152 .flat_map(|accent| accent.0.clone())
22153 .collect();
22154
22155 Some(AccentData {
22156 colors: accent_colors,
22157 overrides: accent_overrides,
22158 })
22159 }
22160
22161 fn fetch_applicable_language_settings(
22162 &self,
22163 cx: &App,
22164 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22165 if !self.mode.is_full() {
22166 return HashMap::default();
22167 }
22168
22169 self.buffer().read(cx).all_buffers().into_iter().fold(
22170 HashMap::default(),
22171 |mut acc, buffer| {
22172 let buffer = buffer.read(cx);
22173 let language = buffer.language().map(|language| language.name());
22174 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22175 let file = buffer.file();
22176 v.insert(language_settings(language, file, cx).into_owned());
22177 }
22178 acc
22179 },
22180 )
22181 }
22182
22183 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22184 let new_language_settings = self.fetch_applicable_language_settings(cx);
22185 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22186 self.applicable_language_settings = new_language_settings;
22187
22188 let new_accents = self.fetch_accent_data(cx);
22189 let accents_changed = new_accents != self.accent_data;
22190 self.accent_data = new_accents;
22191
22192 if self.diagnostics_enabled() {
22193 let new_severity = EditorSettings::get_global(cx)
22194 .diagnostics_max_severity
22195 .unwrap_or(DiagnosticSeverity::Hint);
22196 self.set_max_diagnostics_severity(new_severity, cx);
22197 }
22198 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22199 self.update_edit_prediction_settings(cx);
22200 self.refresh_edit_prediction(true, false, window, cx);
22201 self.refresh_inline_values(cx);
22202 self.refresh_inlay_hints(
22203 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22204 self.selections.newest_anchor().head(),
22205 &self.buffer.read(cx).snapshot(cx),
22206 cx,
22207 )),
22208 cx,
22209 );
22210
22211 let old_cursor_shape = self.cursor_shape;
22212 let old_show_breadcrumbs = self.show_breadcrumbs;
22213
22214 {
22215 let editor_settings = EditorSettings::get_global(cx);
22216 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22217 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22218 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22219 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22220 }
22221
22222 if old_cursor_shape != self.cursor_shape {
22223 cx.emit(EditorEvent::CursorShapeChanged);
22224 }
22225
22226 if old_show_breadcrumbs != self.show_breadcrumbs {
22227 cx.emit(EditorEvent::BreadcrumbsChanged);
22228 }
22229
22230 let project_settings = ProjectSettings::get_global(cx);
22231 self.buffer_serialization = self
22232 .should_serialize_buffer()
22233 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22234
22235 if self.mode.is_full() {
22236 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22237 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22238 if self.show_inline_diagnostics != show_inline_diagnostics {
22239 self.show_inline_diagnostics = show_inline_diagnostics;
22240 self.refresh_inline_diagnostics(false, window, cx);
22241 }
22242
22243 if self.git_blame_inline_enabled != inline_blame_enabled {
22244 self.toggle_git_blame_inline_internal(false, window, cx);
22245 }
22246
22247 let minimap_settings = EditorSettings::get_global(cx).minimap;
22248 if self.minimap_visibility != MinimapVisibility::Disabled {
22249 if self.minimap_visibility.settings_visibility()
22250 != minimap_settings.minimap_enabled()
22251 {
22252 self.set_minimap_visibility(
22253 MinimapVisibility::for_mode(self.mode(), cx),
22254 window,
22255 cx,
22256 );
22257 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22258 minimap_entity.update(cx, |minimap_editor, cx| {
22259 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22260 })
22261 }
22262 }
22263
22264 if language_settings_changed || accents_changed {
22265 self.colorize_brackets(true, cx);
22266 }
22267
22268 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22269 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22270 }) {
22271 if !inlay_splice.is_empty() {
22272 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22273 }
22274 self.refresh_colors_for_visible_range(None, window, cx);
22275 }
22276 }
22277
22278 cx.notify();
22279 }
22280
22281 pub fn set_searchable(&mut self, searchable: bool) {
22282 self.searchable = searchable;
22283 }
22284
22285 pub fn searchable(&self) -> bool {
22286 self.searchable
22287 }
22288
22289 pub fn open_excerpts_in_split(
22290 &mut self,
22291 _: &OpenExcerptsSplit,
22292 window: &mut Window,
22293 cx: &mut Context<Self>,
22294 ) {
22295 self.open_excerpts_common(None, true, window, cx)
22296 }
22297
22298 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22299 self.open_excerpts_common(None, false, window, cx)
22300 }
22301
22302 fn open_excerpts_common(
22303 &mut self,
22304 jump_data: Option<JumpData>,
22305 split: bool,
22306 window: &mut Window,
22307 cx: &mut Context<Self>,
22308 ) {
22309 let Some(workspace) = self.workspace() else {
22310 cx.propagate();
22311 return;
22312 };
22313
22314 if self.buffer.read(cx).is_singleton() {
22315 cx.propagate();
22316 return;
22317 }
22318
22319 let mut new_selections_by_buffer = HashMap::default();
22320 match &jump_data {
22321 Some(JumpData::MultiBufferPoint {
22322 excerpt_id,
22323 position,
22324 anchor,
22325 line_offset_from_top,
22326 }) => {
22327 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22328 if let Some(buffer) = multi_buffer_snapshot
22329 .buffer_id_for_excerpt(*excerpt_id)
22330 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22331 {
22332 let buffer_snapshot = buffer.read(cx).snapshot();
22333 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22334 language::ToPoint::to_point(anchor, &buffer_snapshot)
22335 } else {
22336 buffer_snapshot.clip_point(*position, Bias::Left)
22337 };
22338 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22339 new_selections_by_buffer.insert(
22340 buffer,
22341 (
22342 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22343 Some(*line_offset_from_top),
22344 ),
22345 );
22346 }
22347 }
22348 Some(JumpData::MultiBufferRow {
22349 row,
22350 line_offset_from_top,
22351 }) => {
22352 let point = MultiBufferPoint::new(row.0, 0);
22353 if let Some((buffer, buffer_point, _)) =
22354 self.buffer.read(cx).point_to_buffer_point(point, cx)
22355 {
22356 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22357 new_selections_by_buffer
22358 .entry(buffer)
22359 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22360 .0
22361 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22362 }
22363 }
22364 None => {
22365 let selections = self
22366 .selections
22367 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22368 let multi_buffer = self.buffer.read(cx);
22369 for selection in selections {
22370 for (snapshot, range, _, anchor) in multi_buffer
22371 .snapshot(cx)
22372 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22373 {
22374 if let Some(anchor) = anchor {
22375 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22376 else {
22377 continue;
22378 };
22379 let offset = text::ToOffset::to_offset(
22380 &anchor.text_anchor,
22381 &buffer_handle.read(cx).snapshot(),
22382 );
22383 let range = BufferOffset(offset)..BufferOffset(offset);
22384 new_selections_by_buffer
22385 .entry(buffer_handle)
22386 .or_insert((Vec::new(), None))
22387 .0
22388 .push(range)
22389 } else {
22390 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22391 else {
22392 continue;
22393 };
22394 new_selections_by_buffer
22395 .entry(buffer_handle)
22396 .or_insert((Vec::new(), None))
22397 .0
22398 .push(range)
22399 }
22400 }
22401 }
22402 }
22403 }
22404
22405 new_selections_by_buffer
22406 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22407
22408 if new_selections_by_buffer.is_empty() {
22409 return;
22410 }
22411
22412 // We defer the pane interaction because we ourselves are a workspace item
22413 // and activating a new item causes the pane to call a method on us reentrantly,
22414 // which panics if we're on the stack.
22415 window.defer(cx, move |window, cx| {
22416 workspace.update(cx, |workspace, cx| {
22417 let pane = if split {
22418 workspace.adjacent_pane(window, cx)
22419 } else {
22420 workspace.active_pane().clone()
22421 };
22422
22423 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22424 let buffer_read = buffer.read(cx);
22425 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22426 (true, project::File::from_dyn(Some(file)).is_some())
22427 } else {
22428 (false, false)
22429 };
22430
22431 // If project file is none workspace.open_project_item will fail to open the excerpt
22432 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22433 // so we check if there's a tab match in that case first
22434 let editor = (!has_file || !is_project_file)
22435 .then(|| {
22436 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22437 // so `workspace.open_project_item` will never find them, always opening a new editor.
22438 // Instead, we try to activate the existing editor in the pane first.
22439 let (editor, pane_item_index, pane_item_id) =
22440 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22441 let editor = item.downcast::<Editor>()?;
22442 let singleton_buffer =
22443 editor.read(cx).buffer().read(cx).as_singleton()?;
22444 if singleton_buffer == buffer {
22445 Some((editor, i, item.item_id()))
22446 } else {
22447 None
22448 }
22449 })?;
22450 pane.update(cx, |pane, cx| {
22451 pane.activate_item(pane_item_index, true, true, window, cx);
22452 if !PreviewTabsSettings::get_global(cx)
22453 .enable_preview_from_multibuffer
22454 {
22455 pane.unpreview_item_if_preview(pane_item_id);
22456 }
22457 });
22458 Some(editor)
22459 })
22460 .flatten()
22461 .unwrap_or_else(|| {
22462 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22463 .enable_keep_preview_on_code_navigation;
22464 let allow_new_preview =
22465 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22466 workspace.open_project_item::<Self>(
22467 pane.clone(),
22468 buffer,
22469 true,
22470 true,
22471 keep_old_preview,
22472 allow_new_preview,
22473 window,
22474 cx,
22475 )
22476 });
22477
22478 editor.update(cx, |editor, cx| {
22479 if has_file && !is_project_file {
22480 editor.set_read_only(true);
22481 }
22482 let autoscroll = match scroll_offset {
22483 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22484 None => Autoscroll::newest(),
22485 };
22486 let nav_history = editor.nav_history.take();
22487 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22488 let Some((&excerpt_id, _, buffer_snapshot)) =
22489 multibuffer_snapshot.as_singleton()
22490 else {
22491 return;
22492 };
22493 editor.change_selections(
22494 SelectionEffects::scroll(autoscroll),
22495 window,
22496 cx,
22497 |s| {
22498 s.select_ranges(ranges.into_iter().map(|range| {
22499 let range = buffer_snapshot.anchor_before(range.start)
22500 ..buffer_snapshot.anchor_after(range.end);
22501 multibuffer_snapshot
22502 .anchor_range_in_excerpt(excerpt_id, range)
22503 .unwrap()
22504 }));
22505 },
22506 );
22507 editor.nav_history = nav_history;
22508 });
22509 }
22510 })
22511 });
22512 }
22513
22514 // Allow opening excerpts for buffers that either belong to the current project
22515 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22516 // are also supported so tests and other in-memory views keep working.
22517 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22518 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22519 }
22520
22521 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22522 let snapshot = self.buffer.read(cx).read(cx);
22523 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22524 Some(
22525 ranges
22526 .iter()
22527 .map(move |range| {
22528 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22529 })
22530 .collect(),
22531 )
22532 }
22533
22534 fn selection_replacement_ranges(
22535 &self,
22536 range: Range<MultiBufferOffsetUtf16>,
22537 cx: &mut App,
22538 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22539 let selections = self
22540 .selections
22541 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22542 let newest_selection = selections
22543 .iter()
22544 .max_by_key(|selection| selection.id)
22545 .unwrap();
22546 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22547 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22548 let snapshot = self.buffer.read(cx).read(cx);
22549 selections
22550 .into_iter()
22551 .map(|mut selection| {
22552 selection.start.0.0 =
22553 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22554 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22555 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22556 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22557 })
22558 .collect()
22559 }
22560
22561 fn report_editor_event(
22562 &self,
22563 reported_event: ReportEditorEvent,
22564 file_extension: Option<String>,
22565 cx: &App,
22566 ) {
22567 if cfg!(any(test, feature = "test-support")) {
22568 return;
22569 }
22570
22571 let Some(project) = &self.project else { return };
22572
22573 // If None, we are in a file without an extension
22574 let file = self
22575 .buffer
22576 .read(cx)
22577 .as_singleton()
22578 .and_then(|b| b.read(cx).file());
22579 let file_extension = file_extension.or(file
22580 .as_ref()
22581 .and_then(|file| Path::new(file.file_name(cx)).extension())
22582 .and_then(|e| e.to_str())
22583 .map(|a| a.to_string()));
22584
22585 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22586 .map(|vim_mode| vim_mode.0)
22587 .unwrap_or(false);
22588
22589 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22590 let copilot_enabled = edit_predictions_provider
22591 == language::language_settings::EditPredictionProvider::Copilot;
22592 let copilot_enabled_for_language = self
22593 .buffer
22594 .read(cx)
22595 .language_settings(cx)
22596 .show_edit_predictions;
22597
22598 let project = project.read(cx);
22599 let event_type = reported_event.event_type();
22600
22601 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22602 telemetry::event!(
22603 event_type,
22604 type = if auto_saved {"autosave"} else {"manual"},
22605 file_extension,
22606 vim_mode,
22607 copilot_enabled,
22608 copilot_enabled_for_language,
22609 edit_predictions_provider,
22610 is_via_ssh = project.is_via_remote_server(),
22611 );
22612 } else {
22613 telemetry::event!(
22614 event_type,
22615 file_extension,
22616 vim_mode,
22617 copilot_enabled,
22618 copilot_enabled_for_language,
22619 edit_predictions_provider,
22620 is_via_ssh = project.is_via_remote_server(),
22621 );
22622 };
22623 }
22624
22625 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22626 /// with each line being an array of {text, highlight} objects.
22627 fn copy_highlight_json(
22628 &mut self,
22629 _: &CopyHighlightJson,
22630 window: &mut Window,
22631 cx: &mut Context<Self>,
22632 ) {
22633 #[derive(Serialize)]
22634 struct Chunk<'a> {
22635 text: String,
22636 highlight: Option<&'a str>,
22637 }
22638
22639 let snapshot = self.buffer.read(cx).snapshot(cx);
22640 let range = self
22641 .selected_text_range(false, window, cx)
22642 .and_then(|selection| {
22643 if selection.range.is_empty() {
22644 None
22645 } else {
22646 Some(
22647 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22648 selection.range.start,
22649 )))
22650 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22651 selection.range.end,
22652 ))),
22653 )
22654 }
22655 })
22656 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22657
22658 let chunks = snapshot.chunks(range, true);
22659 let mut lines = Vec::new();
22660 let mut line: VecDeque<Chunk> = VecDeque::new();
22661
22662 let Some(style) = self.style.as_ref() else {
22663 return;
22664 };
22665
22666 for chunk in chunks {
22667 let highlight = chunk
22668 .syntax_highlight_id
22669 .and_then(|id| id.name(&style.syntax));
22670 let mut chunk_lines = chunk.text.split('\n').peekable();
22671 while let Some(text) = chunk_lines.next() {
22672 let mut merged_with_last_token = false;
22673 if let Some(last_token) = line.back_mut()
22674 && last_token.highlight == highlight
22675 {
22676 last_token.text.push_str(text);
22677 merged_with_last_token = true;
22678 }
22679
22680 if !merged_with_last_token {
22681 line.push_back(Chunk {
22682 text: text.into(),
22683 highlight,
22684 });
22685 }
22686
22687 if chunk_lines.peek().is_some() {
22688 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22689 line.pop_front();
22690 }
22691 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22692 line.pop_back();
22693 }
22694
22695 lines.push(mem::take(&mut line));
22696 }
22697 }
22698 }
22699
22700 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22701 return;
22702 };
22703 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22704 }
22705
22706 pub fn open_context_menu(
22707 &mut self,
22708 _: &OpenContextMenu,
22709 window: &mut Window,
22710 cx: &mut Context<Self>,
22711 ) {
22712 self.request_autoscroll(Autoscroll::newest(), cx);
22713 let position = self
22714 .selections
22715 .newest_display(&self.display_snapshot(cx))
22716 .start;
22717 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22718 }
22719
22720 pub fn replay_insert_event(
22721 &mut self,
22722 text: &str,
22723 relative_utf16_range: Option<Range<isize>>,
22724 window: &mut Window,
22725 cx: &mut Context<Self>,
22726 ) {
22727 if !self.input_enabled {
22728 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22729 return;
22730 }
22731 if let Some(relative_utf16_range) = relative_utf16_range {
22732 let selections = self
22733 .selections
22734 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22735 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22736 let new_ranges = selections.into_iter().map(|range| {
22737 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22738 range
22739 .head()
22740 .0
22741 .0
22742 .saturating_add_signed(relative_utf16_range.start),
22743 ));
22744 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22745 range
22746 .head()
22747 .0
22748 .0
22749 .saturating_add_signed(relative_utf16_range.end),
22750 ));
22751 start..end
22752 });
22753 s.select_ranges(new_ranges);
22754 });
22755 }
22756
22757 self.handle_input(text, window, cx);
22758 }
22759
22760 pub fn is_focused(&self, window: &Window) -> bool {
22761 self.focus_handle.is_focused(window)
22762 }
22763
22764 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22765 cx.emit(EditorEvent::Focused);
22766
22767 if let Some(descendant) = self
22768 .last_focused_descendant
22769 .take()
22770 .and_then(|descendant| descendant.upgrade())
22771 {
22772 window.focus(&descendant);
22773 } else {
22774 if let Some(blame) = self.blame.as_ref() {
22775 blame.update(cx, GitBlame::focus)
22776 }
22777
22778 self.blink_manager.update(cx, BlinkManager::enable);
22779 self.show_cursor_names(window, cx);
22780 self.buffer.update(cx, |buffer, cx| {
22781 buffer.finalize_last_transaction(cx);
22782 if self.leader_id.is_none() {
22783 buffer.set_active_selections(
22784 &self.selections.disjoint_anchors_arc(),
22785 self.selections.line_mode(),
22786 self.cursor_shape,
22787 cx,
22788 );
22789 }
22790 });
22791
22792 if let Some(position_map) = self.last_position_map.clone() {
22793 EditorElement::mouse_moved(
22794 self,
22795 &MouseMoveEvent {
22796 position: window.mouse_position(),
22797 pressed_button: None,
22798 modifiers: window.modifiers(),
22799 },
22800 &position_map,
22801 window,
22802 cx,
22803 );
22804 }
22805 }
22806 }
22807
22808 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22809 cx.emit(EditorEvent::FocusedIn)
22810 }
22811
22812 fn handle_focus_out(
22813 &mut self,
22814 event: FocusOutEvent,
22815 _window: &mut Window,
22816 cx: &mut Context<Self>,
22817 ) {
22818 if event.blurred != self.focus_handle {
22819 self.last_focused_descendant = Some(event.blurred);
22820 }
22821 self.selection_drag_state = SelectionDragState::None;
22822 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22823 }
22824
22825 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22826 self.blink_manager.update(cx, BlinkManager::disable);
22827 self.buffer
22828 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22829
22830 if let Some(blame) = self.blame.as_ref() {
22831 blame.update(cx, GitBlame::blur)
22832 }
22833 if !self.hover_state.focused(window, cx) {
22834 hide_hover(self, cx);
22835 }
22836 if !self
22837 .context_menu
22838 .borrow()
22839 .as_ref()
22840 .is_some_and(|context_menu| context_menu.focused(window, cx))
22841 {
22842 self.hide_context_menu(window, cx);
22843 }
22844 self.take_active_edit_prediction(cx);
22845 cx.emit(EditorEvent::Blurred);
22846 cx.notify();
22847 }
22848
22849 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22850 let mut pending: String = window
22851 .pending_input_keystrokes()
22852 .into_iter()
22853 .flatten()
22854 .filter_map(|keystroke| keystroke.key_char.clone())
22855 .collect();
22856
22857 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22858 pending = "".to_string();
22859 }
22860
22861 let existing_pending = self
22862 .text_highlights::<PendingInput>(cx)
22863 .map(|(_, ranges)| ranges.to_vec());
22864 if existing_pending.is_none() && pending.is_empty() {
22865 return;
22866 }
22867 let transaction =
22868 self.transact(window, cx, |this, window, cx| {
22869 let selections = this
22870 .selections
22871 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22872 let edits = selections
22873 .iter()
22874 .map(|selection| (selection.end..selection.end, pending.clone()));
22875 this.edit(edits, cx);
22876 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22877 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22878 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22879 }));
22880 });
22881 if let Some(existing_ranges) = existing_pending {
22882 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22883 this.edit(edits, cx);
22884 }
22885 });
22886
22887 let snapshot = self.snapshot(window, cx);
22888 let ranges = self
22889 .selections
22890 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22891 .into_iter()
22892 .map(|selection| {
22893 snapshot.buffer_snapshot().anchor_after(selection.end)
22894 ..snapshot
22895 .buffer_snapshot()
22896 .anchor_before(selection.end + pending.len())
22897 })
22898 .collect();
22899
22900 if pending.is_empty() {
22901 self.clear_highlights::<PendingInput>(cx);
22902 } else {
22903 self.highlight_text::<PendingInput>(
22904 ranges,
22905 HighlightStyle {
22906 underline: Some(UnderlineStyle {
22907 thickness: px(1.),
22908 color: None,
22909 wavy: false,
22910 }),
22911 ..Default::default()
22912 },
22913 cx,
22914 );
22915 }
22916
22917 self.ime_transaction = self.ime_transaction.or(transaction);
22918 if let Some(transaction) = self.ime_transaction {
22919 self.buffer.update(cx, |buffer, cx| {
22920 buffer.group_until_transaction(transaction, cx);
22921 });
22922 }
22923
22924 if self.text_highlights::<PendingInput>(cx).is_none() {
22925 self.ime_transaction.take();
22926 }
22927 }
22928
22929 pub fn register_action_renderer(
22930 &mut self,
22931 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22932 ) -> Subscription {
22933 let id = self.next_editor_action_id.post_inc();
22934 self.editor_actions
22935 .borrow_mut()
22936 .insert(id, Box::new(listener));
22937
22938 let editor_actions = self.editor_actions.clone();
22939 Subscription::new(move || {
22940 editor_actions.borrow_mut().remove(&id);
22941 })
22942 }
22943
22944 pub fn register_action<A: Action>(
22945 &mut self,
22946 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22947 ) -> Subscription {
22948 let id = self.next_editor_action_id.post_inc();
22949 let listener = Arc::new(listener);
22950 self.editor_actions.borrow_mut().insert(
22951 id,
22952 Box::new(move |_, window, _| {
22953 let listener = listener.clone();
22954 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22955 let action = action.downcast_ref().unwrap();
22956 if phase == DispatchPhase::Bubble {
22957 listener(action, window, cx)
22958 }
22959 })
22960 }),
22961 );
22962
22963 let editor_actions = self.editor_actions.clone();
22964 Subscription::new(move || {
22965 editor_actions.borrow_mut().remove(&id);
22966 })
22967 }
22968
22969 pub fn file_header_size(&self) -> u32 {
22970 FILE_HEADER_HEIGHT
22971 }
22972
22973 pub fn restore(
22974 &mut self,
22975 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22976 window: &mut Window,
22977 cx: &mut Context<Self>,
22978 ) {
22979 self.buffer().update(cx, |multi_buffer, cx| {
22980 for (buffer_id, changes) in revert_changes {
22981 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22982 buffer.update(cx, |buffer, cx| {
22983 buffer.edit(
22984 changes
22985 .into_iter()
22986 .map(|(range, text)| (range, text.to_string())),
22987 None,
22988 cx,
22989 );
22990 });
22991 }
22992 }
22993 });
22994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22995 selections.refresh()
22996 });
22997 }
22998
22999 pub fn to_pixel_point(
23000 &mut self,
23001 source: multi_buffer::Anchor,
23002 editor_snapshot: &EditorSnapshot,
23003 window: &mut Window,
23004 cx: &App,
23005 ) -> Option<gpui::Point<Pixels>> {
23006 let source_point = source.to_display_point(editor_snapshot);
23007 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23008 }
23009
23010 pub fn display_to_pixel_point(
23011 &mut self,
23012 source: DisplayPoint,
23013 editor_snapshot: &EditorSnapshot,
23014 window: &mut Window,
23015 cx: &App,
23016 ) -> Option<gpui::Point<Pixels>> {
23017 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23018 let text_layout_details = self.text_layout_details(window);
23019 let scroll_top = text_layout_details
23020 .scroll_anchor
23021 .scroll_position(editor_snapshot)
23022 .y;
23023
23024 if source.row().as_f64() < scroll_top.floor() {
23025 return None;
23026 }
23027 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23028 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23029 Some(gpui::Point::new(source_x, source_y))
23030 }
23031
23032 pub fn has_visible_completions_menu(&self) -> bool {
23033 !self.edit_prediction_preview_is_active()
23034 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23035 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23036 })
23037 }
23038
23039 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23040 if self.mode.is_minimap() {
23041 return;
23042 }
23043 self.addons
23044 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23045 }
23046
23047 pub fn unregister_addon<T: Addon>(&mut self) {
23048 self.addons.remove(&std::any::TypeId::of::<T>());
23049 }
23050
23051 pub fn addon<T: Addon>(&self) -> Option<&T> {
23052 let type_id = std::any::TypeId::of::<T>();
23053 self.addons
23054 .get(&type_id)
23055 .and_then(|item| item.to_any().downcast_ref::<T>())
23056 }
23057
23058 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23059 let type_id = std::any::TypeId::of::<T>();
23060 self.addons
23061 .get_mut(&type_id)
23062 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23063 }
23064
23065 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23066 let text_layout_details = self.text_layout_details(window);
23067 let style = &text_layout_details.editor_style;
23068 let font_id = window.text_system().resolve_font(&style.text.font());
23069 let font_size = style.text.font_size.to_pixels(window.rem_size());
23070 let line_height = style.text.line_height_in_pixels(window.rem_size());
23071 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23072 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23073
23074 CharacterDimensions {
23075 em_width,
23076 em_advance,
23077 line_height,
23078 }
23079 }
23080
23081 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23082 self.load_diff_task.clone()
23083 }
23084
23085 fn read_metadata_from_db(
23086 &mut self,
23087 item_id: u64,
23088 workspace_id: WorkspaceId,
23089 window: &mut Window,
23090 cx: &mut Context<Editor>,
23091 ) {
23092 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23093 && !self.mode.is_minimap()
23094 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
23095 {
23096 let buffer_snapshot = OnceCell::new();
23097
23098 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23099 && !folds.is_empty()
23100 {
23101 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23102 self.fold_ranges(
23103 folds
23104 .into_iter()
23105 .map(|(start, end)| {
23106 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23107 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23108 })
23109 .collect(),
23110 false,
23111 window,
23112 cx,
23113 );
23114 }
23115
23116 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23117 && !selections.is_empty()
23118 {
23119 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23120 // skip adding the initial selection to selection history
23121 self.selection_history.mode = SelectionHistoryMode::Skipping;
23122 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23123 s.select_ranges(selections.into_iter().map(|(start, end)| {
23124 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23125 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23126 }));
23127 });
23128 self.selection_history.mode = SelectionHistoryMode::Normal;
23129 };
23130 }
23131
23132 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23133 }
23134
23135 fn update_lsp_data(
23136 &mut self,
23137 for_buffer: Option<BufferId>,
23138 window: &mut Window,
23139 cx: &mut Context<'_, Self>,
23140 ) {
23141 self.pull_diagnostics(for_buffer, window, cx);
23142 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23143 }
23144
23145 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23146 if self.ignore_lsp_data() {
23147 return;
23148 }
23149 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23150 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23151 }
23152 }
23153
23154 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23155 if self.ignore_lsp_data() {
23156 return;
23157 }
23158
23159 if !self.registered_buffers.contains_key(&buffer_id)
23160 && let Some(project) = self.project.as_ref()
23161 {
23162 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23163 project.update(cx, |project, cx| {
23164 self.registered_buffers.insert(
23165 buffer_id,
23166 project.register_buffer_with_language_servers(&buffer, cx),
23167 );
23168 });
23169 } else {
23170 self.registered_buffers.remove(&buffer_id);
23171 }
23172 }
23173 }
23174
23175 fn ignore_lsp_data(&self) -> bool {
23176 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23177 // skip any LSP updates for it.
23178 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23179 }
23180
23181 fn create_style(&self, cx: &App) -> EditorStyle {
23182 let settings = ThemeSettings::get_global(cx);
23183
23184 let mut text_style = match self.mode {
23185 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23186 color: cx.theme().colors().editor_foreground,
23187 font_family: settings.ui_font.family.clone(),
23188 font_features: settings.ui_font.features.clone(),
23189 font_fallbacks: settings.ui_font.fallbacks.clone(),
23190 font_size: rems(0.875).into(),
23191 font_weight: settings.ui_font.weight,
23192 line_height: relative(settings.buffer_line_height.value()),
23193 ..Default::default()
23194 },
23195 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23196 color: cx.theme().colors().editor_foreground,
23197 font_family: settings.buffer_font.family.clone(),
23198 font_features: settings.buffer_font.features.clone(),
23199 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23200 font_size: settings.buffer_font_size(cx).into(),
23201 font_weight: settings.buffer_font.weight,
23202 line_height: relative(settings.buffer_line_height.value()),
23203 ..Default::default()
23204 },
23205 };
23206 if let Some(text_style_refinement) = &self.text_style_refinement {
23207 text_style.refine(text_style_refinement)
23208 }
23209
23210 let background = match self.mode {
23211 EditorMode::SingleLine => cx.theme().system().transparent,
23212 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23213 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23214 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23215 };
23216
23217 EditorStyle {
23218 background,
23219 border: cx.theme().colors().border,
23220 local_player: cx.theme().players().local(),
23221 text: text_style,
23222 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23223 syntax: cx.theme().syntax().clone(),
23224 status: cx.theme().status().clone(),
23225 inlay_hints_style: make_inlay_hints_style(cx),
23226 edit_prediction_styles: make_suggestion_styles(cx),
23227 unnecessary_code_fade: settings.unnecessary_code_fade,
23228 show_underlines: self.diagnostics_enabled(),
23229 }
23230 }
23231}
23232
23233fn edit_for_markdown_paste<'a>(
23234 buffer: &MultiBufferSnapshot,
23235 range: Range<MultiBufferOffset>,
23236 to_insert: &'a str,
23237 url: Option<url::Url>,
23238) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23239 if url.is_none() {
23240 return (range, Cow::Borrowed(to_insert));
23241 };
23242
23243 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23244
23245 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23246 Cow::Borrowed(to_insert)
23247 } else {
23248 Cow::Owned(format!("[{old_text}]({to_insert})"))
23249 };
23250 (range, new_text)
23251}
23252
23253fn process_completion_for_edit(
23254 completion: &Completion,
23255 intent: CompletionIntent,
23256 buffer: &Entity<Buffer>,
23257 cursor_position: &text::Anchor,
23258 cx: &mut Context<Editor>,
23259) -> CompletionEdit {
23260 let buffer = buffer.read(cx);
23261 let buffer_snapshot = buffer.snapshot();
23262 let (snippet, new_text) = if completion.is_snippet() {
23263 let mut snippet_source = completion.new_text.clone();
23264 // Workaround for typescript language server issues so that methods don't expand within
23265 // strings and functions with type expressions. The previous point is used because the query
23266 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23267 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23268 let previous_point = if previous_point.column > 0 {
23269 cursor_position.to_previous_offset(&buffer_snapshot)
23270 } else {
23271 cursor_position.to_offset(&buffer_snapshot)
23272 };
23273 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23274 && scope.prefers_label_for_snippet_in_completion()
23275 && let Some(label) = completion.label()
23276 && matches!(
23277 completion.kind(),
23278 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23279 )
23280 {
23281 snippet_source = label;
23282 }
23283 match Snippet::parse(&snippet_source).log_err() {
23284 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23285 None => (None, completion.new_text.clone()),
23286 }
23287 } else {
23288 (None, completion.new_text.clone())
23289 };
23290
23291 let mut range_to_replace = {
23292 let replace_range = &completion.replace_range;
23293 if let CompletionSource::Lsp {
23294 insert_range: Some(insert_range),
23295 ..
23296 } = &completion.source
23297 {
23298 debug_assert_eq!(
23299 insert_range.start, replace_range.start,
23300 "insert_range and replace_range should start at the same position"
23301 );
23302 debug_assert!(
23303 insert_range
23304 .start
23305 .cmp(cursor_position, &buffer_snapshot)
23306 .is_le(),
23307 "insert_range should start before or at cursor position"
23308 );
23309 debug_assert!(
23310 replace_range
23311 .start
23312 .cmp(cursor_position, &buffer_snapshot)
23313 .is_le(),
23314 "replace_range should start before or at cursor position"
23315 );
23316
23317 let should_replace = match intent {
23318 CompletionIntent::CompleteWithInsert => false,
23319 CompletionIntent::CompleteWithReplace => true,
23320 CompletionIntent::Complete | CompletionIntent::Compose => {
23321 let insert_mode =
23322 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23323 .completions
23324 .lsp_insert_mode;
23325 match insert_mode {
23326 LspInsertMode::Insert => false,
23327 LspInsertMode::Replace => true,
23328 LspInsertMode::ReplaceSubsequence => {
23329 let mut text_to_replace = buffer.chars_for_range(
23330 buffer.anchor_before(replace_range.start)
23331 ..buffer.anchor_after(replace_range.end),
23332 );
23333 let mut current_needle = text_to_replace.next();
23334 for haystack_ch in completion.label.text.chars() {
23335 if let Some(needle_ch) = current_needle
23336 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23337 {
23338 current_needle = text_to_replace.next();
23339 }
23340 }
23341 current_needle.is_none()
23342 }
23343 LspInsertMode::ReplaceSuffix => {
23344 if replace_range
23345 .end
23346 .cmp(cursor_position, &buffer_snapshot)
23347 .is_gt()
23348 {
23349 let range_after_cursor = *cursor_position..replace_range.end;
23350 let text_after_cursor = buffer
23351 .text_for_range(
23352 buffer.anchor_before(range_after_cursor.start)
23353 ..buffer.anchor_after(range_after_cursor.end),
23354 )
23355 .collect::<String>()
23356 .to_ascii_lowercase();
23357 completion
23358 .label
23359 .text
23360 .to_ascii_lowercase()
23361 .ends_with(&text_after_cursor)
23362 } else {
23363 true
23364 }
23365 }
23366 }
23367 }
23368 };
23369
23370 if should_replace {
23371 replace_range.clone()
23372 } else {
23373 insert_range.clone()
23374 }
23375 } else {
23376 replace_range.clone()
23377 }
23378 };
23379
23380 if range_to_replace
23381 .end
23382 .cmp(cursor_position, &buffer_snapshot)
23383 .is_lt()
23384 {
23385 range_to_replace.end = *cursor_position;
23386 }
23387
23388 let replace_range = range_to_replace.to_offset(buffer);
23389 CompletionEdit {
23390 new_text,
23391 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23392 snippet,
23393 }
23394}
23395
23396struct CompletionEdit {
23397 new_text: String,
23398 replace_range: Range<BufferOffset>,
23399 snippet: Option<Snippet>,
23400}
23401
23402fn insert_extra_newline_brackets(
23403 buffer: &MultiBufferSnapshot,
23404 range: Range<MultiBufferOffset>,
23405 language: &language::LanguageScope,
23406) -> bool {
23407 let leading_whitespace_len = buffer
23408 .reversed_chars_at(range.start)
23409 .take_while(|c| c.is_whitespace() && *c != '\n')
23410 .map(|c| c.len_utf8())
23411 .sum::<usize>();
23412 let trailing_whitespace_len = buffer
23413 .chars_at(range.end)
23414 .take_while(|c| c.is_whitespace() && *c != '\n')
23415 .map(|c| c.len_utf8())
23416 .sum::<usize>();
23417 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23418
23419 language.brackets().any(|(pair, enabled)| {
23420 let pair_start = pair.start.trim_end();
23421 let pair_end = pair.end.trim_start();
23422
23423 enabled
23424 && pair.newline
23425 && buffer.contains_str_at(range.end, pair_end)
23426 && buffer.contains_str_at(
23427 range.start.saturating_sub_usize(pair_start.len()),
23428 pair_start,
23429 )
23430 })
23431}
23432
23433fn insert_extra_newline_tree_sitter(
23434 buffer: &MultiBufferSnapshot,
23435 range: Range<MultiBufferOffset>,
23436) -> bool {
23437 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23438 [(buffer, range, _)] => (*buffer, range.clone()),
23439 _ => return false,
23440 };
23441 let pair = {
23442 let mut result: Option<BracketMatch<usize>> = None;
23443
23444 for pair in buffer
23445 .all_bracket_ranges(range.start.0..range.end.0)
23446 .filter(move |pair| {
23447 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23448 })
23449 {
23450 let len = pair.close_range.end - pair.open_range.start;
23451
23452 if let Some(existing) = &result {
23453 let existing_len = existing.close_range.end - existing.open_range.start;
23454 if len > existing_len {
23455 continue;
23456 }
23457 }
23458
23459 result = Some(pair);
23460 }
23461
23462 result
23463 };
23464 let Some(pair) = pair else {
23465 return false;
23466 };
23467 pair.newline_only
23468 && buffer
23469 .chars_for_range(pair.open_range.end..range.start.0)
23470 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23471 .all(|c| c.is_whitespace() && c != '\n')
23472}
23473
23474fn update_uncommitted_diff_for_buffer(
23475 editor: Entity<Editor>,
23476 project: &Entity<Project>,
23477 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23478 buffer: Entity<MultiBuffer>,
23479 cx: &mut App,
23480) -> Task<()> {
23481 let mut tasks = Vec::new();
23482 project.update(cx, |project, cx| {
23483 for buffer in buffers {
23484 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23485 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23486 }
23487 }
23488 });
23489 cx.spawn(async move |cx| {
23490 let diffs = future::join_all(tasks).await;
23491 if editor
23492 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23493 .unwrap_or(false)
23494 {
23495 return;
23496 }
23497
23498 buffer
23499 .update(cx, |buffer, cx| {
23500 for diff in diffs.into_iter().flatten() {
23501 buffer.add_diff(diff, cx);
23502 }
23503 })
23504 .ok();
23505 })
23506}
23507
23508fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23509 let tab_size = tab_size.get() as usize;
23510 let mut width = offset;
23511
23512 for ch in text.chars() {
23513 width += if ch == '\t' {
23514 tab_size - (width % tab_size)
23515 } else {
23516 1
23517 };
23518 }
23519
23520 width - offset
23521}
23522
23523#[cfg(test)]
23524mod tests {
23525 use super::*;
23526
23527 #[test]
23528 fn test_string_size_with_expanded_tabs() {
23529 let nz = |val| NonZeroU32::new(val).unwrap();
23530 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23531 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23532 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23533 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23534 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23535 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23536 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23537 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23538 }
23539}
23540
23541/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23542struct WordBreakingTokenizer<'a> {
23543 input: &'a str,
23544}
23545
23546impl<'a> WordBreakingTokenizer<'a> {
23547 fn new(input: &'a str) -> Self {
23548 Self { input }
23549 }
23550}
23551
23552fn is_char_ideographic(ch: char) -> bool {
23553 use unicode_script::Script::*;
23554 use unicode_script::UnicodeScript;
23555 matches!(ch.script(), Han | Tangut | Yi)
23556}
23557
23558fn is_grapheme_ideographic(text: &str) -> bool {
23559 text.chars().any(is_char_ideographic)
23560}
23561
23562fn is_grapheme_whitespace(text: &str) -> bool {
23563 text.chars().any(|x| x.is_whitespace())
23564}
23565
23566fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23567 text.chars()
23568 .next()
23569 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23570}
23571
23572#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23573enum WordBreakToken<'a> {
23574 Word { token: &'a str, grapheme_len: usize },
23575 InlineWhitespace { token: &'a str, grapheme_len: usize },
23576 Newline,
23577}
23578
23579impl<'a> Iterator for WordBreakingTokenizer<'a> {
23580 /// Yields a span, the count of graphemes in the token, and whether it was
23581 /// whitespace. Note that it also breaks at word boundaries.
23582 type Item = WordBreakToken<'a>;
23583
23584 fn next(&mut self) -> Option<Self::Item> {
23585 use unicode_segmentation::UnicodeSegmentation;
23586 if self.input.is_empty() {
23587 return None;
23588 }
23589
23590 let mut iter = self.input.graphemes(true).peekable();
23591 let mut offset = 0;
23592 let mut grapheme_len = 0;
23593 if let Some(first_grapheme) = iter.next() {
23594 let is_newline = first_grapheme == "\n";
23595 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23596 offset += first_grapheme.len();
23597 grapheme_len += 1;
23598 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23599 if let Some(grapheme) = iter.peek().copied()
23600 && should_stay_with_preceding_ideograph(grapheme)
23601 {
23602 offset += grapheme.len();
23603 grapheme_len += 1;
23604 }
23605 } else {
23606 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23607 let mut next_word_bound = words.peek().copied();
23608 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23609 next_word_bound = words.next();
23610 }
23611 while let Some(grapheme) = iter.peek().copied() {
23612 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23613 break;
23614 };
23615 if is_grapheme_whitespace(grapheme) != is_whitespace
23616 || (grapheme == "\n") != is_newline
23617 {
23618 break;
23619 };
23620 offset += grapheme.len();
23621 grapheme_len += 1;
23622 iter.next();
23623 }
23624 }
23625 let token = &self.input[..offset];
23626 self.input = &self.input[offset..];
23627 if token == "\n" {
23628 Some(WordBreakToken::Newline)
23629 } else if is_whitespace {
23630 Some(WordBreakToken::InlineWhitespace {
23631 token,
23632 grapheme_len,
23633 })
23634 } else {
23635 Some(WordBreakToken::Word {
23636 token,
23637 grapheme_len,
23638 })
23639 }
23640 } else {
23641 None
23642 }
23643 }
23644}
23645
23646#[test]
23647fn test_word_breaking_tokenizer() {
23648 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23649 ("", &[]),
23650 (" ", &[whitespace(" ", 2)]),
23651 ("Ʒ", &[word("Ʒ", 1)]),
23652 ("Ǽ", &[word("Ǽ", 1)]),
23653 ("⋑", &[word("⋑", 1)]),
23654 ("⋑⋑", &[word("⋑⋑", 2)]),
23655 (
23656 "原理,进而",
23657 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23658 ),
23659 (
23660 "hello world",
23661 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23662 ),
23663 (
23664 "hello, world",
23665 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23666 ),
23667 (
23668 " hello world",
23669 &[
23670 whitespace(" ", 2),
23671 word("hello", 5),
23672 whitespace(" ", 1),
23673 word("world", 5),
23674 ],
23675 ),
23676 (
23677 "这是什么 \n 钢笔",
23678 &[
23679 word("这", 1),
23680 word("是", 1),
23681 word("什", 1),
23682 word("么", 1),
23683 whitespace(" ", 1),
23684 newline(),
23685 whitespace(" ", 1),
23686 word("钢", 1),
23687 word("笔", 1),
23688 ],
23689 ),
23690 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23691 ];
23692
23693 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23694 WordBreakToken::Word {
23695 token,
23696 grapheme_len,
23697 }
23698 }
23699
23700 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23701 WordBreakToken::InlineWhitespace {
23702 token,
23703 grapheme_len,
23704 }
23705 }
23706
23707 fn newline() -> WordBreakToken<'static> {
23708 WordBreakToken::Newline
23709 }
23710
23711 for (input, result) in tests {
23712 assert_eq!(
23713 WordBreakingTokenizer::new(input)
23714 .collect::<Vec<_>>()
23715 .as_slice(),
23716 *result,
23717 );
23718 }
23719}
23720
23721fn wrap_with_prefix(
23722 first_line_prefix: String,
23723 subsequent_lines_prefix: String,
23724 unwrapped_text: String,
23725 wrap_column: usize,
23726 tab_size: NonZeroU32,
23727 preserve_existing_whitespace: bool,
23728) -> String {
23729 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23730 let subsequent_lines_prefix_len =
23731 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23732 let mut wrapped_text = String::new();
23733 let mut current_line = first_line_prefix;
23734 let mut is_first_line = true;
23735
23736 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23737 let mut current_line_len = first_line_prefix_len;
23738 let mut in_whitespace = false;
23739 for token in tokenizer {
23740 let have_preceding_whitespace = in_whitespace;
23741 match token {
23742 WordBreakToken::Word {
23743 token,
23744 grapheme_len,
23745 } => {
23746 in_whitespace = false;
23747 let current_prefix_len = if is_first_line {
23748 first_line_prefix_len
23749 } else {
23750 subsequent_lines_prefix_len
23751 };
23752 if current_line_len + grapheme_len > wrap_column
23753 && current_line_len != current_prefix_len
23754 {
23755 wrapped_text.push_str(current_line.trim_end());
23756 wrapped_text.push('\n');
23757 is_first_line = false;
23758 current_line = subsequent_lines_prefix.clone();
23759 current_line_len = subsequent_lines_prefix_len;
23760 }
23761 current_line.push_str(token);
23762 current_line_len += grapheme_len;
23763 }
23764 WordBreakToken::InlineWhitespace {
23765 mut token,
23766 mut grapheme_len,
23767 } => {
23768 in_whitespace = true;
23769 if have_preceding_whitespace && !preserve_existing_whitespace {
23770 continue;
23771 }
23772 if !preserve_existing_whitespace {
23773 // Keep a single whitespace grapheme as-is
23774 if let Some(first) =
23775 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23776 {
23777 token = first;
23778 } else {
23779 token = " ";
23780 }
23781 grapheme_len = 1;
23782 }
23783 let current_prefix_len = if is_first_line {
23784 first_line_prefix_len
23785 } else {
23786 subsequent_lines_prefix_len
23787 };
23788 if current_line_len + grapheme_len > wrap_column {
23789 wrapped_text.push_str(current_line.trim_end());
23790 wrapped_text.push('\n');
23791 is_first_line = false;
23792 current_line = subsequent_lines_prefix.clone();
23793 current_line_len = subsequent_lines_prefix_len;
23794 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23795 current_line.push_str(token);
23796 current_line_len += grapheme_len;
23797 }
23798 }
23799 WordBreakToken::Newline => {
23800 in_whitespace = true;
23801 let current_prefix_len = if is_first_line {
23802 first_line_prefix_len
23803 } else {
23804 subsequent_lines_prefix_len
23805 };
23806 if preserve_existing_whitespace {
23807 wrapped_text.push_str(current_line.trim_end());
23808 wrapped_text.push('\n');
23809 is_first_line = false;
23810 current_line = subsequent_lines_prefix.clone();
23811 current_line_len = subsequent_lines_prefix_len;
23812 } else if have_preceding_whitespace {
23813 continue;
23814 } else if current_line_len + 1 > wrap_column
23815 && current_line_len != current_prefix_len
23816 {
23817 wrapped_text.push_str(current_line.trim_end());
23818 wrapped_text.push('\n');
23819 is_first_line = false;
23820 current_line = subsequent_lines_prefix.clone();
23821 current_line_len = subsequent_lines_prefix_len;
23822 } else if current_line_len != current_prefix_len {
23823 current_line.push(' ');
23824 current_line_len += 1;
23825 }
23826 }
23827 }
23828 }
23829
23830 if !current_line.is_empty() {
23831 wrapped_text.push_str(¤t_line);
23832 }
23833 wrapped_text
23834}
23835
23836#[test]
23837fn test_wrap_with_prefix() {
23838 assert_eq!(
23839 wrap_with_prefix(
23840 "# ".to_string(),
23841 "# ".to_string(),
23842 "abcdefg".to_string(),
23843 4,
23844 NonZeroU32::new(4).unwrap(),
23845 false,
23846 ),
23847 "# abcdefg"
23848 );
23849 assert_eq!(
23850 wrap_with_prefix(
23851 "".to_string(),
23852 "".to_string(),
23853 "\thello world".to_string(),
23854 8,
23855 NonZeroU32::new(4).unwrap(),
23856 false,
23857 ),
23858 "hello\nworld"
23859 );
23860 assert_eq!(
23861 wrap_with_prefix(
23862 "// ".to_string(),
23863 "// ".to_string(),
23864 "xx \nyy zz aa bb cc".to_string(),
23865 12,
23866 NonZeroU32::new(4).unwrap(),
23867 false,
23868 ),
23869 "// xx yy zz\n// aa bb cc"
23870 );
23871 assert_eq!(
23872 wrap_with_prefix(
23873 String::new(),
23874 String::new(),
23875 "这是什么 \n 钢笔".to_string(),
23876 3,
23877 NonZeroU32::new(4).unwrap(),
23878 false,
23879 ),
23880 "这是什\n么 钢\n笔"
23881 );
23882 assert_eq!(
23883 wrap_with_prefix(
23884 String::new(),
23885 String::new(),
23886 format!("foo{}bar", '\u{2009}'), // thin space
23887 80,
23888 NonZeroU32::new(4).unwrap(),
23889 false,
23890 ),
23891 format!("foo{}bar", '\u{2009}')
23892 );
23893}
23894
23895pub trait CollaborationHub {
23896 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23897 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23898 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23899}
23900
23901impl CollaborationHub for Entity<Project> {
23902 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23903 self.read(cx).collaborators()
23904 }
23905
23906 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23907 self.read(cx).user_store().read(cx).participant_indices()
23908 }
23909
23910 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23911 let this = self.read(cx);
23912 let user_ids = this.collaborators().values().map(|c| c.user_id);
23913 this.user_store().read(cx).participant_names(user_ids, cx)
23914 }
23915}
23916
23917pub trait SemanticsProvider {
23918 fn hover(
23919 &self,
23920 buffer: &Entity<Buffer>,
23921 position: text::Anchor,
23922 cx: &mut App,
23923 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23924
23925 fn inline_values(
23926 &self,
23927 buffer_handle: Entity<Buffer>,
23928 range: Range<text::Anchor>,
23929 cx: &mut App,
23930 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23931
23932 fn applicable_inlay_chunks(
23933 &self,
23934 buffer: &Entity<Buffer>,
23935 ranges: &[Range<text::Anchor>],
23936 cx: &mut App,
23937 ) -> Vec<Range<BufferRow>>;
23938
23939 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23940
23941 fn inlay_hints(
23942 &self,
23943 invalidate: InvalidationStrategy,
23944 buffer: Entity<Buffer>,
23945 ranges: Vec<Range<text::Anchor>>,
23946 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23947 cx: &mut App,
23948 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23949
23950 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23951
23952 fn document_highlights(
23953 &self,
23954 buffer: &Entity<Buffer>,
23955 position: text::Anchor,
23956 cx: &mut App,
23957 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23958
23959 fn definitions(
23960 &self,
23961 buffer: &Entity<Buffer>,
23962 position: text::Anchor,
23963 kind: GotoDefinitionKind,
23964 cx: &mut App,
23965 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23966
23967 fn range_for_rename(
23968 &self,
23969 buffer: &Entity<Buffer>,
23970 position: text::Anchor,
23971 cx: &mut App,
23972 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23973
23974 fn perform_rename(
23975 &self,
23976 buffer: &Entity<Buffer>,
23977 position: text::Anchor,
23978 new_name: String,
23979 cx: &mut App,
23980 ) -> Option<Task<Result<ProjectTransaction>>>;
23981}
23982
23983pub trait CompletionProvider {
23984 fn completions(
23985 &self,
23986 excerpt_id: ExcerptId,
23987 buffer: &Entity<Buffer>,
23988 buffer_position: text::Anchor,
23989 trigger: CompletionContext,
23990 window: &mut Window,
23991 cx: &mut Context<Editor>,
23992 ) -> Task<Result<Vec<CompletionResponse>>>;
23993
23994 fn resolve_completions(
23995 &self,
23996 _buffer: Entity<Buffer>,
23997 _completion_indices: Vec<usize>,
23998 _completions: Rc<RefCell<Box<[Completion]>>>,
23999 _cx: &mut Context<Editor>,
24000 ) -> Task<Result<bool>> {
24001 Task::ready(Ok(false))
24002 }
24003
24004 fn apply_additional_edits_for_completion(
24005 &self,
24006 _buffer: Entity<Buffer>,
24007 _completions: Rc<RefCell<Box<[Completion]>>>,
24008 _completion_index: usize,
24009 _push_to_history: bool,
24010 _cx: &mut Context<Editor>,
24011 ) -> Task<Result<Option<language::Transaction>>> {
24012 Task::ready(Ok(None))
24013 }
24014
24015 fn is_completion_trigger(
24016 &self,
24017 buffer: &Entity<Buffer>,
24018 position: language::Anchor,
24019 text: &str,
24020 trigger_in_words: bool,
24021 cx: &mut Context<Editor>,
24022 ) -> bool;
24023
24024 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24025
24026 fn sort_completions(&self) -> bool {
24027 true
24028 }
24029
24030 fn filter_completions(&self) -> bool {
24031 true
24032 }
24033
24034 fn show_snippets(&self) -> bool {
24035 false
24036 }
24037}
24038
24039pub trait CodeActionProvider {
24040 fn id(&self) -> Arc<str>;
24041
24042 fn code_actions(
24043 &self,
24044 buffer: &Entity<Buffer>,
24045 range: Range<text::Anchor>,
24046 window: &mut Window,
24047 cx: &mut App,
24048 ) -> Task<Result<Vec<CodeAction>>>;
24049
24050 fn apply_code_action(
24051 &self,
24052 buffer_handle: Entity<Buffer>,
24053 action: CodeAction,
24054 excerpt_id: ExcerptId,
24055 push_to_history: bool,
24056 window: &mut Window,
24057 cx: &mut App,
24058 ) -> Task<Result<ProjectTransaction>>;
24059}
24060
24061impl CodeActionProvider for Entity<Project> {
24062 fn id(&self) -> Arc<str> {
24063 "project".into()
24064 }
24065
24066 fn code_actions(
24067 &self,
24068 buffer: &Entity<Buffer>,
24069 range: Range<text::Anchor>,
24070 _window: &mut Window,
24071 cx: &mut App,
24072 ) -> Task<Result<Vec<CodeAction>>> {
24073 self.update(cx, |project, cx| {
24074 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24075 let code_actions = project.code_actions(buffer, range, None, cx);
24076 cx.background_spawn(async move {
24077 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24078 Ok(code_lens_actions
24079 .context("code lens fetch")?
24080 .into_iter()
24081 .flatten()
24082 .chain(
24083 code_actions
24084 .context("code action fetch")?
24085 .into_iter()
24086 .flatten(),
24087 )
24088 .collect())
24089 })
24090 })
24091 }
24092
24093 fn apply_code_action(
24094 &self,
24095 buffer_handle: Entity<Buffer>,
24096 action: CodeAction,
24097 _excerpt_id: ExcerptId,
24098 push_to_history: bool,
24099 _window: &mut Window,
24100 cx: &mut App,
24101 ) -> Task<Result<ProjectTransaction>> {
24102 self.update(cx, |project, cx| {
24103 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24104 })
24105 }
24106}
24107
24108fn snippet_completions(
24109 project: &Project,
24110 buffer: &Entity<Buffer>,
24111 buffer_anchor: text::Anchor,
24112 classifier: CharClassifier,
24113 cx: &mut App,
24114) -> Task<Result<CompletionResponse>> {
24115 let languages = buffer.read(cx).languages_at(buffer_anchor);
24116 let snippet_store = project.snippets().read(cx);
24117
24118 let scopes: Vec<_> = languages
24119 .iter()
24120 .filter_map(|language| {
24121 let language_name = language.lsp_id();
24122 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24123
24124 if snippets.is_empty() {
24125 None
24126 } else {
24127 Some((language.default_scope(), snippets))
24128 }
24129 })
24130 .collect();
24131
24132 if scopes.is_empty() {
24133 return Task::ready(Ok(CompletionResponse {
24134 completions: vec![],
24135 display_options: CompletionDisplayOptions::default(),
24136 is_incomplete: false,
24137 }));
24138 }
24139
24140 let snapshot = buffer.read(cx).text_snapshot();
24141 let executor = cx.background_executor().clone();
24142
24143 cx.background_spawn(async move {
24144 let is_word_char = |c| classifier.is_word(c);
24145
24146 let mut is_incomplete = false;
24147 let mut completions: Vec<Completion> = Vec::new();
24148
24149 const MAX_PREFIX_LEN: usize = 128;
24150 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24151 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24152 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24153
24154 let max_buffer_window: String = snapshot
24155 .text_for_range(window_start..buffer_offset)
24156 .collect();
24157
24158 if max_buffer_window.is_empty() {
24159 return Ok(CompletionResponse {
24160 completions: vec![],
24161 display_options: CompletionDisplayOptions::default(),
24162 is_incomplete: true,
24163 });
24164 }
24165
24166 for (_scope, snippets) in scopes.into_iter() {
24167 // Sort snippets by word count to match longer snippet prefixes first.
24168 let mut sorted_snippet_candidates = snippets
24169 .iter()
24170 .enumerate()
24171 .flat_map(|(snippet_ix, snippet)| {
24172 snippet
24173 .prefix
24174 .iter()
24175 .enumerate()
24176 .map(move |(prefix_ix, prefix)| {
24177 let word_count =
24178 snippet_candidate_suffixes(prefix, is_word_char).count();
24179 ((snippet_ix, prefix_ix), prefix, word_count)
24180 })
24181 })
24182 .collect_vec();
24183 sorted_snippet_candidates
24184 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24185
24186 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24187
24188 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24189 .take(
24190 sorted_snippet_candidates
24191 .first()
24192 .map(|(_, _, word_count)| *word_count)
24193 .unwrap_or_default(),
24194 )
24195 .collect_vec();
24196
24197 const MAX_RESULTS: usize = 100;
24198 // Each match also remembers how many characters from the buffer it consumed
24199 let mut matches: Vec<(StringMatch, usize)> = vec![];
24200
24201 let mut snippet_list_cutoff_index = 0;
24202 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24203 let word_count = buffer_index + 1;
24204 // Increase `snippet_list_cutoff_index` until we have all of the
24205 // snippets with sufficiently many words.
24206 while sorted_snippet_candidates
24207 .get(snippet_list_cutoff_index)
24208 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24209 *snippet_word_count >= word_count
24210 })
24211 {
24212 snippet_list_cutoff_index += 1;
24213 }
24214
24215 // Take only the candidates with at least `word_count` many words
24216 let snippet_candidates_at_word_len =
24217 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24218
24219 let candidates = snippet_candidates_at_word_len
24220 .iter()
24221 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24222 .enumerate() // index in `sorted_snippet_candidates`
24223 // First char must match
24224 .filter(|(_ix, prefix)| {
24225 itertools::equal(
24226 prefix
24227 .chars()
24228 .next()
24229 .into_iter()
24230 .flat_map(|c| c.to_lowercase()),
24231 buffer_window
24232 .chars()
24233 .next()
24234 .into_iter()
24235 .flat_map(|c| c.to_lowercase()),
24236 )
24237 })
24238 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24239 .collect::<Vec<StringMatchCandidate>>();
24240
24241 matches.extend(
24242 fuzzy::match_strings(
24243 &candidates,
24244 &buffer_window,
24245 buffer_window.chars().any(|c| c.is_uppercase()),
24246 true,
24247 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24248 &Default::default(),
24249 executor.clone(),
24250 )
24251 .await
24252 .into_iter()
24253 .map(|string_match| (string_match, buffer_window.len())),
24254 );
24255
24256 if matches.len() >= MAX_RESULTS {
24257 break;
24258 }
24259 }
24260
24261 let to_lsp = |point: &text::Anchor| {
24262 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24263 point_to_lsp(end)
24264 };
24265 let lsp_end = to_lsp(&buffer_anchor);
24266
24267 if matches.len() >= MAX_RESULTS {
24268 is_incomplete = true;
24269 }
24270
24271 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24272 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24273 sorted_snippet_candidates[string_match.candidate_id];
24274 let snippet = &snippets[snippet_index];
24275 let start = buffer_offset - buffer_window_len;
24276 let start = snapshot.anchor_before(start);
24277 let range = start..buffer_anchor;
24278 let lsp_start = to_lsp(&start);
24279 let lsp_range = lsp::Range {
24280 start: lsp_start,
24281 end: lsp_end,
24282 };
24283 Completion {
24284 replace_range: range,
24285 new_text: snippet.body.clone(),
24286 source: CompletionSource::Lsp {
24287 insert_range: None,
24288 server_id: LanguageServerId(usize::MAX),
24289 resolved: true,
24290 lsp_completion: Box::new(lsp::CompletionItem {
24291 label: snippet.prefix.first().unwrap().clone(),
24292 kind: Some(CompletionItemKind::SNIPPET),
24293 label_details: snippet.description.as_ref().map(|description| {
24294 lsp::CompletionItemLabelDetails {
24295 detail: Some(description.clone()),
24296 description: None,
24297 }
24298 }),
24299 insert_text_format: Some(InsertTextFormat::SNIPPET),
24300 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24301 lsp::InsertReplaceEdit {
24302 new_text: snippet.body.clone(),
24303 insert: lsp_range,
24304 replace: lsp_range,
24305 },
24306 )),
24307 filter_text: Some(snippet.body.clone()),
24308 sort_text: Some(char::MAX.to_string()),
24309 ..lsp::CompletionItem::default()
24310 }),
24311 lsp_defaults: None,
24312 },
24313 label: CodeLabel {
24314 text: matching_prefix.clone(),
24315 runs: Vec::new(),
24316 filter_range: 0..matching_prefix.len(),
24317 },
24318 icon_path: None,
24319 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24320 single_line: snippet.name.clone().into(),
24321 plain_text: snippet
24322 .description
24323 .clone()
24324 .map(|description| description.into()),
24325 }),
24326 insert_text_mode: None,
24327 confirm: None,
24328 match_start: Some(start),
24329 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24330 }
24331 }));
24332 }
24333
24334 Ok(CompletionResponse {
24335 completions,
24336 display_options: CompletionDisplayOptions::default(),
24337 is_incomplete,
24338 })
24339 })
24340}
24341
24342impl CompletionProvider for Entity<Project> {
24343 fn completions(
24344 &self,
24345 _excerpt_id: ExcerptId,
24346 buffer: &Entity<Buffer>,
24347 buffer_position: text::Anchor,
24348 options: CompletionContext,
24349 _window: &mut Window,
24350 cx: &mut Context<Editor>,
24351 ) -> Task<Result<Vec<CompletionResponse>>> {
24352 self.update(cx, |project, cx| {
24353 let task = project.completions(buffer, buffer_position, options, cx);
24354 cx.background_spawn(task)
24355 })
24356 }
24357
24358 fn resolve_completions(
24359 &self,
24360 buffer: Entity<Buffer>,
24361 completion_indices: Vec<usize>,
24362 completions: Rc<RefCell<Box<[Completion]>>>,
24363 cx: &mut Context<Editor>,
24364 ) -> Task<Result<bool>> {
24365 self.update(cx, |project, cx| {
24366 project.lsp_store().update(cx, |lsp_store, cx| {
24367 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24368 })
24369 })
24370 }
24371
24372 fn apply_additional_edits_for_completion(
24373 &self,
24374 buffer: Entity<Buffer>,
24375 completions: Rc<RefCell<Box<[Completion]>>>,
24376 completion_index: usize,
24377 push_to_history: bool,
24378 cx: &mut Context<Editor>,
24379 ) -> Task<Result<Option<language::Transaction>>> {
24380 self.update(cx, |project, cx| {
24381 project.lsp_store().update(cx, |lsp_store, cx| {
24382 lsp_store.apply_additional_edits_for_completion(
24383 buffer,
24384 completions,
24385 completion_index,
24386 push_to_history,
24387 cx,
24388 )
24389 })
24390 })
24391 }
24392
24393 fn is_completion_trigger(
24394 &self,
24395 buffer: &Entity<Buffer>,
24396 position: language::Anchor,
24397 text: &str,
24398 trigger_in_words: bool,
24399 cx: &mut Context<Editor>,
24400 ) -> bool {
24401 let mut chars = text.chars();
24402 let char = if let Some(char) = chars.next() {
24403 char
24404 } else {
24405 return false;
24406 };
24407 if chars.next().is_some() {
24408 return false;
24409 }
24410
24411 let buffer = buffer.read(cx);
24412 let snapshot = buffer.snapshot();
24413 let classifier = snapshot
24414 .char_classifier_at(position)
24415 .scope_context(Some(CharScopeContext::Completion));
24416 if trigger_in_words && classifier.is_word(char) {
24417 return true;
24418 }
24419
24420 buffer.completion_triggers().contains(text)
24421 }
24422
24423 fn show_snippets(&self) -> bool {
24424 true
24425 }
24426}
24427
24428impl SemanticsProvider for Entity<Project> {
24429 fn hover(
24430 &self,
24431 buffer: &Entity<Buffer>,
24432 position: text::Anchor,
24433 cx: &mut App,
24434 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24435 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24436 }
24437
24438 fn document_highlights(
24439 &self,
24440 buffer: &Entity<Buffer>,
24441 position: text::Anchor,
24442 cx: &mut App,
24443 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24444 Some(self.update(cx, |project, cx| {
24445 project.document_highlights(buffer, position, cx)
24446 }))
24447 }
24448
24449 fn definitions(
24450 &self,
24451 buffer: &Entity<Buffer>,
24452 position: text::Anchor,
24453 kind: GotoDefinitionKind,
24454 cx: &mut App,
24455 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24456 Some(self.update(cx, |project, cx| match kind {
24457 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24458 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24459 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24460 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24461 }))
24462 }
24463
24464 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24465 self.update(cx, |project, cx| {
24466 if project
24467 .active_debug_session(cx)
24468 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24469 {
24470 return true;
24471 }
24472
24473 buffer.update(cx, |buffer, cx| {
24474 project.any_language_server_supports_inlay_hints(buffer, cx)
24475 })
24476 })
24477 }
24478
24479 fn inline_values(
24480 &self,
24481 buffer_handle: Entity<Buffer>,
24482 range: Range<text::Anchor>,
24483 cx: &mut App,
24484 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24485 self.update(cx, |project, cx| {
24486 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24487
24488 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24489 })
24490 }
24491
24492 fn applicable_inlay_chunks(
24493 &self,
24494 buffer: &Entity<Buffer>,
24495 ranges: &[Range<text::Anchor>],
24496 cx: &mut App,
24497 ) -> Vec<Range<BufferRow>> {
24498 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24499 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24500 })
24501 }
24502
24503 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24504 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24505 lsp_store.invalidate_inlay_hints(for_buffers)
24506 });
24507 }
24508
24509 fn inlay_hints(
24510 &self,
24511 invalidate: InvalidationStrategy,
24512 buffer: Entity<Buffer>,
24513 ranges: Vec<Range<text::Anchor>>,
24514 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24515 cx: &mut App,
24516 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24517 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24518 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24519 }))
24520 }
24521
24522 fn range_for_rename(
24523 &self,
24524 buffer: &Entity<Buffer>,
24525 position: text::Anchor,
24526 cx: &mut App,
24527 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24528 Some(self.update(cx, |project, cx| {
24529 let buffer = buffer.clone();
24530 let task = project.prepare_rename(buffer.clone(), position, cx);
24531 cx.spawn(async move |_, cx| {
24532 Ok(match task.await? {
24533 PrepareRenameResponse::Success(range) => Some(range),
24534 PrepareRenameResponse::InvalidPosition => None,
24535 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24536 // Fallback on using TreeSitter info to determine identifier range
24537 buffer.read_with(cx, |buffer, _| {
24538 let snapshot = buffer.snapshot();
24539 let (range, kind) = snapshot.surrounding_word(position, None);
24540 if kind != Some(CharKind::Word) {
24541 return None;
24542 }
24543 Some(
24544 snapshot.anchor_before(range.start)
24545 ..snapshot.anchor_after(range.end),
24546 )
24547 })?
24548 }
24549 })
24550 })
24551 }))
24552 }
24553
24554 fn perform_rename(
24555 &self,
24556 buffer: &Entity<Buffer>,
24557 position: text::Anchor,
24558 new_name: String,
24559 cx: &mut App,
24560 ) -> Option<Task<Result<ProjectTransaction>>> {
24561 Some(self.update(cx, |project, cx| {
24562 project.perform_rename(buffer.clone(), position, new_name, cx)
24563 }))
24564 }
24565}
24566
24567fn consume_contiguous_rows(
24568 contiguous_row_selections: &mut Vec<Selection<Point>>,
24569 selection: &Selection<Point>,
24570 display_map: &DisplaySnapshot,
24571 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24572) -> (MultiBufferRow, MultiBufferRow) {
24573 contiguous_row_selections.push(selection.clone());
24574 let start_row = starting_row(selection, display_map);
24575 let mut end_row = ending_row(selection, display_map);
24576
24577 while let Some(next_selection) = selections.peek() {
24578 if next_selection.start.row <= end_row.0 {
24579 end_row = ending_row(next_selection, display_map);
24580 contiguous_row_selections.push(selections.next().unwrap().clone());
24581 } else {
24582 break;
24583 }
24584 }
24585 (start_row, end_row)
24586}
24587
24588fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24589 if selection.start.column > 0 {
24590 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24591 } else {
24592 MultiBufferRow(selection.start.row)
24593 }
24594}
24595
24596fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24597 if next_selection.end.column > 0 || next_selection.is_empty() {
24598 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24599 } else {
24600 MultiBufferRow(next_selection.end.row)
24601 }
24602}
24603
24604impl EditorSnapshot {
24605 pub fn remote_selections_in_range<'a>(
24606 &'a self,
24607 range: &'a Range<Anchor>,
24608 collaboration_hub: &dyn CollaborationHub,
24609 cx: &'a App,
24610 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24611 let participant_names = collaboration_hub.user_names(cx);
24612 let participant_indices = collaboration_hub.user_participant_indices(cx);
24613 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24614 let collaborators_by_replica_id = collaborators_by_peer_id
24615 .values()
24616 .map(|collaborator| (collaborator.replica_id, collaborator))
24617 .collect::<HashMap<_, _>>();
24618 self.buffer_snapshot()
24619 .selections_in_range(range, false)
24620 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24621 if replica_id == ReplicaId::AGENT {
24622 Some(RemoteSelection {
24623 replica_id,
24624 selection,
24625 cursor_shape,
24626 line_mode,
24627 collaborator_id: CollaboratorId::Agent,
24628 user_name: Some("Agent".into()),
24629 color: cx.theme().players().agent(),
24630 })
24631 } else {
24632 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24633 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24634 let user_name = participant_names.get(&collaborator.user_id).cloned();
24635 Some(RemoteSelection {
24636 replica_id,
24637 selection,
24638 cursor_shape,
24639 line_mode,
24640 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24641 user_name,
24642 color: if let Some(index) = participant_index {
24643 cx.theme().players().color_for_participant(index.0)
24644 } else {
24645 cx.theme().players().absent()
24646 },
24647 })
24648 }
24649 })
24650 }
24651
24652 pub fn hunks_for_ranges(
24653 &self,
24654 ranges: impl IntoIterator<Item = Range<Point>>,
24655 ) -> Vec<MultiBufferDiffHunk> {
24656 let mut hunks = Vec::new();
24657 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24658 HashMap::default();
24659 for query_range in ranges {
24660 let query_rows =
24661 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24662 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24663 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24664 ) {
24665 // Include deleted hunks that are adjacent to the query range, because
24666 // otherwise they would be missed.
24667 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24668 if hunk.status().is_deleted() {
24669 intersects_range |= hunk.row_range.start == query_rows.end;
24670 intersects_range |= hunk.row_range.end == query_rows.start;
24671 }
24672 if intersects_range {
24673 if !processed_buffer_rows
24674 .entry(hunk.buffer_id)
24675 .or_default()
24676 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24677 {
24678 continue;
24679 }
24680 hunks.push(hunk);
24681 }
24682 }
24683 }
24684
24685 hunks
24686 }
24687
24688 fn display_diff_hunks_for_rows<'a>(
24689 &'a self,
24690 display_rows: Range<DisplayRow>,
24691 folded_buffers: &'a HashSet<BufferId>,
24692 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24693 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24694 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24695
24696 self.buffer_snapshot()
24697 .diff_hunks_in_range(buffer_start..buffer_end)
24698 .filter_map(|hunk| {
24699 if folded_buffers.contains(&hunk.buffer_id) {
24700 return None;
24701 }
24702
24703 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24704 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24705
24706 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24707 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24708
24709 let display_hunk = if hunk_display_start.column() != 0 {
24710 DisplayDiffHunk::Folded {
24711 display_row: hunk_display_start.row(),
24712 }
24713 } else {
24714 let mut end_row = hunk_display_end.row();
24715 if hunk_display_end.column() > 0 {
24716 end_row.0 += 1;
24717 }
24718 let is_created_file = hunk.is_created_file();
24719
24720 DisplayDiffHunk::Unfolded {
24721 status: hunk.status(),
24722 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24723 ..hunk.diff_base_byte_range.end.0,
24724 word_diffs: hunk.word_diffs,
24725 display_row_range: hunk_display_start.row()..end_row,
24726 multi_buffer_range: Anchor::range_in_buffer(
24727 hunk.excerpt_id,
24728 hunk.buffer_range,
24729 ),
24730 is_created_file,
24731 }
24732 };
24733
24734 Some(display_hunk)
24735 })
24736 }
24737
24738 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24739 self.display_snapshot
24740 .buffer_snapshot()
24741 .language_at(position)
24742 }
24743
24744 pub fn is_focused(&self) -> bool {
24745 self.is_focused
24746 }
24747
24748 pub fn placeholder_text(&self) -> Option<String> {
24749 self.placeholder_display_snapshot
24750 .as_ref()
24751 .map(|display_map| display_map.text())
24752 }
24753
24754 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24755 self.scroll_anchor.scroll_position(&self.display_snapshot)
24756 }
24757
24758 pub fn gutter_dimensions(
24759 &self,
24760 font_id: FontId,
24761 font_size: Pixels,
24762 style: &EditorStyle,
24763 window: &mut Window,
24764 cx: &App,
24765 ) -> GutterDimensions {
24766 if self.show_gutter
24767 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24768 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24769 {
24770 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24771 matches!(
24772 ProjectSettings::get_global(cx).git.git_gutter,
24773 GitGutterSetting::TrackedFiles
24774 )
24775 });
24776 let gutter_settings = EditorSettings::get_global(cx).gutter;
24777 let show_line_numbers = self
24778 .show_line_numbers
24779 .unwrap_or(gutter_settings.line_numbers);
24780 let line_gutter_width = if show_line_numbers {
24781 // Avoid flicker-like gutter resizes when the line number gains another digit by
24782 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24783 let min_width_for_number_on_gutter =
24784 ch_advance * gutter_settings.min_line_number_digits as f32;
24785 self.max_line_number_width(style, window)
24786 .max(min_width_for_number_on_gutter)
24787 } else {
24788 0.0.into()
24789 };
24790
24791 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24792 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24793
24794 let git_blame_entries_width =
24795 self.git_blame_gutter_max_author_length
24796 .map(|max_author_length| {
24797 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24798 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24799
24800 /// The number of characters to dedicate to gaps and margins.
24801 const SPACING_WIDTH: usize = 4;
24802
24803 let max_char_count = max_author_length.min(renderer.max_author_length())
24804 + ::git::SHORT_SHA_LENGTH
24805 + MAX_RELATIVE_TIMESTAMP.len()
24806 + SPACING_WIDTH;
24807
24808 ch_advance * max_char_count
24809 });
24810
24811 let is_singleton = self.buffer_snapshot().is_singleton();
24812
24813 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24814 left_padding += if !is_singleton {
24815 ch_width * 4.0
24816 } else if show_runnables || show_breakpoints {
24817 ch_width * 3.0
24818 } else if show_git_gutter && show_line_numbers {
24819 ch_width * 2.0
24820 } else if show_git_gutter || show_line_numbers {
24821 ch_width
24822 } else {
24823 px(0.)
24824 };
24825
24826 let shows_folds = is_singleton && gutter_settings.folds;
24827
24828 let right_padding = if shows_folds && show_line_numbers {
24829 ch_width * 4.0
24830 } else if shows_folds || (!is_singleton && show_line_numbers) {
24831 ch_width * 3.0
24832 } else if show_line_numbers {
24833 ch_width
24834 } else {
24835 px(0.)
24836 };
24837
24838 GutterDimensions {
24839 left_padding,
24840 right_padding,
24841 width: line_gutter_width + left_padding + right_padding,
24842 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24843 git_blame_entries_width,
24844 }
24845 } else if self.offset_content {
24846 GutterDimensions::default_with_margin(font_id, font_size, cx)
24847 } else {
24848 GutterDimensions::default()
24849 }
24850 }
24851
24852 pub fn render_crease_toggle(
24853 &self,
24854 buffer_row: MultiBufferRow,
24855 row_contains_cursor: bool,
24856 editor: Entity<Editor>,
24857 window: &mut Window,
24858 cx: &mut App,
24859 ) -> Option<AnyElement> {
24860 let folded = self.is_line_folded(buffer_row);
24861 let mut is_foldable = false;
24862
24863 if let Some(crease) = self
24864 .crease_snapshot
24865 .query_row(buffer_row, self.buffer_snapshot())
24866 {
24867 is_foldable = true;
24868 match crease {
24869 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24870 if let Some(render_toggle) = render_toggle {
24871 let toggle_callback =
24872 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24873 if folded {
24874 editor.update(cx, |editor, cx| {
24875 editor.fold_at(buffer_row, window, cx)
24876 });
24877 } else {
24878 editor.update(cx, |editor, cx| {
24879 editor.unfold_at(buffer_row, window, cx)
24880 });
24881 }
24882 });
24883 return Some((render_toggle)(
24884 buffer_row,
24885 folded,
24886 toggle_callback,
24887 window,
24888 cx,
24889 ));
24890 }
24891 }
24892 }
24893 }
24894
24895 is_foldable |= self.starts_indent(buffer_row);
24896
24897 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24898 Some(
24899 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24900 .toggle_state(folded)
24901 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24902 if folded {
24903 this.unfold_at(buffer_row, window, cx);
24904 } else {
24905 this.fold_at(buffer_row, window, cx);
24906 }
24907 }))
24908 .into_any_element(),
24909 )
24910 } else {
24911 None
24912 }
24913 }
24914
24915 pub fn render_crease_trailer(
24916 &self,
24917 buffer_row: MultiBufferRow,
24918 window: &mut Window,
24919 cx: &mut App,
24920 ) -> Option<AnyElement> {
24921 let folded = self.is_line_folded(buffer_row);
24922 if let Crease::Inline { render_trailer, .. } = self
24923 .crease_snapshot
24924 .query_row(buffer_row, self.buffer_snapshot())?
24925 {
24926 let render_trailer = render_trailer.as_ref()?;
24927 Some(render_trailer(buffer_row, folded, window, cx))
24928 } else {
24929 None
24930 }
24931 }
24932
24933 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
24934 let digit_count = self.widest_line_number().ilog10() + 1;
24935 column_pixels(style, digit_count as usize, window)
24936 }
24937}
24938
24939pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
24940 let font_size = style.text.font_size.to_pixels(window.rem_size());
24941 let layout = window.text_system().shape_line(
24942 SharedString::from(" ".repeat(column)),
24943 font_size,
24944 &[TextRun {
24945 len: column,
24946 font: style.text.font(),
24947 color: Hsla::default(),
24948 ..Default::default()
24949 }],
24950 None,
24951 );
24952
24953 layout.width
24954}
24955
24956impl Deref for EditorSnapshot {
24957 type Target = DisplaySnapshot;
24958
24959 fn deref(&self) -> &Self::Target {
24960 &self.display_snapshot
24961 }
24962}
24963
24964#[derive(Clone, Debug, PartialEq, Eq)]
24965pub enum EditorEvent {
24966 InputIgnored {
24967 text: Arc<str>,
24968 },
24969 InputHandled {
24970 utf16_range_to_replace: Option<Range<isize>>,
24971 text: Arc<str>,
24972 },
24973 ExcerptsAdded {
24974 buffer: Entity<Buffer>,
24975 predecessor: ExcerptId,
24976 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24977 },
24978 ExcerptsRemoved {
24979 ids: Vec<ExcerptId>,
24980 removed_buffer_ids: Vec<BufferId>,
24981 },
24982 BufferFoldToggled {
24983 ids: Vec<ExcerptId>,
24984 folded: bool,
24985 },
24986 ExcerptsEdited {
24987 ids: Vec<ExcerptId>,
24988 },
24989 ExcerptsExpanded {
24990 ids: Vec<ExcerptId>,
24991 },
24992 BufferEdited,
24993 Edited {
24994 transaction_id: clock::Lamport,
24995 },
24996 Reparsed(BufferId),
24997 Focused,
24998 FocusedIn,
24999 Blurred,
25000 DirtyChanged,
25001 Saved,
25002 TitleChanged,
25003 SelectionsChanged {
25004 local: bool,
25005 },
25006 ScrollPositionChanged {
25007 local: bool,
25008 autoscroll: bool,
25009 },
25010 TransactionUndone {
25011 transaction_id: clock::Lamport,
25012 },
25013 TransactionBegun {
25014 transaction_id: clock::Lamport,
25015 },
25016 CursorShapeChanged,
25017 BreadcrumbsChanged,
25018 PushedToNavHistory {
25019 anchor: Anchor,
25020 is_deactivate: bool,
25021 },
25022}
25023
25024impl EventEmitter<EditorEvent> for Editor {}
25025
25026impl Focusable for Editor {
25027 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25028 self.focus_handle.clone()
25029 }
25030}
25031
25032impl Render for Editor {
25033 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25034 EditorElement::new(&cx.entity(), self.create_style(cx))
25035 }
25036}
25037
25038impl EntityInputHandler for Editor {
25039 fn text_for_range(
25040 &mut self,
25041 range_utf16: Range<usize>,
25042 adjusted_range: &mut Option<Range<usize>>,
25043 _: &mut Window,
25044 cx: &mut Context<Self>,
25045 ) -> Option<String> {
25046 let snapshot = self.buffer.read(cx).read(cx);
25047 let start = snapshot.clip_offset_utf16(
25048 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25049 Bias::Left,
25050 );
25051 let end = snapshot.clip_offset_utf16(
25052 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25053 Bias::Right,
25054 );
25055 if (start.0.0..end.0.0) != range_utf16 {
25056 adjusted_range.replace(start.0.0..end.0.0);
25057 }
25058 Some(snapshot.text_for_range(start..end).collect())
25059 }
25060
25061 fn selected_text_range(
25062 &mut self,
25063 ignore_disabled_input: bool,
25064 _: &mut Window,
25065 cx: &mut Context<Self>,
25066 ) -> Option<UTF16Selection> {
25067 // Prevent the IME menu from appearing when holding down an alphabetic key
25068 // while input is disabled.
25069 if !ignore_disabled_input && !self.input_enabled {
25070 return None;
25071 }
25072
25073 let selection = self
25074 .selections
25075 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25076 let range = selection.range();
25077
25078 Some(UTF16Selection {
25079 range: range.start.0.0..range.end.0.0,
25080 reversed: selection.reversed,
25081 })
25082 }
25083
25084 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25085 let snapshot = self.buffer.read(cx).read(cx);
25086 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25087 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25088 }
25089
25090 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25091 self.clear_highlights::<InputComposition>(cx);
25092 self.ime_transaction.take();
25093 }
25094
25095 fn replace_text_in_range(
25096 &mut self,
25097 range_utf16: Option<Range<usize>>,
25098 text: &str,
25099 window: &mut Window,
25100 cx: &mut Context<Self>,
25101 ) {
25102 if !self.input_enabled {
25103 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25104 return;
25105 }
25106
25107 self.transact(window, cx, |this, window, cx| {
25108 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25109 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25110 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25111 Some(this.selection_replacement_ranges(range_utf16, cx))
25112 } else {
25113 this.marked_text_ranges(cx)
25114 };
25115
25116 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25117 let newest_selection_id = this.selections.newest_anchor().id;
25118 this.selections
25119 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25120 .iter()
25121 .zip(ranges_to_replace.iter())
25122 .find_map(|(selection, range)| {
25123 if selection.id == newest_selection_id {
25124 Some(
25125 (range.start.0.0 as isize - selection.head().0.0 as isize)
25126 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25127 )
25128 } else {
25129 None
25130 }
25131 })
25132 });
25133
25134 cx.emit(EditorEvent::InputHandled {
25135 utf16_range_to_replace: range_to_replace,
25136 text: text.into(),
25137 });
25138
25139 if let Some(new_selected_ranges) = new_selected_ranges {
25140 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25141 selections.select_ranges(new_selected_ranges)
25142 });
25143 this.backspace(&Default::default(), window, cx);
25144 }
25145
25146 this.handle_input(text, window, cx);
25147 });
25148
25149 if let Some(transaction) = self.ime_transaction {
25150 self.buffer.update(cx, |buffer, cx| {
25151 buffer.group_until_transaction(transaction, cx);
25152 });
25153 }
25154
25155 self.unmark_text(window, cx);
25156 }
25157
25158 fn replace_and_mark_text_in_range(
25159 &mut self,
25160 range_utf16: Option<Range<usize>>,
25161 text: &str,
25162 new_selected_range_utf16: Option<Range<usize>>,
25163 window: &mut Window,
25164 cx: &mut Context<Self>,
25165 ) {
25166 if !self.input_enabled {
25167 return;
25168 }
25169
25170 let transaction = self.transact(window, cx, |this, window, cx| {
25171 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25172 let snapshot = this.buffer.read(cx).read(cx);
25173 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25174 for marked_range in &mut marked_ranges {
25175 marked_range.end = marked_range.start + relative_range_utf16.end;
25176 marked_range.start += relative_range_utf16.start;
25177 marked_range.start =
25178 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25179 marked_range.end =
25180 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25181 }
25182 }
25183 Some(marked_ranges)
25184 } else if let Some(range_utf16) = range_utf16 {
25185 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25186 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25187 Some(this.selection_replacement_ranges(range_utf16, cx))
25188 } else {
25189 None
25190 };
25191
25192 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25193 let newest_selection_id = this.selections.newest_anchor().id;
25194 this.selections
25195 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25196 .iter()
25197 .zip(ranges_to_replace.iter())
25198 .find_map(|(selection, range)| {
25199 if selection.id == newest_selection_id {
25200 Some(
25201 (range.start.0.0 as isize - selection.head().0.0 as isize)
25202 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25203 )
25204 } else {
25205 None
25206 }
25207 })
25208 });
25209
25210 cx.emit(EditorEvent::InputHandled {
25211 utf16_range_to_replace: range_to_replace,
25212 text: text.into(),
25213 });
25214
25215 if let Some(ranges) = ranges_to_replace {
25216 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25217 s.select_ranges(ranges)
25218 });
25219 }
25220
25221 let marked_ranges = {
25222 let snapshot = this.buffer.read(cx).read(cx);
25223 this.selections
25224 .disjoint_anchors_arc()
25225 .iter()
25226 .map(|selection| {
25227 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25228 })
25229 .collect::<Vec<_>>()
25230 };
25231
25232 if text.is_empty() {
25233 this.unmark_text(window, cx);
25234 } else {
25235 this.highlight_text::<InputComposition>(
25236 marked_ranges.clone(),
25237 HighlightStyle {
25238 underline: Some(UnderlineStyle {
25239 thickness: px(1.),
25240 color: None,
25241 wavy: false,
25242 }),
25243 ..Default::default()
25244 },
25245 cx,
25246 );
25247 }
25248
25249 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25250 let use_autoclose = this.use_autoclose;
25251 let use_auto_surround = this.use_auto_surround;
25252 this.set_use_autoclose(false);
25253 this.set_use_auto_surround(false);
25254 this.handle_input(text, window, cx);
25255 this.set_use_autoclose(use_autoclose);
25256 this.set_use_auto_surround(use_auto_surround);
25257
25258 if let Some(new_selected_range) = new_selected_range_utf16 {
25259 let snapshot = this.buffer.read(cx).read(cx);
25260 let new_selected_ranges = marked_ranges
25261 .into_iter()
25262 .map(|marked_range| {
25263 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25264 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25265 insertion_start.0 + new_selected_range.start,
25266 ));
25267 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25268 insertion_start.0 + new_selected_range.end,
25269 ));
25270 snapshot.clip_offset_utf16(new_start, Bias::Left)
25271 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25272 })
25273 .collect::<Vec<_>>();
25274
25275 drop(snapshot);
25276 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25277 selections.select_ranges(new_selected_ranges)
25278 });
25279 }
25280 });
25281
25282 self.ime_transaction = self.ime_transaction.or(transaction);
25283 if let Some(transaction) = self.ime_transaction {
25284 self.buffer.update(cx, |buffer, cx| {
25285 buffer.group_until_transaction(transaction, cx);
25286 });
25287 }
25288
25289 if self.text_highlights::<InputComposition>(cx).is_none() {
25290 self.ime_transaction.take();
25291 }
25292 }
25293
25294 fn bounds_for_range(
25295 &mut self,
25296 range_utf16: Range<usize>,
25297 element_bounds: gpui::Bounds<Pixels>,
25298 window: &mut Window,
25299 cx: &mut Context<Self>,
25300 ) -> Option<gpui::Bounds<Pixels>> {
25301 let text_layout_details = self.text_layout_details(window);
25302 let CharacterDimensions {
25303 em_width,
25304 em_advance,
25305 line_height,
25306 } = self.character_dimensions(window);
25307
25308 let snapshot = self.snapshot(window, cx);
25309 let scroll_position = snapshot.scroll_position();
25310 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25311
25312 let start =
25313 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25314 let x = Pixels::from(
25315 ScrollOffset::from(
25316 snapshot.x_for_display_point(start, &text_layout_details)
25317 + self.gutter_dimensions.full_width(),
25318 ) - scroll_left,
25319 );
25320 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25321
25322 Some(Bounds {
25323 origin: element_bounds.origin + point(x, y),
25324 size: size(em_width, line_height),
25325 })
25326 }
25327
25328 fn character_index_for_point(
25329 &mut self,
25330 point: gpui::Point<Pixels>,
25331 _window: &mut Window,
25332 _cx: &mut Context<Self>,
25333 ) -> Option<usize> {
25334 let position_map = self.last_position_map.as_ref()?;
25335 if !position_map.text_hitbox.contains(&point) {
25336 return None;
25337 }
25338 let display_point = position_map.point_for_position(point).previous_valid;
25339 let anchor = position_map
25340 .snapshot
25341 .display_point_to_anchor(display_point, Bias::Left);
25342 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25343 Some(utf16_offset.0.0)
25344 }
25345
25346 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25347 self.input_enabled
25348 }
25349}
25350
25351trait SelectionExt {
25352 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25353 fn spanned_rows(
25354 &self,
25355 include_end_if_at_line_start: bool,
25356 map: &DisplaySnapshot,
25357 ) -> Range<MultiBufferRow>;
25358}
25359
25360impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25361 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25362 let start = self
25363 .start
25364 .to_point(map.buffer_snapshot())
25365 .to_display_point(map);
25366 let end = self
25367 .end
25368 .to_point(map.buffer_snapshot())
25369 .to_display_point(map);
25370 if self.reversed {
25371 end..start
25372 } else {
25373 start..end
25374 }
25375 }
25376
25377 fn spanned_rows(
25378 &self,
25379 include_end_if_at_line_start: bool,
25380 map: &DisplaySnapshot,
25381 ) -> Range<MultiBufferRow> {
25382 let start = self.start.to_point(map.buffer_snapshot());
25383 let mut end = self.end.to_point(map.buffer_snapshot());
25384 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25385 end.row -= 1;
25386 }
25387
25388 let buffer_start = map.prev_line_boundary(start).0;
25389 let buffer_end = map.next_line_boundary(end).0;
25390 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25391 }
25392}
25393
25394impl<T: InvalidationRegion> InvalidationStack<T> {
25395 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25396 where
25397 S: Clone + ToOffset,
25398 {
25399 while let Some(region) = self.last() {
25400 let all_selections_inside_invalidation_ranges =
25401 if selections.len() == region.ranges().len() {
25402 selections
25403 .iter()
25404 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25405 .all(|(selection, invalidation_range)| {
25406 let head = selection.head().to_offset(buffer);
25407 invalidation_range.start <= head && invalidation_range.end >= head
25408 })
25409 } else {
25410 false
25411 };
25412
25413 if all_selections_inside_invalidation_ranges {
25414 break;
25415 } else {
25416 self.pop();
25417 }
25418 }
25419 }
25420}
25421
25422impl<T> Default for InvalidationStack<T> {
25423 fn default() -> Self {
25424 Self(Default::default())
25425 }
25426}
25427
25428impl<T> Deref for InvalidationStack<T> {
25429 type Target = Vec<T>;
25430
25431 fn deref(&self) -> &Self::Target {
25432 &self.0
25433 }
25434}
25435
25436impl<T> DerefMut for InvalidationStack<T> {
25437 fn deref_mut(&mut self) -> &mut Self::Target {
25438 &mut self.0
25439 }
25440}
25441
25442impl InvalidationRegion for SnippetState {
25443 fn ranges(&self) -> &[Range<Anchor>] {
25444 &self.ranges[self.active_index]
25445 }
25446}
25447
25448fn edit_prediction_edit_text(
25449 current_snapshot: &BufferSnapshot,
25450 edits: &[(Range<Anchor>, impl AsRef<str>)],
25451 edit_preview: &EditPreview,
25452 include_deletions: bool,
25453 cx: &App,
25454) -> HighlightedText {
25455 let edits = edits
25456 .iter()
25457 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25458 .collect::<Vec<_>>();
25459
25460 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25461}
25462
25463fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25464 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25465 // Just show the raw edit text with basic styling
25466 let mut text = String::new();
25467 let mut highlights = Vec::new();
25468
25469 let insertion_highlight_style = HighlightStyle {
25470 color: Some(cx.theme().colors().text),
25471 ..Default::default()
25472 };
25473
25474 for (_, edit_text) in edits {
25475 let start_offset = text.len();
25476 text.push_str(edit_text);
25477 let end_offset = text.len();
25478
25479 if start_offset < end_offset {
25480 highlights.push((start_offset..end_offset, insertion_highlight_style));
25481 }
25482 }
25483
25484 HighlightedText {
25485 text: text.into(),
25486 highlights,
25487 }
25488}
25489
25490pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25491 match severity {
25492 lsp::DiagnosticSeverity::ERROR => colors.error,
25493 lsp::DiagnosticSeverity::WARNING => colors.warning,
25494 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25495 lsp::DiagnosticSeverity::HINT => colors.info,
25496 _ => colors.ignored,
25497 }
25498}
25499
25500pub fn styled_runs_for_code_label<'a>(
25501 label: &'a CodeLabel,
25502 syntax_theme: &'a theme::SyntaxTheme,
25503 local_player: &'a theme::PlayerColor,
25504) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25505 let fade_out = HighlightStyle {
25506 fade_out: Some(0.35),
25507 ..Default::default()
25508 };
25509
25510 let mut prev_end = label.filter_range.end;
25511 label
25512 .runs
25513 .iter()
25514 .enumerate()
25515 .flat_map(move |(ix, (range, highlight_id))| {
25516 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25517 HighlightStyle {
25518 color: Some(local_player.cursor),
25519 ..Default::default()
25520 }
25521 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25522 HighlightStyle {
25523 background_color: Some(local_player.selection),
25524 ..Default::default()
25525 }
25526 } else if let Some(style) = highlight_id.style(syntax_theme) {
25527 style
25528 } else {
25529 return Default::default();
25530 };
25531 let muted_style = style.highlight(fade_out);
25532
25533 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25534 if range.start >= label.filter_range.end {
25535 if range.start > prev_end {
25536 runs.push((prev_end..range.start, fade_out));
25537 }
25538 runs.push((range.clone(), muted_style));
25539 } else if range.end <= label.filter_range.end {
25540 runs.push((range.clone(), style));
25541 } else {
25542 runs.push((range.start..label.filter_range.end, style));
25543 runs.push((label.filter_range.end..range.end, muted_style));
25544 }
25545 prev_end = cmp::max(prev_end, range.end);
25546
25547 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25548 runs.push((prev_end..label.text.len(), fade_out));
25549 }
25550
25551 runs
25552 })
25553}
25554
25555pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25556 let mut prev_index = 0;
25557 let mut prev_codepoint: Option<char> = None;
25558 text.char_indices()
25559 .chain([(text.len(), '\0')])
25560 .filter_map(move |(index, codepoint)| {
25561 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25562 let is_boundary = index == text.len()
25563 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25564 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25565 if is_boundary {
25566 let chunk = &text[prev_index..index];
25567 prev_index = index;
25568 Some(chunk)
25569 } else {
25570 None
25571 }
25572 })
25573}
25574
25575/// Given a string of text immediately before the cursor, iterates over possible
25576/// strings a snippet could match to. More precisely: returns an iterator over
25577/// suffixes of `text` created by splitting at word boundaries (before & after
25578/// every non-word character).
25579///
25580/// Shorter suffixes are returned first.
25581pub(crate) fn snippet_candidate_suffixes(
25582 text: &str,
25583 is_word_char: impl Fn(char) -> bool,
25584) -> impl std::iter::Iterator<Item = &str> {
25585 let mut prev_index = text.len();
25586 let mut prev_codepoint = None;
25587 text.char_indices()
25588 .rev()
25589 .chain([(0, '\0')])
25590 .filter_map(move |(index, codepoint)| {
25591 let prev_index = std::mem::replace(&mut prev_index, index);
25592 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25593 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25594 None
25595 } else {
25596 let chunk = &text[prev_index..]; // go to end of string
25597 Some(chunk)
25598 }
25599 })
25600}
25601
25602pub trait RangeToAnchorExt: Sized {
25603 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25604
25605 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25606 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25607 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25608 }
25609}
25610
25611impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25612 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25613 let start_offset = self.start.to_offset(snapshot);
25614 let end_offset = self.end.to_offset(snapshot);
25615 if start_offset == end_offset {
25616 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25617 } else {
25618 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25619 }
25620 }
25621}
25622
25623pub trait RowExt {
25624 fn as_f64(&self) -> f64;
25625
25626 fn next_row(&self) -> Self;
25627
25628 fn previous_row(&self) -> Self;
25629
25630 fn minus(&self, other: Self) -> u32;
25631}
25632
25633impl RowExt for DisplayRow {
25634 fn as_f64(&self) -> f64 {
25635 self.0 as _
25636 }
25637
25638 fn next_row(&self) -> Self {
25639 Self(self.0 + 1)
25640 }
25641
25642 fn previous_row(&self) -> Self {
25643 Self(self.0.saturating_sub(1))
25644 }
25645
25646 fn minus(&self, other: Self) -> u32 {
25647 self.0 - other.0
25648 }
25649}
25650
25651impl RowExt for MultiBufferRow {
25652 fn as_f64(&self) -> f64 {
25653 self.0 as _
25654 }
25655
25656 fn next_row(&self) -> Self {
25657 Self(self.0 + 1)
25658 }
25659
25660 fn previous_row(&self) -> Self {
25661 Self(self.0.saturating_sub(1))
25662 }
25663
25664 fn minus(&self, other: Self) -> u32 {
25665 self.0 - other.0
25666 }
25667}
25668
25669trait RowRangeExt {
25670 type Row;
25671
25672 fn len(&self) -> usize;
25673
25674 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25675}
25676
25677impl RowRangeExt for Range<MultiBufferRow> {
25678 type Row = MultiBufferRow;
25679
25680 fn len(&self) -> usize {
25681 (self.end.0 - self.start.0) as usize
25682 }
25683
25684 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25685 (self.start.0..self.end.0).map(MultiBufferRow)
25686 }
25687}
25688
25689impl RowRangeExt for Range<DisplayRow> {
25690 type Row = DisplayRow;
25691
25692 fn len(&self) -> usize {
25693 (self.end.0 - self.start.0) as usize
25694 }
25695
25696 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25697 (self.start.0..self.end.0).map(DisplayRow)
25698 }
25699}
25700
25701/// If select range has more than one line, we
25702/// just point the cursor to range.start.
25703fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25704 if range.start.row == range.end.row {
25705 range
25706 } else {
25707 range.start..range.start
25708 }
25709}
25710pub struct KillRing(ClipboardItem);
25711impl Global for KillRing {}
25712
25713const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25714
25715enum BreakpointPromptEditAction {
25716 Log,
25717 Condition,
25718 HitCondition,
25719}
25720
25721struct BreakpointPromptEditor {
25722 pub(crate) prompt: Entity<Editor>,
25723 editor: WeakEntity<Editor>,
25724 breakpoint_anchor: Anchor,
25725 breakpoint: Breakpoint,
25726 edit_action: BreakpointPromptEditAction,
25727 block_ids: HashSet<CustomBlockId>,
25728 editor_margins: Arc<Mutex<EditorMargins>>,
25729 _subscriptions: Vec<Subscription>,
25730}
25731
25732impl BreakpointPromptEditor {
25733 const MAX_LINES: u8 = 4;
25734
25735 fn new(
25736 editor: WeakEntity<Editor>,
25737 breakpoint_anchor: Anchor,
25738 breakpoint: Breakpoint,
25739 edit_action: BreakpointPromptEditAction,
25740 window: &mut Window,
25741 cx: &mut Context<Self>,
25742 ) -> Self {
25743 let base_text = match edit_action {
25744 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25745 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25746 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25747 }
25748 .map(|msg| msg.to_string())
25749 .unwrap_or_default();
25750
25751 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25752 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25753
25754 let prompt = cx.new(|cx| {
25755 let mut prompt = Editor::new(
25756 EditorMode::AutoHeight {
25757 min_lines: 1,
25758 max_lines: Some(Self::MAX_LINES as usize),
25759 },
25760 buffer,
25761 None,
25762 window,
25763 cx,
25764 );
25765 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25766 prompt.set_show_cursor_when_unfocused(false, cx);
25767 prompt.set_placeholder_text(
25768 match edit_action {
25769 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25770 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25771 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25772 },
25773 window,
25774 cx,
25775 );
25776
25777 prompt
25778 });
25779
25780 Self {
25781 prompt,
25782 editor,
25783 breakpoint_anchor,
25784 breakpoint,
25785 edit_action,
25786 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25787 block_ids: Default::default(),
25788 _subscriptions: vec![],
25789 }
25790 }
25791
25792 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25793 self.block_ids.extend(block_ids)
25794 }
25795
25796 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25797 if let Some(editor) = self.editor.upgrade() {
25798 let message = self
25799 .prompt
25800 .read(cx)
25801 .buffer
25802 .read(cx)
25803 .as_singleton()
25804 .expect("A multi buffer in breakpoint prompt isn't possible")
25805 .read(cx)
25806 .as_rope()
25807 .to_string();
25808
25809 editor.update(cx, |editor, cx| {
25810 editor.edit_breakpoint_at_anchor(
25811 self.breakpoint_anchor,
25812 self.breakpoint.clone(),
25813 match self.edit_action {
25814 BreakpointPromptEditAction::Log => {
25815 BreakpointEditAction::EditLogMessage(message.into())
25816 }
25817 BreakpointPromptEditAction::Condition => {
25818 BreakpointEditAction::EditCondition(message.into())
25819 }
25820 BreakpointPromptEditAction::HitCondition => {
25821 BreakpointEditAction::EditHitCondition(message.into())
25822 }
25823 },
25824 cx,
25825 );
25826
25827 editor.remove_blocks(self.block_ids.clone(), None, cx);
25828 cx.focus_self(window);
25829 });
25830 }
25831 }
25832
25833 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25834 self.editor
25835 .update(cx, |editor, cx| {
25836 editor.remove_blocks(self.block_ids.clone(), None, cx);
25837 window.focus(&editor.focus_handle);
25838 })
25839 .log_err();
25840 }
25841
25842 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25843 let settings = ThemeSettings::get_global(cx);
25844 let text_style = TextStyle {
25845 color: if self.prompt.read(cx).read_only(cx) {
25846 cx.theme().colors().text_disabled
25847 } else {
25848 cx.theme().colors().text
25849 },
25850 font_family: settings.buffer_font.family.clone(),
25851 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25852 font_size: settings.buffer_font_size(cx).into(),
25853 font_weight: settings.buffer_font.weight,
25854 line_height: relative(settings.buffer_line_height.value()),
25855 ..Default::default()
25856 };
25857 EditorElement::new(
25858 &self.prompt,
25859 EditorStyle {
25860 background: cx.theme().colors().editor_background,
25861 local_player: cx.theme().players().local(),
25862 text: text_style,
25863 ..Default::default()
25864 },
25865 )
25866 }
25867}
25868
25869impl Render for BreakpointPromptEditor {
25870 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25871 let editor_margins = *self.editor_margins.lock();
25872 let gutter_dimensions = editor_margins.gutter;
25873 h_flex()
25874 .key_context("Editor")
25875 .bg(cx.theme().colors().editor_background)
25876 .border_y_1()
25877 .border_color(cx.theme().status().info_border)
25878 .size_full()
25879 .py(window.line_height() / 2.5)
25880 .on_action(cx.listener(Self::confirm))
25881 .on_action(cx.listener(Self::cancel))
25882 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25883 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25884 }
25885}
25886
25887impl Focusable for BreakpointPromptEditor {
25888 fn focus_handle(&self, cx: &App) -> FocusHandle {
25889 self.prompt.focus_handle(cx)
25890 }
25891}
25892
25893fn all_edits_insertions_or_deletions(
25894 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25895 snapshot: &MultiBufferSnapshot,
25896) -> bool {
25897 let mut all_insertions = true;
25898 let mut all_deletions = true;
25899
25900 for (range, new_text) in edits.iter() {
25901 let range_is_empty = range.to_offset(snapshot).is_empty();
25902 let text_is_empty = new_text.is_empty();
25903
25904 if range_is_empty != text_is_empty {
25905 if range_is_empty {
25906 all_deletions = false;
25907 } else {
25908 all_insertions = false;
25909 }
25910 } else {
25911 return false;
25912 }
25913
25914 if !all_insertions && !all_deletions {
25915 return false;
25916 }
25917 }
25918 all_insertions || all_deletions
25919}
25920
25921struct MissingEditPredictionKeybindingTooltip;
25922
25923impl Render for MissingEditPredictionKeybindingTooltip {
25924 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25925 ui::tooltip_container(cx, |container, cx| {
25926 container
25927 .flex_shrink_0()
25928 .max_w_80()
25929 .min_h(rems_from_px(124.))
25930 .justify_between()
25931 .child(
25932 v_flex()
25933 .flex_1()
25934 .text_ui_sm(cx)
25935 .child(Label::new("Conflict with Accept Keybinding"))
25936 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25937 )
25938 .child(
25939 h_flex()
25940 .pb_1()
25941 .gap_1()
25942 .items_end()
25943 .w_full()
25944 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25945 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25946 }))
25947 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25948 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25949 })),
25950 )
25951 })
25952 }
25953}
25954
25955#[derive(Debug, Clone, Copy, PartialEq)]
25956pub struct LineHighlight {
25957 pub background: Background,
25958 pub border: Option<gpui::Hsla>,
25959 pub include_gutter: bool,
25960 pub type_id: Option<TypeId>,
25961}
25962
25963struct LineManipulationResult {
25964 pub new_text: String,
25965 pub line_count_before: usize,
25966 pub line_count_after: usize,
25967}
25968
25969fn render_diff_hunk_controls(
25970 row: u32,
25971 status: &DiffHunkStatus,
25972 hunk_range: Range<Anchor>,
25973 is_created_file: bool,
25974 line_height: Pixels,
25975 editor: &Entity<Editor>,
25976 _window: &mut Window,
25977 cx: &mut App,
25978) -> AnyElement {
25979 h_flex()
25980 .h(line_height)
25981 .mr_1()
25982 .gap_1()
25983 .px_0p5()
25984 .pb_1()
25985 .border_x_1()
25986 .border_b_1()
25987 .border_color(cx.theme().colors().border_variant)
25988 .rounded_b_lg()
25989 .bg(cx.theme().colors().editor_background)
25990 .gap_1()
25991 .block_mouse_except_scroll()
25992 .shadow_md()
25993 .child(if status.has_secondary_hunk() {
25994 Button::new(("stage", row as u64), "Stage")
25995 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25996 .tooltip({
25997 let focus_handle = editor.focus_handle(cx);
25998 move |_window, cx| {
25999 Tooltip::for_action_in(
26000 "Stage Hunk",
26001 &::git::ToggleStaged,
26002 &focus_handle,
26003 cx,
26004 )
26005 }
26006 })
26007 .on_click({
26008 let editor = editor.clone();
26009 move |_event, _window, cx| {
26010 editor.update(cx, |editor, cx| {
26011 editor.stage_or_unstage_diff_hunks(
26012 true,
26013 vec![hunk_range.start..hunk_range.start],
26014 cx,
26015 );
26016 });
26017 }
26018 })
26019 } else {
26020 Button::new(("unstage", row as u64), "Unstage")
26021 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26022 .tooltip({
26023 let focus_handle = editor.focus_handle(cx);
26024 move |_window, cx| {
26025 Tooltip::for_action_in(
26026 "Unstage Hunk",
26027 &::git::ToggleStaged,
26028 &focus_handle,
26029 cx,
26030 )
26031 }
26032 })
26033 .on_click({
26034 let editor = editor.clone();
26035 move |_event, _window, cx| {
26036 editor.update(cx, |editor, cx| {
26037 editor.stage_or_unstage_diff_hunks(
26038 false,
26039 vec![hunk_range.start..hunk_range.start],
26040 cx,
26041 );
26042 });
26043 }
26044 })
26045 })
26046 .child(
26047 Button::new(("restore", row as u64), "Restore")
26048 .tooltip({
26049 let focus_handle = editor.focus_handle(cx);
26050 move |_window, cx| {
26051 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26052 }
26053 })
26054 .on_click({
26055 let editor = editor.clone();
26056 move |_event, window, cx| {
26057 editor.update(cx, |editor, cx| {
26058 let snapshot = editor.snapshot(window, cx);
26059 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26060 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26061 });
26062 }
26063 })
26064 .disabled(is_created_file),
26065 )
26066 .when(
26067 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26068 |el| {
26069 el.child(
26070 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26071 .shape(IconButtonShape::Square)
26072 .icon_size(IconSize::Small)
26073 // .disabled(!has_multiple_hunks)
26074 .tooltip({
26075 let focus_handle = editor.focus_handle(cx);
26076 move |_window, cx| {
26077 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26078 }
26079 })
26080 .on_click({
26081 let editor = editor.clone();
26082 move |_event, window, cx| {
26083 editor.update(cx, |editor, cx| {
26084 let snapshot = editor.snapshot(window, cx);
26085 let position =
26086 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26087 editor.go_to_hunk_before_or_after_position(
26088 &snapshot,
26089 position,
26090 Direction::Next,
26091 window,
26092 cx,
26093 );
26094 editor.expand_selected_diff_hunks(cx);
26095 });
26096 }
26097 }),
26098 )
26099 .child(
26100 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26101 .shape(IconButtonShape::Square)
26102 .icon_size(IconSize::Small)
26103 // .disabled(!has_multiple_hunks)
26104 .tooltip({
26105 let focus_handle = editor.focus_handle(cx);
26106 move |_window, cx| {
26107 Tooltip::for_action_in(
26108 "Previous Hunk",
26109 &GoToPreviousHunk,
26110 &focus_handle,
26111 cx,
26112 )
26113 }
26114 })
26115 .on_click({
26116 let editor = editor.clone();
26117 move |_event, window, cx| {
26118 editor.update(cx, |editor, cx| {
26119 let snapshot = editor.snapshot(window, cx);
26120 let point =
26121 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26122 editor.go_to_hunk_before_or_after_position(
26123 &snapshot,
26124 point,
26125 Direction::Prev,
26126 window,
26127 cx,
26128 );
26129 editor.expand_selected_diff_hunks(cx);
26130 });
26131 }
26132 }),
26133 )
26134 },
26135 )
26136 .into_any_element()
26137}
26138
26139pub fn multibuffer_context_lines(cx: &App) -> u32 {
26140 EditorSettings::try_get(cx)
26141 .map(|settings| settings.excerpt_context_lines)
26142 .unwrap_or(2)
26143 .min(32)
26144}