1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79 status::FileStatus,
80};
81use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
82use anyhow::{Context as _, Result, anyhow};
83use blink_manager::BlinkManager;
84use buffer_diff::DiffHunkStatus;
85use client::{Collaborator, ParticipantIndex, parse_zed_link};
86use clock::ReplicaId;
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use collections::{BTreeMap, HashMap, HashSet, VecDeque};
92use convert_case::{Case, Casing};
93use dap::TelemetrySpawnLocation;
94use display_map::*;
95use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
111 ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
112 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
113 WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
114};
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
125 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
150 ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, Peekable},
183 mem,
184 num::NonZeroU32,
185 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
193use theme::{
194 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287#[derive(Debug, Copy, Clone, PartialEq, Eq)]
288pub enum Navigated {
289 Yes,
290 No,
291}
292
293impl Navigated {
294 pub fn from_bool(yes: bool) -> Navigated {
295 if yes { Navigated::Yes } else { Navigated::No }
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq)]
300enum DisplayDiffHunk {
301 Folded {
302 display_row: DisplayRow,
303 },
304 Unfolded {
305 is_created_file: bool,
306 diff_base_byte_range: Range<usize>,
307 display_row_range: Range<DisplayRow>,
308 multi_buffer_range: Range<Anchor>,
309 status: DiffHunkStatus,
310 },
311}
312
313pub enum HideMouseCursorOrigin {
314 TypingAction,
315 MovementAction,
316}
317
318pub fn init(cx: &mut App) {
319 cx.set_global(GlobalBlameRenderer(Arc::new(())));
320
321 workspace::register_project_item::<Editor>(cx);
322 workspace::FollowableViewRegistry::register::<Editor>(cx);
323 workspace::register_serializable_item::<Editor>(cx);
324
325 cx.observe_new(
326 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
327 workspace.register_action(Editor::new_file);
328 workspace.register_action(Editor::new_file_split);
329 workspace.register_action(Editor::new_file_vertical);
330 workspace.register_action(Editor::new_file_horizontal);
331 workspace.register_action(Editor::cancel_language_server_work);
332 workspace.register_action(Editor::toggle_focus);
333 },
334 )
335 .detach();
336
337 cx.on_action(move |_: &workspace::NewFile, cx| {
338 let app_state = workspace::AppState::global(cx);
339 if let Some(app_state) = app_state.upgrade() {
340 workspace::open_new(
341 Default::default(),
342 app_state,
343 cx,
344 |workspace, window, cx| {
345 Editor::new_file(workspace, &Default::default(), window, cx)
346 },
347 )
348 .detach();
349 }
350 });
351 cx.on_action(move |_: &workspace::NewWindow, cx| {
352 let app_state = workspace::AppState::global(cx);
353 if let Some(app_state) = app_state.upgrade() {
354 workspace::open_new(
355 Default::default(),
356 app_state,
357 cx,
358 |workspace, window, cx| {
359 cx.activate(true);
360 Editor::new_file(workspace, &Default::default(), window, cx)
361 },
362 )
363 .detach();
364 }
365 });
366}
367
368pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
369 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
370}
371
372pub trait DiagnosticRenderer {
373 fn render_group(
374 &self,
375 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
376 buffer_id: BufferId,
377 snapshot: EditorSnapshot,
378 editor: WeakEntity<Editor>,
379 language_registry: Option<Arc<LanguageRegistry>>,
380 cx: &mut App,
381 ) -> Vec<BlockProperties<Anchor>>;
382
383 fn render_hover(
384 &self,
385 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
386 range: Range<Point>,
387 buffer_id: BufferId,
388 language_registry: Option<Arc<LanguageRegistry>>,
389 cx: &mut App,
390 ) -> Option<Entity<markdown::Markdown>>;
391
392 fn open_link(
393 &self,
394 editor: &mut Editor,
395 link: SharedString,
396 window: &mut Window,
397 cx: &mut Context<Editor>,
398 );
399}
400
401pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
402
403impl GlobalDiagnosticRenderer {
404 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
405 cx.try_global::<Self>().map(|g| g.0.clone())
406 }
407}
408
409impl gpui::Global for GlobalDiagnosticRenderer {}
410pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
411 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
412}
413
414pub struct SearchWithinRange;
415
416trait InvalidationRegion {
417 fn ranges(&self) -> &[Range<Anchor>];
418}
419
420#[derive(Clone, Debug, PartialEq)]
421pub enum SelectPhase {
422 Begin {
423 position: DisplayPoint,
424 add: bool,
425 click_count: usize,
426 },
427 BeginColumnar {
428 position: DisplayPoint,
429 reset: bool,
430 mode: ColumnarMode,
431 goal_column: u32,
432 },
433 Extend {
434 position: DisplayPoint,
435 click_count: usize,
436 },
437 Update {
438 position: DisplayPoint,
439 goal_column: u32,
440 scroll_delta: gpui::Point<f32>,
441 },
442 End,
443}
444
445#[derive(Clone, Debug, PartialEq)]
446pub enum ColumnarMode {
447 FromMouse,
448 FromSelection,
449}
450
451#[derive(Clone, Debug)]
452pub enum SelectMode {
453 Character,
454 Word(Range<Anchor>),
455 Line(Range<Anchor>),
456 All,
457}
458
459#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
460pub enum SizingBehavior {
461 /// The editor will layout itself using `size_full` and will include the vertical
462 /// scroll margin as requested by user settings.
463 #[default]
464 Default,
465 /// The editor will layout itself using `size_full`, but will not have any
466 /// vertical overscroll.
467 ExcludeOverscrollMargin,
468 /// The editor will request a vertical size according to its content and will be
469 /// layouted without a vertical scroll margin.
470 SizeByContent,
471}
472
473#[derive(Clone, PartialEq, Eq, Debug)]
474pub enum EditorMode {
475 SingleLine,
476 AutoHeight {
477 min_lines: usize,
478 max_lines: Option<usize>,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// Determines the sizing behavior for this editor
486 sizing_behavior: SizingBehavior,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sizing_behavior: SizingBehavior::Default,
499 }
500 }
501
502 #[inline]
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 #[inline]
508 pub fn is_single_line(&self) -> bool {
509 matches!(self, Self::SingleLine { .. })
510 }
511
512 #[inline]
513 fn is_minimap(&self) -> bool {
514 matches!(self, Self::Minimap { .. })
515 }
516}
517
518#[derive(Copy, Clone, Debug)]
519pub enum SoftWrap {
520 /// Prefer not to wrap at all.
521 ///
522 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
523 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
524 GitDiff,
525 /// Prefer a single line generally, unless an overly long line is encountered.
526 None,
527 /// Soft wrap lines that exceed the editor width.
528 EditorWidth,
529 /// Soft wrap lines at the preferred line length.
530 Column(u32),
531 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
532 Bounded(u32),
533}
534
535#[derive(Clone)]
536pub struct EditorStyle {
537 pub background: Hsla,
538 pub border: Hsla,
539 pub local_player: PlayerColor,
540 pub text: TextStyle,
541 pub scrollbar_width: Pixels,
542 pub syntax: Arc<SyntaxTheme>,
543 pub status: StatusColors,
544 pub inlay_hints_style: HighlightStyle,
545 pub edit_prediction_styles: EditPredictionStyles,
546 pub unnecessary_code_fade: f32,
547 pub show_underlines: bool,
548}
549
550impl Default for EditorStyle {
551 fn default() -> Self {
552 Self {
553 background: Hsla::default(),
554 border: Hsla::default(),
555 local_player: PlayerColor::default(),
556 text: TextStyle::default(),
557 scrollbar_width: Pixels::default(),
558 syntax: Default::default(),
559 // HACK: Status colors don't have a real default.
560 // We should look into removing the status colors from the editor
561 // style and retrieve them directly from the theme.
562 status: StatusColors::dark(),
563 inlay_hints_style: HighlightStyle::default(),
564 edit_prediction_styles: EditPredictionStyles {
565 insertion: HighlightStyle::default(),
566 whitespace: HighlightStyle::default(),
567 },
568 unnecessary_code_fade: Default::default(),
569 show_underlines: true,
570 }
571 }
572}
573
574pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
575 let show_background = language_settings::language_settings(None, None, cx)
576 .inlay_hints
577 .show_background;
578
579 let mut style = cx.theme().syntax().get("hint");
580
581 if style.color.is_none() {
582 style.color = Some(cx.theme().status().hint);
583 }
584
585 if !show_background {
586 style.background_color = None;
587 return style;
588 }
589
590 if style.background_color.is_none() {
591 style.background_color = Some(cx.theme().status().hint_background);
592 }
593
594 style
595}
596
597pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
598 EditPredictionStyles {
599 insertion: HighlightStyle {
600 color: Some(cx.theme().status().predictive),
601 ..HighlightStyle::default()
602 },
603 whitespace: HighlightStyle {
604 background_color: Some(cx.theme().status().created_background),
605 ..HighlightStyle::default()
606 },
607 }
608}
609
610type CompletionId = usize;
611
612pub(crate) enum EditDisplayMode {
613 TabAccept,
614 DiffPopover,
615 Inline,
616}
617
618enum EditPrediction {
619 Edit {
620 edits: Vec<(Range<Anchor>, Arc<str>)>,
621 edit_preview: Option<EditPreview>,
622 display_mode: EditDisplayMode,
623 snapshot: BufferSnapshot,
624 },
625 /// Move to a specific location in the active editor
626 MoveWithin {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630 /// Move to a specific location in a different editor (not the active one)
631 MoveOutside {
632 target: language::Anchor,
633 snapshot: BufferSnapshot,
634 },
635}
636
637struct EditPredictionState {
638 inlay_ids: Vec<InlayId>,
639 completion: EditPrediction,
640 completion_id: Option<SharedString>,
641 invalidation_range: Option<Range<Anchor>>,
642}
643
644enum EditPredictionSettings {
645 Disabled,
646 Enabled {
647 show_in_menu: bool,
648 preview_requires_modifier: bool,
649 },
650}
651
652enum EditPredictionHighlight {}
653
654#[derive(Debug, Clone)]
655struct InlineDiagnostic {
656 message: SharedString,
657 group_id: usize,
658 is_primary: bool,
659 start: Point,
660 severity: lsp::DiagnosticSeverity,
661}
662
663pub enum MenuEditPredictionsPolicy {
664 Never,
665 ByProvider,
666}
667
668pub enum EditPredictionPreview {
669 /// Modifier is not pressed
670 Inactive { released_too_fast: bool },
671 /// Modifier pressed
672 Active {
673 since: Instant,
674 previous_scroll_position: Option<ScrollAnchor>,
675 },
676}
677
678impl EditPredictionPreview {
679 pub fn released_too_fast(&self) -> bool {
680 match self {
681 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
682 EditPredictionPreview::Active { .. } => false,
683 }
684 }
685
686 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
687 if let EditPredictionPreview::Active {
688 previous_scroll_position,
689 ..
690 } = self
691 {
692 *previous_scroll_position = scroll_position;
693 }
694 }
695}
696
697pub struct ContextMenuOptions {
698 pub min_entries_visible: usize,
699 pub max_entries_visible: usize,
700 pub placement: Option<ContextMenuPlacement>,
701}
702
703#[derive(Debug, Clone, PartialEq, Eq)]
704pub enum ContextMenuPlacement {
705 Above,
706 Below,
707}
708
709#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
710struct EditorActionId(usize);
711
712impl EditorActionId {
713 pub fn post_inc(&mut self) -> Self {
714 let answer = self.0;
715
716 *self = Self(answer + 1);
717
718 Self(answer)
719 }
720}
721
722// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
723// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
724
725type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
726type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
727
728#[derive(Default)]
729struct ScrollbarMarkerState {
730 scrollbar_size: Size<Pixels>,
731 dirty: bool,
732 markers: Arc<[PaintQuad]>,
733 pending_refresh: Option<Task<Result<()>>>,
734}
735
736impl ScrollbarMarkerState {
737 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
738 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
739 }
740}
741
742#[derive(Clone, Copy, PartialEq, Eq)]
743pub enum MinimapVisibility {
744 Disabled,
745 Enabled {
746 /// The configuration currently present in the users settings.
747 setting_configuration: bool,
748 /// Whether to override the currently set visibility from the users setting.
749 toggle_override: bool,
750 },
751}
752
753impl MinimapVisibility {
754 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
755 if mode.is_full() {
756 Self::Enabled {
757 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
758 toggle_override: false,
759 }
760 } else {
761 Self::Disabled
762 }
763 }
764
765 fn hidden(&self) -> Self {
766 match *self {
767 Self::Enabled {
768 setting_configuration,
769 ..
770 } => Self::Enabled {
771 setting_configuration,
772 toggle_override: setting_configuration,
773 },
774 Self::Disabled => Self::Disabled,
775 }
776 }
777
778 fn disabled(&self) -> bool {
779 matches!(*self, Self::Disabled)
780 }
781
782 fn settings_visibility(&self) -> bool {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 ..
787 } => setting_configuration,
788 _ => false,
789 }
790 }
791
792 fn visible(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 toggle_override,
797 } => setting_configuration ^ toggle_override,
798 _ => false,
799 }
800 }
801
802 fn toggle_visibility(&self) -> Self {
803 match *self {
804 Self::Enabled {
805 toggle_override,
806 setting_configuration,
807 } => Self::Enabled {
808 setting_configuration,
809 toggle_override: !toggle_override,
810 },
811 Self::Disabled => Self::Disabled,
812 }
813 }
814}
815
816#[derive(Debug, Clone, Copy, PartialEq, Eq)]
817pub enum BufferSerialization {
818 All,
819 NonDirtyBuffers,
820}
821
822impl BufferSerialization {
823 fn new(restore_unsaved_buffers: bool) -> Self {
824 if restore_unsaved_buffers {
825 Self::All
826 } else {
827 Self::NonDirtyBuffers
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863/// Addons allow storing per-editor state in other crates (e.g. Vim)
864pub trait Addon: 'static {
865 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
866
867 fn render_buffer_header_controls(
868 &self,
869 _: &ExcerptInfo,
870 _: &Window,
871 _: &App,
872 ) -> Option<AnyElement> {
873 None
874 }
875
876 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
877 None
878 }
879
880 fn to_any(&self) -> &dyn std::any::Any;
881
882 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
883 None
884 }
885}
886
887struct ChangeLocation {
888 current: Option<Vec<Anchor>>,
889 original: Vec<Anchor>,
890}
891impl ChangeLocation {
892 fn locations(&self) -> &[Anchor] {
893 self.current.as_ref().unwrap_or(&self.original)
894 }
895}
896
897/// A set of caret positions, registered when the editor was edited.
898pub struct ChangeList {
899 changes: Vec<ChangeLocation>,
900 /// Currently "selected" change.
901 position: Option<usize>,
902}
903
904impl ChangeList {
905 pub fn new() -> Self {
906 Self {
907 changes: Vec::new(),
908 position: None,
909 }
910 }
911
912 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
913 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
914 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
915 if self.changes.is_empty() {
916 return None;
917 }
918
919 let prev = self.position.unwrap_or(self.changes.len());
920 let next = if direction == Direction::Prev {
921 prev.saturating_sub(count)
922 } else {
923 (prev + count).min(self.changes.len() - 1)
924 };
925 self.position = Some(next);
926 self.changes.get(next).map(|change| change.locations())
927 }
928
929 /// Adds a new change to the list, resetting the change list position.
930 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
931 self.position.take();
932 if let Some(last) = self.changes.last_mut()
933 && group
934 {
935 last.current = Some(new_positions)
936 } else {
937 self.changes.push(ChangeLocation {
938 original: new_positions,
939 current: None,
940 });
941 }
942 }
943
944 pub fn last(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.locations())
946 }
947
948 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
949 self.changes.last().map(|change| change.original.as_slice())
950 }
951
952 pub fn invert_last_group(&mut self) {
953 if let Some(last) = self.changes.last_mut()
954 && let Some(current) = last.current.as_mut()
955 {
956 mem::swap(&mut last.original, current);
957 }
958 }
959}
960
961#[derive(Clone)]
962struct InlineBlamePopoverState {
963 scroll_handle: ScrollHandle,
964 commit_message: Option<ParsedCommitMessage>,
965 markdown: Entity<Markdown>,
966}
967
968struct InlineBlamePopover {
969 position: gpui::Point<Pixels>,
970 hide_task: Option<Task<()>>,
971 popover_bounds: Option<Bounds<Pixels>>,
972 popover_state: InlineBlamePopoverState,
973 keyboard_grace: bool,
974}
975
976enum SelectionDragState {
977 /// State when no drag related activity is detected.
978 None,
979 /// State when the mouse is down on a selection that is about to be dragged.
980 ReadyToDrag {
981 selection: Selection<Anchor>,
982 click_position: gpui::Point<Pixels>,
983 mouse_down_time: Instant,
984 },
985 /// State when the mouse is dragging the selection in the editor.
986 Dragging {
987 selection: Selection<Anchor>,
988 drop_cursor: Selection<Anchor>,
989 hide_drop_cursor: bool,
990 },
991}
992
993enum ColumnarSelectionState {
994 FromMouse {
995 selection_tail: Anchor,
996 display_point: Option<DisplayPoint>,
997 },
998 FromSelection {
999 selection_tail: Anchor,
1000 },
1001}
1002
1003/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1004/// a breakpoint on them.
1005#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1006struct PhantomBreakpointIndicator {
1007 display_row: DisplayRow,
1008 /// There's a small debounce between hovering over the line and showing the indicator.
1009 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1010 is_active: bool,
1011 collides_with_existing_breakpoint: bool,
1012}
1013
1014/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1015///
1016/// See the [module level documentation](self) for more information.
1017pub struct Editor {
1018 focus_handle: FocusHandle,
1019 last_focused_descendant: Option<WeakFocusHandle>,
1020 /// The text buffer being edited
1021 buffer: Entity<MultiBuffer>,
1022 /// Map of how text in the buffer should be displayed.
1023 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1024 pub display_map: Entity<DisplayMap>,
1025 placeholder_display_map: Option<Entity<DisplayMap>>,
1026 pub selections: SelectionsCollection,
1027 pub scroll_manager: ScrollManager,
1028 /// When inline assist editors are linked, they all render cursors because
1029 /// typing enters text into each of them, even the ones that aren't focused.
1030 pub(crate) show_cursor_when_unfocused: bool,
1031 columnar_selection_state: Option<ColumnarSelectionState>,
1032 add_selections_state: Option<AddSelectionsState>,
1033 select_next_state: Option<SelectNextState>,
1034 select_prev_state: Option<SelectNextState>,
1035 selection_history: SelectionHistory,
1036 defer_selection_effects: bool,
1037 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1038 autoclose_regions: Vec<AutocloseRegion>,
1039 snippet_stack: InvalidationStack<SnippetState>,
1040 select_syntax_node_history: SelectSyntaxNodeHistory,
1041 ime_transaction: Option<TransactionId>,
1042 pub diagnostics_max_severity: DiagnosticSeverity,
1043 active_diagnostics: ActiveDiagnostic,
1044 show_inline_diagnostics: bool,
1045 inline_diagnostics_update: Task<()>,
1046 inline_diagnostics_enabled: bool,
1047 diagnostics_enabled: bool,
1048 word_completions_enabled: bool,
1049 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1050 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1051 hard_wrap: Option<usize>,
1052 project: Option<Entity<Project>>,
1053 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1054 completion_provider: Option<Rc<dyn CompletionProvider>>,
1055 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1056 blink_manager: Entity<BlinkManager>,
1057 show_cursor_names: bool,
1058 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1059 pub show_local_selections: bool,
1060 mode: EditorMode,
1061 show_breadcrumbs: bool,
1062 show_gutter: bool,
1063 show_scrollbars: ScrollbarAxes,
1064 minimap_visibility: MinimapVisibility,
1065 offset_content: bool,
1066 disable_expand_excerpt_buttons: bool,
1067 show_line_numbers: Option<bool>,
1068 use_relative_line_numbers: Option<bool>,
1069 show_git_diff_gutter: Option<bool>,
1070 show_code_actions: Option<bool>,
1071 show_runnables: Option<bool>,
1072 show_breakpoints: Option<bool>,
1073 show_wrap_guides: Option<bool>,
1074 show_indent_guides: Option<bool>,
1075 highlight_order: usize,
1076 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1077 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1078 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1079 scrollbar_marker_state: ScrollbarMarkerState,
1080 active_indent_guides_state: ActiveIndentGuidesState,
1081 nav_history: Option<ItemNavHistory>,
1082 context_menu: RefCell<Option<CodeContextMenu>>,
1083 context_menu_options: Option<ContextMenuOptions>,
1084 mouse_context_menu: Option<MouseContextMenu>,
1085 completion_tasks: Vec<(CompletionId, Task<()>)>,
1086 inline_blame_popover: Option<InlineBlamePopover>,
1087 inline_blame_popover_show_task: Option<Task<()>>,
1088 signature_help_state: SignatureHelpState,
1089 auto_signature_help: Option<bool>,
1090 find_all_references_task_sources: Vec<Anchor>,
1091 next_completion_id: CompletionId,
1092 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1093 code_actions_task: Option<Task<Result<()>>>,
1094 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1095 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1096 document_highlights_task: Option<Task<()>>,
1097 linked_editing_range_task: Option<Task<Option<()>>>,
1098 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1099 pending_rename: Option<RenameState>,
1100 searchable: bool,
1101 cursor_shape: CursorShape,
1102 current_line_highlight: Option<CurrentLineHighlight>,
1103 pub collapse_matches: bool,
1104 autoindent_mode: Option<AutoindentMode>,
1105 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1106 input_enabled: bool,
1107 use_modal_editing: bool,
1108 read_only: bool,
1109 leader_id: Option<CollaboratorId>,
1110 remote_id: Option<ViewId>,
1111 pub hover_state: HoverState,
1112 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1113 gutter_hovered: bool,
1114 hovered_link_state: Option<HoveredLinkState>,
1115 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1116 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1117 active_edit_prediction: Option<EditPredictionState>,
1118 /// Used to prevent flickering as the user types while the menu is open
1119 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1120 edit_prediction_settings: EditPredictionSettings,
1121 edit_predictions_hidden_for_vim_mode: bool,
1122 show_edit_predictions_override: Option<bool>,
1123 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1124 edit_prediction_preview: EditPredictionPreview,
1125 edit_prediction_indent_conflict: bool,
1126 edit_prediction_requires_modifier_in_indent_conflict: bool,
1127 next_inlay_id: usize,
1128 next_color_inlay_id: usize,
1129 _subscriptions: Vec<Subscription>,
1130 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1131 gutter_dimensions: GutterDimensions,
1132 style: Option<EditorStyle>,
1133 text_style_refinement: Option<TextStyleRefinement>,
1134 next_editor_action_id: EditorActionId,
1135 editor_actions: Rc<
1136 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1137 >,
1138 use_autoclose: bool,
1139 use_auto_surround: bool,
1140 auto_replace_emoji_shortcode: bool,
1141 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1142 show_git_blame_gutter: bool,
1143 show_git_blame_inline: bool,
1144 show_git_blame_inline_delay_task: Option<Task<()>>,
1145 git_blame_inline_enabled: bool,
1146 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1147 buffer_serialization: Option<BufferSerialization>,
1148 show_selection_menu: Option<bool>,
1149 blame: Option<Entity<GitBlame>>,
1150 blame_subscription: Option<Subscription>,
1151 custom_context_menu: Option<
1152 Box<
1153 dyn 'static
1154 + Fn(
1155 &mut Self,
1156 DisplayPoint,
1157 &mut Window,
1158 &mut Context<Self>,
1159 ) -> Option<Entity<ui::ContextMenu>>,
1160 >,
1161 >,
1162 last_bounds: Option<Bounds<Pixels>>,
1163 last_position_map: Option<Rc<PositionMap>>,
1164 expect_bounds_change: Option<Bounds<Pixels>>,
1165 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1166 tasks_update_task: Option<Task<()>>,
1167 breakpoint_store: Option<Entity<BreakpointStore>>,
1168 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1169 hovered_diff_hunk_row: Option<DisplayRow>,
1170 pull_diagnostics_task: Task<()>,
1171 in_project_search: bool,
1172 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1173 breadcrumb_header: Option<String>,
1174 focused_block: Option<FocusedBlock>,
1175 next_scroll_position: NextScrollCursorCenterTopBottom,
1176 addons: HashMap<TypeId, Box<dyn Addon>>,
1177 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1178 load_diff_task: Option<Shared<Task<()>>>,
1179 /// Whether we are temporarily displaying a diff other than git's
1180 temporary_diff_override: bool,
1181 selection_mark_mode: bool,
1182 toggle_fold_multiple_buffers: Task<()>,
1183 _scroll_cursor_center_top_bottom_task: Task<()>,
1184 serialize_selections: Task<()>,
1185 serialize_folds: Task<()>,
1186 mouse_cursor_hidden: bool,
1187 minimap: Option<Entity<Self>>,
1188 hide_mouse_mode: HideMouseMode,
1189 pub change_list: ChangeList,
1190 inline_value_cache: InlineValueCache,
1191
1192 selection_drag_state: SelectionDragState,
1193 colors: Option<LspColorData>,
1194 post_scroll_update: Task<()>,
1195 refresh_colors_task: Task<()>,
1196 inlay_hints: Option<LspInlayHintData>,
1197 folding_newlines: Task<()>,
1198 select_next_is_case_sensitive: Option<bool>,
1199 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1200 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1201 accent_overrides: Vec<SharedString>,
1202 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1203 use_base_text_line_numbers: bool,
1204}
1205
1206fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1207 if debounce_ms > 0 {
1208 Some(Duration::from_millis(debounce_ms))
1209 } else {
1210 None
1211 }
1212}
1213
1214#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1215enum NextScrollCursorCenterTopBottom {
1216 #[default]
1217 Center,
1218 Top,
1219 Bottom,
1220}
1221
1222impl NextScrollCursorCenterTopBottom {
1223 fn next(&self) -> Self {
1224 match self {
1225 Self::Center => Self::Top,
1226 Self::Top => Self::Bottom,
1227 Self::Bottom => Self::Center,
1228 }
1229 }
1230}
1231
1232#[derive(Clone)]
1233pub struct EditorSnapshot {
1234 pub mode: EditorMode,
1235 show_gutter: bool,
1236 show_line_numbers: Option<bool>,
1237 show_git_diff_gutter: Option<bool>,
1238 show_code_actions: Option<bool>,
1239 show_runnables: Option<bool>,
1240 show_breakpoints: Option<bool>,
1241 git_blame_gutter_max_author_length: Option<usize>,
1242 pub display_snapshot: DisplaySnapshot,
1243 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1244 is_focused: bool,
1245 scroll_anchor: ScrollAnchor,
1246 ongoing_scroll: OngoingScroll,
1247 current_line_highlight: CurrentLineHighlight,
1248 gutter_hovered: bool,
1249}
1250
1251#[derive(Default, Debug, Clone, Copy)]
1252pub struct GutterDimensions {
1253 pub left_padding: Pixels,
1254 pub right_padding: Pixels,
1255 pub width: Pixels,
1256 pub margin: Pixels,
1257 pub git_blame_entries_width: Option<Pixels>,
1258}
1259
1260impl GutterDimensions {
1261 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1262 Self {
1263 margin: Self::default_gutter_margin(font_id, font_size, cx),
1264 ..Default::default()
1265 }
1266 }
1267
1268 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1269 -cx.text_system().descent(font_id, font_size)
1270 }
1271 /// The full width of the space taken up by the gutter.
1272 pub fn full_width(&self) -> Pixels {
1273 self.margin + self.width
1274 }
1275
1276 /// The width of the space reserved for the fold indicators,
1277 /// use alongside 'justify_end' and `gutter_width` to
1278 /// right align content with the line numbers
1279 pub fn fold_area_width(&self) -> Pixels {
1280 self.margin + self.right_padding
1281 }
1282}
1283
1284struct CharacterDimensions {
1285 em_width: Pixels,
1286 em_advance: Pixels,
1287 line_height: Pixels,
1288}
1289
1290#[derive(Debug)]
1291pub struct RemoteSelection {
1292 pub replica_id: ReplicaId,
1293 pub selection: Selection<Anchor>,
1294 pub cursor_shape: CursorShape,
1295 pub collaborator_id: CollaboratorId,
1296 pub line_mode: bool,
1297 pub user_name: Option<SharedString>,
1298 pub color: PlayerColor,
1299}
1300
1301#[derive(Clone, Debug)]
1302struct SelectionHistoryEntry {
1303 selections: Arc<[Selection<Anchor>]>,
1304 select_next_state: Option<SelectNextState>,
1305 select_prev_state: Option<SelectNextState>,
1306 add_selections_state: Option<AddSelectionsState>,
1307}
1308
1309#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1310enum SelectionHistoryMode {
1311 #[default]
1312 Normal,
1313 Undoing,
1314 Redoing,
1315 Skipping,
1316}
1317
1318#[derive(Clone, PartialEq, Eq, Hash)]
1319struct HoveredCursor {
1320 replica_id: ReplicaId,
1321 selection_id: usize,
1322}
1323
1324#[derive(Debug)]
1325/// SelectionEffects controls the side-effects of updating the selection.
1326///
1327/// The default behaviour does "what you mostly want":
1328/// - it pushes to the nav history if the cursor moved by >10 lines
1329/// - it re-triggers completion requests
1330/// - it scrolls to fit
1331///
1332/// You might want to modify these behaviours. For example when doing a "jump"
1333/// like go to definition, we always want to add to nav history; but when scrolling
1334/// in vim mode we never do.
1335///
1336/// Similarly, you might want to disable scrolling if you don't want the viewport to
1337/// move.
1338#[derive(Clone)]
1339pub struct SelectionEffects {
1340 nav_history: Option<bool>,
1341 completions: bool,
1342 scroll: Option<Autoscroll>,
1343}
1344
1345impl Default for SelectionEffects {
1346 fn default() -> Self {
1347 Self {
1348 nav_history: None,
1349 completions: true,
1350 scroll: Some(Autoscroll::fit()),
1351 }
1352 }
1353}
1354impl SelectionEffects {
1355 pub fn scroll(scroll: Autoscroll) -> Self {
1356 Self {
1357 scroll: Some(scroll),
1358 ..Default::default()
1359 }
1360 }
1361
1362 pub fn no_scroll() -> Self {
1363 Self {
1364 scroll: None,
1365 ..Default::default()
1366 }
1367 }
1368
1369 pub fn completions(self, completions: bool) -> Self {
1370 Self {
1371 completions,
1372 ..self
1373 }
1374 }
1375
1376 pub fn nav_history(self, nav_history: bool) -> Self {
1377 Self {
1378 nav_history: Some(nav_history),
1379 ..self
1380 }
1381 }
1382}
1383
1384struct DeferredSelectionEffectsState {
1385 changed: bool,
1386 effects: SelectionEffects,
1387 old_cursor_position: Anchor,
1388 history_entry: SelectionHistoryEntry,
1389}
1390
1391#[derive(Default)]
1392struct SelectionHistory {
1393 #[allow(clippy::type_complexity)]
1394 selections_by_transaction:
1395 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1396 mode: SelectionHistoryMode,
1397 undo_stack: VecDeque<SelectionHistoryEntry>,
1398 redo_stack: VecDeque<SelectionHistoryEntry>,
1399}
1400
1401impl SelectionHistory {
1402 #[track_caller]
1403 fn insert_transaction(
1404 &mut self,
1405 transaction_id: TransactionId,
1406 selections: Arc<[Selection<Anchor>]>,
1407 ) {
1408 if selections.is_empty() {
1409 log::error!(
1410 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1411 std::panic::Location::caller()
1412 );
1413 return;
1414 }
1415 self.selections_by_transaction
1416 .insert(transaction_id, (selections, None));
1417 }
1418
1419 #[allow(clippy::type_complexity)]
1420 fn transaction(
1421 &self,
1422 transaction_id: TransactionId,
1423 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1424 self.selections_by_transaction.get(&transaction_id)
1425 }
1426
1427 #[allow(clippy::type_complexity)]
1428 fn transaction_mut(
1429 &mut self,
1430 transaction_id: TransactionId,
1431 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1432 self.selections_by_transaction.get_mut(&transaction_id)
1433 }
1434
1435 fn push(&mut self, entry: SelectionHistoryEntry) {
1436 if !entry.selections.is_empty() {
1437 match self.mode {
1438 SelectionHistoryMode::Normal => {
1439 self.push_undo(entry);
1440 self.redo_stack.clear();
1441 }
1442 SelectionHistoryMode::Undoing => self.push_redo(entry),
1443 SelectionHistoryMode::Redoing => self.push_undo(entry),
1444 SelectionHistoryMode::Skipping => {}
1445 }
1446 }
1447 }
1448
1449 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1450 if self
1451 .undo_stack
1452 .back()
1453 .is_none_or(|e| e.selections != entry.selections)
1454 {
1455 self.undo_stack.push_back(entry);
1456 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1457 self.undo_stack.pop_front();
1458 }
1459 }
1460 }
1461
1462 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1463 if self
1464 .redo_stack
1465 .back()
1466 .is_none_or(|e| e.selections != entry.selections)
1467 {
1468 self.redo_stack.push_back(entry);
1469 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1470 self.redo_stack.pop_front();
1471 }
1472 }
1473 }
1474}
1475
1476#[derive(Clone, Copy)]
1477pub struct RowHighlightOptions {
1478 pub autoscroll: bool,
1479 pub include_gutter: bool,
1480}
1481
1482impl Default for RowHighlightOptions {
1483 fn default() -> Self {
1484 Self {
1485 autoscroll: Default::default(),
1486 include_gutter: true,
1487 }
1488 }
1489}
1490
1491struct RowHighlight {
1492 index: usize,
1493 range: Range<Anchor>,
1494 color: Hsla,
1495 options: RowHighlightOptions,
1496 type_id: TypeId,
1497}
1498
1499#[derive(Clone, Debug)]
1500struct AddSelectionsState {
1501 groups: Vec<AddSelectionsGroup>,
1502}
1503
1504#[derive(Clone, Debug)]
1505struct AddSelectionsGroup {
1506 above: bool,
1507 stack: Vec<usize>,
1508}
1509
1510#[derive(Clone)]
1511struct SelectNextState {
1512 query: AhoCorasick,
1513 wordwise: bool,
1514 done: bool,
1515}
1516
1517impl std::fmt::Debug for SelectNextState {
1518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1519 f.debug_struct(std::any::type_name::<Self>())
1520 .field("wordwise", &self.wordwise)
1521 .field("done", &self.done)
1522 .finish()
1523 }
1524}
1525
1526#[derive(Debug)]
1527struct AutocloseRegion {
1528 selection_id: usize,
1529 range: Range<Anchor>,
1530 pair: BracketPair,
1531}
1532
1533#[derive(Debug)]
1534struct SnippetState {
1535 ranges: Vec<Vec<Range<Anchor>>>,
1536 active_index: usize,
1537 choices: Vec<Option<Vec<String>>>,
1538}
1539
1540#[doc(hidden)]
1541pub struct RenameState {
1542 pub range: Range<Anchor>,
1543 pub old_name: Arc<str>,
1544 pub editor: Entity<Editor>,
1545 block_id: CustomBlockId,
1546}
1547
1548struct InvalidationStack<T>(Vec<T>);
1549
1550struct RegisteredEditPredictionProvider {
1551 provider: Arc<dyn EditPredictionProviderHandle>,
1552 _subscription: Subscription,
1553}
1554
1555#[derive(Debug, PartialEq, Eq)]
1556pub struct ActiveDiagnosticGroup {
1557 pub active_range: Range<Anchor>,
1558 pub active_message: String,
1559 pub group_id: usize,
1560 pub blocks: HashSet<CustomBlockId>,
1561}
1562
1563#[derive(Debug, PartialEq, Eq)]
1564
1565pub(crate) enum ActiveDiagnostic {
1566 None,
1567 All,
1568 Group(ActiveDiagnosticGroup),
1569}
1570
1571#[derive(Serialize, Deserialize, Clone, Debug)]
1572pub struct ClipboardSelection {
1573 /// The number of bytes in this selection.
1574 pub len: usize,
1575 /// Whether this was a full-line selection.
1576 pub is_entire_line: bool,
1577 /// The indentation of the first line when this content was originally copied.
1578 pub first_line_indent: u32,
1579}
1580
1581// selections, scroll behavior, was newest selection reversed
1582type SelectSyntaxNodeHistoryState = (
1583 Box<[Selection<MultiBufferOffset>]>,
1584 SelectSyntaxNodeScrollBehavior,
1585 bool,
1586);
1587
1588#[derive(Default)]
1589struct SelectSyntaxNodeHistory {
1590 stack: Vec<SelectSyntaxNodeHistoryState>,
1591 // disable temporarily to allow changing selections without losing the stack
1592 pub disable_clearing: bool,
1593}
1594
1595impl SelectSyntaxNodeHistory {
1596 pub fn try_clear(&mut self) {
1597 if !self.disable_clearing {
1598 self.stack.clear();
1599 }
1600 }
1601
1602 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1603 self.stack.push(selection);
1604 }
1605
1606 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1607 self.stack.pop()
1608 }
1609}
1610
1611enum SelectSyntaxNodeScrollBehavior {
1612 CursorTop,
1613 FitSelection,
1614 CursorBottom,
1615}
1616
1617#[derive(Debug)]
1618pub(crate) struct NavigationData {
1619 cursor_anchor: Anchor,
1620 cursor_position: Point,
1621 scroll_anchor: ScrollAnchor,
1622 scroll_top_row: u32,
1623}
1624
1625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1626pub enum GotoDefinitionKind {
1627 Symbol,
1628 Declaration,
1629 Type,
1630 Implementation,
1631}
1632
1633pub enum FormatTarget {
1634 Buffers(HashSet<Entity<Buffer>>),
1635 Ranges(Vec<Range<MultiBufferPoint>>),
1636}
1637
1638pub(crate) struct FocusedBlock {
1639 id: BlockId,
1640 focus_handle: WeakFocusHandle,
1641}
1642
1643#[derive(Clone, Debug)]
1644enum JumpData {
1645 MultiBufferRow {
1646 row: MultiBufferRow,
1647 line_offset_from_top: u32,
1648 },
1649 MultiBufferPoint {
1650 excerpt_id: ExcerptId,
1651 position: Point,
1652 anchor: text::Anchor,
1653 line_offset_from_top: u32,
1654 },
1655}
1656
1657pub enum MultibufferSelectionMode {
1658 First,
1659 All,
1660}
1661
1662#[derive(Clone, Copy, Debug, Default)]
1663pub struct RewrapOptions {
1664 pub override_language_settings: bool,
1665 pub preserve_existing_whitespace: bool,
1666}
1667
1668impl Editor {
1669 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1673 }
1674
1675 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(EditorMode::full(), buffer, None, window, cx)
1679 }
1680
1681 pub fn auto_height(
1682 min_lines: usize,
1683 max_lines: usize,
1684 window: &mut Window,
1685 cx: &mut Context<Self>,
1686 ) -> Self {
1687 let buffer = cx.new(|cx| Buffer::local("", cx));
1688 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1689 Self::new(
1690 EditorMode::AutoHeight {
1691 min_lines,
1692 max_lines: Some(max_lines),
1693 },
1694 buffer,
1695 None,
1696 window,
1697 cx,
1698 )
1699 }
1700
1701 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1702 /// The editor grows as tall as needed to fit its content.
1703 pub fn auto_height_unbounded(
1704 min_lines: usize,
1705 window: &mut Window,
1706 cx: &mut Context<Self>,
1707 ) -> Self {
1708 let buffer = cx.new(|cx| Buffer::local("", cx));
1709 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1710 Self::new(
1711 EditorMode::AutoHeight {
1712 min_lines,
1713 max_lines: None,
1714 },
1715 buffer,
1716 None,
1717 window,
1718 cx,
1719 )
1720 }
1721
1722 pub fn for_buffer(
1723 buffer: Entity<Buffer>,
1724 project: Option<Entity<Project>>,
1725 window: &mut Window,
1726 cx: &mut Context<Self>,
1727 ) -> Self {
1728 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1729 Self::new(EditorMode::full(), buffer, project, window, cx)
1730 }
1731
1732 pub fn for_multibuffer(
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Self::new(EditorMode::full(), buffer, project, window, cx)
1739 }
1740
1741 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1742 let mut clone = Self::new(
1743 self.mode.clone(),
1744 self.buffer.clone(),
1745 self.project.clone(),
1746 window,
1747 cx,
1748 );
1749 self.display_map.update(cx, |display_map, cx| {
1750 let snapshot = display_map.snapshot(cx);
1751 clone.display_map.update(cx, |display_map, cx| {
1752 display_map.set_state(&snapshot, cx);
1753 });
1754 });
1755 clone.folds_did_change(cx);
1756 clone.selections.clone_state(&self.selections);
1757 clone.scroll_manager.clone_state(&self.scroll_manager);
1758 clone.searchable = self.searchable;
1759 clone.read_only = self.read_only;
1760 clone
1761 }
1762
1763 pub fn new(
1764 mode: EditorMode,
1765 buffer: Entity<MultiBuffer>,
1766 project: Option<Entity<Project>>,
1767 window: &mut Window,
1768 cx: &mut Context<Self>,
1769 ) -> Self {
1770 Editor::new_internal(mode, buffer, project, None, window, cx)
1771 }
1772
1773 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1774 let multi_buffer = self.buffer().read(cx);
1775 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1776 let multi_buffer_visible_start = self
1777 .scroll_manager
1778 .anchor()
1779 .anchor
1780 .to_point(&multi_buffer_snapshot);
1781 let max_row = multi_buffer_snapshot.max_point().row;
1782
1783 let start_row = (multi_buffer_visible_start.row).min(max_row);
1784 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1785
1786 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1787 let outline_items = buffer
1788 .outline_items_containing(
1789 Point::new(start_row, 0)..Point::new(end_row, 0),
1790 true,
1791 self.style().map(|style| style.syntax.as_ref()),
1792 )
1793 .into_iter()
1794 .map(|outline_item| OutlineItem {
1795 depth: outline_item.depth,
1796 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1797 source_range_for_text: Anchor::range_in_buffer(
1798 *excerpt_id,
1799 outline_item.source_range_for_text,
1800 ),
1801 text: outline_item.text,
1802 highlight_ranges: outline_item.highlight_ranges,
1803 name_ranges: outline_item.name_ranges,
1804 body_range: outline_item
1805 .body_range
1806 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1807 annotation_range: outline_item
1808 .annotation_range
1809 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1810 });
1811 return Some(outline_items.collect());
1812 }
1813
1814 None
1815 }
1816
1817 fn new_internal(
1818 mode: EditorMode,
1819 multi_buffer: Entity<MultiBuffer>,
1820 project: Option<Entity<Project>>,
1821 display_map: Option<Entity<DisplayMap>>,
1822 window: &mut Window,
1823 cx: &mut Context<Self>,
1824 ) -> Self {
1825 debug_assert!(
1826 display_map.is_none() || mode.is_minimap(),
1827 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1828 );
1829
1830 let full_mode = mode.is_full();
1831 let is_minimap = mode.is_minimap();
1832 let diagnostics_max_severity = if full_mode {
1833 EditorSettings::get_global(cx)
1834 .diagnostics_max_severity
1835 .unwrap_or(DiagnosticSeverity::Hint)
1836 } else {
1837 DiagnosticSeverity::Off
1838 };
1839 let style = window.text_style();
1840 let font_size = style.font_size.to_pixels(window.rem_size());
1841 let editor = cx.entity().downgrade();
1842 let fold_placeholder = FoldPlaceholder {
1843 constrain_width: false,
1844 render: Arc::new(move |fold_id, fold_range, cx| {
1845 let editor = editor.clone();
1846 div()
1847 .id(fold_id)
1848 .bg(cx.theme().colors().ghost_element_background)
1849 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1850 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1851 .rounded_xs()
1852 .size_full()
1853 .cursor_pointer()
1854 .child("⋯")
1855 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1856 .on_click(move |_, _window, cx| {
1857 editor
1858 .update(cx, |editor, cx| {
1859 editor.unfold_ranges(
1860 &[fold_range.start..fold_range.end],
1861 true,
1862 false,
1863 cx,
1864 );
1865 cx.stop_propagation();
1866 })
1867 .ok();
1868 })
1869 .into_any()
1870 }),
1871 merge_adjacent: true,
1872 ..FoldPlaceholder::default()
1873 };
1874 let display_map = display_map.unwrap_or_else(|| {
1875 cx.new(|cx| {
1876 DisplayMap::new(
1877 multi_buffer.clone(),
1878 style.font(),
1879 font_size,
1880 None,
1881 FILE_HEADER_HEIGHT,
1882 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1883 fold_placeholder,
1884 diagnostics_max_severity,
1885 cx,
1886 )
1887 })
1888 });
1889
1890 let selections = SelectionsCollection::new();
1891
1892 let blink_manager = cx.new(|cx| {
1893 let mut blink_manager = BlinkManager::new(
1894 CURSOR_BLINK_INTERVAL,
1895 |cx| EditorSettings::get_global(cx).cursor_blink,
1896 cx,
1897 );
1898 if is_minimap {
1899 blink_manager.disable(cx);
1900 }
1901 blink_manager
1902 });
1903
1904 let soft_wrap_mode_override =
1905 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1906
1907 let mut project_subscriptions = Vec::new();
1908 if full_mode && let Some(project) = project.as_ref() {
1909 project_subscriptions.push(cx.subscribe_in(
1910 project,
1911 window,
1912 |editor, _, event, window, cx| match event {
1913 project::Event::RefreshCodeLens => {
1914 // we always query lens with actions, without storing them, always refreshing them
1915 }
1916 project::Event::RefreshInlayHints {
1917 server_id,
1918 request_id,
1919 } => {
1920 editor.refresh_inlay_hints(
1921 InlayHintRefreshReason::RefreshRequested {
1922 server_id: *server_id,
1923 request_id: *request_id,
1924 },
1925 cx,
1926 );
1927 }
1928 project::Event::LanguageServerRemoved(..) => {
1929 if editor.tasks_update_task.is_none() {
1930 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1931 }
1932 editor.registered_buffers.clear();
1933 editor.register_visible_buffers(cx);
1934 }
1935 project::Event::LanguageServerAdded(..) => {
1936 if editor.tasks_update_task.is_none() {
1937 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1938 }
1939 }
1940 project::Event::SnippetEdit(id, snippet_edits) => {
1941 // todo(lw): Non singletons
1942 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1943 let snapshot = buffer.read(cx).snapshot();
1944 let focus_handle = editor.focus_handle(cx);
1945 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1946 for (range, snippet) in snippet_edits {
1947 let buffer_range =
1948 language::range_from_lsp(*range).to_offset(&snapshot);
1949 editor
1950 .insert_snippet(
1951 &[MultiBufferOffset(buffer_range.start)
1952 ..MultiBufferOffset(buffer_range.end)],
1953 snippet.clone(),
1954 window,
1955 cx,
1956 )
1957 .ok();
1958 }
1959 }
1960 }
1961 }
1962 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1963 let buffer_id = *buffer_id;
1964 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1965 editor.register_buffer(buffer_id, cx);
1966 editor.update_lsp_data(Some(buffer_id), window, cx);
1967 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1968 refresh_linked_ranges(editor, window, cx);
1969 editor.refresh_code_actions(window, cx);
1970 editor.refresh_document_highlights(cx);
1971 }
1972 }
1973
1974 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1975 let Some(workspace) = editor.workspace() else {
1976 return;
1977 };
1978 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1979 else {
1980 return;
1981 };
1982
1983 if active_editor.entity_id() == cx.entity_id() {
1984 let entity_id = cx.entity_id();
1985 workspace.update(cx, |this, cx| {
1986 this.panes_mut()
1987 .iter_mut()
1988 .filter(|pane| pane.entity_id() != entity_id)
1989 .for_each(|p| {
1990 p.update(cx, |pane, _| {
1991 pane.nav_history_mut().rename_item(
1992 entity_id,
1993 project_path.clone(),
1994 abs_path.clone().into(),
1995 );
1996 })
1997 });
1998 });
1999 let edited_buffers_already_open = {
2000 let other_editors: Vec<Entity<Editor>> = workspace
2001 .read(cx)
2002 .panes()
2003 .iter()
2004 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2005 .filter(|editor| editor.entity_id() != cx.entity_id())
2006 .collect();
2007
2008 transaction.0.keys().all(|buffer| {
2009 other_editors.iter().any(|editor| {
2010 let multi_buffer = editor.read(cx).buffer();
2011 multi_buffer.read(cx).is_singleton()
2012 && multi_buffer.read(cx).as_singleton().map_or(
2013 false,
2014 |singleton| {
2015 singleton.entity_id() == buffer.entity_id()
2016 },
2017 )
2018 })
2019 })
2020 };
2021 if !edited_buffers_already_open {
2022 let workspace = workspace.downgrade();
2023 let transaction = transaction.clone();
2024 cx.defer_in(window, move |_, window, cx| {
2025 cx.spawn_in(window, async move |editor, cx| {
2026 Self::open_project_transaction(
2027 &editor,
2028 workspace,
2029 transaction,
2030 "Rename".to_string(),
2031 cx,
2032 )
2033 .await
2034 .ok()
2035 })
2036 .detach();
2037 });
2038 }
2039 }
2040 }
2041
2042 _ => {}
2043 },
2044 ));
2045 if let Some(task_inventory) = project
2046 .read(cx)
2047 .task_store()
2048 .read(cx)
2049 .task_inventory()
2050 .cloned()
2051 {
2052 project_subscriptions.push(cx.observe_in(
2053 &task_inventory,
2054 window,
2055 |editor, _, window, cx| {
2056 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2057 },
2058 ));
2059 };
2060
2061 project_subscriptions.push(cx.subscribe_in(
2062 &project.read(cx).breakpoint_store(),
2063 window,
2064 |editor, _, event, window, cx| match event {
2065 BreakpointStoreEvent::ClearDebugLines => {
2066 editor.clear_row_highlights::<ActiveDebugLine>();
2067 editor.refresh_inline_values(cx);
2068 }
2069 BreakpointStoreEvent::SetDebugLine => {
2070 if editor.go_to_active_debug_line(window, cx) {
2071 cx.stop_propagation();
2072 }
2073
2074 editor.refresh_inline_values(cx);
2075 }
2076 _ => {}
2077 },
2078 ));
2079 let git_store = project.read(cx).git_store().clone();
2080 let project = project.clone();
2081 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2082 if let GitStoreEvent::RepositoryAdded = event {
2083 this.load_diff_task = Some(
2084 update_uncommitted_diff_for_buffer(
2085 cx.entity(),
2086 &project,
2087 this.buffer.read(cx).all_buffers(),
2088 this.buffer.clone(),
2089 cx,
2090 )
2091 .shared(),
2092 );
2093 }
2094 }));
2095 }
2096
2097 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2098
2099 let inlay_hint_settings =
2100 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2101 let focus_handle = cx.focus_handle();
2102 if !is_minimap {
2103 cx.on_focus(&focus_handle, window, Self::handle_focus)
2104 .detach();
2105 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2106 .detach();
2107 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2108 .detach();
2109 cx.on_blur(&focus_handle, window, Self::handle_blur)
2110 .detach();
2111 cx.observe_pending_input(window, Self::observe_pending_input)
2112 .detach();
2113 }
2114
2115 let show_indent_guides =
2116 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2117 Some(false)
2118 } else {
2119 None
2120 };
2121
2122 let breakpoint_store = match (&mode, project.as_ref()) {
2123 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2124 _ => None,
2125 };
2126
2127 let mut code_action_providers = Vec::new();
2128 let mut load_uncommitted_diff = None;
2129 if let Some(project) = project.clone() {
2130 load_uncommitted_diff = Some(
2131 update_uncommitted_diff_for_buffer(
2132 cx.entity(),
2133 &project,
2134 multi_buffer.read(cx).all_buffers(),
2135 multi_buffer.clone(),
2136 cx,
2137 )
2138 .shared(),
2139 );
2140 code_action_providers.push(Rc::new(project) as Rc<_>);
2141 }
2142
2143 let mut editor = Self {
2144 focus_handle,
2145 show_cursor_when_unfocused: false,
2146 last_focused_descendant: None,
2147 buffer: multi_buffer.clone(),
2148 display_map: display_map.clone(),
2149 placeholder_display_map: None,
2150 selections,
2151 scroll_manager: ScrollManager::new(cx),
2152 columnar_selection_state: None,
2153 add_selections_state: None,
2154 select_next_state: None,
2155 select_prev_state: None,
2156 selection_history: SelectionHistory::default(),
2157 defer_selection_effects: false,
2158 deferred_selection_effects_state: None,
2159 autoclose_regions: Vec::new(),
2160 snippet_stack: InvalidationStack::default(),
2161 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2162 ime_transaction: None,
2163 active_diagnostics: ActiveDiagnostic::None,
2164 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2165 inline_diagnostics_update: Task::ready(()),
2166 inline_diagnostics: Vec::new(),
2167 soft_wrap_mode_override,
2168 diagnostics_max_severity,
2169 hard_wrap: None,
2170 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2171 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2172 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2173 project,
2174 blink_manager: blink_manager.clone(),
2175 show_local_selections: true,
2176 show_scrollbars: ScrollbarAxes {
2177 horizontal: full_mode,
2178 vertical: full_mode,
2179 },
2180 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2181 offset_content: !matches!(mode, EditorMode::SingleLine),
2182 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2183 show_gutter: full_mode,
2184 show_line_numbers: (!full_mode).then_some(false),
2185 use_relative_line_numbers: None,
2186 disable_expand_excerpt_buttons: !full_mode,
2187 show_git_diff_gutter: None,
2188 show_code_actions: None,
2189 show_runnables: None,
2190 show_breakpoints: None,
2191 show_wrap_guides: None,
2192 show_indent_guides,
2193 highlight_order: 0,
2194 highlighted_rows: HashMap::default(),
2195 background_highlights: HashMap::default(),
2196 gutter_highlights: HashMap::default(),
2197 scrollbar_marker_state: ScrollbarMarkerState::default(),
2198 active_indent_guides_state: ActiveIndentGuidesState::default(),
2199 nav_history: None,
2200 context_menu: RefCell::new(None),
2201 context_menu_options: None,
2202 mouse_context_menu: None,
2203 completion_tasks: Vec::new(),
2204 inline_blame_popover: None,
2205 inline_blame_popover_show_task: None,
2206 signature_help_state: SignatureHelpState::default(),
2207 auto_signature_help: None,
2208 find_all_references_task_sources: Vec::new(),
2209 next_completion_id: 0,
2210 next_inlay_id: 0,
2211 code_action_providers,
2212 available_code_actions: None,
2213 code_actions_task: None,
2214 quick_selection_highlight_task: None,
2215 debounced_selection_highlight_task: None,
2216 document_highlights_task: None,
2217 linked_editing_range_task: None,
2218 pending_rename: None,
2219 searchable: !is_minimap,
2220 cursor_shape: EditorSettings::get_global(cx)
2221 .cursor_shape
2222 .unwrap_or_default(),
2223 current_line_highlight: None,
2224 autoindent_mode: Some(AutoindentMode::EachLine),
2225 collapse_matches: false,
2226 workspace: None,
2227 input_enabled: !is_minimap,
2228 use_modal_editing: full_mode,
2229 read_only: is_minimap,
2230 use_autoclose: true,
2231 use_auto_surround: true,
2232 auto_replace_emoji_shortcode: false,
2233 jsx_tag_auto_close_enabled_in_any_buffer: false,
2234 leader_id: None,
2235 remote_id: None,
2236 hover_state: HoverState::default(),
2237 pending_mouse_down: None,
2238 hovered_link_state: None,
2239 edit_prediction_provider: None,
2240 active_edit_prediction: None,
2241 stale_edit_prediction_in_menu: None,
2242 edit_prediction_preview: EditPredictionPreview::Inactive {
2243 released_too_fast: false,
2244 },
2245 inline_diagnostics_enabled: full_mode,
2246 diagnostics_enabled: full_mode,
2247 word_completions_enabled: full_mode,
2248 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2249 gutter_hovered: false,
2250 pixel_position_of_newest_cursor: None,
2251 last_bounds: None,
2252 last_position_map: None,
2253 expect_bounds_change: None,
2254 gutter_dimensions: GutterDimensions::default(),
2255 style: None,
2256 show_cursor_names: false,
2257 hovered_cursors: HashMap::default(),
2258 next_editor_action_id: EditorActionId::default(),
2259 editor_actions: Rc::default(),
2260 edit_predictions_hidden_for_vim_mode: false,
2261 show_edit_predictions_override: None,
2262 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2263 edit_prediction_settings: EditPredictionSettings::Disabled,
2264 edit_prediction_indent_conflict: false,
2265 edit_prediction_requires_modifier_in_indent_conflict: true,
2266 custom_context_menu: None,
2267 show_git_blame_gutter: false,
2268 show_git_blame_inline: false,
2269 show_selection_menu: None,
2270 show_git_blame_inline_delay_task: None,
2271 git_blame_inline_enabled: full_mode
2272 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2273 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2274 buffer_serialization: is_minimap.not().then(|| {
2275 BufferSerialization::new(
2276 ProjectSettings::get_global(cx)
2277 .session
2278 .restore_unsaved_buffers,
2279 )
2280 }),
2281 blame: None,
2282 blame_subscription: None,
2283 tasks: BTreeMap::default(),
2284
2285 breakpoint_store,
2286 gutter_breakpoint_indicator: (None, None),
2287 hovered_diff_hunk_row: None,
2288 _subscriptions: (!is_minimap)
2289 .then(|| {
2290 vec![
2291 cx.observe(&multi_buffer, Self::on_buffer_changed),
2292 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2293 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2294 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2295 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2296 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2297 cx.observe_window_activation(window, |editor, window, cx| {
2298 let active = window.is_window_active();
2299 editor.blink_manager.update(cx, |blink_manager, cx| {
2300 if active {
2301 blink_manager.enable(cx);
2302 } else {
2303 blink_manager.disable(cx);
2304 }
2305 });
2306 if active {
2307 editor.show_mouse_cursor(cx);
2308 }
2309 }),
2310 ]
2311 })
2312 .unwrap_or_default(),
2313 tasks_update_task: None,
2314 pull_diagnostics_task: Task::ready(()),
2315 colors: None,
2316 refresh_colors_task: Task::ready(()),
2317 inlay_hints: None,
2318 next_color_inlay_id: 0,
2319 post_scroll_update: Task::ready(()),
2320 linked_edit_ranges: Default::default(),
2321 in_project_search: false,
2322 previous_search_ranges: None,
2323 breadcrumb_header: None,
2324 focused_block: None,
2325 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2326 addons: HashMap::default(),
2327 registered_buffers: HashMap::default(),
2328 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2329 selection_mark_mode: false,
2330 toggle_fold_multiple_buffers: Task::ready(()),
2331 serialize_selections: Task::ready(()),
2332 serialize_folds: Task::ready(()),
2333 text_style_refinement: None,
2334 load_diff_task: load_uncommitted_diff,
2335 temporary_diff_override: false,
2336 mouse_cursor_hidden: false,
2337 minimap: None,
2338 hide_mouse_mode: EditorSettings::get_global(cx)
2339 .hide_mouse
2340 .unwrap_or_default(),
2341 change_list: ChangeList::new(),
2342 mode,
2343 selection_drag_state: SelectionDragState::None,
2344 folding_newlines: Task::ready(()),
2345 lookup_key: None,
2346 select_next_is_case_sensitive: None,
2347 applicable_language_settings: HashMap::default(),
2348 accent_overrides: Vec::new(),
2349 fetched_tree_sitter_chunks: HashMap::default(),
2350 use_base_text_line_numbers: false,
2351 };
2352
2353 if is_minimap {
2354 return editor;
2355 }
2356
2357 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2358 editor.accent_overrides = editor.fetch_accent_overrides(cx);
2359
2360 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2361 editor
2362 ._subscriptions
2363 .push(cx.observe(breakpoints, |_, _, cx| {
2364 cx.notify();
2365 }));
2366 }
2367 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2368 editor._subscriptions.extend(project_subscriptions);
2369
2370 editor._subscriptions.push(cx.subscribe_in(
2371 &cx.entity(),
2372 window,
2373 |editor, _, e: &EditorEvent, window, cx| match e {
2374 EditorEvent::ScrollPositionChanged { local, .. } => {
2375 if *local {
2376 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2377 editor.inline_blame_popover.take();
2378 let new_anchor = editor.scroll_manager.anchor();
2379 let snapshot = editor.snapshot(window, cx);
2380 editor.update_restoration_data(cx, move |data| {
2381 data.scroll_position = (
2382 new_anchor.top_row(snapshot.buffer_snapshot()),
2383 new_anchor.offset,
2384 );
2385 });
2386
2387 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2388 cx.background_executor()
2389 .timer(Duration::from_millis(50))
2390 .await;
2391 editor
2392 .update_in(cx, |editor, window, cx| {
2393 editor.register_visible_buffers(cx);
2394 editor.refresh_colors_for_visible_range(None, window, cx);
2395 editor.refresh_inlay_hints(
2396 InlayHintRefreshReason::NewLinesShown,
2397 cx,
2398 );
2399 editor.colorize_brackets(false, cx);
2400 })
2401 .ok();
2402 });
2403 }
2404 }
2405 EditorEvent::Edited { .. } => {
2406 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2407 .map(|vim_mode| vim_mode.0)
2408 .unwrap_or(false);
2409 if !vim_mode {
2410 let display_map = editor.display_snapshot(cx);
2411 let selections = editor.selections.all_adjusted_display(&display_map);
2412 let pop_state = editor
2413 .change_list
2414 .last()
2415 .map(|previous| {
2416 previous.len() == selections.len()
2417 && previous.iter().enumerate().all(|(ix, p)| {
2418 p.to_display_point(&display_map).row()
2419 == selections[ix].head().row()
2420 })
2421 })
2422 .unwrap_or(false);
2423 let new_positions = selections
2424 .into_iter()
2425 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2426 .collect();
2427 editor
2428 .change_list
2429 .push_to_change_list(pop_state, new_positions);
2430 }
2431 }
2432 _ => (),
2433 },
2434 ));
2435
2436 if let Some(dap_store) = editor
2437 .project
2438 .as_ref()
2439 .map(|project| project.read(cx).dap_store())
2440 {
2441 let weak_editor = cx.weak_entity();
2442
2443 editor
2444 ._subscriptions
2445 .push(
2446 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2447 let session_entity = cx.entity();
2448 weak_editor
2449 .update(cx, |editor, cx| {
2450 editor._subscriptions.push(
2451 cx.subscribe(&session_entity, Self::on_debug_session_event),
2452 );
2453 })
2454 .ok();
2455 }),
2456 );
2457
2458 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2459 editor
2460 ._subscriptions
2461 .push(cx.subscribe(&session, Self::on_debug_session_event));
2462 }
2463 }
2464
2465 // skip adding the initial selection to selection history
2466 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2467 editor.end_selection(window, cx);
2468 editor.selection_history.mode = SelectionHistoryMode::Normal;
2469
2470 editor.scroll_manager.show_scrollbars(window, cx);
2471 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2472
2473 if full_mode {
2474 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2475 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2476
2477 if editor.git_blame_inline_enabled {
2478 editor.start_git_blame_inline(false, window, cx);
2479 }
2480
2481 editor.go_to_active_debug_line(window, cx);
2482
2483 editor.minimap =
2484 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2485 editor.colors = Some(LspColorData::new(cx));
2486 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2487
2488 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2489 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2490 }
2491 editor.update_lsp_data(None, window, cx);
2492 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2493 }
2494
2495 editor
2496 }
2497
2498 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2499 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2500 }
2501
2502 pub fn deploy_mouse_context_menu(
2503 &mut self,
2504 position: gpui::Point<Pixels>,
2505 context_menu: Entity<ContextMenu>,
2506 window: &mut Window,
2507 cx: &mut Context<Self>,
2508 ) {
2509 self.mouse_context_menu = Some(MouseContextMenu::new(
2510 self,
2511 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2512 context_menu,
2513 window,
2514 cx,
2515 ));
2516 }
2517
2518 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2519 self.mouse_context_menu
2520 .as_ref()
2521 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2522 }
2523
2524 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2525 if self
2526 .selections
2527 .pending_anchor()
2528 .is_some_and(|pending_selection| {
2529 let snapshot = self.buffer().read(cx).snapshot(cx);
2530 pending_selection.range().includes(range, &snapshot)
2531 })
2532 {
2533 return true;
2534 }
2535
2536 self.selections
2537 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2538 .into_iter()
2539 .any(|selection| {
2540 // This is needed to cover a corner case, if we just check for an existing
2541 // selection in the fold range, having a cursor at the start of the fold
2542 // marks it as selected. Non-empty selections don't cause this.
2543 let length = selection.end - selection.start;
2544 length > 0
2545 })
2546 }
2547
2548 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2549 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2550 }
2551
2552 fn key_context_internal(
2553 &self,
2554 has_active_edit_prediction: bool,
2555 window: &mut Window,
2556 cx: &mut App,
2557 ) -> KeyContext {
2558 let mut key_context = KeyContext::new_with_defaults();
2559 key_context.add("Editor");
2560 let mode = match self.mode {
2561 EditorMode::SingleLine => "single_line",
2562 EditorMode::AutoHeight { .. } => "auto_height",
2563 EditorMode::Minimap { .. } => "minimap",
2564 EditorMode::Full { .. } => "full",
2565 };
2566
2567 if EditorSettings::jupyter_enabled(cx) {
2568 key_context.add("jupyter");
2569 }
2570
2571 key_context.set("mode", mode);
2572 if self.pending_rename.is_some() {
2573 key_context.add("renaming");
2574 }
2575
2576 if let Some(snippet_stack) = self.snippet_stack.last() {
2577 key_context.add("in_snippet");
2578
2579 if snippet_stack.active_index > 0 {
2580 key_context.add("has_previous_tabstop");
2581 }
2582
2583 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2584 key_context.add("has_next_tabstop");
2585 }
2586 }
2587
2588 match self.context_menu.borrow().as_ref() {
2589 Some(CodeContextMenu::Completions(menu)) => {
2590 if menu.visible() {
2591 key_context.add("menu");
2592 key_context.add("showing_completions");
2593 }
2594 }
2595 Some(CodeContextMenu::CodeActions(menu)) => {
2596 if menu.visible() {
2597 key_context.add("menu");
2598 key_context.add("showing_code_actions")
2599 }
2600 }
2601 None => {}
2602 }
2603
2604 if self.signature_help_state.has_multiple_signatures() {
2605 key_context.add("showing_signature_help");
2606 }
2607
2608 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2609 if !self.focus_handle(cx).contains_focused(window, cx)
2610 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2611 {
2612 for addon in self.addons.values() {
2613 addon.extend_key_context(&mut key_context, cx)
2614 }
2615 }
2616
2617 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2618 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2619 Some(
2620 file.full_path(cx)
2621 .extension()?
2622 .to_string_lossy()
2623 .into_owned(),
2624 )
2625 }) {
2626 key_context.set("extension", extension);
2627 }
2628 } else {
2629 key_context.add("multibuffer");
2630 }
2631
2632 if has_active_edit_prediction {
2633 if self.edit_prediction_in_conflict() {
2634 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2635 } else {
2636 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2637 key_context.add("copilot_suggestion");
2638 }
2639 }
2640
2641 if self.selection_mark_mode {
2642 key_context.add("selection_mode");
2643 }
2644
2645 let disjoint = self.selections.disjoint_anchors();
2646 let snapshot = self.snapshot(window, cx);
2647 let snapshot = snapshot.buffer_snapshot();
2648 if self.mode == EditorMode::SingleLine
2649 && let [selection] = disjoint
2650 && selection.start == selection.end
2651 && selection.end.to_offset(snapshot) == snapshot.len()
2652 {
2653 key_context.add("end_of_input");
2654 }
2655
2656 if self.has_any_expanded_diff_hunks(cx) {
2657 key_context.add("diffs_expanded");
2658 }
2659
2660 key_context
2661 }
2662
2663 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2664 self.last_bounds.as_ref()
2665 }
2666
2667 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2668 if self.mouse_cursor_hidden {
2669 self.mouse_cursor_hidden = false;
2670 cx.notify();
2671 }
2672 }
2673
2674 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2675 let hide_mouse_cursor = match origin {
2676 HideMouseCursorOrigin::TypingAction => {
2677 matches!(
2678 self.hide_mouse_mode,
2679 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2680 )
2681 }
2682 HideMouseCursorOrigin::MovementAction => {
2683 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2684 }
2685 };
2686 if self.mouse_cursor_hidden != hide_mouse_cursor {
2687 self.mouse_cursor_hidden = hide_mouse_cursor;
2688 cx.notify();
2689 }
2690 }
2691
2692 pub fn edit_prediction_in_conflict(&self) -> bool {
2693 if !self.show_edit_predictions_in_menu() {
2694 return false;
2695 }
2696
2697 let showing_completions = self
2698 .context_menu
2699 .borrow()
2700 .as_ref()
2701 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2702
2703 showing_completions
2704 || self.edit_prediction_requires_modifier()
2705 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2706 // bindings to insert tab characters.
2707 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2708 }
2709
2710 pub fn accept_edit_prediction_keybind(
2711 &self,
2712 accept_partial: bool,
2713 window: &mut Window,
2714 cx: &mut App,
2715 ) -> AcceptEditPredictionBinding {
2716 let key_context = self.key_context_internal(true, window, cx);
2717 let in_conflict = self.edit_prediction_in_conflict();
2718
2719 let bindings = if accept_partial {
2720 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2721 } else {
2722 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2723 };
2724
2725 // TODO: if the binding contains multiple keystrokes, display all of them, not
2726 // just the first one.
2727 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2728 !in_conflict
2729 || binding
2730 .keystrokes()
2731 .first()
2732 .is_some_and(|keystroke| keystroke.modifiers().modified())
2733 }))
2734 }
2735
2736 pub fn new_file(
2737 workspace: &mut Workspace,
2738 _: &workspace::NewFile,
2739 window: &mut Window,
2740 cx: &mut Context<Workspace>,
2741 ) {
2742 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2743 "Failed to create buffer",
2744 window,
2745 cx,
2746 |e, _, _| match e.error_code() {
2747 ErrorCode::RemoteUpgradeRequired => Some(format!(
2748 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2749 e.error_tag("required").unwrap_or("the latest version")
2750 )),
2751 _ => None,
2752 },
2753 );
2754 }
2755
2756 pub fn new_in_workspace(
2757 workspace: &mut Workspace,
2758 window: &mut Window,
2759 cx: &mut Context<Workspace>,
2760 ) -> Task<Result<Entity<Editor>>> {
2761 let project = workspace.project().clone();
2762 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2763
2764 cx.spawn_in(window, async move |workspace, cx| {
2765 let buffer = create.await?;
2766 workspace.update_in(cx, |workspace, window, cx| {
2767 let editor =
2768 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2769 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2770 editor
2771 })
2772 })
2773 }
2774
2775 fn new_file_vertical(
2776 workspace: &mut Workspace,
2777 _: &workspace::NewFileSplitVertical,
2778 window: &mut Window,
2779 cx: &mut Context<Workspace>,
2780 ) {
2781 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2782 }
2783
2784 fn new_file_horizontal(
2785 workspace: &mut Workspace,
2786 _: &workspace::NewFileSplitHorizontal,
2787 window: &mut Window,
2788 cx: &mut Context<Workspace>,
2789 ) {
2790 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2791 }
2792
2793 fn new_file_split(
2794 workspace: &mut Workspace,
2795 action: &workspace::NewFileSplit,
2796 window: &mut Window,
2797 cx: &mut Context<Workspace>,
2798 ) {
2799 Self::new_file_in_direction(workspace, action.0, window, cx)
2800 }
2801
2802 fn new_file_in_direction(
2803 workspace: &mut Workspace,
2804 direction: SplitDirection,
2805 window: &mut Window,
2806 cx: &mut Context<Workspace>,
2807 ) {
2808 let project = workspace.project().clone();
2809 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2810
2811 cx.spawn_in(window, async move |workspace, cx| {
2812 let buffer = create.await?;
2813 workspace.update_in(cx, move |workspace, window, cx| {
2814 workspace.split_item(
2815 direction,
2816 Box::new(
2817 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2818 ),
2819 window,
2820 cx,
2821 )
2822 })?;
2823 anyhow::Ok(())
2824 })
2825 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2826 match e.error_code() {
2827 ErrorCode::RemoteUpgradeRequired => Some(format!(
2828 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2829 e.error_tag("required").unwrap_or("the latest version")
2830 )),
2831 _ => None,
2832 }
2833 });
2834 }
2835
2836 pub fn leader_id(&self) -> Option<CollaboratorId> {
2837 self.leader_id
2838 }
2839
2840 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2841 &self.buffer
2842 }
2843
2844 pub fn project(&self) -> Option<&Entity<Project>> {
2845 self.project.as_ref()
2846 }
2847
2848 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2849 self.workspace.as_ref()?.0.upgrade()
2850 }
2851
2852 /// Returns the workspace serialization ID if this editor should be serialized.
2853 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2854 self.workspace
2855 .as_ref()
2856 .filter(|_| self.should_serialize_buffer())
2857 .and_then(|workspace| workspace.1)
2858 }
2859
2860 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2861 self.buffer().read(cx).title(cx)
2862 }
2863
2864 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2865 let git_blame_gutter_max_author_length = self
2866 .render_git_blame_gutter(cx)
2867 .then(|| {
2868 if let Some(blame) = self.blame.as_ref() {
2869 let max_author_length =
2870 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2871 Some(max_author_length)
2872 } else {
2873 None
2874 }
2875 })
2876 .flatten();
2877
2878 EditorSnapshot {
2879 mode: self.mode.clone(),
2880 show_gutter: self.show_gutter,
2881 show_line_numbers: self.show_line_numbers,
2882 show_git_diff_gutter: self.show_git_diff_gutter,
2883 show_code_actions: self.show_code_actions,
2884 show_runnables: self.show_runnables,
2885 show_breakpoints: self.show_breakpoints,
2886 git_blame_gutter_max_author_length,
2887 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2888 placeholder_display_snapshot: self
2889 .placeholder_display_map
2890 .as_ref()
2891 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2892 scroll_anchor: self.scroll_manager.anchor(),
2893 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2894 is_focused: self.focus_handle.is_focused(window),
2895 current_line_highlight: self
2896 .current_line_highlight
2897 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2898 gutter_hovered: self.gutter_hovered,
2899 }
2900 }
2901
2902 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2903 self.buffer.read(cx).language_at(point, cx)
2904 }
2905
2906 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2907 self.buffer.read(cx).read(cx).file_at(point).cloned()
2908 }
2909
2910 pub fn active_excerpt(
2911 &self,
2912 cx: &App,
2913 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2914 self.buffer
2915 .read(cx)
2916 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2917 }
2918
2919 pub fn mode(&self) -> &EditorMode {
2920 &self.mode
2921 }
2922
2923 pub fn set_mode(&mut self, mode: EditorMode) {
2924 self.mode = mode;
2925 }
2926
2927 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2928 self.collaboration_hub.as_deref()
2929 }
2930
2931 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2932 self.collaboration_hub = Some(hub);
2933 }
2934
2935 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2936 self.in_project_search = in_project_search;
2937 }
2938
2939 pub fn set_custom_context_menu(
2940 &mut self,
2941 f: impl 'static
2942 + Fn(
2943 &mut Self,
2944 DisplayPoint,
2945 &mut Window,
2946 &mut Context<Self>,
2947 ) -> Option<Entity<ui::ContextMenu>>,
2948 ) {
2949 self.custom_context_menu = Some(Box::new(f))
2950 }
2951
2952 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2953 self.completion_provider = provider;
2954 }
2955
2956 #[cfg(any(test, feature = "test-support"))]
2957 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2958 self.completion_provider.clone()
2959 }
2960
2961 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2962 self.semantics_provider.clone()
2963 }
2964
2965 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2966 self.semantics_provider = provider;
2967 }
2968
2969 pub fn set_edit_prediction_provider<T>(
2970 &mut self,
2971 provider: Option<Entity<T>>,
2972 window: &mut Window,
2973 cx: &mut Context<Self>,
2974 ) where
2975 T: EditPredictionProvider,
2976 {
2977 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2978 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2979 if this.focus_handle.is_focused(window) {
2980 this.update_visible_edit_prediction(window, cx);
2981 }
2982 }),
2983 provider: Arc::new(provider),
2984 });
2985 self.update_edit_prediction_settings(cx);
2986 self.refresh_edit_prediction(false, false, window, cx);
2987 }
2988
2989 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2990 self.placeholder_display_map
2991 .as_ref()
2992 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2993 }
2994
2995 pub fn set_placeholder_text(
2996 &mut self,
2997 placeholder_text: &str,
2998 window: &mut Window,
2999 cx: &mut Context<Self>,
3000 ) {
3001 let multibuffer = cx
3002 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3003
3004 let style = window.text_style();
3005
3006 self.placeholder_display_map = Some(cx.new(|cx| {
3007 DisplayMap::new(
3008 multibuffer,
3009 style.font(),
3010 style.font_size.to_pixels(window.rem_size()),
3011 None,
3012 FILE_HEADER_HEIGHT,
3013 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3014 Default::default(),
3015 DiagnosticSeverity::Off,
3016 cx,
3017 )
3018 }));
3019 cx.notify();
3020 }
3021
3022 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3023 self.cursor_shape = cursor_shape;
3024
3025 // Disrupt blink for immediate user feedback that the cursor shape has changed
3026 self.blink_manager.update(cx, BlinkManager::show_cursor);
3027
3028 cx.notify();
3029 }
3030
3031 pub fn cursor_shape(&self) -> CursorShape {
3032 self.cursor_shape
3033 }
3034
3035 pub fn set_current_line_highlight(
3036 &mut self,
3037 current_line_highlight: Option<CurrentLineHighlight>,
3038 ) {
3039 self.current_line_highlight = current_line_highlight;
3040 }
3041
3042 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3043 self.collapse_matches = collapse_matches;
3044 }
3045
3046 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3047 if self.collapse_matches {
3048 return range.start..range.start;
3049 }
3050 range.clone()
3051 }
3052
3053 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3054 self.display_map.read(cx).clip_at_line_ends
3055 }
3056
3057 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3058 if self.display_map.read(cx).clip_at_line_ends != clip {
3059 self.display_map
3060 .update(cx, |map, _| map.clip_at_line_ends = clip);
3061 }
3062 }
3063
3064 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3065 self.input_enabled = input_enabled;
3066 }
3067
3068 pub fn set_edit_predictions_hidden_for_vim_mode(
3069 &mut self,
3070 hidden: bool,
3071 window: &mut Window,
3072 cx: &mut Context<Self>,
3073 ) {
3074 if hidden != self.edit_predictions_hidden_for_vim_mode {
3075 self.edit_predictions_hidden_for_vim_mode = hidden;
3076 if hidden {
3077 self.update_visible_edit_prediction(window, cx);
3078 } else {
3079 self.refresh_edit_prediction(true, false, window, cx);
3080 }
3081 }
3082 }
3083
3084 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3085 self.menu_edit_predictions_policy = value;
3086 }
3087
3088 pub fn set_autoindent(&mut self, autoindent: bool) {
3089 if autoindent {
3090 self.autoindent_mode = Some(AutoindentMode::EachLine);
3091 } else {
3092 self.autoindent_mode = None;
3093 }
3094 }
3095
3096 pub fn read_only(&self, cx: &App) -> bool {
3097 self.read_only || self.buffer.read(cx).read_only()
3098 }
3099
3100 pub fn set_read_only(&mut self, read_only: bool) {
3101 self.read_only = read_only;
3102 }
3103
3104 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3105 self.use_autoclose = autoclose;
3106 }
3107
3108 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3109 self.use_auto_surround = auto_surround;
3110 }
3111
3112 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3113 self.auto_replace_emoji_shortcode = auto_replace;
3114 }
3115
3116 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3117 self.buffer_serialization = should_serialize.then(|| {
3118 BufferSerialization::new(
3119 ProjectSettings::get_global(cx)
3120 .session
3121 .restore_unsaved_buffers,
3122 )
3123 })
3124 }
3125
3126 fn should_serialize_buffer(&self) -> bool {
3127 self.buffer_serialization.is_some()
3128 }
3129
3130 pub fn toggle_edit_predictions(
3131 &mut self,
3132 _: &ToggleEditPrediction,
3133 window: &mut Window,
3134 cx: &mut Context<Self>,
3135 ) {
3136 if self.show_edit_predictions_override.is_some() {
3137 self.set_show_edit_predictions(None, window, cx);
3138 } else {
3139 let show_edit_predictions = !self.edit_predictions_enabled();
3140 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3141 }
3142 }
3143
3144 pub fn set_show_edit_predictions(
3145 &mut self,
3146 show_edit_predictions: Option<bool>,
3147 window: &mut Window,
3148 cx: &mut Context<Self>,
3149 ) {
3150 self.show_edit_predictions_override = show_edit_predictions;
3151 self.update_edit_prediction_settings(cx);
3152
3153 if let Some(false) = show_edit_predictions {
3154 self.discard_edit_prediction(false, cx);
3155 } else {
3156 self.refresh_edit_prediction(false, true, window, cx);
3157 }
3158 }
3159
3160 fn edit_predictions_disabled_in_scope(
3161 &self,
3162 buffer: &Entity<Buffer>,
3163 buffer_position: language::Anchor,
3164 cx: &App,
3165 ) -> bool {
3166 let snapshot = buffer.read(cx).snapshot();
3167 let settings = snapshot.settings_at(buffer_position, cx);
3168
3169 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3170 return false;
3171 };
3172
3173 scope.override_name().is_some_and(|scope_name| {
3174 settings
3175 .edit_predictions_disabled_in
3176 .iter()
3177 .any(|s| s == scope_name)
3178 })
3179 }
3180
3181 pub fn set_use_modal_editing(&mut self, to: bool) {
3182 self.use_modal_editing = to;
3183 }
3184
3185 pub fn use_modal_editing(&self) -> bool {
3186 self.use_modal_editing
3187 }
3188
3189 fn selections_did_change(
3190 &mut self,
3191 local: bool,
3192 old_cursor_position: &Anchor,
3193 effects: SelectionEffects,
3194 window: &mut Window,
3195 cx: &mut Context<Self>,
3196 ) {
3197 window.invalidate_character_coordinates();
3198
3199 // Copy selections to primary selection buffer
3200 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3201 if local {
3202 let selections = self
3203 .selections
3204 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3205 let buffer_handle = self.buffer.read(cx).read(cx);
3206
3207 let mut text = String::new();
3208 for (index, selection) in selections.iter().enumerate() {
3209 let text_for_selection = buffer_handle
3210 .text_for_range(selection.start..selection.end)
3211 .collect::<String>();
3212
3213 text.push_str(&text_for_selection);
3214 if index != selections.len() - 1 {
3215 text.push('\n');
3216 }
3217 }
3218
3219 if !text.is_empty() {
3220 cx.write_to_primary(ClipboardItem::new_string(text));
3221 }
3222 }
3223
3224 let selection_anchors = self.selections.disjoint_anchors_arc();
3225
3226 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3227 self.buffer.update(cx, |buffer, cx| {
3228 buffer.set_active_selections(
3229 &selection_anchors,
3230 self.selections.line_mode(),
3231 self.cursor_shape,
3232 cx,
3233 )
3234 });
3235 }
3236 let display_map = self
3237 .display_map
3238 .update(cx, |display_map, cx| display_map.snapshot(cx));
3239 let buffer = display_map.buffer_snapshot();
3240 if self.selections.count() == 1 {
3241 self.add_selections_state = None;
3242 }
3243 self.select_next_state = None;
3244 self.select_prev_state = None;
3245 self.select_syntax_node_history.try_clear();
3246 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3247 self.snippet_stack.invalidate(&selection_anchors, buffer);
3248 self.take_rename(false, window, cx);
3249
3250 let newest_selection = self.selections.newest_anchor();
3251 let new_cursor_position = newest_selection.head();
3252 let selection_start = newest_selection.start;
3253
3254 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3255 self.push_to_nav_history(
3256 *old_cursor_position,
3257 Some(new_cursor_position.to_point(buffer)),
3258 false,
3259 effects.nav_history == Some(true),
3260 cx,
3261 );
3262 }
3263
3264 if local {
3265 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3266 self.register_buffer(buffer_id, cx);
3267 }
3268
3269 let mut context_menu = self.context_menu.borrow_mut();
3270 let completion_menu = match context_menu.as_ref() {
3271 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3272 Some(CodeContextMenu::CodeActions(_)) => {
3273 *context_menu = None;
3274 None
3275 }
3276 None => None,
3277 };
3278 let completion_position = completion_menu.map(|menu| menu.initial_position);
3279 drop(context_menu);
3280
3281 if effects.completions
3282 && let Some(completion_position) = completion_position
3283 {
3284 let start_offset = selection_start.to_offset(buffer);
3285 let position_matches = start_offset == completion_position.to_offset(buffer);
3286 let continue_showing = if position_matches {
3287 if self.snippet_stack.is_empty() {
3288 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3289 == Some(CharKind::Word)
3290 } else {
3291 // Snippet choices can be shown even when the cursor is in whitespace.
3292 // Dismissing the menu with actions like backspace is handled by
3293 // invalidation regions.
3294 true
3295 }
3296 } else {
3297 false
3298 };
3299
3300 if continue_showing {
3301 self.open_or_update_completions_menu(None, None, false, window, cx);
3302 } else {
3303 self.hide_context_menu(window, cx);
3304 }
3305 }
3306
3307 hide_hover(self, cx);
3308
3309 if old_cursor_position.to_display_point(&display_map).row()
3310 != new_cursor_position.to_display_point(&display_map).row()
3311 {
3312 self.available_code_actions.take();
3313 }
3314 self.refresh_code_actions(window, cx);
3315 self.refresh_document_highlights(cx);
3316 refresh_linked_ranges(self, window, cx);
3317
3318 self.refresh_selected_text_highlights(false, window, cx);
3319 self.refresh_matching_bracket_highlights(window, cx);
3320 self.update_visible_edit_prediction(window, cx);
3321 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3322 self.inline_blame_popover.take();
3323 if self.git_blame_inline_enabled {
3324 self.start_inline_blame_timer(window, cx);
3325 }
3326 }
3327
3328 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3329 cx.emit(EditorEvent::SelectionsChanged { local });
3330
3331 let selections = &self.selections.disjoint_anchors_arc();
3332 if selections.len() == 1 {
3333 cx.emit(SearchEvent::ActiveMatchChanged)
3334 }
3335 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3336 let inmemory_selections = selections
3337 .iter()
3338 .map(|s| {
3339 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3340 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3341 })
3342 .collect();
3343 self.update_restoration_data(cx, |data| {
3344 data.selections = inmemory_selections;
3345 });
3346
3347 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3348 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3349 {
3350 let snapshot = self.buffer().read(cx).snapshot(cx);
3351 let selections = selections.clone();
3352 let background_executor = cx.background_executor().clone();
3353 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3354 self.serialize_selections = cx.background_spawn(async move {
3355 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3356 let db_selections = selections
3357 .iter()
3358 .map(|selection| {
3359 (
3360 selection.start.to_offset(&snapshot).0,
3361 selection.end.to_offset(&snapshot).0,
3362 )
3363 })
3364 .collect();
3365
3366 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3367 .await
3368 .with_context(|| {
3369 format!(
3370 "persisting editor selections for editor {editor_id}, \
3371 workspace {workspace_id:?}"
3372 )
3373 })
3374 .log_err();
3375 });
3376 }
3377 }
3378
3379 cx.notify();
3380 }
3381
3382 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3383 use text::ToOffset as _;
3384 use text::ToPoint as _;
3385
3386 if self.mode.is_minimap()
3387 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3388 {
3389 return;
3390 }
3391
3392 if !self.buffer().read(cx).is_singleton() {
3393 return;
3394 }
3395
3396 let display_snapshot = self
3397 .display_map
3398 .update(cx, |display_map, cx| display_map.snapshot(cx));
3399 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3400 return;
3401 };
3402 let inmemory_folds = display_snapshot
3403 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3404 .map(|fold| {
3405 fold.range.start.text_anchor.to_point(&snapshot)
3406 ..fold.range.end.text_anchor.to_point(&snapshot)
3407 })
3408 .collect();
3409 self.update_restoration_data(cx, |data| {
3410 data.folds = inmemory_folds;
3411 });
3412
3413 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3414 return;
3415 };
3416 let background_executor = cx.background_executor().clone();
3417 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3418 let db_folds = display_snapshot
3419 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3420 .map(|fold| {
3421 (
3422 fold.range.start.text_anchor.to_offset(&snapshot),
3423 fold.range.end.text_anchor.to_offset(&snapshot),
3424 )
3425 })
3426 .collect();
3427 self.serialize_folds = cx.background_spawn(async move {
3428 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3429 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3430 .await
3431 .with_context(|| {
3432 format!(
3433 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3434 )
3435 })
3436 .log_err();
3437 });
3438 }
3439
3440 pub fn sync_selections(
3441 &mut self,
3442 other: Entity<Editor>,
3443 cx: &mut Context<Self>,
3444 ) -> gpui::Subscription {
3445 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3446 if !other_selections.is_empty() {
3447 self.selections
3448 .change_with(&self.display_snapshot(cx), |selections| {
3449 selections.select_anchors(other_selections);
3450 });
3451 }
3452
3453 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3454 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3455 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3456 if other_selections.is_empty() {
3457 return;
3458 }
3459 let snapshot = this.display_snapshot(cx);
3460 this.selections.change_with(&snapshot, |selections| {
3461 selections.select_anchors(other_selections);
3462 });
3463 }
3464 });
3465
3466 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3467 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3468 let these_selections = this.selections.disjoint_anchors().to_vec();
3469 if these_selections.is_empty() {
3470 return;
3471 }
3472 other.update(cx, |other_editor, cx| {
3473 let snapshot = other_editor.display_snapshot(cx);
3474 other_editor
3475 .selections
3476 .change_with(&snapshot, |selections| {
3477 selections.select_anchors(these_selections);
3478 })
3479 });
3480 }
3481 });
3482
3483 Subscription::join(other_subscription, this_subscription)
3484 }
3485
3486 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3487 if self.buffer().read(cx).is_singleton() {
3488 return;
3489 }
3490 let snapshot = self.buffer.read(cx).snapshot(cx);
3491 let buffer_ids: HashSet<BufferId> = self
3492 .selections
3493 .disjoint_anchor_ranges()
3494 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3495 .collect();
3496 for buffer_id in buffer_ids {
3497 self.unfold_buffer(buffer_id, cx);
3498 }
3499 }
3500
3501 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3502 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3503 /// effects of selection change occur at the end of the transaction.
3504 pub fn change_selections<R>(
3505 &mut self,
3506 effects: SelectionEffects,
3507 window: &mut Window,
3508 cx: &mut Context<Self>,
3509 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3510 ) -> R {
3511 let snapshot = self.display_snapshot(cx);
3512 if let Some(state) = &mut self.deferred_selection_effects_state {
3513 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3514 state.effects.completions = effects.completions;
3515 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3516 let (changed, result) = self.selections.change_with(&snapshot, change);
3517 state.changed |= changed;
3518 return result;
3519 }
3520 let mut state = DeferredSelectionEffectsState {
3521 changed: false,
3522 effects,
3523 old_cursor_position: self.selections.newest_anchor().head(),
3524 history_entry: SelectionHistoryEntry {
3525 selections: self.selections.disjoint_anchors_arc(),
3526 select_next_state: self.select_next_state.clone(),
3527 select_prev_state: self.select_prev_state.clone(),
3528 add_selections_state: self.add_selections_state.clone(),
3529 },
3530 };
3531 let (changed, result) = self.selections.change_with(&snapshot, change);
3532 state.changed = state.changed || changed;
3533 if self.defer_selection_effects {
3534 self.deferred_selection_effects_state = Some(state);
3535 } else {
3536 self.apply_selection_effects(state, window, cx);
3537 }
3538 result
3539 }
3540
3541 /// Defers the effects of selection change, so that the effects of multiple calls to
3542 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3543 /// to selection history and the state of popovers based on selection position aren't
3544 /// erroneously updated.
3545 pub fn with_selection_effects_deferred<R>(
3546 &mut self,
3547 window: &mut Window,
3548 cx: &mut Context<Self>,
3549 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3550 ) -> R {
3551 let already_deferred = self.defer_selection_effects;
3552 self.defer_selection_effects = true;
3553 let result = update(self, window, cx);
3554 if !already_deferred {
3555 self.defer_selection_effects = false;
3556 if let Some(state) = self.deferred_selection_effects_state.take() {
3557 self.apply_selection_effects(state, window, cx);
3558 }
3559 }
3560 result
3561 }
3562
3563 fn apply_selection_effects(
3564 &mut self,
3565 state: DeferredSelectionEffectsState,
3566 window: &mut Window,
3567 cx: &mut Context<Self>,
3568 ) {
3569 if state.changed {
3570 self.selection_history.push(state.history_entry);
3571
3572 if let Some(autoscroll) = state.effects.scroll {
3573 self.request_autoscroll(autoscroll, cx);
3574 }
3575
3576 let old_cursor_position = &state.old_cursor_position;
3577
3578 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3579
3580 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3581 self.show_signature_help(&ShowSignatureHelp, window, cx);
3582 }
3583 }
3584 }
3585
3586 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3587 where
3588 I: IntoIterator<Item = (Range<S>, T)>,
3589 S: ToOffset,
3590 T: Into<Arc<str>>,
3591 {
3592 if self.read_only(cx) {
3593 return;
3594 }
3595
3596 self.buffer
3597 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3598 }
3599
3600 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3601 where
3602 I: IntoIterator<Item = (Range<S>, T)>,
3603 S: ToOffset,
3604 T: Into<Arc<str>>,
3605 {
3606 if self.read_only(cx) {
3607 return;
3608 }
3609
3610 self.buffer.update(cx, |buffer, cx| {
3611 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3612 });
3613 }
3614
3615 pub fn edit_with_block_indent<I, S, T>(
3616 &mut self,
3617 edits: I,
3618 original_indent_columns: Vec<Option<u32>>,
3619 cx: &mut Context<Self>,
3620 ) where
3621 I: IntoIterator<Item = (Range<S>, T)>,
3622 S: ToOffset,
3623 T: Into<Arc<str>>,
3624 {
3625 if self.read_only(cx) {
3626 return;
3627 }
3628
3629 self.buffer.update(cx, |buffer, cx| {
3630 buffer.edit(
3631 edits,
3632 Some(AutoindentMode::Block {
3633 original_indent_columns,
3634 }),
3635 cx,
3636 )
3637 });
3638 }
3639
3640 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3641 self.hide_context_menu(window, cx);
3642
3643 match phase {
3644 SelectPhase::Begin {
3645 position,
3646 add,
3647 click_count,
3648 } => self.begin_selection(position, add, click_count, window, cx),
3649 SelectPhase::BeginColumnar {
3650 position,
3651 goal_column,
3652 reset,
3653 mode,
3654 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3655 SelectPhase::Extend {
3656 position,
3657 click_count,
3658 } => self.extend_selection(position, click_count, window, cx),
3659 SelectPhase::Update {
3660 position,
3661 goal_column,
3662 scroll_delta,
3663 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3664 SelectPhase::End => self.end_selection(window, cx),
3665 }
3666 }
3667
3668 fn extend_selection(
3669 &mut self,
3670 position: DisplayPoint,
3671 click_count: usize,
3672 window: &mut Window,
3673 cx: &mut Context<Self>,
3674 ) {
3675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3676 let tail = self
3677 .selections
3678 .newest::<MultiBufferOffset>(&display_map)
3679 .tail();
3680 let click_count = click_count.max(match self.selections.select_mode() {
3681 SelectMode::Character => 1,
3682 SelectMode::Word(_) => 2,
3683 SelectMode::Line(_) => 3,
3684 SelectMode::All => 4,
3685 });
3686 self.begin_selection(position, false, click_count, window, cx);
3687
3688 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3689
3690 let current_selection = match self.selections.select_mode() {
3691 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3692 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3693 };
3694
3695 let mut pending_selection = self
3696 .selections
3697 .pending_anchor()
3698 .cloned()
3699 .expect("extend_selection not called with pending selection");
3700
3701 if pending_selection
3702 .start
3703 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3704 == Ordering::Greater
3705 {
3706 pending_selection.start = current_selection.start;
3707 }
3708 if pending_selection
3709 .end
3710 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3711 == Ordering::Less
3712 {
3713 pending_selection.end = current_selection.end;
3714 pending_selection.reversed = true;
3715 }
3716
3717 let mut pending_mode = self.selections.pending_mode().unwrap();
3718 match &mut pending_mode {
3719 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3720 _ => {}
3721 }
3722
3723 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3724 SelectionEffects::scroll(Autoscroll::fit())
3725 } else {
3726 SelectionEffects::no_scroll()
3727 };
3728
3729 self.change_selections(effects, window, cx, |s| {
3730 s.set_pending(pending_selection.clone(), pending_mode);
3731 s.set_is_extending(true);
3732 });
3733 }
3734
3735 fn begin_selection(
3736 &mut self,
3737 position: DisplayPoint,
3738 add: bool,
3739 click_count: usize,
3740 window: &mut Window,
3741 cx: &mut Context<Self>,
3742 ) {
3743 if !self.focus_handle.is_focused(window) {
3744 self.last_focused_descendant = None;
3745 window.focus(&self.focus_handle);
3746 }
3747
3748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3749 let buffer = display_map.buffer_snapshot();
3750 let position = display_map.clip_point(position, Bias::Left);
3751
3752 let start;
3753 let end;
3754 let mode;
3755 let mut auto_scroll;
3756 match click_count {
3757 1 => {
3758 start = buffer.anchor_before(position.to_point(&display_map));
3759 end = start;
3760 mode = SelectMode::Character;
3761 auto_scroll = true;
3762 }
3763 2 => {
3764 let position = display_map
3765 .clip_point(position, Bias::Left)
3766 .to_offset(&display_map, Bias::Left);
3767 let (range, _) = buffer.surrounding_word(position, None);
3768 start = buffer.anchor_before(range.start);
3769 end = buffer.anchor_before(range.end);
3770 mode = SelectMode::Word(start..end);
3771 auto_scroll = true;
3772 }
3773 3 => {
3774 let position = display_map
3775 .clip_point(position, Bias::Left)
3776 .to_point(&display_map);
3777 let line_start = display_map.prev_line_boundary(position).0;
3778 let next_line_start = buffer.clip_point(
3779 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3780 Bias::Left,
3781 );
3782 start = buffer.anchor_before(line_start);
3783 end = buffer.anchor_before(next_line_start);
3784 mode = SelectMode::Line(start..end);
3785 auto_scroll = true;
3786 }
3787 _ => {
3788 start = buffer.anchor_before(MultiBufferOffset(0));
3789 end = buffer.anchor_before(buffer.len());
3790 mode = SelectMode::All;
3791 auto_scroll = false;
3792 }
3793 }
3794 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3795
3796 let point_to_delete: Option<usize> = {
3797 let selected_points: Vec<Selection<Point>> =
3798 self.selections.disjoint_in_range(start..end, &display_map);
3799
3800 if !add || click_count > 1 {
3801 None
3802 } else if !selected_points.is_empty() {
3803 Some(selected_points[0].id)
3804 } else {
3805 let clicked_point_already_selected =
3806 self.selections.disjoint_anchors().iter().find(|selection| {
3807 selection.start.to_point(buffer) == start.to_point(buffer)
3808 || selection.end.to_point(buffer) == end.to_point(buffer)
3809 });
3810
3811 clicked_point_already_selected.map(|selection| selection.id)
3812 }
3813 };
3814
3815 let selections_count = self.selections.count();
3816 let effects = if auto_scroll {
3817 SelectionEffects::default()
3818 } else {
3819 SelectionEffects::no_scroll()
3820 };
3821
3822 self.change_selections(effects, window, cx, |s| {
3823 if let Some(point_to_delete) = point_to_delete {
3824 s.delete(point_to_delete);
3825
3826 if selections_count == 1 {
3827 s.set_pending_anchor_range(start..end, mode);
3828 }
3829 } else {
3830 if !add {
3831 s.clear_disjoint();
3832 }
3833
3834 s.set_pending_anchor_range(start..end, mode);
3835 }
3836 });
3837 }
3838
3839 fn begin_columnar_selection(
3840 &mut self,
3841 position: DisplayPoint,
3842 goal_column: u32,
3843 reset: bool,
3844 mode: ColumnarMode,
3845 window: &mut Window,
3846 cx: &mut Context<Self>,
3847 ) {
3848 if !self.focus_handle.is_focused(window) {
3849 self.last_focused_descendant = None;
3850 window.focus(&self.focus_handle);
3851 }
3852
3853 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3854
3855 if reset {
3856 let pointer_position = display_map
3857 .buffer_snapshot()
3858 .anchor_before(position.to_point(&display_map));
3859
3860 self.change_selections(
3861 SelectionEffects::scroll(Autoscroll::newest()),
3862 window,
3863 cx,
3864 |s| {
3865 s.clear_disjoint();
3866 s.set_pending_anchor_range(
3867 pointer_position..pointer_position,
3868 SelectMode::Character,
3869 );
3870 },
3871 );
3872 };
3873
3874 let tail = self.selections.newest::<Point>(&display_map).tail();
3875 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3876 self.columnar_selection_state = match mode {
3877 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3878 selection_tail: selection_anchor,
3879 display_point: if reset {
3880 if position.column() != goal_column {
3881 Some(DisplayPoint::new(position.row(), goal_column))
3882 } else {
3883 None
3884 }
3885 } else {
3886 None
3887 },
3888 }),
3889 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3890 selection_tail: selection_anchor,
3891 }),
3892 };
3893
3894 if !reset {
3895 self.select_columns(position, goal_column, &display_map, window, cx);
3896 }
3897 }
3898
3899 fn update_selection(
3900 &mut self,
3901 position: DisplayPoint,
3902 goal_column: u32,
3903 scroll_delta: gpui::Point<f32>,
3904 window: &mut Window,
3905 cx: &mut Context<Self>,
3906 ) {
3907 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3908
3909 if self.columnar_selection_state.is_some() {
3910 self.select_columns(position, goal_column, &display_map, window, cx);
3911 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3912 let buffer = display_map.buffer_snapshot();
3913 let head;
3914 let tail;
3915 let mode = self.selections.pending_mode().unwrap();
3916 match &mode {
3917 SelectMode::Character => {
3918 head = position.to_point(&display_map);
3919 tail = pending.tail().to_point(buffer);
3920 }
3921 SelectMode::Word(original_range) => {
3922 let offset = display_map
3923 .clip_point(position, Bias::Left)
3924 .to_offset(&display_map, Bias::Left);
3925 let original_range = original_range.to_offset(buffer);
3926
3927 let head_offset = if buffer.is_inside_word(offset, None)
3928 || original_range.contains(&offset)
3929 {
3930 let (word_range, _) = buffer.surrounding_word(offset, None);
3931 if word_range.start < original_range.start {
3932 word_range.start
3933 } else {
3934 word_range.end
3935 }
3936 } else {
3937 offset
3938 };
3939
3940 head = head_offset.to_point(buffer);
3941 if head_offset <= original_range.start {
3942 tail = original_range.end.to_point(buffer);
3943 } else {
3944 tail = original_range.start.to_point(buffer);
3945 }
3946 }
3947 SelectMode::Line(original_range) => {
3948 let original_range = original_range.to_point(display_map.buffer_snapshot());
3949
3950 let position = display_map
3951 .clip_point(position, Bias::Left)
3952 .to_point(&display_map);
3953 let line_start = display_map.prev_line_boundary(position).0;
3954 let next_line_start = buffer.clip_point(
3955 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3956 Bias::Left,
3957 );
3958
3959 if line_start < original_range.start {
3960 head = line_start
3961 } else {
3962 head = next_line_start
3963 }
3964
3965 if head <= original_range.start {
3966 tail = original_range.end;
3967 } else {
3968 tail = original_range.start;
3969 }
3970 }
3971 SelectMode::All => {
3972 return;
3973 }
3974 };
3975
3976 if head < tail {
3977 pending.start = buffer.anchor_before(head);
3978 pending.end = buffer.anchor_before(tail);
3979 pending.reversed = true;
3980 } else {
3981 pending.start = buffer.anchor_before(tail);
3982 pending.end = buffer.anchor_before(head);
3983 pending.reversed = false;
3984 }
3985
3986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3987 s.set_pending(pending.clone(), mode);
3988 });
3989 } else {
3990 log::error!("update_selection dispatched with no pending selection");
3991 return;
3992 }
3993
3994 self.apply_scroll_delta(scroll_delta, window, cx);
3995 cx.notify();
3996 }
3997
3998 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3999 self.columnar_selection_state.take();
4000 if let Some(pending_mode) = self.selections.pending_mode() {
4001 let selections = self
4002 .selections
4003 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4004 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4005 s.select(selections);
4006 s.clear_pending();
4007 if s.is_extending() {
4008 s.set_is_extending(false);
4009 } else {
4010 s.set_select_mode(pending_mode);
4011 }
4012 });
4013 }
4014 }
4015
4016 fn select_columns(
4017 &mut self,
4018 head: DisplayPoint,
4019 goal_column: u32,
4020 display_map: &DisplaySnapshot,
4021 window: &mut Window,
4022 cx: &mut Context<Self>,
4023 ) {
4024 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4025 return;
4026 };
4027
4028 let tail = match columnar_state {
4029 ColumnarSelectionState::FromMouse {
4030 selection_tail,
4031 display_point,
4032 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4033 ColumnarSelectionState::FromSelection { selection_tail } => {
4034 selection_tail.to_display_point(display_map)
4035 }
4036 };
4037
4038 let start_row = cmp::min(tail.row(), head.row());
4039 let end_row = cmp::max(tail.row(), head.row());
4040 let start_column = cmp::min(tail.column(), goal_column);
4041 let end_column = cmp::max(tail.column(), goal_column);
4042 let reversed = start_column < tail.column();
4043
4044 let selection_ranges = (start_row.0..=end_row.0)
4045 .map(DisplayRow)
4046 .filter_map(|row| {
4047 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4048 || start_column <= display_map.line_len(row))
4049 && !display_map.is_block_line(row)
4050 {
4051 let start = display_map
4052 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4053 .to_point(display_map);
4054 let end = display_map
4055 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4056 .to_point(display_map);
4057 if reversed {
4058 Some(end..start)
4059 } else {
4060 Some(start..end)
4061 }
4062 } else {
4063 None
4064 }
4065 })
4066 .collect::<Vec<_>>();
4067 if selection_ranges.is_empty() {
4068 return;
4069 }
4070
4071 let ranges = match columnar_state {
4072 ColumnarSelectionState::FromMouse { .. } => {
4073 let mut non_empty_ranges = selection_ranges
4074 .iter()
4075 .filter(|selection_range| selection_range.start != selection_range.end)
4076 .peekable();
4077 if non_empty_ranges.peek().is_some() {
4078 non_empty_ranges.cloned().collect()
4079 } else {
4080 selection_ranges
4081 }
4082 }
4083 _ => selection_ranges,
4084 };
4085
4086 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4087 s.select_ranges(ranges);
4088 });
4089 cx.notify();
4090 }
4091
4092 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4093 self.selections
4094 .all_adjusted(snapshot)
4095 .iter()
4096 .any(|selection| !selection.is_empty())
4097 }
4098
4099 pub fn has_pending_nonempty_selection(&self) -> bool {
4100 let pending_nonempty_selection = match self.selections.pending_anchor() {
4101 Some(Selection { start, end, .. }) => start != end,
4102 None => false,
4103 };
4104
4105 pending_nonempty_selection
4106 || (self.columnar_selection_state.is_some()
4107 && self.selections.disjoint_anchors().len() > 1)
4108 }
4109
4110 pub fn has_pending_selection(&self) -> bool {
4111 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4112 }
4113
4114 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4115 self.selection_mark_mode = false;
4116 self.selection_drag_state = SelectionDragState::None;
4117
4118 if self.dismiss_menus_and_popups(true, window, cx) {
4119 cx.notify();
4120 return;
4121 }
4122 if self.clear_expanded_diff_hunks(cx) {
4123 cx.notify();
4124 return;
4125 }
4126 if self.show_git_blame_gutter {
4127 self.show_git_blame_gutter = false;
4128 cx.notify();
4129 return;
4130 }
4131
4132 if self.mode.is_full()
4133 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4134 {
4135 cx.notify();
4136 return;
4137 }
4138
4139 cx.propagate();
4140 }
4141
4142 pub fn dismiss_menus_and_popups(
4143 &mut self,
4144 is_user_requested: bool,
4145 window: &mut Window,
4146 cx: &mut Context<Self>,
4147 ) -> bool {
4148 let mut dismissed = false;
4149
4150 dismissed |= self.take_rename(false, window, cx).is_some();
4151 dismissed |= self.hide_blame_popover(true, cx);
4152 dismissed |= hide_hover(self, cx);
4153 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4154 dismissed |= self.hide_context_menu(window, cx).is_some();
4155 dismissed |= self.mouse_context_menu.take().is_some();
4156 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4157 dismissed |= self.snippet_stack.pop().is_some();
4158
4159 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4160 self.dismiss_diagnostics(cx);
4161 dismissed = true;
4162 }
4163
4164 dismissed
4165 }
4166
4167 fn linked_editing_ranges_for(
4168 &self,
4169 selection: Range<text::Anchor>,
4170 cx: &App,
4171 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4172 if self.linked_edit_ranges.is_empty() {
4173 return None;
4174 }
4175 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4176 selection.end.buffer_id.and_then(|end_buffer_id| {
4177 if selection.start.buffer_id != Some(end_buffer_id) {
4178 return None;
4179 }
4180 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4181 let snapshot = buffer.read(cx).snapshot();
4182 self.linked_edit_ranges
4183 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4184 .map(|ranges| (ranges, snapshot, buffer))
4185 })?;
4186 use text::ToOffset as TO;
4187 // find offset from the start of current range to current cursor position
4188 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4189
4190 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4191 let start_difference = start_offset - start_byte_offset;
4192 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4193 let end_difference = end_offset - start_byte_offset;
4194 // Current range has associated linked ranges.
4195 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4196 for range in linked_ranges.iter() {
4197 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4198 let end_offset = start_offset + end_difference;
4199 let start_offset = start_offset + start_difference;
4200 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4201 continue;
4202 }
4203 if self.selections.disjoint_anchor_ranges().any(|s| {
4204 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4205 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4206 {
4207 return false;
4208 }
4209 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4210 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4211 }) {
4212 continue;
4213 }
4214 let start = buffer_snapshot.anchor_after(start_offset);
4215 let end = buffer_snapshot.anchor_after(end_offset);
4216 linked_edits
4217 .entry(buffer.clone())
4218 .or_default()
4219 .push(start..end);
4220 }
4221 Some(linked_edits)
4222 }
4223
4224 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4225 let text: Arc<str> = text.into();
4226
4227 if self.read_only(cx) {
4228 return;
4229 }
4230
4231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4232
4233 self.unfold_buffers_with_selections(cx);
4234
4235 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4236 let mut bracket_inserted = false;
4237 let mut edits = Vec::new();
4238 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4239 let mut new_selections = Vec::with_capacity(selections.len());
4240 let mut new_autoclose_regions = Vec::new();
4241 let snapshot = self.buffer.read(cx).read(cx);
4242 let mut clear_linked_edit_ranges = false;
4243
4244 for (selection, autoclose_region) in
4245 self.selections_with_autoclose_regions(selections, &snapshot)
4246 {
4247 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4248 // Determine if the inserted text matches the opening or closing
4249 // bracket of any of this language's bracket pairs.
4250 let mut bracket_pair = None;
4251 let mut is_bracket_pair_start = false;
4252 let mut is_bracket_pair_end = false;
4253 if !text.is_empty() {
4254 let mut bracket_pair_matching_end = None;
4255 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4256 // and they are removing the character that triggered IME popup.
4257 for (pair, enabled) in scope.brackets() {
4258 if !pair.close && !pair.surround {
4259 continue;
4260 }
4261
4262 if enabled && pair.start.ends_with(text.as_ref()) {
4263 let prefix_len = pair.start.len() - text.len();
4264 let preceding_text_matches_prefix = prefix_len == 0
4265 || (selection.start.column >= (prefix_len as u32)
4266 && snapshot.contains_str_at(
4267 Point::new(
4268 selection.start.row,
4269 selection.start.column - (prefix_len as u32),
4270 ),
4271 &pair.start[..prefix_len],
4272 ));
4273 if preceding_text_matches_prefix {
4274 bracket_pair = Some(pair.clone());
4275 is_bracket_pair_start = true;
4276 break;
4277 }
4278 }
4279 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4280 {
4281 // take first bracket pair matching end, but don't break in case a later bracket
4282 // pair matches start
4283 bracket_pair_matching_end = Some(pair.clone());
4284 }
4285 }
4286 if let Some(end) = bracket_pair_matching_end
4287 && bracket_pair.is_none()
4288 {
4289 bracket_pair = Some(end);
4290 is_bracket_pair_end = true;
4291 }
4292 }
4293
4294 if let Some(bracket_pair) = bracket_pair {
4295 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4296 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4297 let auto_surround =
4298 self.use_auto_surround && snapshot_settings.use_auto_surround;
4299 if selection.is_empty() {
4300 if is_bracket_pair_start {
4301 // If the inserted text is a suffix of an opening bracket and the
4302 // selection is preceded by the rest of the opening bracket, then
4303 // insert the closing bracket.
4304 let following_text_allows_autoclose = snapshot
4305 .chars_at(selection.start)
4306 .next()
4307 .is_none_or(|c| scope.should_autoclose_before(c));
4308
4309 let preceding_text_allows_autoclose = selection.start.column == 0
4310 || snapshot
4311 .reversed_chars_at(selection.start)
4312 .next()
4313 .is_none_or(|c| {
4314 bracket_pair.start != bracket_pair.end
4315 || !snapshot
4316 .char_classifier_at(selection.start)
4317 .is_word(c)
4318 });
4319
4320 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4321 && bracket_pair.start.len() == 1
4322 {
4323 let target = bracket_pair.start.chars().next().unwrap();
4324 let current_line_count = snapshot
4325 .reversed_chars_at(selection.start)
4326 .take_while(|&c| c != '\n')
4327 .filter(|&c| c == target)
4328 .count();
4329 current_line_count % 2 == 1
4330 } else {
4331 false
4332 };
4333
4334 if autoclose
4335 && bracket_pair.close
4336 && following_text_allows_autoclose
4337 && preceding_text_allows_autoclose
4338 && !is_closing_quote
4339 {
4340 let anchor = snapshot.anchor_before(selection.end);
4341 new_selections.push((selection.map(|_| anchor), text.len()));
4342 new_autoclose_regions.push((
4343 anchor,
4344 text.len(),
4345 selection.id,
4346 bracket_pair.clone(),
4347 ));
4348 edits.push((
4349 selection.range(),
4350 format!("{}{}", text, bracket_pair.end).into(),
4351 ));
4352 bracket_inserted = true;
4353 continue;
4354 }
4355 }
4356
4357 if let Some(region) = autoclose_region {
4358 // If the selection is followed by an auto-inserted closing bracket,
4359 // then don't insert that closing bracket again; just move the selection
4360 // past the closing bracket.
4361 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4362 && text.as_ref() == region.pair.end.as_str()
4363 && snapshot.contains_str_at(region.range.end, text.as_ref());
4364 if should_skip {
4365 let anchor = snapshot.anchor_after(selection.end);
4366 new_selections
4367 .push((selection.map(|_| anchor), region.pair.end.len()));
4368 continue;
4369 }
4370 }
4371
4372 let always_treat_brackets_as_autoclosed = snapshot
4373 .language_settings_at(selection.start, cx)
4374 .always_treat_brackets_as_autoclosed;
4375 if always_treat_brackets_as_autoclosed
4376 && is_bracket_pair_end
4377 && snapshot.contains_str_at(selection.end, text.as_ref())
4378 {
4379 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4380 // and the inserted text is a closing bracket and the selection is followed
4381 // by the closing bracket then move the selection past the closing bracket.
4382 let anchor = snapshot.anchor_after(selection.end);
4383 new_selections.push((selection.map(|_| anchor), text.len()));
4384 continue;
4385 }
4386 }
4387 // If an opening bracket is 1 character long and is typed while
4388 // text is selected, then surround that text with the bracket pair.
4389 else if auto_surround
4390 && bracket_pair.surround
4391 && is_bracket_pair_start
4392 && bracket_pair.start.chars().count() == 1
4393 {
4394 edits.push((selection.start..selection.start, text.clone()));
4395 edits.push((
4396 selection.end..selection.end,
4397 bracket_pair.end.as_str().into(),
4398 ));
4399 bracket_inserted = true;
4400 new_selections.push((
4401 Selection {
4402 id: selection.id,
4403 start: snapshot.anchor_after(selection.start),
4404 end: snapshot.anchor_before(selection.end),
4405 reversed: selection.reversed,
4406 goal: selection.goal,
4407 },
4408 0,
4409 ));
4410 continue;
4411 }
4412 }
4413 }
4414
4415 if self.auto_replace_emoji_shortcode
4416 && selection.is_empty()
4417 && text.as_ref().ends_with(':')
4418 && let Some(possible_emoji_short_code) =
4419 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4420 && !possible_emoji_short_code.is_empty()
4421 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4422 {
4423 let emoji_shortcode_start = Point::new(
4424 selection.start.row,
4425 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4426 );
4427
4428 // Remove shortcode from buffer
4429 edits.push((
4430 emoji_shortcode_start..selection.start,
4431 "".to_string().into(),
4432 ));
4433 new_selections.push((
4434 Selection {
4435 id: selection.id,
4436 start: snapshot.anchor_after(emoji_shortcode_start),
4437 end: snapshot.anchor_before(selection.start),
4438 reversed: selection.reversed,
4439 goal: selection.goal,
4440 },
4441 0,
4442 ));
4443
4444 // Insert emoji
4445 let selection_start_anchor = snapshot.anchor_after(selection.start);
4446 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4447 edits.push((selection.start..selection.end, emoji.to_string().into()));
4448
4449 continue;
4450 }
4451
4452 // If not handling any auto-close operation, then just replace the selected
4453 // text with the given input and move the selection to the end of the
4454 // newly inserted text.
4455 let anchor = snapshot.anchor_after(selection.end);
4456 if !self.linked_edit_ranges.is_empty() {
4457 let start_anchor = snapshot.anchor_before(selection.start);
4458
4459 let is_word_char = text.chars().next().is_none_or(|char| {
4460 let classifier = snapshot
4461 .char_classifier_at(start_anchor.to_offset(&snapshot))
4462 .scope_context(Some(CharScopeContext::LinkedEdit));
4463 classifier.is_word(char)
4464 });
4465
4466 if is_word_char {
4467 if let Some(ranges) = self
4468 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4469 {
4470 for (buffer, edits) in ranges {
4471 linked_edits
4472 .entry(buffer.clone())
4473 .or_default()
4474 .extend(edits.into_iter().map(|range| (range, text.clone())));
4475 }
4476 }
4477 } else {
4478 clear_linked_edit_ranges = true;
4479 }
4480 }
4481
4482 new_selections.push((selection.map(|_| anchor), 0));
4483 edits.push((selection.start..selection.end, text.clone()));
4484 }
4485
4486 drop(snapshot);
4487
4488 self.transact(window, cx, |this, window, cx| {
4489 if clear_linked_edit_ranges {
4490 this.linked_edit_ranges.clear();
4491 }
4492 let initial_buffer_versions =
4493 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4494
4495 this.buffer.update(cx, |buffer, cx| {
4496 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4497 });
4498 for (buffer, edits) in linked_edits {
4499 buffer.update(cx, |buffer, cx| {
4500 let snapshot = buffer.snapshot();
4501 let edits = edits
4502 .into_iter()
4503 .map(|(range, text)| {
4504 use text::ToPoint as TP;
4505 let end_point = TP::to_point(&range.end, &snapshot);
4506 let start_point = TP::to_point(&range.start, &snapshot);
4507 (start_point..end_point, text)
4508 })
4509 .sorted_by_key(|(range, _)| range.start);
4510 buffer.edit(edits, None, cx);
4511 })
4512 }
4513 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4514 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4515 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4516 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4517 new_anchor_selections,
4518 &map,
4519 )
4520 .zip(new_selection_deltas)
4521 .map(|(selection, delta)| Selection {
4522 id: selection.id,
4523 start: selection.start + delta,
4524 end: selection.end + delta,
4525 reversed: selection.reversed,
4526 goal: SelectionGoal::None,
4527 })
4528 .collect::<Vec<_>>();
4529
4530 let mut i = 0;
4531 for (position, delta, selection_id, pair) in new_autoclose_regions {
4532 let position = position.to_offset(map.buffer_snapshot()) + delta;
4533 let start = map.buffer_snapshot().anchor_before(position);
4534 let end = map.buffer_snapshot().anchor_after(position);
4535 while let Some(existing_state) = this.autoclose_regions.get(i) {
4536 match existing_state
4537 .range
4538 .start
4539 .cmp(&start, map.buffer_snapshot())
4540 {
4541 Ordering::Less => i += 1,
4542 Ordering::Greater => break,
4543 Ordering::Equal => {
4544 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4545 Ordering::Less => i += 1,
4546 Ordering::Equal => break,
4547 Ordering::Greater => break,
4548 }
4549 }
4550 }
4551 }
4552 this.autoclose_regions.insert(
4553 i,
4554 AutocloseRegion {
4555 selection_id,
4556 range: start..end,
4557 pair,
4558 },
4559 );
4560 }
4561
4562 let had_active_edit_prediction = this.has_active_edit_prediction();
4563 this.change_selections(
4564 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4565 window,
4566 cx,
4567 |s| s.select(new_selections),
4568 );
4569
4570 if !bracket_inserted
4571 && let Some(on_type_format_task) =
4572 this.trigger_on_type_formatting(text.to_string(), window, cx)
4573 {
4574 on_type_format_task.detach_and_log_err(cx);
4575 }
4576
4577 let editor_settings = EditorSettings::get_global(cx);
4578 if bracket_inserted
4579 && (editor_settings.auto_signature_help
4580 || editor_settings.show_signature_help_after_edits)
4581 {
4582 this.show_signature_help(&ShowSignatureHelp, window, cx);
4583 }
4584
4585 let trigger_in_words =
4586 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4587 if this.hard_wrap.is_some() {
4588 let latest: Range<Point> = this.selections.newest(&map).range();
4589 if latest.is_empty()
4590 && this
4591 .buffer()
4592 .read(cx)
4593 .snapshot(cx)
4594 .line_len(MultiBufferRow(latest.start.row))
4595 == latest.start.column
4596 {
4597 this.rewrap_impl(
4598 RewrapOptions {
4599 override_language_settings: true,
4600 preserve_existing_whitespace: true,
4601 },
4602 cx,
4603 )
4604 }
4605 }
4606 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4607 refresh_linked_ranges(this, window, cx);
4608 this.refresh_edit_prediction(true, false, window, cx);
4609 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4610 });
4611 }
4612
4613 fn find_possible_emoji_shortcode_at_position(
4614 snapshot: &MultiBufferSnapshot,
4615 position: Point,
4616 ) -> Option<String> {
4617 let mut chars = Vec::new();
4618 let mut found_colon = false;
4619 for char in snapshot.reversed_chars_at(position).take(100) {
4620 // Found a possible emoji shortcode in the middle of the buffer
4621 if found_colon {
4622 if char.is_whitespace() {
4623 chars.reverse();
4624 return Some(chars.iter().collect());
4625 }
4626 // If the previous character is not a whitespace, we are in the middle of a word
4627 // and we only want to complete the shortcode if the word is made up of other emojis
4628 let mut containing_word = String::new();
4629 for ch in snapshot
4630 .reversed_chars_at(position)
4631 .skip(chars.len() + 1)
4632 .take(100)
4633 {
4634 if ch.is_whitespace() {
4635 break;
4636 }
4637 containing_word.push(ch);
4638 }
4639 let containing_word = containing_word.chars().rev().collect::<String>();
4640 if util::word_consists_of_emojis(containing_word.as_str()) {
4641 chars.reverse();
4642 return Some(chars.iter().collect());
4643 }
4644 }
4645
4646 if char.is_whitespace() || !char.is_ascii() {
4647 return None;
4648 }
4649 if char == ':' {
4650 found_colon = true;
4651 } else {
4652 chars.push(char);
4653 }
4654 }
4655 // Found a possible emoji shortcode at the beginning of the buffer
4656 chars.reverse();
4657 Some(chars.iter().collect())
4658 }
4659
4660 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4661 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4662 self.transact(window, cx, |this, window, cx| {
4663 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4664 let selections = this
4665 .selections
4666 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4667 let multi_buffer = this.buffer.read(cx);
4668 let buffer = multi_buffer.snapshot(cx);
4669 selections
4670 .iter()
4671 .map(|selection| {
4672 let start_point = selection.start.to_point(&buffer);
4673 let mut existing_indent =
4674 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4675 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4676 let start = selection.start;
4677 let end = selection.end;
4678 let selection_is_empty = start == end;
4679 let language_scope = buffer.language_scope_at(start);
4680 let (
4681 comment_delimiter,
4682 doc_delimiter,
4683 insert_extra_newline,
4684 indent_on_newline,
4685 indent_on_extra_newline,
4686 ) = if let Some(language) = &language_scope {
4687 let mut insert_extra_newline =
4688 insert_extra_newline_brackets(&buffer, start..end, language)
4689 || insert_extra_newline_tree_sitter(&buffer, start..end);
4690
4691 // Comment extension on newline is allowed only for cursor selections
4692 let comment_delimiter = maybe!({
4693 if !selection_is_empty {
4694 return None;
4695 }
4696
4697 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4698 return None;
4699 }
4700
4701 let delimiters = language.line_comment_prefixes();
4702 let max_len_of_delimiter =
4703 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4704 let (snapshot, range) =
4705 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4706
4707 let num_of_whitespaces = snapshot
4708 .chars_for_range(range.clone())
4709 .take_while(|c| c.is_whitespace())
4710 .count();
4711 let comment_candidate = snapshot
4712 .chars_for_range(range.clone())
4713 .skip(num_of_whitespaces)
4714 .take(max_len_of_delimiter)
4715 .collect::<String>();
4716 let (delimiter, trimmed_len) = delimiters
4717 .iter()
4718 .filter_map(|delimiter| {
4719 let prefix = delimiter.trim_end();
4720 if comment_candidate.starts_with(prefix) {
4721 Some((delimiter, prefix.len()))
4722 } else {
4723 None
4724 }
4725 })
4726 .max_by_key(|(_, len)| *len)?;
4727
4728 if let Some(BlockCommentConfig {
4729 start: block_start, ..
4730 }) = language.block_comment()
4731 {
4732 let block_start_trimmed = block_start.trim_end();
4733 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4734 let line_content = snapshot
4735 .chars_for_range(range)
4736 .skip(num_of_whitespaces)
4737 .take(block_start_trimmed.len())
4738 .collect::<String>();
4739
4740 if line_content.starts_with(block_start_trimmed) {
4741 return None;
4742 }
4743 }
4744 }
4745
4746 let cursor_is_placed_after_comment_marker =
4747 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4748 if cursor_is_placed_after_comment_marker {
4749 Some(delimiter.clone())
4750 } else {
4751 None
4752 }
4753 });
4754
4755 let mut indent_on_newline = IndentSize::spaces(0);
4756 let mut indent_on_extra_newline = IndentSize::spaces(0);
4757
4758 let doc_delimiter = maybe!({
4759 if !selection_is_empty {
4760 return None;
4761 }
4762
4763 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4764 return None;
4765 }
4766
4767 let BlockCommentConfig {
4768 start: start_tag,
4769 end: end_tag,
4770 prefix: delimiter,
4771 tab_size: len,
4772 } = language.documentation_comment()?;
4773 let is_within_block_comment = buffer
4774 .language_scope_at(start_point)
4775 .is_some_and(|scope| scope.override_name() == Some("comment"));
4776 if !is_within_block_comment {
4777 return None;
4778 }
4779
4780 let (snapshot, range) =
4781 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4782
4783 let num_of_whitespaces = snapshot
4784 .chars_for_range(range.clone())
4785 .take_while(|c| c.is_whitespace())
4786 .count();
4787
4788 // 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.
4789 let column = start_point.column;
4790 let cursor_is_after_start_tag = {
4791 let start_tag_len = start_tag.len();
4792 let start_tag_line = snapshot
4793 .chars_for_range(range.clone())
4794 .skip(num_of_whitespaces)
4795 .take(start_tag_len)
4796 .collect::<String>();
4797 if start_tag_line.starts_with(start_tag.as_ref()) {
4798 num_of_whitespaces + start_tag_len <= column as usize
4799 } else {
4800 false
4801 }
4802 };
4803
4804 let cursor_is_after_delimiter = {
4805 let delimiter_trim = delimiter.trim_end();
4806 let delimiter_line = snapshot
4807 .chars_for_range(range.clone())
4808 .skip(num_of_whitespaces)
4809 .take(delimiter_trim.len())
4810 .collect::<String>();
4811 if delimiter_line.starts_with(delimiter_trim) {
4812 num_of_whitespaces + delimiter_trim.len() <= column as usize
4813 } else {
4814 false
4815 }
4816 };
4817
4818 let cursor_is_before_end_tag_if_exists = {
4819 let mut char_position = 0u32;
4820 let mut end_tag_offset = None;
4821
4822 'outer: for chunk in snapshot.text_for_range(range) {
4823 if let Some(byte_pos) = chunk.find(&**end_tag) {
4824 let chars_before_match =
4825 chunk[..byte_pos].chars().count() as u32;
4826 end_tag_offset =
4827 Some(char_position + chars_before_match);
4828 break 'outer;
4829 }
4830 char_position += chunk.chars().count() as u32;
4831 }
4832
4833 if let Some(end_tag_offset) = end_tag_offset {
4834 let cursor_is_before_end_tag = column <= end_tag_offset;
4835 if cursor_is_after_start_tag {
4836 if cursor_is_before_end_tag {
4837 insert_extra_newline = true;
4838 }
4839 let cursor_is_at_start_of_end_tag =
4840 column == end_tag_offset;
4841 if cursor_is_at_start_of_end_tag {
4842 indent_on_extra_newline.len = *len;
4843 }
4844 }
4845 cursor_is_before_end_tag
4846 } else {
4847 true
4848 }
4849 };
4850
4851 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4852 && cursor_is_before_end_tag_if_exists
4853 {
4854 if cursor_is_after_start_tag {
4855 indent_on_newline.len = *len;
4856 }
4857 Some(delimiter.clone())
4858 } else {
4859 None
4860 }
4861 });
4862
4863 (
4864 comment_delimiter,
4865 doc_delimiter,
4866 insert_extra_newline,
4867 indent_on_newline,
4868 indent_on_extra_newline,
4869 )
4870 } else {
4871 (
4872 None,
4873 None,
4874 false,
4875 IndentSize::default(),
4876 IndentSize::default(),
4877 )
4878 };
4879
4880 let prevent_auto_indent = doc_delimiter.is_some();
4881 let delimiter = comment_delimiter.or(doc_delimiter);
4882
4883 let capacity_for_delimiter =
4884 delimiter.as_deref().map(str::len).unwrap_or_default();
4885 let mut new_text = String::with_capacity(
4886 1 + capacity_for_delimiter
4887 + existing_indent.len as usize
4888 + indent_on_newline.len as usize
4889 + indent_on_extra_newline.len as usize,
4890 );
4891 new_text.push('\n');
4892 new_text.extend(existing_indent.chars());
4893 new_text.extend(indent_on_newline.chars());
4894
4895 if let Some(delimiter) = &delimiter {
4896 new_text.push_str(delimiter);
4897 }
4898
4899 if insert_extra_newline {
4900 new_text.push('\n');
4901 new_text.extend(existing_indent.chars());
4902 new_text.extend(indent_on_extra_newline.chars());
4903 }
4904
4905 let anchor = buffer.anchor_after(end);
4906 let new_selection = selection.map(|_| anchor);
4907 (
4908 ((start..end, new_text), prevent_auto_indent),
4909 (insert_extra_newline, new_selection),
4910 )
4911 })
4912 .unzip()
4913 };
4914
4915 let mut auto_indent_edits = Vec::new();
4916 let mut edits = Vec::new();
4917 for (edit, prevent_auto_indent) in edits_with_flags {
4918 if prevent_auto_indent {
4919 edits.push(edit);
4920 } else {
4921 auto_indent_edits.push(edit);
4922 }
4923 }
4924 if !edits.is_empty() {
4925 this.edit(edits, cx);
4926 }
4927 if !auto_indent_edits.is_empty() {
4928 this.edit_with_autoindent(auto_indent_edits, cx);
4929 }
4930
4931 let buffer = this.buffer.read(cx).snapshot(cx);
4932 let new_selections = selection_info
4933 .into_iter()
4934 .map(|(extra_newline_inserted, new_selection)| {
4935 let mut cursor = new_selection.end.to_point(&buffer);
4936 if extra_newline_inserted {
4937 cursor.row -= 1;
4938 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4939 }
4940 new_selection.map(|_| cursor)
4941 })
4942 .collect();
4943
4944 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4945 this.refresh_edit_prediction(true, false, window, cx);
4946 });
4947 }
4948
4949 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4951
4952 let buffer = self.buffer.read(cx);
4953 let snapshot = buffer.snapshot(cx);
4954
4955 let mut edits = Vec::new();
4956 let mut rows = Vec::new();
4957
4958 for (rows_inserted, selection) in self
4959 .selections
4960 .all_adjusted(&self.display_snapshot(cx))
4961 .into_iter()
4962 .enumerate()
4963 {
4964 let cursor = selection.head();
4965 let row = cursor.row;
4966
4967 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4968
4969 let newline = "\n".to_string();
4970 edits.push((start_of_line..start_of_line, newline));
4971
4972 rows.push(row + rows_inserted as u32);
4973 }
4974
4975 self.transact(window, cx, |editor, window, cx| {
4976 editor.edit(edits, cx);
4977
4978 editor.change_selections(Default::default(), window, cx, |s| {
4979 let mut index = 0;
4980 s.move_cursors_with(|map, _, _| {
4981 let row = rows[index];
4982 index += 1;
4983
4984 let point = Point::new(row, 0);
4985 let boundary = map.next_line_boundary(point).1;
4986 let clipped = map.clip_point(boundary, Bias::Left);
4987
4988 (clipped, SelectionGoal::None)
4989 });
4990 });
4991
4992 let mut indent_edits = Vec::new();
4993 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4994 for row in rows {
4995 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4996 for (row, indent) in indents {
4997 if indent.len == 0 {
4998 continue;
4999 }
5000
5001 let text = match indent.kind {
5002 IndentKind::Space => " ".repeat(indent.len as usize),
5003 IndentKind::Tab => "\t".repeat(indent.len as usize),
5004 };
5005 let point = Point::new(row.0, 0);
5006 indent_edits.push((point..point, text));
5007 }
5008 }
5009 editor.edit(indent_edits, cx);
5010 });
5011 }
5012
5013 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5014 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5015
5016 let buffer = self.buffer.read(cx);
5017 let snapshot = buffer.snapshot(cx);
5018
5019 let mut edits = Vec::new();
5020 let mut rows = Vec::new();
5021 let mut rows_inserted = 0;
5022
5023 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5024 let cursor = selection.head();
5025 let row = cursor.row;
5026
5027 let point = Point::new(row + 1, 0);
5028 let start_of_line = snapshot.clip_point(point, Bias::Left);
5029
5030 let newline = "\n".to_string();
5031 edits.push((start_of_line..start_of_line, newline));
5032
5033 rows_inserted += 1;
5034 rows.push(row + rows_inserted);
5035 }
5036
5037 self.transact(window, cx, |editor, window, cx| {
5038 editor.edit(edits, cx);
5039
5040 editor.change_selections(Default::default(), window, cx, |s| {
5041 let mut index = 0;
5042 s.move_cursors_with(|map, _, _| {
5043 let row = rows[index];
5044 index += 1;
5045
5046 let point = Point::new(row, 0);
5047 let boundary = map.next_line_boundary(point).1;
5048 let clipped = map.clip_point(boundary, Bias::Left);
5049
5050 (clipped, SelectionGoal::None)
5051 });
5052 });
5053
5054 let mut indent_edits = Vec::new();
5055 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5056 for row in rows {
5057 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5058 for (row, indent) in indents {
5059 if indent.len == 0 {
5060 continue;
5061 }
5062
5063 let text = match indent.kind {
5064 IndentKind::Space => " ".repeat(indent.len as usize),
5065 IndentKind::Tab => "\t".repeat(indent.len as usize),
5066 };
5067 let point = Point::new(row.0, 0);
5068 indent_edits.push((point..point, text));
5069 }
5070 }
5071 editor.edit(indent_edits, cx);
5072 });
5073 }
5074
5075 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5076 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5077 original_indent_columns: Vec::new(),
5078 });
5079 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5080 }
5081
5082 fn insert_with_autoindent_mode(
5083 &mut self,
5084 text: &str,
5085 autoindent_mode: Option<AutoindentMode>,
5086 window: &mut Window,
5087 cx: &mut Context<Self>,
5088 ) {
5089 if self.read_only(cx) {
5090 return;
5091 }
5092
5093 let text: Arc<str> = text.into();
5094 self.transact(window, cx, |this, window, cx| {
5095 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5096 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5097 let anchors = {
5098 let snapshot = buffer.read(cx);
5099 old_selections
5100 .iter()
5101 .map(|s| {
5102 let anchor = snapshot.anchor_after(s.head());
5103 s.map(|_| anchor)
5104 })
5105 .collect::<Vec<_>>()
5106 };
5107 buffer.edit(
5108 old_selections
5109 .iter()
5110 .map(|s| (s.start..s.end, text.clone())),
5111 autoindent_mode,
5112 cx,
5113 );
5114 anchors
5115 });
5116
5117 this.change_selections(Default::default(), window, cx, |s| {
5118 s.select_anchors(selection_anchors);
5119 });
5120
5121 cx.notify();
5122 });
5123 }
5124
5125 fn trigger_completion_on_input(
5126 &mut self,
5127 text: &str,
5128 trigger_in_words: bool,
5129 window: &mut Window,
5130 cx: &mut Context<Self>,
5131 ) {
5132 let completions_source = self
5133 .context_menu
5134 .borrow()
5135 .as_ref()
5136 .and_then(|menu| match menu {
5137 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5138 CodeContextMenu::CodeActions(_) => None,
5139 });
5140
5141 match completions_source {
5142 Some(CompletionsMenuSource::Words { .. }) => {
5143 self.open_or_update_completions_menu(
5144 Some(CompletionsMenuSource::Words {
5145 ignore_threshold: false,
5146 }),
5147 None,
5148 trigger_in_words,
5149 window,
5150 cx,
5151 );
5152 }
5153 _ => self.open_or_update_completions_menu(
5154 None,
5155 Some(text.to_owned()).filter(|x| !x.is_empty()),
5156 true,
5157 window,
5158 cx,
5159 ),
5160 }
5161 }
5162
5163 /// If any empty selections is touching the start of its innermost containing autoclose
5164 /// region, expand it to select the brackets.
5165 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5166 let selections = self
5167 .selections
5168 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5169 let buffer = self.buffer.read(cx).read(cx);
5170 let new_selections = self
5171 .selections_with_autoclose_regions(selections, &buffer)
5172 .map(|(mut selection, region)| {
5173 if !selection.is_empty() {
5174 return selection;
5175 }
5176
5177 if let Some(region) = region {
5178 let mut range = region.range.to_offset(&buffer);
5179 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5180 range.start -= region.pair.start.len();
5181 if buffer.contains_str_at(range.start, ®ion.pair.start)
5182 && buffer.contains_str_at(range.end, ®ion.pair.end)
5183 {
5184 range.end += region.pair.end.len();
5185 selection.start = range.start;
5186 selection.end = range.end;
5187
5188 return selection;
5189 }
5190 }
5191 }
5192
5193 let always_treat_brackets_as_autoclosed = buffer
5194 .language_settings_at(selection.start, cx)
5195 .always_treat_brackets_as_autoclosed;
5196
5197 if !always_treat_brackets_as_autoclosed {
5198 return selection;
5199 }
5200
5201 if let Some(scope) = buffer.language_scope_at(selection.start) {
5202 for (pair, enabled) in scope.brackets() {
5203 if !enabled || !pair.close {
5204 continue;
5205 }
5206
5207 if buffer.contains_str_at(selection.start, &pair.end) {
5208 let pair_start_len = pair.start.len();
5209 if buffer.contains_str_at(
5210 selection.start.saturating_sub_usize(pair_start_len),
5211 &pair.start,
5212 ) {
5213 selection.start -= pair_start_len;
5214 selection.end += pair.end.len();
5215
5216 return selection;
5217 }
5218 }
5219 }
5220 }
5221
5222 selection
5223 })
5224 .collect();
5225
5226 drop(buffer);
5227 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5228 selections.select(new_selections)
5229 });
5230 }
5231
5232 /// Iterate the given selections, and for each one, find the smallest surrounding
5233 /// autoclose region. This uses the ordering of the selections and the autoclose
5234 /// regions to avoid repeated comparisons.
5235 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5236 &'a self,
5237 selections: impl IntoIterator<Item = Selection<D>>,
5238 buffer: &'a MultiBufferSnapshot,
5239 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5240 let mut i = 0;
5241 let mut regions = self.autoclose_regions.as_slice();
5242 selections.into_iter().map(move |selection| {
5243 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5244
5245 let mut enclosing = None;
5246 while let Some(pair_state) = regions.get(i) {
5247 if pair_state.range.end.to_offset(buffer) < range.start {
5248 regions = ®ions[i + 1..];
5249 i = 0;
5250 } else if pair_state.range.start.to_offset(buffer) > range.end {
5251 break;
5252 } else {
5253 if pair_state.selection_id == selection.id {
5254 enclosing = Some(pair_state);
5255 }
5256 i += 1;
5257 }
5258 }
5259
5260 (selection, enclosing)
5261 })
5262 }
5263
5264 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5265 fn invalidate_autoclose_regions(
5266 &mut self,
5267 mut selections: &[Selection<Anchor>],
5268 buffer: &MultiBufferSnapshot,
5269 ) {
5270 self.autoclose_regions.retain(|state| {
5271 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5272 return false;
5273 }
5274
5275 let mut i = 0;
5276 while let Some(selection) = selections.get(i) {
5277 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5278 selections = &selections[1..];
5279 continue;
5280 }
5281 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5282 break;
5283 }
5284 if selection.id == state.selection_id {
5285 return true;
5286 } else {
5287 i += 1;
5288 }
5289 }
5290 false
5291 });
5292 }
5293
5294 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5295 let offset = position.to_offset(buffer);
5296 let (word_range, kind) =
5297 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5298 if offset > word_range.start && kind == Some(CharKind::Word) {
5299 Some(
5300 buffer
5301 .text_for_range(word_range.start..offset)
5302 .collect::<String>(),
5303 )
5304 } else {
5305 None
5306 }
5307 }
5308
5309 pub fn visible_excerpts(
5310 &self,
5311 cx: &mut Context<Editor>,
5312 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5313 let Some(project) = self.project() else {
5314 return HashMap::default();
5315 };
5316 let project = project.read(cx);
5317 let multi_buffer = self.buffer().read(cx);
5318 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5319 let multi_buffer_visible_start = self
5320 .scroll_manager
5321 .anchor()
5322 .anchor
5323 .to_point(&multi_buffer_snapshot);
5324 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5325 multi_buffer_visible_start
5326 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5327 Bias::Left,
5328 );
5329 multi_buffer_snapshot
5330 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5331 .into_iter()
5332 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5333 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5334 let buffer_file = project::File::from_dyn(buffer.file())?;
5335 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5336 let worktree_entry = buffer_worktree
5337 .read(cx)
5338 .entry_for_id(buffer_file.project_entry_id()?)?;
5339 if worktree_entry.is_ignored {
5340 None
5341 } else {
5342 Some((
5343 excerpt_id,
5344 (
5345 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5346 buffer.version().clone(),
5347 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5348 ),
5349 ))
5350 }
5351 })
5352 .collect()
5353 }
5354
5355 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5356 TextLayoutDetails {
5357 text_system: window.text_system().clone(),
5358 editor_style: self.style.clone().unwrap(),
5359 rem_size: window.rem_size(),
5360 scroll_anchor: self.scroll_manager.anchor(),
5361 visible_rows: self.visible_line_count(),
5362 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5363 }
5364 }
5365
5366 fn trigger_on_type_formatting(
5367 &self,
5368 input: String,
5369 window: &mut Window,
5370 cx: &mut Context<Self>,
5371 ) -> Option<Task<Result<()>>> {
5372 if input.len() != 1 {
5373 return None;
5374 }
5375
5376 let project = self.project()?;
5377 let position = self.selections.newest_anchor().head();
5378 let (buffer, buffer_position) = self
5379 .buffer
5380 .read(cx)
5381 .text_anchor_for_position(position, cx)?;
5382
5383 let settings = language_settings::language_settings(
5384 buffer
5385 .read(cx)
5386 .language_at(buffer_position)
5387 .map(|l| l.name()),
5388 buffer.read(cx).file(),
5389 cx,
5390 );
5391 if !settings.use_on_type_format {
5392 return None;
5393 }
5394
5395 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5396 // hence we do LSP request & edit on host side only — add formats to host's history.
5397 let push_to_lsp_host_history = true;
5398 // If this is not the host, append its history with new edits.
5399 let push_to_client_history = project.read(cx).is_via_collab();
5400
5401 let on_type_formatting = project.update(cx, |project, cx| {
5402 project.on_type_format(
5403 buffer.clone(),
5404 buffer_position,
5405 input,
5406 push_to_lsp_host_history,
5407 cx,
5408 )
5409 });
5410 Some(cx.spawn_in(window, async move |editor, cx| {
5411 if let Some(transaction) = on_type_formatting.await? {
5412 if push_to_client_history {
5413 buffer
5414 .update(cx, |buffer, _| {
5415 buffer.push_transaction(transaction, Instant::now());
5416 buffer.finalize_last_transaction();
5417 })
5418 .ok();
5419 }
5420 editor.update(cx, |editor, cx| {
5421 editor.refresh_document_highlights(cx);
5422 })?;
5423 }
5424 Ok(())
5425 }))
5426 }
5427
5428 pub fn show_word_completions(
5429 &mut self,
5430 _: &ShowWordCompletions,
5431 window: &mut Window,
5432 cx: &mut Context<Self>,
5433 ) {
5434 self.open_or_update_completions_menu(
5435 Some(CompletionsMenuSource::Words {
5436 ignore_threshold: true,
5437 }),
5438 None,
5439 false,
5440 window,
5441 cx,
5442 );
5443 }
5444
5445 pub fn show_completions(
5446 &mut self,
5447 _: &ShowCompletions,
5448 window: &mut Window,
5449 cx: &mut Context<Self>,
5450 ) {
5451 self.open_or_update_completions_menu(None, None, false, window, cx);
5452 }
5453
5454 fn open_or_update_completions_menu(
5455 &mut self,
5456 requested_source: Option<CompletionsMenuSource>,
5457 trigger: Option<String>,
5458 trigger_in_words: bool,
5459 window: &mut Window,
5460 cx: &mut Context<Self>,
5461 ) {
5462 if self.pending_rename.is_some() {
5463 return;
5464 }
5465
5466 let completions_source = self
5467 .context_menu
5468 .borrow()
5469 .as_ref()
5470 .and_then(|menu| match menu {
5471 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5472 CodeContextMenu::CodeActions(_) => None,
5473 });
5474
5475 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5476
5477 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5478 // inserted and selected. To handle that case, the start of the selection is used so that
5479 // the menu starts with all choices.
5480 let position = self
5481 .selections
5482 .newest_anchor()
5483 .start
5484 .bias_right(&multibuffer_snapshot);
5485 if position.diff_base_anchor.is_some() {
5486 return;
5487 }
5488 let buffer_position = multibuffer_snapshot.anchor_before(position);
5489 let Some(buffer) = buffer_position
5490 .text_anchor
5491 .buffer_id
5492 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5493 else {
5494 return;
5495 };
5496 let buffer_snapshot = buffer.read(cx).snapshot();
5497
5498 let query: Option<Arc<String>> =
5499 Self::completion_query(&multibuffer_snapshot, buffer_position)
5500 .map(|query| query.into());
5501
5502 drop(multibuffer_snapshot);
5503
5504 // Hide the current completions menu when query is empty. Without this, cached
5505 // completions from before the trigger char may be reused (#32774).
5506 if query.is_none() {
5507 let menu_is_open = matches!(
5508 self.context_menu.borrow().as_ref(),
5509 Some(CodeContextMenu::Completions(_))
5510 );
5511 if menu_is_open {
5512 self.hide_context_menu(window, cx);
5513 }
5514 }
5515
5516 let mut ignore_word_threshold = false;
5517 let provider = match requested_source {
5518 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5519 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5520 ignore_word_threshold = ignore_threshold;
5521 None
5522 }
5523 Some(CompletionsMenuSource::SnippetChoices)
5524 | Some(CompletionsMenuSource::SnippetsOnly) => {
5525 log::error!("bug: SnippetChoices requested_source is not handled");
5526 None
5527 }
5528 };
5529
5530 let sort_completions = provider
5531 .as_ref()
5532 .is_some_and(|provider| provider.sort_completions());
5533
5534 let filter_completions = provider
5535 .as_ref()
5536 .is_none_or(|provider| provider.filter_completions());
5537
5538 let was_snippets_only = matches!(
5539 completions_source,
5540 Some(CompletionsMenuSource::SnippetsOnly)
5541 );
5542
5543 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5544 if filter_completions {
5545 menu.filter(
5546 query.clone().unwrap_or_default(),
5547 buffer_position.text_anchor,
5548 &buffer,
5549 provider.clone(),
5550 window,
5551 cx,
5552 );
5553 }
5554 // When `is_incomplete` is false, no need to re-query completions when the current query
5555 // is a suffix of the initial query.
5556 let was_complete = !menu.is_incomplete;
5557 if was_complete && !was_snippets_only {
5558 // If the new query is a suffix of the old query (typing more characters) and
5559 // the previous result was complete, the existing completions can be filtered.
5560 //
5561 // Note that snippet completions are always complete.
5562 let query_matches = match (&menu.initial_query, &query) {
5563 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5564 (None, _) => true,
5565 _ => false,
5566 };
5567 if query_matches {
5568 let position_matches = if menu.initial_position == position {
5569 true
5570 } else {
5571 let snapshot = self.buffer.read(cx).read(cx);
5572 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5573 };
5574 if position_matches {
5575 return;
5576 }
5577 }
5578 }
5579 };
5580
5581 let Anchor {
5582 excerpt_id: buffer_excerpt_id,
5583 text_anchor: buffer_position,
5584 ..
5585 } = buffer_position;
5586
5587 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5588 buffer_snapshot.surrounding_word(buffer_position, None)
5589 {
5590 let word_to_exclude = buffer_snapshot
5591 .text_for_range(word_range.clone())
5592 .collect::<String>();
5593 (
5594 buffer_snapshot.anchor_before(word_range.start)
5595 ..buffer_snapshot.anchor_after(buffer_position),
5596 Some(word_to_exclude),
5597 )
5598 } else {
5599 (buffer_position..buffer_position, None)
5600 };
5601
5602 let language = buffer_snapshot
5603 .language_at(buffer_position)
5604 .map(|language| language.name());
5605
5606 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5607 .completions
5608 .clone();
5609
5610 let show_completion_documentation = buffer_snapshot
5611 .settings_at(buffer_position, cx)
5612 .show_completion_documentation;
5613
5614 // The document can be large, so stay in reasonable bounds when searching for words,
5615 // otherwise completion pop-up might be slow to appear.
5616 const WORD_LOOKUP_ROWS: u32 = 5_000;
5617 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5618 let min_word_search = buffer_snapshot.clip_point(
5619 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5620 Bias::Left,
5621 );
5622 let max_word_search = buffer_snapshot.clip_point(
5623 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5624 Bias::Right,
5625 );
5626 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5627 ..buffer_snapshot.point_to_offset(max_word_search);
5628
5629 let skip_digits = query
5630 .as_ref()
5631 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5632
5633 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5634 trigger.as_ref().is_none_or(|trigger| {
5635 provider.is_completion_trigger(
5636 &buffer,
5637 position.text_anchor,
5638 trigger,
5639 trigger_in_words,
5640 completions_source.is_some(),
5641 cx,
5642 )
5643 })
5644 });
5645
5646 let provider_responses = if let Some(provider) = &provider
5647 && load_provider_completions
5648 {
5649 let trigger_character =
5650 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5651 let completion_context = CompletionContext {
5652 trigger_kind: match &trigger_character {
5653 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5654 None => CompletionTriggerKind::INVOKED,
5655 },
5656 trigger_character,
5657 };
5658
5659 provider.completions(
5660 buffer_excerpt_id,
5661 &buffer,
5662 buffer_position,
5663 completion_context,
5664 window,
5665 cx,
5666 )
5667 } else {
5668 Task::ready(Ok(Vec::new()))
5669 };
5670
5671 let load_word_completions = if !self.word_completions_enabled {
5672 false
5673 } else if requested_source
5674 == Some(CompletionsMenuSource::Words {
5675 ignore_threshold: true,
5676 })
5677 {
5678 true
5679 } else {
5680 load_provider_completions
5681 && completion_settings.words != WordsCompletionMode::Disabled
5682 && (ignore_word_threshold || {
5683 let words_min_length = completion_settings.words_min_length;
5684 // check whether word has at least `words_min_length` characters
5685 let query_chars = query.iter().flat_map(|q| q.chars());
5686 query_chars.take(words_min_length).count() == words_min_length
5687 })
5688 };
5689
5690 let mut words = if load_word_completions {
5691 cx.background_spawn({
5692 let buffer_snapshot = buffer_snapshot.clone();
5693 async move {
5694 buffer_snapshot.words_in_range(WordsQuery {
5695 fuzzy_contents: None,
5696 range: word_search_range,
5697 skip_digits,
5698 })
5699 }
5700 })
5701 } else {
5702 Task::ready(BTreeMap::default())
5703 };
5704
5705 let snippets = if let Some(provider) = &provider
5706 && provider.show_snippets()
5707 && let Some(project) = self.project()
5708 {
5709 let char_classifier = buffer_snapshot
5710 .char_classifier_at(buffer_position)
5711 .scope_context(Some(CharScopeContext::Completion));
5712 project.update(cx, |project, cx| {
5713 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5714 })
5715 } else {
5716 Task::ready(Ok(CompletionResponse {
5717 completions: Vec::new(),
5718 display_options: Default::default(),
5719 is_incomplete: false,
5720 }))
5721 };
5722
5723 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5724
5725 let id = post_inc(&mut self.next_completion_id);
5726 let task = cx.spawn_in(window, async move |editor, cx| {
5727 let Ok(()) = editor.update(cx, |this, _| {
5728 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5729 }) else {
5730 return;
5731 };
5732
5733 // TODO: Ideally completions from different sources would be selectively re-queried, so
5734 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5735 let mut completions = Vec::new();
5736 let mut is_incomplete = false;
5737 let mut display_options: Option<CompletionDisplayOptions> = None;
5738 if let Some(provider_responses) = provider_responses.await.log_err()
5739 && !provider_responses.is_empty()
5740 {
5741 for response in provider_responses {
5742 completions.extend(response.completions);
5743 is_incomplete = is_incomplete || response.is_incomplete;
5744 match display_options.as_mut() {
5745 None => {
5746 display_options = Some(response.display_options);
5747 }
5748 Some(options) => options.merge(&response.display_options),
5749 }
5750 }
5751 if completion_settings.words == WordsCompletionMode::Fallback {
5752 words = Task::ready(BTreeMap::default());
5753 }
5754 }
5755 let display_options = display_options.unwrap_or_default();
5756
5757 let mut words = words.await;
5758 if let Some(word_to_exclude) = &word_to_exclude {
5759 words.remove(word_to_exclude);
5760 }
5761 for lsp_completion in &completions {
5762 words.remove(&lsp_completion.new_text);
5763 }
5764 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5765 replace_range: word_replace_range.clone(),
5766 new_text: word.clone(),
5767 label: CodeLabel::plain(word, None),
5768 match_start: None,
5769 snippet_deduplication_key: None,
5770 icon_path: None,
5771 documentation: None,
5772 source: CompletionSource::BufferWord {
5773 word_range,
5774 resolved: false,
5775 },
5776 insert_text_mode: Some(InsertTextMode::AS_IS),
5777 confirm: None,
5778 }));
5779
5780 completions.extend(
5781 snippets
5782 .await
5783 .into_iter()
5784 .flat_map(|response| response.completions),
5785 );
5786
5787 let menu = if completions.is_empty() {
5788 None
5789 } else {
5790 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5791 let languages = editor
5792 .workspace
5793 .as_ref()
5794 .and_then(|(workspace, _)| workspace.upgrade())
5795 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5796 let menu = CompletionsMenu::new(
5797 id,
5798 requested_source.unwrap_or(if load_provider_completions {
5799 CompletionsMenuSource::Normal
5800 } else {
5801 CompletionsMenuSource::SnippetsOnly
5802 }),
5803 sort_completions,
5804 show_completion_documentation,
5805 position,
5806 query.clone(),
5807 is_incomplete,
5808 buffer.clone(),
5809 completions.into(),
5810 display_options,
5811 snippet_sort_order,
5812 languages,
5813 language,
5814 cx,
5815 );
5816
5817 let query = if filter_completions { query } else { None };
5818 let matches_task = menu.do_async_filtering(
5819 query.unwrap_or_default(),
5820 buffer_position,
5821 &buffer,
5822 cx,
5823 );
5824 (menu, matches_task)
5825 }) else {
5826 return;
5827 };
5828
5829 let matches = matches_task.await;
5830
5831 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5832 // Newer menu already set, so exit.
5833 if let Some(CodeContextMenu::Completions(prev_menu)) =
5834 editor.context_menu.borrow().as_ref()
5835 && prev_menu.id > id
5836 {
5837 return;
5838 };
5839
5840 // Only valid to take prev_menu because either the new menu is immediately set
5841 // below, or the menu is hidden.
5842 if let Some(CodeContextMenu::Completions(prev_menu)) =
5843 editor.context_menu.borrow_mut().take()
5844 {
5845 let position_matches =
5846 if prev_menu.initial_position == menu.initial_position {
5847 true
5848 } else {
5849 let snapshot = editor.buffer.read(cx).read(cx);
5850 prev_menu.initial_position.to_offset(&snapshot)
5851 == menu.initial_position.to_offset(&snapshot)
5852 };
5853 if position_matches {
5854 // Preserve markdown cache before `set_filter_results` because it will
5855 // try to populate the documentation cache.
5856 menu.preserve_markdown_cache(prev_menu);
5857 }
5858 };
5859
5860 menu.set_filter_results(matches, provider, window, cx);
5861 }) else {
5862 return;
5863 };
5864
5865 menu.visible().then_some(menu)
5866 };
5867
5868 editor
5869 .update_in(cx, |editor, window, cx| {
5870 if editor.focus_handle.is_focused(window)
5871 && let Some(menu) = menu
5872 {
5873 *editor.context_menu.borrow_mut() =
5874 Some(CodeContextMenu::Completions(menu));
5875
5876 crate::hover_popover::hide_hover(editor, cx);
5877 if editor.show_edit_predictions_in_menu() {
5878 editor.update_visible_edit_prediction(window, cx);
5879 } else {
5880 editor.discard_edit_prediction(false, cx);
5881 }
5882
5883 cx.notify();
5884 return;
5885 }
5886
5887 if editor.completion_tasks.len() <= 1 {
5888 // If there are no more completion tasks and the last menu was empty, we should hide it.
5889 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5890 // If it was already hidden and we don't show edit predictions in the menu,
5891 // we should also show the edit prediction when available.
5892 if was_hidden && editor.show_edit_predictions_in_menu() {
5893 editor.update_visible_edit_prediction(window, cx);
5894 }
5895 }
5896 })
5897 .ok();
5898 });
5899
5900 self.completion_tasks.push((id, task));
5901 }
5902
5903 #[cfg(feature = "test-support")]
5904 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5905 let menu = self.context_menu.borrow();
5906 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5907 let completions = menu.completions.borrow();
5908 Some(completions.to_vec())
5909 } else {
5910 None
5911 }
5912 }
5913
5914 pub fn with_completions_menu_matching_id<R>(
5915 &self,
5916 id: CompletionId,
5917 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5918 ) -> R {
5919 let mut context_menu = self.context_menu.borrow_mut();
5920 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5921 return f(None);
5922 };
5923 if completions_menu.id != id {
5924 return f(None);
5925 }
5926 f(Some(completions_menu))
5927 }
5928
5929 pub fn confirm_completion(
5930 &mut self,
5931 action: &ConfirmCompletion,
5932 window: &mut Window,
5933 cx: &mut Context<Self>,
5934 ) -> Option<Task<Result<()>>> {
5935 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5936 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5937 }
5938
5939 pub fn confirm_completion_insert(
5940 &mut self,
5941 _: &ConfirmCompletionInsert,
5942 window: &mut Window,
5943 cx: &mut Context<Self>,
5944 ) -> Option<Task<Result<()>>> {
5945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5946 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5947 }
5948
5949 pub fn confirm_completion_replace(
5950 &mut self,
5951 _: &ConfirmCompletionReplace,
5952 window: &mut Window,
5953 cx: &mut Context<Self>,
5954 ) -> Option<Task<Result<()>>> {
5955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5956 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5957 }
5958
5959 pub fn compose_completion(
5960 &mut self,
5961 action: &ComposeCompletion,
5962 window: &mut Window,
5963 cx: &mut Context<Self>,
5964 ) -> Option<Task<Result<()>>> {
5965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5966 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5967 }
5968
5969 fn do_completion(
5970 &mut self,
5971 item_ix: Option<usize>,
5972 intent: CompletionIntent,
5973 window: &mut Window,
5974 cx: &mut Context<Editor>,
5975 ) -> Option<Task<Result<()>>> {
5976 use language::ToOffset as _;
5977
5978 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5979 else {
5980 return None;
5981 };
5982
5983 let candidate_id = {
5984 let entries = completions_menu.entries.borrow();
5985 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5986 if self.show_edit_predictions_in_menu() {
5987 self.discard_edit_prediction(true, cx);
5988 }
5989 mat.candidate_id
5990 };
5991
5992 let completion = completions_menu
5993 .completions
5994 .borrow()
5995 .get(candidate_id)?
5996 .clone();
5997 cx.stop_propagation();
5998
5999 let buffer_handle = completions_menu.buffer.clone();
6000
6001 let CompletionEdit {
6002 new_text,
6003 snippet,
6004 replace_range,
6005 } = process_completion_for_edit(
6006 &completion,
6007 intent,
6008 &buffer_handle,
6009 &completions_menu.initial_position.text_anchor,
6010 cx,
6011 );
6012
6013 let buffer = buffer_handle.read(cx);
6014 let snapshot = self.buffer.read(cx).snapshot(cx);
6015 let newest_anchor = self.selections.newest_anchor();
6016 let replace_range_multibuffer = {
6017 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6018 excerpt.map_range_from_buffer(replace_range.clone())
6019 };
6020 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6021 return None;
6022 }
6023
6024 let old_text = buffer
6025 .text_for_range(replace_range.clone())
6026 .collect::<String>();
6027 let lookbehind = newest_anchor
6028 .start
6029 .text_anchor
6030 .to_offset(buffer)
6031 .saturating_sub(replace_range.start.0);
6032 let lookahead = replace_range
6033 .end
6034 .0
6035 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6036 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6037 let suffix = &old_text[lookbehind.min(old_text.len())..];
6038
6039 let selections = self
6040 .selections
6041 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6042 let mut ranges = Vec::new();
6043 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6044
6045 for selection in &selections {
6046 let range = if selection.id == newest_anchor.id {
6047 replace_range_multibuffer.clone()
6048 } else {
6049 let mut range = selection.range();
6050
6051 // if prefix is present, don't duplicate it
6052 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6053 range.start = range.start.saturating_sub_usize(lookbehind);
6054
6055 // if suffix is also present, mimic the newest cursor and replace it
6056 if selection.id != newest_anchor.id
6057 && snapshot.contains_str_at(range.end, suffix)
6058 {
6059 range.end += lookahead;
6060 }
6061 }
6062 range
6063 };
6064
6065 ranges.push(range.clone());
6066
6067 if !self.linked_edit_ranges.is_empty() {
6068 let start_anchor = snapshot.anchor_before(range.start);
6069 let end_anchor = snapshot.anchor_after(range.end);
6070 if let Some(ranges) = self
6071 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6072 {
6073 for (buffer, edits) in ranges {
6074 linked_edits
6075 .entry(buffer.clone())
6076 .or_default()
6077 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6078 }
6079 }
6080 }
6081 }
6082
6083 let common_prefix_len = old_text
6084 .chars()
6085 .zip(new_text.chars())
6086 .take_while(|(a, b)| a == b)
6087 .map(|(a, _)| a.len_utf8())
6088 .sum::<usize>();
6089
6090 cx.emit(EditorEvent::InputHandled {
6091 utf16_range_to_replace: None,
6092 text: new_text[common_prefix_len..].into(),
6093 });
6094
6095 self.transact(window, cx, |editor, window, cx| {
6096 if let Some(mut snippet) = snippet {
6097 snippet.text = new_text.to_string();
6098 editor
6099 .insert_snippet(&ranges, snippet, window, cx)
6100 .log_err();
6101 } else {
6102 editor.buffer.update(cx, |multi_buffer, cx| {
6103 let auto_indent = match completion.insert_text_mode {
6104 Some(InsertTextMode::AS_IS) => None,
6105 _ => editor.autoindent_mode.clone(),
6106 };
6107 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6108 multi_buffer.edit(edits, auto_indent, cx);
6109 });
6110 }
6111 for (buffer, edits) in linked_edits {
6112 buffer.update(cx, |buffer, cx| {
6113 let snapshot = buffer.snapshot();
6114 let edits = edits
6115 .into_iter()
6116 .map(|(range, text)| {
6117 use text::ToPoint as TP;
6118 let end_point = TP::to_point(&range.end, &snapshot);
6119 let start_point = TP::to_point(&range.start, &snapshot);
6120 (start_point..end_point, text)
6121 })
6122 .sorted_by_key(|(range, _)| range.start);
6123 buffer.edit(edits, None, cx);
6124 })
6125 }
6126
6127 editor.refresh_edit_prediction(true, false, window, cx);
6128 });
6129 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6130
6131 let show_new_completions_on_confirm = completion
6132 .confirm
6133 .as_ref()
6134 .is_some_and(|confirm| confirm(intent, window, cx));
6135 if show_new_completions_on_confirm {
6136 self.open_or_update_completions_menu(None, None, false, window, cx);
6137 }
6138
6139 let provider = self.completion_provider.as_ref()?;
6140 drop(completion);
6141 let apply_edits = provider.apply_additional_edits_for_completion(
6142 buffer_handle,
6143 completions_menu.completions.clone(),
6144 candidate_id,
6145 true,
6146 cx,
6147 );
6148
6149 let editor_settings = EditorSettings::get_global(cx);
6150 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6151 // After the code completion is finished, users often want to know what signatures are needed.
6152 // so we should automatically call signature_help
6153 self.show_signature_help(&ShowSignatureHelp, window, cx);
6154 }
6155
6156 Some(cx.foreground_executor().spawn(async move {
6157 apply_edits.await?;
6158 Ok(())
6159 }))
6160 }
6161
6162 pub fn toggle_code_actions(
6163 &mut self,
6164 action: &ToggleCodeActions,
6165 window: &mut Window,
6166 cx: &mut Context<Self>,
6167 ) {
6168 let quick_launch = action.quick_launch;
6169 let mut context_menu = self.context_menu.borrow_mut();
6170 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6171 if code_actions.deployed_from == action.deployed_from {
6172 // Toggle if we're selecting the same one
6173 *context_menu = None;
6174 cx.notify();
6175 return;
6176 } else {
6177 // Otherwise, clear it and start a new one
6178 *context_menu = None;
6179 cx.notify();
6180 }
6181 }
6182 drop(context_menu);
6183 let snapshot = self.snapshot(window, cx);
6184 let deployed_from = action.deployed_from.clone();
6185 let action = action.clone();
6186 self.completion_tasks.clear();
6187 self.discard_edit_prediction(false, cx);
6188
6189 let multibuffer_point = match &action.deployed_from {
6190 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6191 DisplayPoint::new(*row, 0).to_point(&snapshot)
6192 }
6193 _ => self
6194 .selections
6195 .newest::<Point>(&snapshot.display_snapshot)
6196 .head(),
6197 };
6198 let Some((buffer, buffer_row)) = snapshot
6199 .buffer_snapshot()
6200 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6201 .and_then(|(buffer_snapshot, range)| {
6202 self.buffer()
6203 .read(cx)
6204 .buffer(buffer_snapshot.remote_id())
6205 .map(|buffer| (buffer, range.start.row))
6206 })
6207 else {
6208 return;
6209 };
6210 let buffer_id = buffer.read(cx).remote_id();
6211 let tasks = self
6212 .tasks
6213 .get(&(buffer_id, buffer_row))
6214 .map(|t| Arc::new(t.to_owned()));
6215
6216 if !self.focus_handle.is_focused(window) {
6217 return;
6218 }
6219 let project = self.project.clone();
6220
6221 let code_actions_task = match deployed_from {
6222 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6223 _ => self.code_actions(buffer_row, window, cx),
6224 };
6225
6226 let runnable_task = match deployed_from {
6227 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6228 _ => {
6229 let mut task_context_task = Task::ready(None);
6230 if let Some(tasks) = &tasks
6231 && let Some(project) = project
6232 {
6233 task_context_task =
6234 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6235 }
6236
6237 cx.spawn_in(window, {
6238 let buffer = buffer.clone();
6239 async move |editor, cx| {
6240 let task_context = task_context_task.await;
6241
6242 let resolved_tasks =
6243 tasks
6244 .zip(task_context.clone())
6245 .map(|(tasks, task_context)| ResolvedTasks {
6246 templates: tasks.resolve(&task_context).collect(),
6247 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6248 multibuffer_point.row,
6249 tasks.column,
6250 )),
6251 });
6252 let debug_scenarios = editor
6253 .update(cx, |editor, cx| {
6254 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6255 })?
6256 .await;
6257 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6258 }
6259 })
6260 }
6261 };
6262
6263 cx.spawn_in(window, async move |editor, cx| {
6264 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6265 let code_actions = code_actions_task.await;
6266 let spawn_straight_away = quick_launch
6267 && resolved_tasks
6268 .as_ref()
6269 .is_some_and(|tasks| tasks.templates.len() == 1)
6270 && code_actions
6271 .as_ref()
6272 .is_none_or(|actions| actions.is_empty())
6273 && debug_scenarios.is_empty();
6274
6275 editor.update_in(cx, |editor, window, cx| {
6276 crate::hover_popover::hide_hover(editor, cx);
6277 let actions = CodeActionContents::new(
6278 resolved_tasks,
6279 code_actions,
6280 debug_scenarios,
6281 task_context.unwrap_or_default(),
6282 );
6283
6284 // Don't show the menu if there are no actions available
6285 if actions.is_empty() {
6286 cx.notify();
6287 return Task::ready(Ok(()));
6288 }
6289
6290 *editor.context_menu.borrow_mut() =
6291 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6292 buffer,
6293 actions,
6294 selected_item: Default::default(),
6295 scroll_handle: UniformListScrollHandle::default(),
6296 deployed_from,
6297 }));
6298 cx.notify();
6299 if spawn_straight_away
6300 && let Some(task) = editor.confirm_code_action(
6301 &ConfirmCodeAction { item_ix: Some(0) },
6302 window,
6303 cx,
6304 )
6305 {
6306 return task;
6307 }
6308
6309 Task::ready(Ok(()))
6310 })
6311 })
6312 .detach_and_log_err(cx);
6313 }
6314
6315 fn debug_scenarios(
6316 &mut self,
6317 resolved_tasks: &Option<ResolvedTasks>,
6318 buffer: &Entity<Buffer>,
6319 cx: &mut App,
6320 ) -> Task<Vec<task::DebugScenario>> {
6321 maybe!({
6322 let project = self.project()?;
6323 let dap_store = project.read(cx).dap_store();
6324 let mut scenarios = vec![];
6325 let resolved_tasks = resolved_tasks.as_ref()?;
6326 let buffer = buffer.read(cx);
6327 let language = buffer.language()?;
6328 let file = buffer.file();
6329 let debug_adapter = language_settings(language.name().into(), file, cx)
6330 .debuggers
6331 .first()
6332 .map(SharedString::from)
6333 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6334
6335 dap_store.update(cx, |dap_store, cx| {
6336 for (_, task) in &resolved_tasks.templates {
6337 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6338 task.original_task().clone(),
6339 debug_adapter.clone().into(),
6340 task.display_label().to_owned().into(),
6341 cx,
6342 );
6343 scenarios.push(maybe_scenario);
6344 }
6345 });
6346 Some(cx.background_spawn(async move {
6347 futures::future::join_all(scenarios)
6348 .await
6349 .into_iter()
6350 .flatten()
6351 .collect::<Vec<_>>()
6352 }))
6353 })
6354 .unwrap_or_else(|| Task::ready(vec![]))
6355 }
6356
6357 fn code_actions(
6358 &mut self,
6359 buffer_row: u32,
6360 window: &mut Window,
6361 cx: &mut Context<Self>,
6362 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6363 let mut task = self.code_actions_task.take();
6364 cx.spawn_in(window, async move |editor, cx| {
6365 while let Some(prev_task) = task {
6366 prev_task.await.log_err();
6367 task = editor
6368 .update(cx, |this, _| this.code_actions_task.take())
6369 .ok()?;
6370 }
6371
6372 editor
6373 .update(cx, |editor, cx| {
6374 editor
6375 .available_code_actions
6376 .clone()
6377 .and_then(|(location, code_actions)| {
6378 let snapshot = location.buffer.read(cx).snapshot();
6379 let point_range = location.range.to_point(&snapshot);
6380 let point_range = point_range.start.row..=point_range.end.row;
6381 if point_range.contains(&buffer_row) {
6382 Some(code_actions)
6383 } else {
6384 None
6385 }
6386 })
6387 })
6388 .ok()
6389 .flatten()
6390 })
6391 }
6392
6393 pub fn confirm_code_action(
6394 &mut self,
6395 action: &ConfirmCodeAction,
6396 window: &mut Window,
6397 cx: &mut Context<Self>,
6398 ) -> Option<Task<Result<()>>> {
6399 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6400
6401 let actions_menu =
6402 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6403 menu
6404 } else {
6405 return None;
6406 };
6407
6408 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6409 let action = actions_menu.actions.get(action_ix)?;
6410 let title = action.label();
6411 let buffer = actions_menu.buffer;
6412 let workspace = self.workspace()?;
6413
6414 match action {
6415 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6416 workspace.update(cx, |workspace, cx| {
6417 workspace.schedule_resolved_task(
6418 task_source_kind,
6419 resolved_task,
6420 false,
6421 window,
6422 cx,
6423 );
6424
6425 Some(Task::ready(Ok(())))
6426 })
6427 }
6428 CodeActionsItem::CodeAction {
6429 excerpt_id,
6430 action,
6431 provider,
6432 } => {
6433 let apply_code_action =
6434 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6435 let workspace = workspace.downgrade();
6436 Some(cx.spawn_in(window, async move |editor, cx| {
6437 let project_transaction = apply_code_action.await?;
6438 Self::open_project_transaction(
6439 &editor,
6440 workspace,
6441 project_transaction,
6442 title,
6443 cx,
6444 )
6445 .await
6446 }))
6447 }
6448 CodeActionsItem::DebugScenario(scenario) => {
6449 let context = actions_menu.actions.context;
6450
6451 workspace.update(cx, |workspace, cx| {
6452 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6453 workspace.start_debug_session(
6454 scenario,
6455 context,
6456 Some(buffer),
6457 None,
6458 window,
6459 cx,
6460 );
6461 });
6462 Some(Task::ready(Ok(())))
6463 }
6464 }
6465 }
6466
6467 pub async fn open_project_transaction(
6468 editor: &WeakEntity<Editor>,
6469 workspace: WeakEntity<Workspace>,
6470 transaction: ProjectTransaction,
6471 title: String,
6472 cx: &mut AsyncWindowContext,
6473 ) -> Result<()> {
6474 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6475 cx.update(|_, cx| {
6476 entries.sort_unstable_by_key(|(buffer, _)| {
6477 buffer.read(cx).file().map(|f| f.path().clone())
6478 });
6479 })?;
6480 if entries.is_empty() {
6481 return Ok(());
6482 }
6483
6484 // If the project transaction's edits are all contained within this editor, then
6485 // avoid opening a new editor to display them.
6486
6487 if let [(buffer, transaction)] = &*entries {
6488 let excerpt = editor.update(cx, |editor, cx| {
6489 editor
6490 .buffer()
6491 .read(cx)
6492 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6493 })?;
6494 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6495 && excerpted_buffer == *buffer
6496 {
6497 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6498 let excerpt_range = excerpt_range.to_offset(buffer);
6499 buffer
6500 .edited_ranges_for_transaction::<usize>(transaction)
6501 .all(|range| {
6502 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6503 })
6504 })?;
6505
6506 if all_edits_within_excerpt {
6507 return Ok(());
6508 }
6509 }
6510 }
6511
6512 let mut ranges_to_highlight = Vec::new();
6513 let excerpt_buffer = cx.new(|cx| {
6514 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6515 for (buffer_handle, transaction) in &entries {
6516 let edited_ranges = buffer_handle
6517 .read(cx)
6518 .edited_ranges_for_transaction::<Point>(transaction)
6519 .collect::<Vec<_>>();
6520 let (ranges, _) = multibuffer.set_excerpts_for_path(
6521 PathKey::for_buffer(buffer_handle, cx),
6522 buffer_handle.clone(),
6523 edited_ranges,
6524 multibuffer_context_lines(cx),
6525 cx,
6526 );
6527
6528 ranges_to_highlight.extend(ranges);
6529 }
6530 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6531 multibuffer
6532 })?;
6533
6534 workspace.update_in(cx, |workspace, window, cx| {
6535 let project = workspace.project().clone();
6536 let editor =
6537 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6538 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6539 editor.update(cx, |editor, cx| {
6540 editor.highlight_background::<Self>(
6541 &ranges_to_highlight,
6542 |theme| theme.colors().editor_highlighted_line_background,
6543 cx,
6544 );
6545 });
6546 })?;
6547
6548 Ok(())
6549 }
6550
6551 pub fn clear_code_action_providers(&mut self) {
6552 self.code_action_providers.clear();
6553 self.available_code_actions.take();
6554 }
6555
6556 pub fn add_code_action_provider(
6557 &mut self,
6558 provider: Rc<dyn CodeActionProvider>,
6559 window: &mut Window,
6560 cx: &mut Context<Self>,
6561 ) {
6562 if self
6563 .code_action_providers
6564 .iter()
6565 .any(|existing_provider| existing_provider.id() == provider.id())
6566 {
6567 return;
6568 }
6569
6570 self.code_action_providers.push(provider);
6571 self.refresh_code_actions(window, cx);
6572 }
6573
6574 pub fn remove_code_action_provider(
6575 &mut self,
6576 id: Arc<str>,
6577 window: &mut Window,
6578 cx: &mut Context<Self>,
6579 ) {
6580 self.code_action_providers
6581 .retain(|provider| provider.id() != id);
6582 self.refresh_code_actions(window, cx);
6583 }
6584
6585 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6586 !self.code_action_providers.is_empty()
6587 && EditorSettings::get_global(cx).toolbar.code_actions
6588 }
6589
6590 pub fn has_available_code_actions(&self) -> bool {
6591 self.available_code_actions
6592 .as_ref()
6593 .is_some_and(|(_, actions)| !actions.is_empty())
6594 }
6595
6596 fn render_inline_code_actions(
6597 &self,
6598 icon_size: ui::IconSize,
6599 display_row: DisplayRow,
6600 is_active: bool,
6601 cx: &mut Context<Self>,
6602 ) -> AnyElement {
6603 let show_tooltip = !self.context_menu_visible();
6604 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6605 .icon_size(icon_size)
6606 .shape(ui::IconButtonShape::Square)
6607 .icon_color(ui::Color::Hidden)
6608 .toggle_state(is_active)
6609 .when(show_tooltip, |this| {
6610 this.tooltip({
6611 let focus_handle = self.focus_handle.clone();
6612 move |_window, cx| {
6613 Tooltip::for_action_in(
6614 "Toggle Code Actions",
6615 &ToggleCodeActions {
6616 deployed_from: None,
6617 quick_launch: false,
6618 },
6619 &focus_handle,
6620 cx,
6621 )
6622 }
6623 })
6624 })
6625 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6626 window.focus(&editor.focus_handle(cx));
6627 editor.toggle_code_actions(
6628 &crate::actions::ToggleCodeActions {
6629 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6630 display_row,
6631 )),
6632 quick_launch: false,
6633 },
6634 window,
6635 cx,
6636 );
6637 }))
6638 .into_any_element()
6639 }
6640
6641 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6642 &self.context_menu
6643 }
6644
6645 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6646 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6647 cx.background_executor()
6648 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6649 .await;
6650
6651 let (start_buffer, start, _, end, newest_selection) = this
6652 .update(cx, |this, cx| {
6653 let newest_selection = this.selections.newest_anchor().clone();
6654 if newest_selection.head().diff_base_anchor.is_some() {
6655 return None;
6656 }
6657 let display_snapshot = this.display_snapshot(cx);
6658 let newest_selection_adjusted =
6659 this.selections.newest_adjusted(&display_snapshot);
6660 let buffer = this.buffer.read(cx);
6661
6662 let (start_buffer, start) =
6663 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6664 let (end_buffer, end) =
6665 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6666
6667 Some((start_buffer, start, end_buffer, end, newest_selection))
6668 })?
6669 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6670 .context(
6671 "Expected selection to lie in a single buffer when refreshing code actions",
6672 )?;
6673 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6674 let providers = this.code_action_providers.clone();
6675 let tasks = this
6676 .code_action_providers
6677 .iter()
6678 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6679 .collect::<Vec<_>>();
6680 (providers, tasks)
6681 })?;
6682
6683 let mut actions = Vec::new();
6684 for (provider, provider_actions) in
6685 providers.into_iter().zip(future::join_all(tasks).await)
6686 {
6687 if let Some(provider_actions) = provider_actions.log_err() {
6688 actions.extend(provider_actions.into_iter().map(|action| {
6689 AvailableCodeAction {
6690 excerpt_id: newest_selection.start.excerpt_id,
6691 action,
6692 provider: provider.clone(),
6693 }
6694 }));
6695 }
6696 }
6697
6698 this.update(cx, |this, cx| {
6699 this.available_code_actions = if actions.is_empty() {
6700 None
6701 } else {
6702 Some((
6703 Location {
6704 buffer: start_buffer,
6705 range: start..end,
6706 },
6707 actions.into(),
6708 ))
6709 };
6710 cx.notify();
6711 })
6712 }));
6713 }
6714
6715 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6716 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6717 self.show_git_blame_inline = false;
6718
6719 self.show_git_blame_inline_delay_task =
6720 Some(cx.spawn_in(window, async move |this, cx| {
6721 cx.background_executor().timer(delay).await;
6722
6723 this.update(cx, |this, cx| {
6724 this.show_git_blame_inline = true;
6725 cx.notify();
6726 })
6727 .log_err();
6728 }));
6729 }
6730 }
6731
6732 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6733 let snapshot = self.snapshot(window, cx);
6734 let cursor = self
6735 .selections
6736 .newest::<Point>(&snapshot.display_snapshot)
6737 .head();
6738 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6739 else {
6740 return;
6741 };
6742
6743 let Some(blame) = self.blame.as_ref() else {
6744 return;
6745 };
6746
6747 let row_info = RowInfo {
6748 buffer_id: Some(buffer.remote_id()),
6749 buffer_row: Some(point.row),
6750 ..Default::default()
6751 };
6752 let Some((buffer, blame_entry)) = blame
6753 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6754 .flatten()
6755 else {
6756 return;
6757 };
6758
6759 let anchor = self.selections.newest_anchor().head();
6760 let position = self.to_pixel_point(anchor, &snapshot, window);
6761 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6762 self.show_blame_popover(
6763 buffer,
6764 &blame_entry,
6765 position + last_bounds.origin,
6766 true,
6767 cx,
6768 );
6769 };
6770 }
6771
6772 fn show_blame_popover(
6773 &mut self,
6774 buffer: BufferId,
6775 blame_entry: &BlameEntry,
6776 position: gpui::Point<Pixels>,
6777 ignore_timeout: bool,
6778 cx: &mut Context<Self>,
6779 ) {
6780 if let Some(state) = &mut self.inline_blame_popover {
6781 state.hide_task.take();
6782 } else {
6783 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6784 let blame_entry = blame_entry.clone();
6785 let show_task = cx.spawn(async move |editor, cx| {
6786 if !ignore_timeout {
6787 cx.background_executor()
6788 .timer(std::time::Duration::from_millis(blame_popover_delay))
6789 .await;
6790 }
6791 editor
6792 .update(cx, |editor, cx| {
6793 editor.inline_blame_popover_show_task.take();
6794 let Some(blame) = editor.blame.as_ref() else {
6795 return;
6796 };
6797 let blame = blame.read(cx);
6798 let details = blame.details_for_entry(buffer, &blame_entry);
6799 let markdown = cx.new(|cx| {
6800 Markdown::new(
6801 details
6802 .as_ref()
6803 .map(|message| message.message.clone())
6804 .unwrap_or_default(),
6805 None,
6806 None,
6807 cx,
6808 )
6809 });
6810 editor.inline_blame_popover = Some(InlineBlamePopover {
6811 position,
6812 hide_task: None,
6813 popover_bounds: None,
6814 popover_state: InlineBlamePopoverState {
6815 scroll_handle: ScrollHandle::new(),
6816 commit_message: details,
6817 markdown,
6818 },
6819 keyboard_grace: ignore_timeout,
6820 });
6821 cx.notify();
6822 })
6823 .ok();
6824 });
6825 self.inline_blame_popover_show_task = Some(show_task);
6826 }
6827 }
6828
6829 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6830 self.inline_blame_popover_show_task.take();
6831 if let Some(state) = &mut self.inline_blame_popover {
6832 let hide_task = cx.spawn(async move |editor, cx| {
6833 if !ignore_timeout {
6834 cx.background_executor()
6835 .timer(std::time::Duration::from_millis(100))
6836 .await;
6837 }
6838 editor
6839 .update(cx, |editor, cx| {
6840 editor.inline_blame_popover.take();
6841 cx.notify();
6842 })
6843 .ok();
6844 });
6845 state.hide_task = Some(hide_task);
6846 true
6847 } else {
6848 false
6849 }
6850 }
6851
6852 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6853 if self.pending_rename.is_some() {
6854 return None;
6855 }
6856
6857 let provider = self.semantics_provider.clone()?;
6858 let buffer = self.buffer.read(cx);
6859 let newest_selection = self.selections.newest_anchor().clone();
6860 let cursor_position = newest_selection.head();
6861 let (cursor_buffer, cursor_buffer_position) =
6862 buffer.text_anchor_for_position(cursor_position, cx)?;
6863 let (tail_buffer, tail_buffer_position) =
6864 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6865 if cursor_buffer != tail_buffer {
6866 return None;
6867 }
6868
6869 let snapshot = cursor_buffer.read(cx).snapshot();
6870 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6871 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6872 if start_word_range != end_word_range {
6873 self.document_highlights_task.take();
6874 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6875 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6876 return None;
6877 }
6878
6879 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6880 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6881 cx.background_executor()
6882 .timer(Duration::from_millis(debounce))
6883 .await;
6884
6885 let highlights = if let Some(highlights) = cx
6886 .update(|cx| {
6887 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6888 })
6889 .ok()
6890 .flatten()
6891 {
6892 highlights.await.log_err()
6893 } else {
6894 None
6895 };
6896
6897 if let Some(highlights) = highlights {
6898 this.update(cx, |this, cx| {
6899 if this.pending_rename.is_some() {
6900 return;
6901 }
6902
6903 let buffer = this.buffer.read(cx);
6904 if buffer
6905 .text_anchor_for_position(cursor_position, cx)
6906 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6907 {
6908 return;
6909 }
6910
6911 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6912 let mut write_ranges = Vec::new();
6913 let mut read_ranges = Vec::new();
6914 for highlight in highlights {
6915 let buffer_id = cursor_buffer.read(cx).remote_id();
6916 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6917 {
6918 let start = highlight
6919 .range
6920 .start
6921 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6922 let end = highlight
6923 .range
6924 .end
6925 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6926 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6927 continue;
6928 }
6929
6930 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
6931 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6932 write_ranges.push(range);
6933 } else {
6934 read_ranges.push(range);
6935 }
6936 }
6937 }
6938
6939 this.highlight_background::<DocumentHighlightRead>(
6940 &read_ranges,
6941 |theme| theme.colors().editor_document_highlight_read_background,
6942 cx,
6943 );
6944 this.highlight_background::<DocumentHighlightWrite>(
6945 &write_ranges,
6946 |theme| theme.colors().editor_document_highlight_write_background,
6947 cx,
6948 );
6949 cx.notify();
6950 })
6951 .log_err();
6952 }
6953 }));
6954 None
6955 }
6956
6957 fn prepare_highlight_query_from_selection(
6958 &mut self,
6959 window: &Window,
6960 cx: &mut Context<Editor>,
6961 ) -> Option<(String, Range<Anchor>)> {
6962 if matches!(self.mode, EditorMode::SingleLine) {
6963 return None;
6964 }
6965 if !EditorSettings::get_global(cx).selection_highlight {
6966 return None;
6967 }
6968 if self.selections.count() != 1 || self.selections.line_mode() {
6969 return None;
6970 }
6971 let snapshot = self.snapshot(window, cx);
6972 let selection = self.selections.newest::<Point>(&snapshot);
6973 // If the selection spans multiple rows OR it is empty
6974 if selection.start.row != selection.end.row
6975 || selection.start.column == selection.end.column
6976 {
6977 return None;
6978 }
6979 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6980 let query = snapshot
6981 .buffer_snapshot()
6982 .text_for_range(selection_anchor_range.clone())
6983 .collect::<String>();
6984 if query.trim().is_empty() {
6985 return None;
6986 }
6987 Some((query, selection_anchor_range))
6988 }
6989
6990 fn update_selection_occurrence_highlights(
6991 &mut self,
6992 query_text: String,
6993 query_range: Range<Anchor>,
6994 multi_buffer_range_to_query: Range<Point>,
6995 use_debounce: bool,
6996 window: &mut Window,
6997 cx: &mut Context<Editor>,
6998 ) -> Task<()> {
6999 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7000 cx.spawn_in(window, async move |editor, cx| {
7001 if use_debounce {
7002 cx.background_executor()
7003 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7004 .await;
7005 }
7006 let match_task = cx.background_spawn(async move {
7007 let buffer_ranges = multi_buffer_snapshot
7008 .range_to_buffer_ranges(multi_buffer_range_to_query)
7009 .into_iter()
7010 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7011 let mut match_ranges = Vec::new();
7012 let Ok(regex) = project::search::SearchQuery::text(
7013 query_text.clone(),
7014 false,
7015 false,
7016 false,
7017 Default::default(),
7018 Default::default(),
7019 false,
7020 None,
7021 ) else {
7022 return Vec::default();
7023 };
7024 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7025 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7026 match_ranges.extend(
7027 regex
7028 .search(
7029 buffer_snapshot,
7030 Some(search_range.start.0..search_range.end.0),
7031 )
7032 .await
7033 .into_iter()
7034 .filter_map(|match_range| {
7035 let match_start = buffer_snapshot
7036 .anchor_after(search_range.start + match_range.start);
7037 let match_end = buffer_snapshot
7038 .anchor_before(search_range.start + match_range.end);
7039 let match_anchor_range =
7040 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7041 (match_anchor_range != query_range).then_some(match_anchor_range)
7042 }),
7043 );
7044 }
7045 match_ranges
7046 });
7047 let match_ranges = match_task.await;
7048 editor
7049 .update_in(cx, |editor, _, cx| {
7050 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7051 if !match_ranges.is_empty() {
7052 editor.highlight_background::<SelectedTextHighlight>(
7053 &match_ranges,
7054 |theme| theme.colors().editor_document_highlight_bracket_background,
7055 cx,
7056 )
7057 }
7058 })
7059 .log_err();
7060 })
7061 }
7062
7063 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7064 struct NewlineFold;
7065 let type_id = std::any::TypeId::of::<NewlineFold>();
7066 if !self.mode.is_single_line() {
7067 return;
7068 }
7069 let snapshot = self.snapshot(window, cx);
7070 if snapshot.buffer_snapshot().max_point().row == 0 {
7071 return;
7072 }
7073 let task = cx.background_spawn(async move {
7074 let new_newlines = snapshot
7075 .buffer_chars_at(MultiBufferOffset(0))
7076 .filter_map(|(c, i)| {
7077 if c == '\n' {
7078 Some(
7079 snapshot.buffer_snapshot().anchor_after(i)
7080 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7081 )
7082 } else {
7083 None
7084 }
7085 })
7086 .collect::<Vec<_>>();
7087 let existing_newlines = snapshot
7088 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7089 .filter_map(|fold| {
7090 if fold.placeholder.type_tag == Some(type_id) {
7091 Some(fold.range.start..fold.range.end)
7092 } else {
7093 None
7094 }
7095 })
7096 .collect::<Vec<_>>();
7097
7098 (new_newlines, existing_newlines)
7099 });
7100 self.folding_newlines = cx.spawn(async move |this, cx| {
7101 let (new_newlines, existing_newlines) = task.await;
7102 if new_newlines == existing_newlines {
7103 return;
7104 }
7105 let placeholder = FoldPlaceholder {
7106 render: Arc::new(move |_, _, cx| {
7107 div()
7108 .bg(cx.theme().status().hint_background)
7109 .border_b_1()
7110 .size_full()
7111 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7112 .border_color(cx.theme().status().hint)
7113 .child("\\n")
7114 .into_any()
7115 }),
7116 constrain_width: false,
7117 merge_adjacent: false,
7118 type_tag: Some(type_id),
7119 };
7120 let creases = new_newlines
7121 .into_iter()
7122 .map(|range| Crease::simple(range, placeholder.clone()))
7123 .collect();
7124 this.update(cx, |this, cx| {
7125 this.display_map.update(cx, |display_map, cx| {
7126 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7127 display_map.fold(creases, cx);
7128 });
7129 })
7130 .ok();
7131 });
7132 }
7133
7134 fn refresh_selected_text_highlights(
7135 &mut self,
7136 on_buffer_edit: bool,
7137 window: &mut Window,
7138 cx: &mut Context<Editor>,
7139 ) {
7140 let Some((query_text, query_range)) =
7141 self.prepare_highlight_query_from_selection(window, cx)
7142 else {
7143 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7144 self.quick_selection_highlight_task.take();
7145 self.debounced_selection_highlight_task.take();
7146 return;
7147 };
7148 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7149 if on_buffer_edit
7150 || self
7151 .quick_selection_highlight_task
7152 .as_ref()
7153 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7154 {
7155 let multi_buffer_visible_start = self
7156 .scroll_manager
7157 .anchor()
7158 .anchor
7159 .to_point(&multi_buffer_snapshot);
7160 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7161 multi_buffer_visible_start
7162 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7163 Bias::Left,
7164 );
7165 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7166 self.quick_selection_highlight_task = Some((
7167 query_range.clone(),
7168 self.update_selection_occurrence_highlights(
7169 query_text.clone(),
7170 query_range.clone(),
7171 multi_buffer_visible_range,
7172 false,
7173 window,
7174 cx,
7175 ),
7176 ));
7177 }
7178 if on_buffer_edit
7179 || self
7180 .debounced_selection_highlight_task
7181 .as_ref()
7182 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7183 {
7184 let multi_buffer_start = multi_buffer_snapshot
7185 .anchor_before(MultiBufferOffset(0))
7186 .to_point(&multi_buffer_snapshot);
7187 let multi_buffer_end = multi_buffer_snapshot
7188 .anchor_after(multi_buffer_snapshot.len())
7189 .to_point(&multi_buffer_snapshot);
7190 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7191 self.debounced_selection_highlight_task = Some((
7192 query_range.clone(),
7193 self.update_selection_occurrence_highlights(
7194 query_text,
7195 query_range,
7196 multi_buffer_full_range,
7197 true,
7198 window,
7199 cx,
7200 ),
7201 ));
7202 }
7203 }
7204
7205 pub fn refresh_edit_prediction(
7206 &mut self,
7207 debounce: bool,
7208 user_requested: bool,
7209 window: &mut Window,
7210 cx: &mut Context<Self>,
7211 ) -> Option<()> {
7212 if DisableAiSettings::get_global(cx).disable_ai {
7213 return None;
7214 }
7215
7216 let provider = self.edit_prediction_provider()?;
7217 let cursor = self.selections.newest_anchor().head();
7218 let (buffer, cursor_buffer_position) =
7219 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7220
7221 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7222 self.discard_edit_prediction(false, cx);
7223 return None;
7224 }
7225
7226 self.update_visible_edit_prediction(window, cx);
7227
7228 if !user_requested
7229 && (!self.should_show_edit_predictions()
7230 || !self.is_focused(window)
7231 || buffer.read(cx).is_empty())
7232 {
7233 self.discard_edit_prediction(false, cx);
7234 return None;
7235 }
7236
7237 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7238 Some(())
7239 }
7240
7241 fn show_edit_predictions_in_menu(&self) -> bool {
7242 match self.edit_prediction_settings {
7243 EditPredictionSettings::Disabled => false,
7244 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7245 }
7246 }
7247
7248 pub fn edit_predictions_enabled(&self) -> bool {
7249 match self.edit_prediction_settings {
7250 EditPredictionSettings::Disabled => false,
7251 EditPredictionSettings::Enabled { .. } => true,
7252 }
7253 }
7254
7255 fn edit_prediction_requires_modifier(&self) -> bool {
7256 match self.edit_prediction_settings {
7257 EditPredictionSettings::Disabled => false,
7258 EditPredictionSettings::Enabled {
7259 preview_requires_modifier,
7260 ..
7261 } => preview_requires_modifier,
7262 }
7263 }
7264
7265 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7266 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7267 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7268 self.discard_edit_prediction(false, cx);
7269 } else {
7270 let selection = self.selections.newest_anchor();
7271 let cursor = selection.head();
7272
7273 if let Some((buffer, cursor_buffer_position)) =
7274 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7275 {
7276 self.edit_prediction_settings =
7277 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7278 }
7279 }
7280 }
7281
7282 fn edit_prediction_settings_at_position(
7283 &self,
7284 buffer: &Entity<Buffer>,
7285 buffer_position: language::Anchor,
7286 cx: &App,
7287 ) -> EditPredictionSettings {
7288 if !self.mode.is_full()
7289 || !self.show_edit_predictions_override.unwrap_or(true)
7290 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7291 {
7292 return EditPredictionSettings::Disabled;
7293 }
7294
7295 let buffer = buffer.read(cx);
7296
7297 let file = buffer.file();
7298
7299 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7300 return EditPredictionSettings::Disabled;
7301 };
7302
7303 let by_provider = matches!(
7304 self.menu_edit_predictions_policy,
7305 MenuEditPredictionsPolicy::ByProvider
7306 );
7307
7308 let show_in_menu = by_provider
7309 && self
7310 .edit_prediction_provider
7311 .as_ref()
7312 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7313
7314 let preview_requires_modifier =
7315 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7316
7317 EditPredictionSettings::Enabled {
7318 show_in_menu,
7319 preview_requires_modifier,
7320 }
7321 }
7322
7323 fn should_show_edit_predictions(&self) -> bool {
7324 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7325 }
7326
7327 pub fn edit_prediction_preview_is_active(&self) -> bool {
7328 matches!(
7329 self.edit_prediction_preview,
7330 EditPredictionPreview::Active { .. }
7331 )
7332 }
7333
7334 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7335 let cursor = self.selections.newest_anchor().head();
7336 if let Some((buffer, cursor_position)) =
7337 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7338 {
7339 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7340 } else {
7341 false
7342 }
7343 }
7344
7345 pub fn supports_minimap(&self, cx: &App) -> bool {
7346 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7347 }
7348
7349 fn edit_predictions_enabled_in_buffer(
7350 &self,
7351 buffer: &Entity<Buffer>,
7352 buffer_position: language::Anchor,
7353 cx: &App,
7354 ) -> bool {
7355 maybe!({
7356 if self.read_only(cx) {
7357 return Some(false);
7358 }
7359 let provider = self.edit_prediction_provider()?;
7360 if !provider.is_enabled(buffer, buffer_position, cx) {
7361 return Some(false);
7362 }
7363 let buffer = buffer.read(cx);
7364 let Some(file) = buffer.file() else {
7365 return Some(true);
7366 };
7367 let settings = all_language_settings(Some(file), cx);
7368 Some(settings.edit_predictions_enabled_for_file(file, cx))
7369 })
7370 .unwrap_or(false)
7371 }
7372
7373 fn cycle_edit_prediction(
7374 &mut self,
7375 direction: Direction,
7376 window: &mut Window,
7377 cx: &mut Context<Self>,
7378 ) -> Option<()> {
7379 let provider = self.edit_prediction_provider()?;
7380 let cursor = self.selections.newest_anchor().head();
7381 let (buffer, cursor_buffer_position) =
7382 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7383 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7384 return None;
7385 }
7386
7387 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7388 self.update_visible_edit_prediction(window, cx);
7389
7390 Some(())
7391 }
7392
7393 pub fn show_edit_prediction(
7394 &mut self,
7395 _: &ShowEditPrediction,
7396 window: &mut Window,
7397 cx: &mut Context<Self>,
7398 ) {
7399 if !self.has_active_edit_prediction() {
7400 self.refresh_edit_prediction(false, true, window, cx);
7401 return;
7402 }
7403
7404 self.update_visible_edit_prediction(window, cx);
7405 }
7406
7407 pub fn display_cursor_names(
7408 &mut self,
7409 _: &DisplayCursorNames,
7410 window: &mut Window,
7411 cx: &mut Context<Self>,
7412 ) {
7413 self.show_cursor_names(window, cx);
7414 }
7415
7416 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7417 self.show_cursor_names = true;
7418 cx.notify();
7419 cx.spawn_in(window, async move |this, cx| {
7420 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7421 this.update(cx, |this, cx| {
7422 this.show_cursor_names = false;
7423 cx.notify()
7424 })
7425 .ok()
7426 })
7427 .detach();
7428 }
7429
7430 pub fn next_edit_prediction(
7431 &mut self,
7432 _: &NextEditPrediction,
7433 window: &mut Window,
7434 cx: &mut Context<Self>,
7435 ) {
7436 if self.has_active_edit_prediction() {
7437 self.cycle_edit_prediction(Direction::Next, window, cx);
7438 } else {
7439 let is_copilot_disabled = self
7440 .refresh_edit_prediction(false, true, window, cx)
7441 .is_none();
7442 if is_copilot_disabled {
7443 cx.propagate();
7444 }
7445 }
7446 }
7447
7448 pub fn previous_edit_prediction(
7449 &mut self,
7450 _: &PreviousEditPrediction,
7451 window: &mut Window,
7452 cx: &mut Context<Self>,
7453 ) {
7454 if self.has_active_edit_prediction() {
7455 self.cycle_edit_prediction(Direction::Prev, window, cx);
7456 } else {
7457 let is_copilot_disabled = self
7458 .refresh_edit_prediction(false, true, window, cx)
7459 .is_none();
7460 if is_copilot_disabled {
7461 cx.propagate();
7462 }
7463 }
7464 }
7465
7466 pub fn accept_edit_prediction(
7467 &mut self,
7468 _: &AcceptEditPrediction,
7469 window: &mut Window,
7470 cx: &mut Context<Self>,
7471 ) {
7472 if self.show_edit_predictions_in_menu() {
7473 self.hide_context_menu(window, cx);
7474 }
7475
7476 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7477 return;
7478 };
7479
7480 match &active_edit_prediction.completion {
7481 EditPrediction::MoveWithin { target, .. } => {
7482 let target = *target;
7483
7484 if let Some(position_map) = &self.last_position_map {
7485 if position_map
7486 .visible_row_range
7487 .contains(&target.to_display_point(&position_map.snapshot).row())
7488 || !self.edit_prediction_requires_modifier()
7489 {
7490 self.unfold_ranges(&[target..target], true, false, cx);
7491 // Note that this is also done in vim's handler of the Tab action.
7492 self.change_selections(
7493 SelectionEffects::scroll(Autoscroll::newest()),
7494 window,
7495 cx,
7496 |selections| {
7497 selections.select_anchor_ranges([target..target]);
7498 },
7499 );
7500 self.clear_row_highlights::<EditPredictionPreview>();
7501
7502 self.edit_prediction_preview
7503 .set_previous_scroll_position(None);
7504 } else {
7505 self.edit_prediction_preview
7506 .set_previous_scroll_position(Some(
7507 position_map.snapshot.scroll_anchor,
7508 ));
7509
7510 self.highlight_rows::<EditPredictionPreview>(
7511 target..target,
7512 cx.theme().colors().editor_highlighted_line_background,
7513 RowHighlightOptions {
7514 autoscroll: true,
7515 ..Default::default()
7516 },
7517 cx,
7518 );
7519 self.request_autoscroll(Autoscroll::fit(), cx);
7520 }
7521 }
7522 }
7523 EditPrediction::MoveOutside { snapshot, target } => {
7524 if let Some(workspace) = self.workspace() {
7525 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7526 .detach_and_log_err(cx);
7527 }
7528 }
7529 EditPrediction::Edit { edits, .. } => {
7530 self.report_edit_prediction_event(
7531 active_edit_prediction.completion_id.clone(),
7532 true,
7533 cx,
7534 );
7535
7536 if let Some(provider) = self.edit_prediction_provider() {
7537 provider.accept(cx);
7538 }
7539
7540 // Store the transaction ID and selections before applying the edit
7541 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7542
7543 let snapshot = self.buffer.read(cx).snapshot(cx);
7544 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7545
7546 self.buffer.update(cx, |buffer, cx| {
7547 buffer.edit(edits.iter().cloned(), None, cx)
7548 });
7549
7550 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7551 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7552 });
7553
7554 let selections = self.selections.disjoint_anchors_arc();
7555 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7556 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7557 if has_new_transaction {
7558 self.selection_history
7559 .insert_transaction(transaction_id_now, selections);
7560 }
7561 }
7562
7563 self.update_visible_edit_prediction(window, cx);
7564 if self.active_edit_prediction.is_none() {
7565 self.refresh_edit_prediction(true, true, window, cx);
7566 }
7567
7568 cx.notify();
7569 }
7570 }
7571
7572 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7573 }
7574
7575 pub fn accept_partial_edit_prediction(
7576 &mut self,
7577 _: &AcceptPartialEditPrediction,
7578 window: &mut Window,
7579 cx: &mut Context<Self>,
7580 ) {
7581 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7582 return;
7583 };
7584 if self.selections.count() != 1 {
7585 return;
7586 }
7587
7588 match &active_edit_prediction.completion {
7589 EditPrediction::MoveWithin { target, .. } => {
7590 let target = *target;
7591 self.change_selections(
7592 SelectionEffects::scroll(Autoscroll::newest()),
7593 window,
7594 cx,
7595 |selections| {
7596 selections.select_anchor_ranges([target..target]);
7597 },
7598 );
7599 }
7600 EditPrediction::MoveOutside { snapshot, target } => {
7601 if let Some(workspace) = self.workspace() {
7602 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7603 .detach_and_log_err(cx);
7604 }
7605 }
7606 EditPrediction::Edit { edits, .. } => {
7607 self.report_edit_prediction_event(
7608 active_edit_prediction.completion_id.clone(),
7609 true,
7610 cx,
7611 );
7612
7613 // Find an insertion that starts at the cursor position.
7614 let snapshot = self.buffer.read(cx).snapshot(cx);
7615 let cursor_offset = self
7616 .selections
7617 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7618 .head();
7619 let insertion = edits.iter().find_map(|(range, text)| {
7620 let range = range.to_offset(&snapshot);
7621 if range.is_empty() && range.start == cursor_offset {
7622 Some(text)
7623 } else {
7624 None
7625 }
7626 });
7627
7628 if let Some(text) = insertion {
7629 let mut partial_completion = text
7630 .chars()
7631 .by_ref()
7632 .take_while(|c| c.is_alphabetic())
7633 .collect::<String>();
7634 if partial_completion.is_empty() {
7635 partial_completion = text
7636 .chars()
7637 .by_ref()
7638 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7639 .collect::<String>();
7640 }
7641
7642 cx.emit(EditorEvent::InputHandled {
7643 utf16_range_to_replace: None,
7644 text: partial_completion.clone().into(),
7645 });
7646
7647 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7648
7649 self.refresh_edit_prediction(true, true, window, cx);
7650 cx.notify();
7651 } else {
7652 self.accept_edit_prediction(&Default::default(), window, cx);
7653 }
7654 }
7655 }
7656 }
7657
7658 fn discard_edit_prediction(
7659 &mut self,
7660 should_report_edit_prediction_event: bool,
7661 cx: &mut Context<Self>,
7662 ) -> bool {
7663 if should_report_edit_prediction_event {
7664 let completion_id = self
7665 .active_edit_prediction
7666 .as_ref()
7667 .and_then(|active_completion| active_completion.completion_id.clone());
7668
7669 self.report_edit_prediction_event(completion_id, false, cx);
7670 }
7671
7672 if let Some(provider) = self.edit_prediction_provider() {
7673 provider.discard(cx);
7674 }
7675
7676 self.take_active_edit_prediction(cx)
7677 }
7678
7679 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7680 let Some(provider) = self.edit_prediction_provider() else {
7681 return;
7682 };
7683
7684 let Some((_, buffer, _)) = self
7685 .buffer
7686 .read(cx)
7687 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7688 else {
7689 return;
7690 };
7691
7692 let extension = buffer
7693 .read(cx)
7694 .file()
7695 .and_then(|file| Some(file.path().extension()?.to_string()));
7696
7697 let event_type = match accepted {
7698 true => "Edit Prediction Accepted",
7699 false => "Edit Prediction Discarded",
7700 };
7701 telemetry::event!(
7702 event_type,
7703 provider = provider.name(),
7704 prediction_id = id,
7705 suggestion_accepted = accepted,
7706 file_extension = extension,
7707 );
7708 }
7709
7710 fn open_editor_at_anchor(
7711 snapshot: &language::BufferSnapshot,
7712 target: language::Anchor,
7713 workspace: &Entity<Workspace>,
7714 window: &mut Window,
7715 cx: &mut App,
7716 ) -> Task<Result<()>> {
7717 workspace.update(cx, |workspace, cx| {
7718 let path = snapshot.file().map(|file| file.full_path(cx));
7719 let Some(path) =
7720 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7721 else {
7722 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7723 };
7724 let target = text::ToPoint::to_point(&target, snapshot);
7725 let item = workspace.open_path(path, None, true, window, cx);
7726 window.spawn(cx, async move |cx| {
7727 let Some(editor) = item.await?.downcast::<Editor>() else {
7728 return Ok(());
7729 };
7730 editor
7731 .update_in(cx, |editor, window, cx| {
7732 editor.go_to_singleton_buffer_point(target, window, cx);
7733 })
7734 .ok();
7735 anyhow::Ok(())
7736 })
7737 })
7738 }
7739
7740 pub fn has_active_edit_prediction(&self) -> bool {
7741 self.active_edit_prediction.is_some()
7742 }
7743
7744 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7745 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7746 return false;
7747 };
7748
7749 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7750 self.clear_highlights::<EditPredictionHighlight>(cx);
7751 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7752 true
7753 }
7754
7755 /// Returns true when we're displaying the edit prediction popover below the cursor
7756 /// like we are not previewing and the LSP autocomplete menu is visible
7757 /// or we are in `when_holding_modifier` mode.
7758 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7759 if self.edit_prediction_preview_is_active()
7760 || !self.show_edit_predictions_in_menu()
7761 || !self.edit_predictions_enabled()
7762 {
7763 return false;
7764 }
7765
7766 if self.has_visible_completions_menu() {
7767 return true;
7768 }
7769
7770 has_completion && self.edit_prediction_requires_modifier()
7771 }
7772
7773 fn handle_modifiers_changed(
7774 &mut self,
7775 modifiers: Modifiers,
7776 position_map: &PositionMap,
7777 window: &mut Window,
7778 cx: &mut Context<Self>,
7779 ) {
7780 // Ensure that the edit prediction preview is updated, even when not
7781 // enabled, if there's an active edit prediction preview.
7782 if self.show_edit_predictions_in_menu()
7783 || matches!(
7784 self.edit_prediction_preview,
7785 EditPredictionPreview::Active { .. }
7786 )
7787 {
7788 self.update_edit_prediction_preview(&modifiers, window, cx);
7789 }
7790
7791 self.update_selection_mode(&modifiers, position_map, window, cx);
7792
7793 let mouse_position = window.mouse_position();
7794 if !position_map.text_hitbox.is_hovered(window) {
7795 return;
7796 }
7797
7798 self.update_hovered_link(
7799 position_map.point_for_position(mouse_position),
7800 &position_map.snapshot,
7801 modifiers,
7802 window,
7803 cx,
7804 )
7805 }
7806
7807 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7808 match EditorSettings::get_global(cx).multi_cursor_modifier {
7809 MultiCursorModifier::Alt => modifiers.secondary(),
7810 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7811 }
7812 }
7813
7814 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7815 match EditorSettings::get_global(cx).multi_cursor_modifier {
7816 MultiCursorModifier::Alt => modifiers.alt,
7817 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7818 }
7819 }
7820
7821 fn columnar_selection_mode(
7822 modifiers: &Modifiers,
7823 cx: &mut Context<Self>,
7824 ) -> Option<ColumnarMode> {
7825 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7826 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7827 Some(ColumnarMode::FromMouse)
7828 } else if Self::is_alt_pressed(modifiers, cx) {
7829 Some(ColumnarMode::FromSelection)
7830 } else {
7831 None
7832 }
7833 } else {
7834 None
7835 }
7836 }
7837
7838 fn update_selection_mode(
7839 &mut self,
7840 modifiers: &Modifiers,
7841 position_map: &PositionMap,
7842 window: &mut Window,
7843 cx: &mut Context<Self>,
7844 ) {
7845 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7846 return;
7847 };
7848 if self.selections.pending_anchor().is_none() {
7849 return;
7850 }
7851
7852 let mouse_position = window.mouse_position();
7853 let point_for_position = position_map.point_for_position(mouse_position);
7854 let position = point_for_position.previous_valid;
7855
7856 self.select(
7857 SelectPhase::BeginColumnar {
7858 position,
7859 reset: false,
7860 mode,
7861 goal_column: point_for_position.exact_unclipped.column(),
7862 },
7863 window,
7864 cx,
7865 );
7866 }
7867
7868 fn update_edit_prediction_preview(
7869 &mut self,
7870 modifiers: &Modifiers,
7871 window: &mut Window,
7872 cx: &mut Context<Self>,
7873 ) {
7874 let mut modifiers_held = false;
7875 if let Some(accept_keystroke) = self
7876 .accept_edit_prediction_keybind(false, window, cx)
7877 .keystroke()
7878 {
7879 modifiers_held = modifiers_held
7880 || (accept_keystroke.modifiers() == modifiers
7881 && accept_keystroke.modifiers().modified());
7882 };
7883 if let Some(accept_partial_keystroke) = self
7884 .accept_edit_prediction_keybind(true, window, cx)
7885 .keystroke()
7886 {
7887 modifiers_held = modifiers_held
7888 || (accept_partial_keystroke.modifiers() == modifiers
7889 && accept_partial_keystroke.modifiers().modified());
7890 }
7891
7892 if modifiers_held {
7893 if matches!(
7894 self.edit_prediction_preview,
7895 EditPredictionPreview::Inactive { .. }
7896 ) {
7897 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7898 provider.provider.did_show(cx)
7899 }
7900
7901 self.edit_prediction_preview = EditPredictionPreview::Active {
7902 previous_scroll_position: None,
7903 since: Instant::now(),
7904 };
7905
7906 self.update_visible_edit_prediction(window, cx);
7907 cx.notify();
7908 }
7909 } else if let EditPredictionPreview::Active {
7910 previous_scroll_position,
7911 since,
7912 } = self.edit_prediction_preview
7913 {
7914 if let (Some(previous_scroll_position), Some(position_map)) =
7915 (previous_scroll_position, self.last_position_map.as_ref())
7916 {
7917 self.set_scroll_position(
7918 previous_scroll_position
7919 .scroll_position(&position_map.snapshot.display_snapshot),
7920 window,
7921 cx,
7922 );
7923 }
7924
7925 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7926 released_too_fast: since.elapsed() < Duration::from_millis(200),
7927 };
7928 self.clear_row_highlights::<EditPredictionPreview>();
7929 self.update_visible_edit_prediction(window, cx);
7930 cx.notify();
7931 }
7932 }
7933
7934 fn update_visible_edit_prediction(
7935 &mut self,
7936 _window: &mut Window,
7937 cx: &mut Context<Self>,
7938 ) -> Option<()> {
7939 if DisableAiSettings::get_global(cx).disable_ai {
7940 return None;
7941 }
7942
7943 if self.ime_transaction.is_some() {
7944 self.discard_edit_prediction(false, cx);
7945 return None;
7946 }
7947
7948 let selection = self.selections.newest_anchor();
7949 let cursor = selection.head();
7950 let multibuffer = self.buffer.read(cx).snapshot(cx);
7951 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7952 let excerpt_id = cursor.excerpt_id;
7953
7954 let show_in_menu = self.show_edit_predictions_in_menu();
7955 let completions_menu_has_precedence = !show_in_menu
7956 && (self.context_menu.borrow().is_some()
7957 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7958
7959 if completions_menu_has_precedence
7960 || !offset_selection.is_empty()
7961 || self
7962 .active_edit_prediction
7963 .as_ref()
7964 .is_some_and(|completion| {
7965 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7966 return false;
7967 };
7968 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7969 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7970 !invalidation_range.contains(&offset_selection.head())
7971 })
7972 {
7973 self.discard_edit_prediction(false, cx);
7974 return None;
7975 }
7976
7977 self.take_active_edit_prediction(cx);
7978 let Some(provider) = self.edit_prediction_provider() else {
7979 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7980 return None;
7981 };
7982
7983 let (buffer, cursor_buffer_position) =
7984 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7985
7986 self.edit_prediction_settings =
7987 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7988
7989 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7990
7991 if self.edit_prediction_indent_conflict {
7992 let cursor_point = cursor.to_point(&multibuffer);
7993
7994 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7995
7996 if let Some((_, indent)) = indents.iter().next()
7997 && indent.len == cursor_point.column
7998 {
7999 self.edit_prediction_indent_conflict = false;
8000 }
8001 }
8002
8003 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8004
8005 let (completion_id, edits, edit_preview) = match edit_prediction {
8006 edit_prediction::EditPrediction::Local {
8007 id,
8008 edits,
8009 edit_preview,
8010 } => (id, edits, edit_preview),
8011 edit_prediction::EditPrediction::Jump {
8012 id,
8013 snapshot,
8014 target,
8015 } => {
8016 self.stale_edit_prediction_in_menu = None;
8017 self.active_edit_prediction = Some(EditPredictionState {
8018 inlay_ids: vec![],
8019 completion: EditPrediction::MoveOutside { snapshot, target },
8020 completion_id: id,
8021 invalidation_range: None,
8022 });
8023 cx.notify();
8024 return Some(());
8025 }
8026 };
8027
8028 let edits = edits
8029 .into_iter()
8030 .flat_map(|(range, new_text)| {
8031 Some((
8032 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8033 new_text,
8034 ))
8035 })
8036 .collect::<Vec<_>>();
8037 if edits.is_empty() {
8038 return None;
8039 }
8040
8041 let first_edit_start = edits.first().unwrap().0.start;
8042 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8043 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8044
8045 let last_edit_end = edits.last().unwrap().0.end;
8046 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8047 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8048
8049 let cursor_row = cursor.to_point(&multibuffer).row;
8050
8051 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8052
8053 let mut inlay_ids = Vec::new();
8054 let invalidation_row_range;
8055 let move_invalidation_row_range = if cursor_row < edit_start_row {
8056 Some(cursor_row..edit_end_row)
8057 } else if cursor_row > edit_end_row {
8058 Some(edit_start_row..cursor_row)
8059 } else {
8060 None
8061 };
8062 let supports_jump = self
8063 .edit_prediction_provider
8064 .as_ref()
8065 .map(|provider| provider.provider.supports_jump_to_edit())
8066 .unwrap_or(true);
8067
8068 let is_move = supports_jump
8069 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8070 let completion = if is_move {
8071 invalidation_row_range =
8072 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8073 let target = first_edit_start;
8074 EditPrediction::MoveWithin { target, snapshot }
8075 } else {
8076 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8077 && !self.edit_predictions_hidden_for_vim_mode;
8078
8079 if show_completions_in_buffer {
8080 if let Some(provider) = &self.edit_prediction_provider {
8081 provider.provider.did_show(cx);
8082 }
8083 if edits
8084 .iter()
8085 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8086 {
8087 let mut inlays = Vec::new();
8088 for (range, new_text) in &edits {
8089 let inlay = Inlay::edit_prediction(
8090 post_inc(&mut self.next_inlay_id),
8091 range.start,
8092 new_text.as_ref(),
8093 );
8094 inlay_ids.push(inlay.id);
8095 inlays.push(inlay);
8096 }
8097
8098 self.splice_inlays(&[], inlays, cx);
8099 } else {
8100 let background_color = cx.theme().status().deleted_background;
8101 self.highlight_text::<EditPredictionHighlight>(
8102 edits.iter().map(|(range, _)| range.clone()).collect(),
8103 HighlightStyle {
8104 background_color: Some(background_color),
8105 ..Default::default()
8106 },
8107 cx,
8108 );
8109 }
8110 }
8111
8112 invalidation_row_range = edit_start_row..edit_end_row;
8113
8114 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8115 if provider.show_tab_accept_marker() {
8116 EditDisplayMode::TabAccept
8117 } else {
8118 EditDisplayMode::Inline
8119 }
8120 } else {
8121 EditDisplayMode::DiffPopover
8122 };
8123
8124 EditPrediction::Edit {
8125 edits,
8126 edit_preview,
8127 display_mode,
8128 snapshot,
8129 }
8130 };
8131
8132 let invalidation_range = multibuffer
8133 .anchor_before(Point::new(invalidation_row_range.start, 0))
8134 ..multibuffer.anchor_after(Point::new(
8135 invalidation_row_range.end,
8136 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8137 ));
8138
8139 self.stale_edit_prediction_in_menu = None;
8140 self.active_edit_prediction = Some(EditPredictionState {
8141 inlay_ids,
8142 completion,
8143 completion_id,
8144 invalidation_range: Some(invalidation_range),
8145 });
8146
8147 cx.notify();
8148
8149 Some(())
8150 }
8151
8152 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8153 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8154 }
8155
8156 fn clear_tasks(&mut self) {
8157 self.tasks.clear()
8158 }
8159
8160 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8161 if self.tasks.insert(key, value).is_some() {
8162 // This case should hopefully be rare, but just in case...
8163 log::error!(
8164 "multiple different run targets found on a single line, only the last target will be rendered"
8165 )
8166 }
8167 }
8168
8169 /// Get all display points of breakpoints that will be rendered within editor
8170 ///
8171 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8172 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8173 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8174 fn active_breakpoints(
8175 &self,
8176 range: Range<DisplayRow>,
8177 window: &mut Window,
8178 cx: &mut Context<Self>,
8179 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8180 let mut breakpoint_display_points = HashMap::default();
8181
8182 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8183 return breakpoint_display_points;
8184 };
8185
8186 let snapshot = self.snapshot(window, cx);
8187
8188 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8189 let Some(project) = self.project() else {
8190 return breakpoint_display_points;
8191 };
8192
8193 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8194 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8195
8196 for (buffer_snapshot, range, excerpt_id) in
8197 multi_buffer_snapshot.range_to_buffer_ranges(range)
8198 {
8199 let Some(buffer) = project
8200 .read(cx)
8201 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8202 else {
8203 continue;
8204 };
8205 let breakpoints = breakpoint_store.read(cx).breakpoints(
8206 &buffer,
8207 Some(
8208 buffer_snapshot.anchor_before(range.start)
8209 ..buffer_snapshot.anchor_after(range.end),
8210 ),
8211 buffer_snapshot,
8212 cx,
8213 );
8214 for (breakpoint, state) in breakpoints {
8215 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8216 let position = multi_buffer_anchor
8217 .to_point(&multi_buffer_snapshot)
8218 .to_display_point(&snapshot);
8219
8220 breakpoint_display_points.insert(
8221 position.row(),
8222 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8223 );
8224 }
8225 }
8226
8227 breakpoint_display_points
8228 }
8229
8230 fn breakpoint_context_menu(
8231 &self,
8232 anchor: Anchor,
8233 window: &mut Window,
8234 cx: &mut Context<Self>,
8235 ) -> Entity<ui::ContextMenu> {
8236 let weak_editor = cx.weak_entity();
8237 let focus_handle = self.focus_handle(cx);
8238
8239 let row = self
8240 .buffer
8241 .read(cx)
8242 .snapshot(cx)
8243 .summary_for_anchor::<Point>(&anchor)
8244 .row;
8245
8246 let breakpoint = self
8247 .breakpoint_at_row(row, window, cx)
8248 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8249
8250 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8251 "Edit Log Breakpoint"
8252 } else {
8253 "Set Log Breakpoint"
8254 };
8255
8256 let condition_breakpoint_msg = if breakpoint
8257 .as_ref()
8258 .is_some_and(|bp| bp.1.condition.is_some())
8259 {
8260 "Edit Condition Breakpoint"
8261 } else {
8262 "Set Condition Breakpoint"
8263 };
8264
8265 let hit_condition_breakpoint_msg = if breakpoint
8266 .as_ref()
8267 .is_some_and(|bp| bp.1.hit_condition.is_some())
8268 {
8269 "Edit Hit Condition Breakpoint"
8270 } else {
8271 "Set Hit Condition Breakpoint"
8272 };
8273
8274 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8275 "Unset Breakpoint"
8276 } else {
8277 "Set Breakpoint"
8278 };
8279
8280 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8281
8282 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8283 BreakpointState::Enabled => Some("Disable"),
8284 BreakpointState::Disabled => Some("Enable"),
8285 });
8286
8287 let (anchor, breakpoint) =
8288 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8289
8290 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8291 menu.on_blur_subscription(Subscription::new(|| {}))
8292 .context(focus_handle)
8293 .when(run_to_cursor, |this| {
8294 let weak_editor = weak_editor.clone();
8295 this.entry("Run to cursor", None, move |window, cx| {
8296 weak_editor
8297 .update(cx, |editor, cx| {
8298 editor.change_selections(
8299 SelectionEffects::no_scroll(),
8300 window,
8301 cx,
8302 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8303 );
8304 })
8305 .ok();
8306
8307 window.dispatch_action(Box::new(RunToCursor), cx);
8308 })
8309 .separator()
8310 })
8311 .when_some(toggle_state_msg, |this, msg| {
8312 this.entry(msg, None, {
8313 let weak_editor = weak_editor.clone();
8314 let breakpoint = breakpoint.clone();
8315 move |_window, cx| {
8316 weak_editor
8317 .update(cx, |this, cx| {
8318 this.edit_breakpoint_at_anchor(
8319 anchor,
8320 breakpoint.as_ref().clone(),
8321 BreakpointEditAction::InvertState,
8322 cx,
8323 );
8324 })
8325 .log_err();
8326 }
8327 })
8328 })
8329 .entry(set_breakpoint_msg, None, {
8330 let weak_editor = weak_editor.clone();
8331 let breakpoint = breakpoint.clone();
8332 move |_window, cx| {
8333 weak_editor
8334 .update(cx, |this, cx| {
8335 this.edit_breakpoint_at_anchor(
8336 anchor,
8337 breakpoint.as_ref().clone(),
8338 BreakpointEditAction::Toggle,
8339 cx,
8340 );
8341 })
8342 .log_err();
8343 }
8344 })
8345 .entry(log_breakpoint_msg, None, {
8346 let breakpoint = breakpoint.clone();
8347 let weak_editor = weak_editor.clone();
8348 move |window, cx| {
8349 weak_editor
8350 .update(cx, |this, cx| {
8351 this.add_edit_breakpoint_block(
8352 anchor,
8353 breakpoint.as_ref(),
8354 BreakpointPromptEditAction::Log,
8355 window,
8356 cx,
8357 );
8358 })
8359 .log_err();
8360 }
8361 })
8362 .entry(condition_breakpoint_msg, None, {
8363 let breakpoint = breakpoint.clone();
8364 let weak_editor = weak_editor.clone();
8365 move |window, cx| {
8366 weak_editor
8367 .update(cx, |this, cx| {
8368 this.add_edit_breakpoint_block(
8369 anchor,
8370 breakpoint.as_ref(),
8371 BreakpointPromptEditAction::Condition,
8372 window,
8373 cx,
8374 );
8375 })
8376 .log_err();
8377 }
8378 })
8379 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8380 weak_editor
8381 .update(cx, |this, cx| {
8382 this.add_edit_breakpoint_block(
8383 anchor,
8384 breakpoint.as_ref(),
8385 BreakpointPromptEditAction::HitCondition,
8386 window,
8387 cx,
8388 );
8389 })
8390 .log_err();
8391 })
8392 })
8393 }
8394
8395 fn render_breakpoint(
8396 &self,
8397 position: Anchor,
8398 row: DisplayRow,
8399 breakpoint: &Breakpoint,
8400 state: Option<BreakpointSessionState>,
8401 cx: &mut Context<Self>,
8402 ) -> IconButton {
8403 let is_rejected = state.is_some_and(|s| !s.verified);
8404 // Is it a breakpoint that shows up when hovering over gutter?
8405 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8406 (false, false),
8407 |PhantomBreakpointIndicator {
8408 is_active,
8409 display_row,
8410 collides_with_existing_breakpoint,
8411 }| {
8412 (
8413 is_active && display_row == row,
8414 collides_with_existing_breakpoint,
8415 )
8416 },
8417 );
8418
8419 let (color, icon) = {
8420 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8421 (false, false) => ui::IconName::DebugBreakpoint,
8422 (true, false) => ui::IconName::DebugLogBreakpoint,
8423 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8424 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8425 };
8426
8427 let color = cx.theme().colors();
8428
8429 let color = if is_phantom {
8430 if collides_with_existing {
8431 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8432 } else {
8433 Color::Hint
8434 }
8435 } else if is_rejected {
8436 Color::Disabled
8437 } else {
8438 Color::Debugger
8439 };
8440
8441 (color, icon)
8442 };
8443
8444 let breakpoint = Arc::from(breakpoint.clone());
8445
8446 let alt_as_text = gpui::Keystroke {
8447 modifiers: Modifiers::secondary_key(),
8448 ..Default::default()
8449 };
8450 let primary_action_text = if breakpoint.is_disabled() {
8451 "Enable breakpoint"
8452 } else if is_phantom && !collides_with_existing {
8453 "Set breakpoint"
8454 } else {
8455 "Unset breakpoint"
8456 };
8457 let focus_handle = self.focus_handle.clone();
8458
8459 let meta = if is_rejected {
8460 SharedString::from("No executable code is associated with this line.")
8461 } else if collides_with_existing && !breakpoint.is_disabled() {
8462 SharedString::from(format!(
8463 "{alt_as_text}-click to disable,\nright-click for more options."
8464 ))
8465 } else {
8466 SharedString::from("Right-click for more options.")
8467 };
8468 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8469 .icon_size(IconSize::XSmall)
8470 .size(ui::ButtonSize::None)
8471 .when(is_rejected, |this| {
8472 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8473 })
8474 .icon_color(color)
8475 .style(ButtonStyle::Transparent)
8476 .on_click(cx.listener({
8477 move |editor, event: &ClickEvent, window, cx| {
8478 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8479 BreakpointEditAction::InvertState
8480 } else {
8481 BreakpointEditAction::Toggle
8482 };
8483
8484 window.focus(&editor.focus_handle(cx));
8485 editor.edit_breakpoint_at_anchor(
8486 position,
8487 breakpoint.as_ref().clone(),
8488 edit_action,
8489 cx,
8490 );
8491 }
8492 }))
8493 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8494 editor.set_breakpoint_context_menu(
8495 row,
8496 Some(position),
8497 event.position(),
8498 window,
8499 cx,
8500 );
8501 }))
8502 .tooltip(move |_window, cx| {
8503 Tooltip::with_meta_in(
8504 primary_action_text,
8505 Some(&ToggleBreakpoint),
8506 meta.clone(),
8507 &focus_handle,
8508 cx,
8509 )
8510 })
8511 }
8512
8513 fn build_tasks_context(
8514 project: &Entity<Project>,
8515 buffer: &Entity<Buffer>,
8516 buffer_row: u32,
8517 tasks: &Arc<RunnableTasks>,
8518 cx: &mut Context<Self>,
8519 ) -> Task<Option<task::TaskContext>> {
8520 let position = Point::new(buffer_row, tasks.column);
8521 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8522 let location = Location {
8523 buffer: buffer.clone(),
8524 range: range_start..range_start,
8525 };
8526 // Fill in the environmental variables from the tree-sitter captures
8527 let mut captured_task_variables = TaskVariables::default();
8528 for (capture_name, value) in tasks.extra_variables.clone() {
8529 captured_task_variables.insert(
8530 task::VariableName::Custom(capture_name.into()),
8531 value.clone(),
8532 );
8533 }
8534 project.update(cx, |project, cx| {
8535 project.task_store().update(cx, |task_store, cx| {
8536 task_store.task_context_for_location(captured_task_variables, location, cx)
8537 })
8538 })
8539 }
8540
8541 pub fn spawn_nearest_task(
8542 &mut self,
8543 action: &SpawnNearestTask,
8544 window: &mut Window,
8545 cx: &mut Context<Self>,
8546 ) {
8547 let Some((workspace, _)) = self.workspace.clone() else {
8548 return;
8549 };
8550 let Some(project) = self.project.clone() else {
8551 return;
8552 };
8553
8554 // Try to find a closest, enclosing node using tree-sitter that has a task
8555 let Some((buffer, buffer_row, tasks)) = self
8556 .find_enclosing_node_task(cx)
8557 // Or find the task that's closest in row-distance.
8558 .or_else(|| self.find_closest_task(cx))
8559 else {
8560 return;
8561 };
8562
8563 let reveal_strategy = action.reveal;
8564 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8565 cx.spawn_in(window, async move |_, cx| {
8566 let context = task_context.await?;
8567 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8568
8569 let resolved = &mut resolved_task.resolved;
8570 resolved.reveal = reveal_strategy;
8571
8572 workspace
8573 .update_in(cx, |workspace, window, cx| {
8574 workspace.schedule_resolved_task(
8575 task_source_kind,
8576 resolved_task,
8577 false,
8578 window,
8579 cx,
8580 );
8581 })
8582 .ok()
8583 })
8584 .detach();
8585 }
8586
8587 fn find_closest_task(
8588 &mut self,
8589 cx: &mut Context<Self>,
8590 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8591 let cursor_row = self
8592 .selections
8593 .newest_adjusted(&self.display_snapshot(cx))
8594 .head()
8595 .row;
8596
8597 let ((buffer_id, row), tasks) = self
8598 .tasks
8599 .iter()
8600 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8601
8602 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8603 let tasks = Arc::new(tasks.to_owned());
8604 Some((buffer, *row, tasks))
8605 }
8606
8607 fn find_enclosing_node_task(
8608 &mut self,
8609 cx: &mut Context<Self>,
8610 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8611 let snapshot = self.buffer.read(cx).snapshot(cx);
8612 let offset = self
8613 .selections
8614 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8615 .head();
8616 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8617 let offset = excerpt.map_offset_to_buffer(offset);
8618 let buffer_id = excerpt.buffer().remote_id();
8619
8620 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8621 let mut cursor = layer.node().walk();
8622
8623 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8624 if cursor.node().end_byte() == offset.0 {
8625 cursor.goto_next_sibling();
8626 }
8627 }
8628
8629 // Ascend to the smallest ancestor that contains the range and has a task.
8630 loop {
8631 let node = cursor.node();
8632 let node_range = node.byte_range();
8633 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8634
8635 // Check if this node contains our offset
8636 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8637 // If it contains offset, check for task
8638 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8639 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8640 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8641 }
8642 }
8643
8644 if !cursor.goto_parent() {
8645 break;
8646 }
8647 }
8648 None
8649 }
8650
8651 fn render_run_indicator(
8652 &self,
8653 _style: &EditorStyle,
8654 is_active: bool,
8655 row: DisplayRow,
8656 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8657 cx: &mut Context<Self>,
8658 ) -> IconButton {
8659 let color = Color::Muted;
8660 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8661
8662 IconButton::new(
8663 ("run_indicator", row.0 as usize),
8664 ui::IconName::PlayOutlined,
8665 )
8666 .shape(ui::IconButtonShape::Square)
8667 .icon_size(IconSize::XSmall)
8668 .icon_color(color)
8669 .toggle_state(is_active)
8670 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8671 let quick_launch = match e {
8672 ClickEvent::Keyboard(_) => true,
8673 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8674 };
8675
8676 window.focus(&editor.focus_handle(cx));
8677 editor.toggle_code_actions(
8678 &ToggleCodeActions {
8679 deployed_from: Some(CodeActionSource::RunMenu(row)),
8680 quick_launch,
8681 },
8682 window,
8683 cx,
8684 );
8685 }))
8686 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8687 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8688 }))
8689 }
8690
8691 pub fn context_menu_visible(&self) -> bool {
8692 !self.edit_prediction_preview_is_active()
8693 && self
8694 .context_menu
8695 .borrow()
8696 .as_ref()
8697 .is_some_and(|menu| menu.visible())
8698 }
8699
8700 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8701 self.context_menu
8702 .borrow()
8703 .as_ref()
8704 .map(|menu| menu.origin())
8705 }
8706
8707 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8708 self.context_menu_options = Some(options);
8709 }
8710
8711 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8712 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8713
8714 fn render_edit_prediction_popover(
8715 &mut self,
8716 text_bounds: &Bounds<Pixels>,
8717 content_origin: gpui::Point<Pixels>,
8718 right_margin: Pixels,
8719 editor_snapshot: &EditorSnapshot,
8720 visible_row_range: Range<DisplayRow>,
8721 scroll_top: ScrollOffset,
8722 scroll_bottom: ScrollOffset,
8723 line_layouts: &[LineWithInvisibles],
8724 line_height: Pixels,
8725 scroll_position: gpui::Point<ScrollOffset>,
8726 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8727 newest_selection_head: Option<DisplayPoint>,
8728 editor_width: Pixels,
8729 style: &EditorStyle,
8730 window: &mut Window,
8731 cx: &mut App,
8732 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8733 if self.mode().is_minimap() {
8734 return None;
8735 }
8736 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8737
8738 if self.edit_prediction_visible_in_cursor_popover(true) {
8739 return None;
8740 }
8741
8742 match &active_edit_prediction.completion {
8743 EditPrediction::MoveWithin { target, .. } => {
8744 let target_display_point = target.to_display_point(editor_snapshot);
8745
8746 if self.edit_prediction_requires_modifier() {
8747 if !self.edit_prediction_preview_is_active() {
8748 return None;
8749 }
8750
8751 self.render_edit_prediction_modifier_jump_popover(
8752 text_bounds,
8753 content_origin,
8754 visible_row_range,
8755 line_layouts,
8756 line_height,
8757 scroll_pixel_position,
8758 newest_selection_head,
8759 target_display_point,
8760 window,
8761 cx,
8762 )
8763 } else {
8764 self.render_edit_prediction_eager_jump_popover(
8765 text_bounds,
8766 content_origin,
8767 editor_snapshot,
8768 visible_row_range,
8769 scroll_top,
8770 scroll_bottom,
8771 line_height,
8772 scroll_pixel_position,
8773 target_display_point,
8774 editor_width,
8775 window,
8776 cx,
8777 )
8778 }
8779 }
8780 EditPrediction::Edit {
8781 display_mode: EditDisplayMode::Inline,
8782 ..
8783 } => None,
8784 EditPrediction::Edit {
8785 display_mode: EditDisplayMode::TabAccept,
8786 edits,
8787 ..
8788 } => {
8789 let range = &edits.first()?.0;
8790 let target_display_point = range.end.to_display_point(editor_snapshot);
8791
8792 self.render_edit_prediction_end_of_line_popover(
8793 "Accept",
8794 editor_snapshot,
8795 visible_row_range,
8796 target_display_point,
8797 line_height,
8798 scroll_pixel_position,
8799 content_origin,
8800 editor_width,
8801 window,
8802 cx,
8803 )
8804 }
8805 EditPrediction::Edit {
8806 edits,
8807 edit_preview,
8808 display_mode: EditDisplayMode::DiffPopover,
8809 snapshot,
8810 } => self.render_edit_prediction_diff_popover(
8811 text_bounds,
8812 content_origin,
8813 right_margin,
8814 editor_snapshot,
8815 visible_row_range,
8816 line_layouts,
8817 line_height,
8818 scroll_position,
8819 scroll_pixel_position,
8820 newest_selection_head,
8821 editor_width,
8822 style,
8823 edits,
8824 edit_preview,
8825 snapshot,
8826 window,
8827 cx,
8828 ),
8829 EditPrediction::MoveOutside { snapshot, .. } => {
8830 let mut element = self
8831 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8832 .into_any();
8833
8834 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8835 let origin_x = text_bounds.size.width - size.width - px(30.);
8836 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8837 element.prepaint_at(origin, window, cx);
8838
8839 Some((element, origin))
8840 }
8841 }
8842 }
8843
8844 fn render_edit_prediction_modifier_jump_popover(
8845 &mut self,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 visible_row_range: Range<DisplayRow>,
8849 line_layouts: &[LineWithInvisibles],
8850 line_height: Pixels,
8851 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8852 newest_selection_head: Option<DisplayPoint>,
8853 target_display_point: DisplayPoint,
8854 window: &mut Window,
8855 cx: &mut App,
8856 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8857 let scrolled_content_origin =
8858 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8859
8860 const SCROLL_PADDING_Y: Pixels = px(12.);
8861
8862 if target_display_point.row() < visible_row_range.start {
8863 return self.render_edit_prediction_scroll_popover(
8864 |_| SCROLL_PADDING_Y,
8865 IconName::ArrowUp,
8866 visible_row_range,
8867 line_layouts,
8868 newest_selection_head,
8869 scrolled_content_origin,
8870 window,
8871 cx,
8872 );
8873 } else if target_display_point.row() >= visible_row_range.end {
8874 return self.render_edit_prediction_scroll_popover(
8875 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8876 IconName::ArrowDown,
8877 visible_row_range,
8878 line_layouts,
8879 newest_selection_head,
8880 scrolled_content_origin,
8881 window,
8882 cx,
8883 );
8884 }
8885
8886 const POLE_WIDTH: Pixels = px(2.);
8887
8888 let line_layout =
8889 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8890 let target_column = target_display_point.column() as usize;
8891
8892 let target_x = line_layout.x_for_index(target_column);
8893 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8894 - scroll_pixel_position.y;
8895
8896 let flag_on_right = target_x < text_bounds.size.width / 2.;
8897
8898 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8899 border_color.l += 0.001;
8900
8901 let mut element = v_flex()
8902 .items_end()
8903 .when(flag_on_right, |el| el.items_start())
8904 .child(if flag_on_right {
8905 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8906 .rounded_bl(px(0.))
8907 .rounded_tl(px(0.))
8908 .border_l_2()
8909 .border_color(border_color)
8910 } else {
8911 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8912 .rounded_br(px(0.))
8913 .rounded_tr(px(0.))
8914 .border_r_2()
8915 .border_color(border_color)
8916 })
8917 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8918 .into_any();
8919
8920 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8921
8922 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8923 - point(
8924 if flag_on_right {
8925 POLE_WIDTH
8926 } else {
8927 size.width - POLE_WIDTH
8928 },
8929 size.height - line_height,
8930 );
8931
8932 origin.x = origin.x.max(content_origin.x);
8933
8934 element.prepaint_at(origin, window, cx);
8935
8936 Some((element, origin))
8937 }
8938
8939 fn render_edit_prediction_scroll_popover(
8940 &mut self,
8941 to_y: impl Fn(Size<Pixels>) -> Pixels,
8942 scroll_icon: IconName,
8943 visible_row_range: Range<DisplayRow>,
8944 line_layouts: &[LineWithInvisibles],
8945 newest_selection_head: Option<DisplayPoint>,
8946 scrolled_content_origin: gpui::Point<Pixels>,
8947 window: &mut Window,
8948 cx: &mut App,
8949 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8950 let mut element = self
8951 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8952 .into_any();
8953
8954 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8955
8956 let cursor = newest_selection_head?;
8957 let cursor_row_layout =
8958 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8959 let cursor_column = cursor.column() as usize;
8960
8961 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8962
8963 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8964
8965 element.prepaint_at(origin, window, cx);
8966 Some((element, origin))
8967 }
8968
8969 fn render_edit_prediction_eager_jump_popover(
8970 &mut self,
8971 text_bounds: &Bounds<Pixels>,
8972 content_origin: gpui::Point<Pixels>,
8973 editor_snapshot: &EditorSnapshot,
8974 visible_row_range: Range<DisplayRow>,
8975 scroll_top: ScrollOffset,
8976 scroll_bottom: ScrollOffset,
8977 line_height: Pixels,
8978 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8979 target_display_point: DisplayPoint,
8980 editor_width: Pixels,
8981 window: &mut Window,
8982 cx: &mut App,
8983 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8984 if target_display_point.row().as_f64() < scroll_top {
8985 let mut element = self
8986 .render_edit_prediction_line_popover(
8987 "Jump to Edit",
8988 Some(IconName::ArrowUp),
8989 window,
8990 cx,
8991 )
8992 .into_any();
8993
8994 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8995 let offset = point(
8996 (text_bounds.size.width - size.width) / 2.,
8997 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8998 );
8999
9000 let origin = text_bounds.origin + offset;
9001 element.prepaint_at(origin, window, cx);
9002 Some((element, origin))
9003 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9004 let mut element = self
9005 .render_edit_prediction_line_popover(
9006 "Jump to Edit",
9007 Some(IconName::ArrowDown),
9008 window,
9009 cx,
9010 )
9011 .into_any();
9012
9013 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9014 let offset = point(
9015 (text_bounds.size.width - size.width) / 2.,
9016 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9017 );
9018
9019 let origin = text_bounds.origin + offset;
9020 element.prepaint_at(origin, window, cx);
9021 Some((element, origin))
9022 } else {
9023 self.render_edit_prediction_end_of_line_popover(
9024 "Jump to Edit",
9025 editor_snapshot,
9026 visible_row_range,
9027 target_display_point,
9028 line_height,
9029 scroll_pixel_position,
9030 content_origin,
9031 editor_width,
9032 window,
9033 cx,
9034 )
9035 }
9036 }
9037
9038 fn render_edit_prediction_end_of_line_popover(
9039 self: &mut Editor,
9040 label: &'static str,
9041 editor_snapshot: &EditorSnapshot,
9042 visible_row_range: Range<DisplayRow>,
9043 target_display_point: DisplayPoint,
9044 line_height: Pixels,
9045 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9046 content_origin: gpui::Point<Pixels>,
9047 editor_width: Pixels,
9048 window: &mut Window,
9049 cx: &mut App,
9050 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9051 let target_line_end = DisplayPoint::new(
9052 target_display_point.row(),
9053 editor_snapshot.line_len(target_display_point.row()),
9054 );
9055
9056 let mut element = self
9057 .render_edit_prediction_line_popover(label, None, window, cx)
9058 .into_any();
9059
9060 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9061
9062 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9063
9064 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9065 let mut origin = start_point
9066 + line_origin
9067 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9068 origin.x = origin.x.max(content_origin.x);
9069
9070 let max_x = content_origin.x + editor_width - size.width;
9071
9072 if origin.x > max_x {
9073 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9074
9075 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9076 origin.y += offset;
9077 IconName::ArrowUp
9078 } else {
9079 origin.y -= offset;
9080 IconName::ArrowDown
9081 };
9082
9083 element = self
9084 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9085 .into_any();
9086
9087 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9088
9089 origin.x = content_origin.x + editor_width - size.width - px(2.);
9090 }
9091
9092 element.prepaint_at(origin, window, cx);
9093 Some((element, origin))
9094 }
9095
9096 fn render_edit_prediction_diff_popover(
9097 self: &Editor,
9098 text_bounds: &Bounds<Pixels>,
9099 content_origin: gpui::Point<Pixels>,
9100 right_margin: Pixels,
9101 editor_snapshot: &EditorSnapshot,
9102 visible_row_range: Range<DisplayRow>,
9103 line_layouts: &[LineWithInvisibles],
9104 line_height: Pixels,
9105 scroll_position: gpui::Point<ScrollOffset>,
9106 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9107 newest_selection_head: Option<DisplayPoint>,
9108 editor_width: Pixels,
9109 style: &EditorStyle,
9110 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9111 edit_preview: &Option<language::EditPreview>,
9112 snapshot: &language::BufferSnapshot,
9113 window: &mut Window,
9114 cx: &mut App,
9115 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9116 let edit_start = edits
9117 .first()
9118 .unwrap()
9119 .0
9120 .start
9121 .to_display_point(editor_snapshot);
9122 let edit_end = edits
9123 .last()
9124 .unwrap()
9125 .0
9126 .end
9127 .to_display_point(editor_snapshot);
9128
9129 let is_visible = visible_row_range.contains(&edit_start.row())
9130 || visible_row_range.contains(&edit_end.row());
9131 if !is_visible {
9132 return None;
9133 }
9134
9135 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9136 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9137 } else {
9138 // Fallback for providers without edit_preview
9139 crate::edit_prediction_fallback_text(edits, cx)
9140 };
9141
9142 let styled_text = highlighted_edits.to_styled_text(&style.text);
9143 let line_count = highlighted_edits.text.lines().count();
9144
9145 const BORDER_WIDTH: Pixels = px(1.);
9146
9147 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9148 let has_keybind = keybind.is_some();
9149
9150 let mut element = h_flex()
9151 .items_start()
9152 .child(
9153 h_flex()
9154 .bg(cx.theme().colors().editor_background)
9155 .border(BORDER_WIDTH)
9156 .shadow_xs()
9157 .border_color(cx.theme().colors().border)
9158 .rounded_l_lg()
9159 .when(line_count > 1, |el| el.rounded_br_lg())
9160 .pr_1()
9161 .child(styled_text),
9162 )
9163 .child(
9164 h_flex()
9165 .h(line_height + BORDER_WIDTH * 2.)
9166 .px_1p5()
9167 .gap_1()
9168 // Workaround: For some reason, there's a gap if we don't do this
9169 .ml(-BORDER_WIDTH)
9170 .shadow(vec![gpui::BoxShadow {
9171 color: gpui::black().opacity(0.05),
9172 offset: point(px(1.), px(1.)),
9173 blur_radius: px(2.),
9174 spread_radius: px(0.),
9175 }])
9176 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9177 .border(BORDER_WIDTH)
9178 .border_color(cx.theme().colors().border)
9179 .rounded_r_lg()
9180 .id("edit_prediction_diff_popover_keybind")
9181 .when(!has_keybind, |el| {
9182 let status_colors = cx.theme().status();
9183
9184 el.bg(status_colors.error_background)
9185 .border_color(status_colors.error.opacity(0.6))
9186 .child(Icon::new(IconName::Info).color(Color::Error))
9187 .cursor_default()
9188 .hoverable_tooltip(move |_window, cx| {
9189 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9190 })
9191 })
9192 .children(keybind),
9193 )
9194 .into_any();
9195
9196 let longest_row =
9197 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9198 let longest_line_width = if visible_row_range.contains(&longest_row) {
9199 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9200 } else {
9201 layout_line(
9202 longest_row,
9203 editor_snapshot,
9204 style,
9205 editor_width,
9206 |_| false,
9207 window,
9208 cx,
9209 )
9210 .width
9211 };
9212
9213 let viewport_bounds =
9214 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9215 right: -right_margin,
9216 ..Default::default()
9217 });
9218
9219 let x_after_longest = Pixels::from(
9220 ScrollPixelOffset::from(
9221 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9222 ) - scroll_pixel_position.x,
9223 );
9224
9225 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9226
9227 // Fully visible if it can be displayed within the window (allow overlapping other
9228 // panes). However, this is only allowed if the popover starts within text_bounds.
9229 let can_position_to_the_right = x_after_longest < text_bounds.right()
9230 && x_after_longest + element_bounds.width < viewport_bounds.right();
9231
9232 let mut origin = if can_position_to_the_right {
9233 point(
9234 x_after_longest,
9235 text_bounds.origin.y
9236 + Pixels::from(
9237 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9238 - scroll_pixel_position.y,
9239 ),
9240 )
9241 } else {
9242 let cursor_row = newest_selection_head.map(|head| head.row());
9243 let above_edit = edit_start
9244 .row()
9245 .0
9246 .checked_sub(line_count as u32)
9247 .map(DisplayRow);
9248 let below_edit = Some(edit_end.row() + 1);
9249 let above_cursor =
9250 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9251 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9252
9253 // Place the edit popover adjacent to the edit if there is a location
9254 // available that is onscreen and does not obscure the cursor. Otherwise,
9255 // place it adjacent to the cursor.
9256 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9257 .into_iter()
9258 .flatten()
9259 .find(|&start_row| {
9260 let end_row = start_row + line_count as u32;
9261 visible_row_range.contains(&start_row)
9262 && visible_row_range.contains(&end_row)
9263 && cursor_row
9264 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9265 })?;
9266
9267 content_origin
9268 + point(
9269 Pixels::from(-scroll_pixel_position.x),
9270 Pixels::from(
9271 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9272 ),
9273 )
9274 };
9275
9276 origin.x -= BORDER_WIDTH;
9277
9278 window.defer_draw(element, origin, 1);
9279
9280 // Do not return an element, since it will already be drawn due to defer_draw.
9281 None
9282 }
9283
9284 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9285 px(30.)
9286 }
9287
9288 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9289 if self.read_only(cx) {
9290 cx.theme().players().read_only()
9291 } else {
9292 self.style.as_ref().unwrap().local_player
9293 }
9294 }
9295
9296 fn render_edit_prediction_accept_keybind(
9297 &self,
9298 window: &mut Window,
9299 cx: &mut App,
9300 ) -> Option<AnyElement> {
9301 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9302 let accept_keystroke = accept_binding.keystroke()?;
9303
9304 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9305
9306 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9307 Color::Accent
9308 } else {
9309 Color::Muted
9310 };
9311
9312 h_flex()
9313 .px_0p5()
9314 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9315 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9316 .text_size(TextSize::XSmall.rems(cx))
9317 .child(h_flex().children(ui::render_modifiers(
9318 accept_keystroke.modifiers(),
9319 PlatformStyle::platform(),
9320 Some(modifiers_color),
9321 Some(IconSize::XSmall.rems().into()),
9322 true,
9323 )))
9324 .when(is_platform_style_mac, |parent| {
9325 parent.child(accept_keystroke.key().to_string())
9326 })
9327 .when(!is_platform_style_mac, |parent| {
9328 parent.child(
9329 Key::new(
9330 util::capitalize(accept_keystroke.key()),
9331 Some(Color::Default),
9332 )
9333 .size(Some(IconSize::XSmall.rems().into())),
9334 )
9335 })
9336 .into_any()
9337 .into()
9338 }
9339
9340 fn render_edit_prediction_line_popover(
9341 &self,
9342 label: impl Into<SharedString>,
9343 icon: Option<IconName>,
9344 window: &mut Window,
9345 cx: &mut App,
9346 ) -> Stateful<Div> {
9347 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9348
9349 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9350 let has_keybind = keybind.is_some();
9351
9352 h_flex()
9353 .id("ep-line-popover")
9354 .py_0p5()
9355 .pl_1()
9356 .pr(padding_right)
9357 .gap_1()
9358 .rounded_md()
9359 .border_1()
9360 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9361 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9362 .shadow_xs()
9363 .when(!has_keybind, |el| {
9364 let status_colors = cx.theme().status();
9365
9366 el.bg(status_colors.error_background)
9367 .border_color(status_colors.error.opacity(0.6))
9368 .pl_2()
9369 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9370 .cursor_default()
9371 .hoverable_tooltip(move |_window, cx| {
9372 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9373 })
9374 })
9375 .children(keybind)
9376 .child(
9377 Label::new(label)
9378 .size(LabelSize::Small)
9379 .when(!has_keybind, |el| {
9380 el.color(cx.theme().status().error.into()).strikethrough()
9381 }),
9382 )
9383 .when(!has_keybind, |el| {
9384 el.child(
9385 h_flex().ml_1().child(
9386 Icon::new(IconName::Info)
9387 .size(IconSize::Small)
9388 .color(cx.theme().status().error.into()),
9389 ),
9390 )
9391 })
9392 .when_some(icon, |element, icon| {
9393 element.child(
9394 div()
9395 .mt(px(1.5))
9396 .child(Icon::new(icon).size(IconSize::Small)),
9397 )
9398 })
9399 }
9400
9401 fn render_edit_prediction_jump_outside_popover(
9402 &self,
9403 snapshot: &BufferSnapshot,
9404 window: &mut Window,
9405 cx: &mut App,
9406 ) -> Stateful<Div> {
9407 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9408 let has_keybind = keybind.is_some();
9409
9410 let file_name = snapshot
9411 .file()
9412 .map(|file| SharedString::new(file.file_name(cx)))
9413 .unwrap_or(SharedString::new_static("untitled"));
9414
9415 h_flex()
9416 .id("ep-jump-outside-popover")
9417 .py_1()
9418 .px_2()
9419 .gap_1()
9420 .rounded_md()
9421 .border_1()
9422 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9423 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9424 .shadow_xs()
9425 .when(!has_keybind, |el| {
9426 let status_colors = cx.theme().status();
9427
9428 el.bg(status_colors.error_background)
9429 .border_color(status_colors.error.opacity(0.6))
9430 .pl_2()
9431 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9432 .cursor_default()
9433 .hoverable_tooltip(move |_window, cx| {
9434 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9435 })
9436 })
9437 .children(keybind)
9438 .child(
9439 Label::new(file_name)
9440 .size(LabelSize::Small)
9441 .buffer_font(cx)
9442 .when(!has_keybind, |el| {
9443 el.color(cx.theme().status().error.into()).strikethrough()
9444 }),
9445 )
9446 .when(!has_keybind, |el| {
9447 el.child(
9448 h_flex().ml_1().child(
9449 Icon::new(IconName::Info)
9450 .size(IconSize::Small)
9451 .color(cx.theme().status().error.into()),
9452 ),
9453 )
9454 })
9455 .child(
9456 div()
9457 .mt(px(1.5))
9458 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9459 )
9460 }
9461
9462 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9463 let accent_color = cx.theme().colors().text_accent;
9464 let editor_bg_color = cx.theme().colors().editor_background;
9465 editor_bg_color.blend(accent_color.opacity(0.1))
9466 }
9467
9468 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9469 let accent_color = cx.theme().colors().text_accent;
9470 let editor_bg_color = cx.theme().colors().editor_background;
9471 editor_bg_color.blend(accent_color.opacity(0.6))
9472 }
9473 fn get_prediction_provider_icon_name(
9474 provider: &Option<RegisteredEditPredictionProvider>,
9475 ) -> IconName {
9476 match provider {
9477 Some(provider) => match provider.provider.name() {
9478 "copilot" => IconName::Copilot,
9479 "supermaven" => IconName::Supermaven,
9480 _ => IconName::ZedPredict,
9481 },
9482 None => IconName::ZedPredict,
9483 }
9484 }
9485
9486 fn render_edit_prediction_cursor_popover(
9487 &self,
9488 min_width: Pixels,
9489 max_width: Pixels,
9490 cursor_point: Point,
9491 style: &EditorStyle,
9492 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9493 _window: &Window,
9494 cx: &mut Context<Editor>,
9495 ) -> Option<AnyElement> {
9496 let provider = self.edit_prediction_provider.as_ref()?;
9497 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9498
9499 let is_refreshing = provider.provider.is_refreshing(cx);
9500
9501 fn pending_completion_container(icon: IconName) -> Div {
9502 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9503 }
9504
9505 let completion = match &self.active_edit_prediction {
9506 Some(prediction) => {
9507 if !self.has_visible_completions_menu() {
9508 const RADIUS: Pixels = px(6.);
9509 const BORDER_WIDTH: Pixels = px(1.);
9510
9511 return Some(
9512 h_flex()
9513 .elevation_2(cx)
9514 .border(BORDER_WIDTH)
9515 .border_color(cx.theme().colors().border)
9516 .when(accept_keystroke.is_none(), |el| {
9517 el.border_color(cx.theme().status().error)
9518 })
9519 .rounded(RADIUS)
9520 .rounded_tl(px(0.))
9521 .overflow_hidden()
9522 .child(div().px_1p5().child(match &prediction.completion {
9523 EditPrediction::MoveWithin { target, snapshot } => {
9524 use text::ToPoint as _;
9525 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9526 {
9527 Icon::new(IconName::ZedPredictDown)
9528 } else {
9529 Icon::new(IconName::ZedPredictUp)
9530 }
9531 }
9532 EditPrediction::MoveOutside { .. } => {
9533 // TODO [zeta2] custom icon for external jump?
9534 Icon::new(provider_icon)
9535 }
9536 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9537 }))
9538 .child(
9539 h_flex()
9540 .gap_1()
9541 .py_1()
9542 .px_2()
9543 .rounded_r(RADIUS - BORDER_WIDTH)
9544 .border_l_1()
9545 .border_color(cx.theme().colors().border)
9546 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9547 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9548 el.child(
9549 Label::new("Hold")
9550 .size(LabelSize::Small)
9551 .when(accept_keystroke.is_none(), |el| {
9552 el.strikethrough()
9553 })
9554 .line_height_style(LineHeightStyle::UiLabel),
9555 )
9556 })
9557 .id("edit_prediction_cursor_popover_keybind")
9558 .when(accept_keystroke.is_none(), |el| {
9559 let status_colors = cx.theme().status();
9560
9561 el.bg(status_colors.error_background)
9562 .border_color(status_colors.error.opacity(0.6))
9563 .child(Icon::new(IconName::Info).color(Color::Error))
9564 .cursor_default()
9565 .hoverable_tooltip(move |_window, cx| {
9566 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9567 .into()
9568 })
9569 })
9570 .when_some(
9571 accept_keystroke.as_ref(),
9572 |el, accept_keystroke| {
9573 el.child(h_flex().children(ui::render_modifiers(
9574 accept_keystroke.modifiers(),
9575 PlatformStyle::platform(),
9576 Some(Color::Default),
9577 Some(IconSize::XSmall.rems().into()),
9578 false,
9579 )))
9580 },
9581 ),
9582 )
9583 .into_any(),
9584 );
9585 }
9586
9587 self.render_edit_prediction_cursor_popover_preview(
9588 prediction,
9589 cursor_point,
9590 style,
9591 cx,
9592 )?
9593 }
9594
9595 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9596 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9597 stale_completion,
9598 cursor_point,
9599 style,
9600 cx,
9601 )?,
9602
9603 None => pending_completion_container(provider_icon)
9604 .child(Label::new("...").size(LabelSize::Small)),
9605 },
9606
9607 None => pending_completion_container(provider_icon)
9608 .child(Label::new("...").size(LabelSize::Small)),
9609 };
9610
9611 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9612 completion
9613 .with_animation(
9614 "loading-completion",
9615 Animation::new(Duration::from_secs(2))
9616 .repeat()
9617 .with_easing(pulsating_between(0.4, 0.8)),
9618 |label, delta| label.opacity(delta),
9619 )
9620 .into_any_element()
9621 } else {
9622 completion.into_any_element()
9623 };
9624
9625 let has_completion = self.active_edit_prediction.is_some();
9626
9627 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9628 Some(
9629 h_flex()
9630 .min_w(min_width)
9631 .max_w(max_width)
9632 .flex_1()
9633 .elevation_2(cx)
9634 .border_color(cx.theme().colors().border)
9635 .child(
9636 div()
9637 .flex_1()
9638 .py_1()
9639 .px_2()
9640 .overflow_hidden()
9641 .child(completion),
9642 )
9643 .when_some(accept_keystroke, |el, accept_keystroke| {
9644 if !accept_keystroke.modifiers().modified() {
9645 return el;
9646 }
9647
9648 el.child(
9649 h_flex()
9650 .h_full()
9651 .border_l_1()
9652 .rounded_r_lg()
9653 .border_color(cx.theme().colors().border)
9654 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9655 .gap_1()
9656 .py_1()
9657 .px_2()
9658 .child(
9659 h_flex()
9660 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9661 .when(is_platform_style_mac, |parent| parent.gap_1())
9662 .child(h_flex().children(ui::render_modifiers(
9663 accept_keystroke.modifiers(),
9664 PlatformStyle::platform(),
9665 Some(if !has_completion {
9666 Color::Muted
9667 } else {
9668 Color::Default
9669 }),
9670 None,
9671 false,
9672 ))),
9673 )
9674 .child(Label::new("Preview").into_any_element())
9675 .opacity(if has_completion { 1.0 } else { 0.4 }),
9676 )
9677 })
9678 .into_any(),
9679 )
9680 }
9681
9682 fn render_edit_prediction_cursor_popover_preview(
9683 &self,
9684 completion: &EditPredictionState,
9685 cursor_point: Point,
9686 style: &EditorStyle,
9687 cx: &mut Context<Editor>,
9688 ) -> Option<Div> {
9689 use text::ToPoint as _;
9690
9691 fn render_relative_row_jump(
9692 prefix: impl Into<String>,
9693 current_row: u32,
9694 target_row: u32,
9695 ) -> Div {
9696 let (row_diff, arrow) = if target_row < current_row {
9697 (current_row - target_row, IconName::ArrowUp)
9698 } else {
9699 (target_row - current_row, IconName::ArrowDown)
9700 };
9701
9702 h_flex()
9703 .child(
9704 Label::new(format!("{}{}", prefix.into(), row_diff))
9705 .color(Color::Muted)
9706 .size(LabelSize::Small),
9707 )
9708 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9709 }
9710
9711 let supports_jump = self
9712 .edit_prediction_provider
9713 .as_ref()
9714 .map(|provider| provider.provider.supports_jump_to_edit())
9715 .unwrap_or(true);
9716
9717 match &completion.completion {
9718 EditPrediction::MoveWithin {
9719 target, snapshot, ..
9720 } => {
9721 if !supports_jump {
9722 return None;
9723 }
9724
9725 Some(
9726 h_flex()
9727 .px_2()
9728 .gap_2()
9729 .flex_1()
9730 .child(
9731 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9732 Icon::new(IconName::ZedPredictDown)
9733 } else {
9734 Icon::new(IconName::ZedPredictUp)
9735 },
9736 )
9737 .child(Label::new("Jump to Edit")),
9738 )
9739 }
9740 EditPrediction::MoveOutside { snapshot, .. } => {
9741 let file_name = snapshot
9742 .file()
9743 .map(|file| file.file_name(cx))
9744 .unwrap_or("untitled");
9745 Some(
9746 h_flex()
9747 .px_2()
9748 .gap_2()
9749 .flex_1()
9750 .child(Icon::new(IconName::ZedPredict))
9751 .child(Label::new(format!("Jump to {file_name}"))),
9752 )
9753 }
9754 EditPrediction::Edit {
9755 edits,
9756 edit_preview,
9757 snapshot,
9758 display_mode: _,
9759 } => {
9760 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9761
9762 let (highlighted_edits, has_more_lines) =
9763 if let Some(edit_preview) = edit_preview.as_ref() {
9764 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9765 .first_line_preview()
9766 } else {
9767 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9768 };
9769
9770 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9771 .with_default_highlights(&style.text, highlighted_edits.highlights);
9772
9773 let preview = h_flex()
9774 .gap_1()
9775 .min_w_16()
9776 .child(styled_text)
9777 .when(has_more_lines, |parent| parent.child("…"));
9778
9779 let left = if supports_jump && first_edit_row != cursor_point.row {
9780 render_relative_row_jump("", cursor_point.row, first_edit_row)
9781 .into_any_element()
9782 } else {
9783 let icon_name =
9784 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9785 Icon::new(icon_name).into_any_element()
9786 };
9787
9788 Some(
9789 h_flex()
9790 .h_full()
9791 .flex_1()
9792 .gap_2()
9793 .pr_1()
9794 .overflow_x_hidden()
9795 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9796 .child(left)
9797 .child(preview),
9798 )
9799 }
9800 }
9801 }
9802
9803 pub fn render_context_menu(
9804 &self,
9805 style: &EditorStyle,
9806 max_height_in_lines: u32,
9807 window: &mut Window,
9808 cx: &mut Context<Editor>,
9809 ) -> Option<AnyElement> {
9810 let menu = self.context_menu.borrow();
9811 let menu = menu.as_ref()?;
9812 if !menu.visible() {
9813 return None;
9814 };
9815 Some(menu.render(style, max_height_in_lines, window, cx))
9816 }
9817
9818 fn render_context_menu_aside(
9819 &mut self,
9820 max_size: Size<Pixels>,
9821 window: &mut Window,
9822 cx: &mut Context<Editor>,
9823 ) -> Option<AnyElement> {
9824 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9825 if menu.visible() {
9826 menu.render_aside(max_size, window, cx)
9827 } else {
9828 None
9829 }
9830 })
9831 }
9832
9833 fn hide_context_menu(
9834 &mut self,
9835 window: &mut Window,
9836 cx: &mut Context<Self>,
9837 ) -> Option<CodeContextMenu> {
9838 cx.notify();
9839 self.completion_tasks.clear();
9840 let context_menu = self.context_menu.borrow_mut().take();
9841 self.stale_edit_prediction_in_menu.take();
9842 self.update_visible_edit_prediction(window, cx);
9843 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9844 && let Some(completion_provider) = &self.completion_provider
9845 {
9846 completion_provider.selection_changed(None, window, cx);
9847 }
9848 context_menu
9849 }
9850
9851 fn show_snippet_choices(
9852 &mut self,
9853 choices: &Vec<String>,
9854 selection: Range<Anchor>,
9855 cx: &mut Context<Self>,
9856 ) {
9857 let Some((_, buffer, _)) = self
9858 .buffer()
9859 .read(cx)
9860 .excerpt_containing(selection.start, cx)
9861 else {
9862 return;
9863 };
9864 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9865 else {
9866 return;
9867 };
9868 if buffer != end_buffer {
9869 log::error!("expected anchor range to have matching buffer IDs");
9870 return;
9871 }
9872
9873 let id = post_inc(&mut self.next_completion_id);
9874 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9875 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9876 CompletionsMenu::new_snippet_choices(
9877 id,
9878 true,
9879 choices,
9880 selection,
9881 buffer,
9882 snippet_sort_order,
9883 ),
9884 ));
9885 }
9886
9887 pub fn insert_snippet(
9888 &mut self,
9889 insertion_ranges: &[Range<MultiBufferOffset>],
9890 snippet: Snippet,
9891 window: &mut Window,
9892 cx: &mut Context<Self>,
9893 ) -> Result<()> {
9894 struct Tabstop<T> {
9895 is_end_tabstop: bool,
9896 ranges: Vec<Range<T>>,
9897 choices: Option<Vec<String>>,
9898 }
9899
9900 let tabstops = self.buffer.update(cx, |buffer, cx| {
9901 let snippet_text: Arc<str> = snippet.text.clone().into();
9902 let edits = insertion_ranges
9903 .iter()
9904 .cloned()
9905 .map(|range| (range, snippet_text.clone()));
9906 let autoindent_mode = AutoindentMode::Block {
9907 original_indent_columns: Vec::new(),
9908 };
9909 buffer.edit(edits, Some(autoindent_mode), cx);
9910
9911 let snapshot = &*buffer.read(cx);
9912 let snippet = &snippet;
9913 snippet
9914 .tabstops
9915 .iter()
9916 .map(|tabstop| {
9917 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9918 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9919 });
9920 let mut tabstop_ranges = tabstop
9921 .ranges
9922 .iter()
9923 .flat_map(|tabstop_range| {
9924 let mut delta = 0_isize;
9925 insertion_ranges.iter().map(move |insertion_range| {
9926 let insertion_start = insertion_range.start + delta;
9927 delta += snippet.text.len() as isize
9928 - (insertion_range.end - insertion_range.start) as isize;
9929
9930 let start =
9931 (insertion_start + tabstop_range.start).min(snapshot.len());
9932 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9933 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9934 })
9935 })
9936 .collect::<Vec<_>>();
9937 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9938
9939 Tabstop {
9940 is_end_tabstop,
9941 ranges: tabstop_ranges,
9942 choices: tabstop.choices.clone(),
9943 }
9944 })
9945 .collect::<Vec<_>>()
9946 });
9947 if let Some(tabstop) = tabstops.first() {
9948 self.change_selections(Default::default(), window, cx, |s| {
9949 // Reverse order so that the first range is the newest created selection.
9950 // Completions will use it and autoscroll will prioritize it.
9951 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9952 });
9953
9954 if let Some(choices) = &tabstop.choices
9955 && let Some(selection) = tabstop.ranges.first()
9956 {
9957 self.show_snippet_choices(choices, selection.clone(), cx)
9958 }
9959
9960 // If we're already at the last tabstop and it's at the end of the snippet,
9961 // we're done, we don't need to keep the state around.
9962 if !tabstop.is_end_tabstop {
9963 let choices = tabstops
9964 .iter()
9965 .map(|tabstop| tabstop.choices.clone())
9966 .collect();
9967
9968 let ranges = tabstops
9969 .into_iter()
9970 .map(|tabstop| tabstop.ranges)
9971 .collect::<Vec<_>>();
9972
9973 self.snippet_stack.push(SnippetState {
9974 active_index: 0,
9975 ranges,
9976 choices,
9977 });
9978 }
9979
9980 // Check whether the just-entered snippet ends with an auto-closable bracket.
9981 if self.autoclose_regions.is_empty() {
9982 let snapshot = self.buffer.read(cx).snapshot(cx);
9983 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9984 let selection_head = selection.head();
9985 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9986 continue;
9987 };
9988
9989 let mut bracket_pair = None;
9990 let max_lookup_length = scope
9991 .brackets()
9992 .map(|(pair, _)| {
9993 pair.start
9994 .as_str()
9995 .chars()
9996 .count()
9997 .max(pair.end.as_str().chars().count())
9998 })
9999 .max();
10000 if let Some(max_lookup_length) = max_lookup_length {
10001 let next_text = snapshot
10002 .chars_at(selection_head)
10003 .take(max_lookup_length)
10004 .collect::<String>();
10005 let prev_text = snapshot
10006 .reversed_chars_at(selection_head)
10007 .take(max_lookup_length)
10008 .collect::<String>();
10009
10010 for (pair, enabled) in scope.brackets() {
10011 if enabled
10012 && pair.close
10013 && prev_text.starts_with(pair.start.as_str())
10014 && next_text.starts_with(pair.end.as_str())
10015 {
10016 bracket_pair = Some(pair.clone());
10017 break;
10018 }
10019 }
10020 }
10021
10022 if let Some(pair) = bracket_pair {
10023 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10024 let autoclose_enabled =
10025 self.use_autoclose && snapshot_settings.use_autoclose;
10026 if autoclose_enabled {
10027 let start = snapshot.anchor_after(selection_head);
10028 let end = snapshot.anchor_after(selection_head);
10029 self.autoclose_regions.push(AutocloseRegion {
10030 selection_id: selection.id,
10031 range: start..end,
10032 pair,
10033 });
10034 }
10035 }
10036 }
10037 }
10038 }
10039 Ok(())
10040 }
10041
10042 pub fn move_to_next_snippet_tabstop(
10043 &mut self,
10044 window: &mut Window,
10045 cx: &mut Context<Self>,
10046 ) -> bool {
10047 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10048 }
10049
10050 pub fn move_to_prev_snippet_tabstop(
10051 &mut self,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) -> bool {
10055 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10056 }
10057
10058 pub fn move_to_snippet_tabstop(
10059 &mut self,
10060 bias: Bias,
10061 window: &mut Window,
10062 cx: &mut Context<Self>,
10063 ) -> bool {
10064 if let Some(mut snippet) = self.snippet_stack.pop() {
10065 match bias {
10066 Bias::Left => {
10067 if snippet.active_index > 0 {
10068 snippet.active_index -= 1;
10069 } else {
10070 self.snippet_stack.push(snippet);
10071 return false;
10072 }
10073 }
10074 Bias::Right => {
10075 if snippet.active_index + 1 < snippet.ranges.len() {
10076 snippet.active_index += 1;
10077 } else {
10078 self.snippet_stack.push(snippet);
10079 return false;
10080 }
10081 }
10082 }
10083 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10084 self.change_selections(Default::default(), window, cx, |s| {
10085 // Reverse order so that the first range is the newest created selection.
10086 // Completions will use it and autoscroll will prioritize it.
10087 s.select_ranges(current_ranges.iter().rev().cloned())
10088 });
10089
10090 if let Some(choices) = &snippet.choices[snippet.active_index]
10091 && let Some(selection) = current_ranges.first()
10092 {
10093 self.show_snippet_choices(choices, selection.clone(), cx);
10094 }
10095
10096 // If snippet state is not at the last tabstop, push it back on the stack
10097 if snippet.active_index + 1 < snippet.ranges.len() {
10098 self.snippet_stack.push(snippet);
10099 }
10100 return true;
10101 }
10102 }
10103
10104 false
10105 }
10106
10107 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10108 self.transact(window, cx, |this, window, cx| {
10109 this.select_all(&SelectAll, window, cx);
10110 this.insert("", window, cx);
10111 });
10112 }
10113
10114 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10115 if self.read_only(cx) {
10116 return;
10117 }
10118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10119 self.transact(window, cx, |this, window, cx| {
10120 this.select_autoclose_pair(window, cx);
10121
10122 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10123
10124 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10125 if !this.linked_edit_ranges.is_empty() {
10126 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10127 let snapshot = this.buffer.read(cx).snapshot(cx);
10128
10129 for selection in selections.iter() {
10130 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10131 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10132 if selection_start.buffer_id != selection_end.buffer_id {
10133 continue;
10134 }
10135 if let Some(ranges) =
10136 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10137 {
10138 for (buffer, entries) in ranges {
10139 linked_ranges.entry(buffer).or_default().extend(entries);
10140 }
10141 }
10142 }
10143 }
10144
10145 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10146 for selection in &mut selections {
10147 if selection.is_empty() {
10148 let old_head = selection.head();
10149 let mut new_head =
10150 movement::left(&display_map, old_head.to_display_point(&display_map))
10151 .to_point(&display_map);
10152 if let Some((buffer, line_buffer_range)) = display_map
10153 .buffer_snapshot()
10154 .buffer_line_for_row(MultiBufferRow(old_head.row))
10155 {
10156 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10157 let indent_len = match indent_size.kind {
10158 IndentKind::Space => {
10159 buffer.settings_at(line_buffer_range.start, cx).tab_size
10160 }
10161 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10162 };
10163 if old_head.column <= indent_size.len && old_head.column > 0 {
10164 let indent_len = indent_len.get();
10165 new_head = cmp::min(
10166 new_head,
10167 MultiBufferPoint::new(
10168 old_head.row,
10169 ((old_head.column - 1) / indent_len) * indent_len,
10170 ),
10171 );
10172 }
10173 }
10174
10175 selection.set_head(new_head, SelectionGoal::None);
10176 }
10177 }
10178
10179 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10180 this.insert("", window, cx);
10181 let empty_str: Arc<str> = Arc::from("");
10182 for (buffer, edits) in linked_ranges {
10183 let snapshot = buffer.read(cx).snapshot();
10184 use text::ToPoint as TP;
10185
10186 let edits = edits
10187 .into_iter()
10188 .map(|range| {
10189 let end_point = TP::to_point(&range.end, &snapshot);
10190 let mut start_point = TP::to_point(&range.start, &snapshot);
10191
10192 if end_point == start_point {
10193 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10194 .saturating_sub(1);
10195 start_point =
10196 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10197 };
10198
10199 (start_point..end_point, empty_str.clone())
10200 })
10201 .sorted_by_key(|(range, _)| range.start)
10202 .collect::<Vec<_>>();
10203 buffer.update(cx, |this, cx| {
10204 this.edit(edits, None, cx);
10205 })
10206 }
10207 this.refresh_edit_prediction(true, false, window, cx);
10208 refresh_linked_ranges(this, window, cx);
10209 });
10210 }
10211
10212 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10213 if self.read_only(cx) {
10214 return;
10215 }
10216 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10217 self.transact(window, cx, |this, window, cx| {
10218 this.change_selections(Default::default(), window, cx, |s| {
10219 s.move_with(|map, selection| {
10220 if selection.is_empty() {
10221 let cursor = movement::right(map, selection.head());
10222 selection.end = cursor;
10223 selection.reversed = true;
10224 selection.goal = SelectionGoal::None;
10225 }
10226 })
10227 });
10228 this.insert("", window, cx);
10229 this.refresh_edit_prediction(true, false, window, cx);
10230 });
10231 }
10232
10233 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10234 if self.mode.is_single_line() {
10235 cx.propagate();
10236 return;
10237 }
10238
10239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10240 if self.move_to_prev_snippet_tabstop(window, cx) {
10241 return;
10242 }
10243 self.outdent(&Outdent, window, cx);
10244 }
10245
10246 pub fn next_snippet_tabstop(
10247 &mut self,
10248 _: &NextSnippetTabstop,
10249 window: &mut Window,
10250 cx: &mut Context<Self>,
10251 ) {
10252 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10253 cx.propagate();
10254 return;
10255 }
10256
10257 if self.move_to_next_snippet_tabstop(window, cx) {
10258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10259 return;
10260 }
10261 cx.propagate();
10262 }
10263
10264 pub fn previous_snippet_tabstop(
10265 &mut self,
10266 _: &PreviousSnippetTabstop,
10267 window: &mut Window,
10268 cx: &mut Context<Self>,
10269 ) {
10270 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10271 cx.propagate();
10272 return;
10273 }
10274
10275 if self.move_to_prev_snippet_tabstop(window, cx) {
10276 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10277 return;
10278 }
10279 cx.propagate();
10280 }
10281
10282 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10283 if self.mode.is_single_line() {
10284 cx.propagate();
10285 return;
10286 }
10287
10288 if self.move_to_next_snippet_tabstop(window, cx) {
10289 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10290 return;
10291 }
10292 if self.read_only(cx) {
10293 return;
10294 }
10295 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10296 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10297 let buffer = self.buffer.read(cx);
10298 let snapshot = buffer.snapshot(cx);
10299 let rows_iter = selections.iter().map(|s| s.head().row);
10300 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10301
10302 let has_some_cursor_in_whitespace = selections
10303 .iter()
10304 .filter(|selection| selection.is_empty())
10305 .any(|selection| {
10306 let cursor = selection.head();
10307 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10308 cursor.column < current_indent.len
10309 });
10310
10311 let mut edits = Vec::new();
10312 let mut prev_edited_row = 0;
10313 let mut row_delta = 0;
10314 for selection in &mut selections {
10315 if selection.start.row != prev_edited_row {
10316 row_delta = 0;
10317 }
10318 prev_edited_row = selection.end.row;
10319
10320 // If the selection is non-empty, then increase the indentation of the selected lines.
10321 if !selection.is_empty() {
10322 row_delta =
10323 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10324 continue;
10325 }
10326
10327 let cursor = selection.head();
10328 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10329 if let Some(suggested_indent) =
10330 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10331 {
10332 // Don't do anything if already at suggested indent
10333 // and there is any other cursor which is not
10334 if has_some_cursor_in_whitespace
10335 && cursor.column == current_indent.len
10336 && current_indent.len == suggested_indent.len
10337 {
10338 continue;
10339 }
10340
10341 // Adjust line and move cursor to suggested indent
10342 // if cursor is not at suggested indent
10343 if cursor.column < suggested_indent.len
10344 && cursor.column <= current_indent.len
10345 && current_indent.len <= suggested_indent.len
10346 {
10347 selection.start = Point::new(cursor.row, suggested_indent.len);
10348 selection.end = selection.start;
10349 if row_delta == 0 {
10350 edits.extend(Buffer::edit_for_indent_size_adjustment(
10351 cursor.row,
10352 current_indent,
10353 suggested_indent,
10354 ));
10355 row_delta = suggested_indent.len - current_indent.len;
10356 }
10357 continue;
10358 }
10359
10360 // If current indent is more than suggested indent
10361 // only move cursor to current indent and skip indent
10362 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10363 selection.start = Point::new(cursor.row, current_indent.len);
10364 selection.end = selection.start;
10365 continue;
10366 }
10367 }
10368
10369 // Otherwise, insert a hard or soft tab.
10370 let settings = buffer.language_settings_at(cursor, cx);
10371 let tab_size = if settings.hard_tabs {
10372 IndentSize::tab()
10373 } else {
10374 let tab_size = settings.tab_size.get();
10375 let indent_remainder = snapshot
10376 .text_for_range(Point::new(cursor.row, 0)..cursor)
10377 .flat_map(str::chars)
10378 .fold(row_delta % tab_size, |counter: u32, c| {
10379 if c == '\t' {
10380 0
10381 } else {
10382 (counter + 1) % tab_size
10383 }
10384 });
10385
10386 let chars_to_next_tab_stop = tab_size - indent_remainder;
10387 IndentSize::spaces(chars_to_next_tab_stop)
10388 };
10389 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10390 selection.end = selection.start;
10391 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10392 row_delta += tab_size.len;
10393 }
10394
10395 self.transact(window, cx, |this, window, cx| {
10396 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10397 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10398 this.refresh_edit_prediction(true, false, window, cx);
10399 });
10400 }
10401
10402 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10403 if self.read_only(cx) {
10404 return;
10405 }
10406 if self.mode.is_single_line() {
10407 cx.propagate();
10408 return;
10409 }
10410
10411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10412 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10413 let mut prev_edited_row = 0;
10414 let mut row_delta = 0;
10415 let mut edits = Vec::new();
10416 let buffer = self.buffer.read(cx);
10417 let snapshot = buffer.snapshot(cx);
10418 for selection in &mut selections {
10419 if selection.start.row != prev_edited_row {
10420 row_delta = 0;
10421 }
10422 prev_edited_row = selection.end.row;
10423
10424 row_delta =
10425 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10426 }
10427
10428 self.transact(window, cx, |this, window, cx| {
10429 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10430 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10431 });
10432 }
10433
10434 fn indent_selection(
10435 buffer: &MultiBuffer,
10436 snapshot: &MultiBufferSnapshot,
10437 selection: &mut Selection<Point>,
10438 edits: &mut Vec<(Range<Point>, String)>,
10439 delta_for_start_row: u32,
10440 cx: &App,
10441 ) -> u32 {
10442 let settings = buffer.language_settings_at(selection.start, cx);
10443 let tab_size = settings.tab_size.get();
10444 let indent_kind = if settings.hard_tabs {
10445 IndentKind::Tab
10446 } else {
10447 IndentKind::Space
10448 };
10449 let mut start_row = selection.start.row;
10450 let mut end_row = selection.end.row + 1;
10451
10452 // If a selection ends at the beginning of a line, don't indent
10453 // that last line.
10454 if selection.end.column == 0 && selection.end.row > selection.start.row {
10455 end_row -= 1;
10456 }
10457
10458 // Avoid re-indenting a row that has already been indented by a
10459 // previous selection, but still update this selection's column
10460 // to reflect that indentation.
10461 if delta_for_start_row > 0 {
10462 start_row += 1;
10463 selection.start.column += delta_for_start_row;
10464 if selection.end.row == selection.start.row {
10465 selection.end.column += delta_for_start_row;
10466 }
10467 }
10468
10469 let mut delta_for_end_row = 0;
10470 let has_multiple_rows = start_row + 1 != end_row;
10471 for row in start_row..end_row {
10472 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10473 let indent_delta = match (current_indent.kind, indent_kind) {
10474 (IndentKind::Space, IndentKind::Space) => {
10475 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10476 IndentSize::spaces(columns_to_next_tab_stop)
10477 }
10478 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10479 (_, IndentKind::Tab) => IndentSize::tab(),
10480 };
10481
10482 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10483 0
10484 } else {
10485 selection.start.column
10486 };
10487 let row_start = Point::new(row, start);
10488 edits.push((
10489 row_start..row_start,
10490 indent_delta.chars().collect::<String>(),
10491 ));
10492
10493 // Update this selection's endpoints to reflect the indentation.
10494 if row == selection.start.row {
10495 selection.start.column += indent_delta.len;
10496 }
10497 if row == selection.end.row {
10498 selection.end.column += indent_delta.len;
10499 delta_for_end_row = indent_delta.len;
10500 }
10501 }
10502
10503 if selection.start.row == selection.end.row {
10504 delta_for_start_row + delta_for_end_row
10505 } else {
10506 delta_for_end_row
10507 }
10508 }
10509
10510 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10511 if self.read_only(cx) {
10512 return;
10513 }
10514 if self.mode.is_single_line() {
10515 cx.propagate();
10516 return;
10517 }
10518
10519 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10521 let selections = self.selections.all::<Point>(&display_map);
10522 let mut deletion_ranges = Vec::new();
10523 let mut last_outdent = None;
10524 {
10525 let buffer = self.buffer.read(cx);
10526 let snapshot = buffer.snapshot(cx);
10527 for selection in &selections {
10528 let settings = buffer.language_settings_at(selection.start, cx);
10529 let tab_size = settings.tab_size.get();
10530 let mut rows = selection.spanned_rows(false, &display_map);
10531
10532 // Avoid re-outdenting a row that has already been outdented by a
10533 // previous selection.
10534 if let Some(last_row) = last_outdent
10535 && last_row == rows.start
10536 {
10537 rows.start = rows.start.next_row();
10538 }
10539 let has_multiple_rows = rows.len() > 1;
10540 for row in rows.iter_rows() {
10541 let indent_size = snapshot.indent_size_for_line(row);
10542 if indent_size.len > 0 {
10543 let deletion_len = match indent_size.kind {
10544 IndentKind::Space => {
10545 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10546 if columns_to_prev_tab_stop == 0 {
10547 tab_size
10548 } else {
10549 columns_to_prev_tab_stop
10550 }
10551 }
10552 IndentKind::Tab => 1,
10553 };
10554 let start = if has_multiple_rows
10555 || deletion_len > selection.start.column
10556 || indent_size.len < selection.start.column
10557 {
10558 0
10559 } else {
10560 selection.start.column - deletion_len
10561 };
10562 deletion_ranges.push(
10563 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10564 );
10565 last_outdent = Some(row);
10566 }
10567 }
10568 }
10569 }
10570
10571 self.transact(window, cx, |this, window, cx| {
10572 this.buffer.update(cx, |buffer, cx| {
10573 let empty_str: Arc<str> = Arc::default();
10574 buffer.edit(
10575 deletion_ranges
10576 .into_iter()
10577 .map(|range| (range, empty_str.clone())),
10578 None,
10579 cx,
10580 );
10581 });
10582 let selections = this
10583 .selections
10584 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10585 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10586 });
10587 }
10588
10589 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10590 if self.read_only(cx) {
10591 return;
10592 }
10593 if self.mode.is_single_line() {
10594 cx.propagate();
10595 return;
10596 }
10597
10598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10599 let selections = self
10600 .selections
10601 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10602 .into_iter()
10603 .map(|s| s.range());
10604
10605 self.transact(window, cx, |this, window, cx| {
10606 this.buffer.update(cx, |buffer, cx| {
10607 buffer.autoindent_ranges(selections, cx);
10608 });
10609 let selections = this
10610 .selections
10611 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10612 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10613 });
10614 }
10615
10616 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10617 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10618 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10619 let selections = self.selections.all::<Point>(&display_map);
10620
10621 let mut new_cursors = Vec::new();
10622 let mut edit_ranges = Vec::new();
10623 let mut selections = selections.iter().peekable();
10624 while let Some(selection) = selections.next() {
10625 let mut rows = selection.spanned_rows(false, &display_map);
10626
10627 // Accumulate contiguous regions of rows that we want to delete.
10628 while let Some(next_selection) = selections.peek() {
10629 let next_rows = next_selection.spanned_rows(false, &display_map);
10630 if next_rows.start <= rows.end {
10631 rows.end = next_rows.end;
10632 selections.next().unwrap();
10633 } else {
10634 break;
10635 }
10636 }
10637
10638 let buffer = display_map.buffer_snapshot();
10639 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10640 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10641 // If there's a line after the range, delete the \n from the end of the row range
10642 (
10643 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10644 rows.end,
10645 )
10646 } else {
10647 // If there isn't a line after the range, delete the \n from the line before the
10648 // start of the row range
10649 edit_start = edit_start.saturating_sub_usize(1);
10650 (buffer.len(), rows.start.previous_row())
10651 };
10652
10653 let text_layout_details = self.text_layout_details(window);
10654 let x = display_map.x_for_display_point(
10655 selection.head().to_display_point(&display_map),
10656 &text_layout_details,
10657 );
10658 let row = Point::new(target_row.0, 0)
10659 .to_display_point(&display_map)
10660 .row();
10661 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10662
10663 new_cursors.push((
10664 selection.id,
10665 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10666 SelectionGoal::None,
10667 ));
10668 edit_ranges.push(edit_start..edit_end);
10669 }
10670
10671 self.transact(window, cx, |this, window, cx| {
10672 let buffer = this.buffer.update(cx, |buffer, cx| {
10673 let empty_str: Arc<str> = Arc::default();
10674 buffer.edit(
10675 edit_ranges
10676 .into_iter()
10677 .map(|range| (range, empty_str.clone())),
10678 None,
10679 cx,
10680 );
10681 buffer.snapshot(cx)
10682 });
10683 let new_selections = new_cursors
10684 .into_iter()
10685 .map(|(id, cursor, goal)| {
10686 let cursor = cursor.to_point(&buffer);
10687 Selection {
10688 id,
10689 start: cursor,
10690 end: cursor,
10691 reversed: false,
10692 goal,
10693 }
10694 })
10695 .collect();
10696
10697 this.change_selections(Default::default(), window, cx, |s| {
10698 s.select(new_selections);
10699 });
10700 });
10701 }
10702
10703 pub fn join_lines_impl(
10704 &mut self,
10705 insert_whitespace: bool,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) {
10709 if self.read_only(cx) {
10710 return;
10711 }
10712 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10713 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10714 let start = MultiBufferRow(selection.start.row);
10715 // Treat single line selections as if they include the next line. Otherwise this action
10716 // would do nothing for single line selections individual cursors.
10717 let end = if selection.start.row == selection.end.row {
10718 MultiBufferRow(selection.start.row + 1)
10719 } else {
10720 MultiBufferRow(selection.end.row)
10721 };
10722
10723 if let Some(last_row_range) = row_ranges.last_mut()
10724 && start <= last_row_range.end
10725 {
10726 last_row_range.end = end;
10727 continue;
10728 }
10729 row_ranges.push(start..end);
10730 }
10731
10732 let snapshot = self.buffer.read(cx).snapshot(cx);
10733 let mut cursor_positions = Vec::new();
10734 for row_range in &row_ranges {
10735 let anchor = snapshot.anchor_before(Point::new(
10736 row_range.end.previous_row().0,
10737 snapshot.line_len(row_range.end.previous_row()),
10738 ));
10739 cursor_positions.push(anchor..anchor);
10740 }
10741
10742 self.transact(window, cx, |this, window, cx| {
10743 for row_range in row_ranges.into_iter().rev() {
10744 for row in row_range.iter_rows().rev() {
10745 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10746 let next_line_row = row.next_row();
10747 let indent = snapshot.indent_size_for_line(next_line_row);
10748 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10749
10750 let replace =
10751 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10752 " "
10753 } else {
10754 ""
10755 };
10756
10757 this.buffer.update(cx, |buffer, cx| {
10758 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10759 });
10760 }
10761 }
10762
10763 this.change_selections(Default::default(), window, cx, |s| {
10764 s.select_anchor_ranges(cursor_positions)
10765 });
10766 });
10767 }
10768
10769 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10771 self.join_lines_impl(true, window, cx);
10772 }
10773
10774 pub fn sort_lines_case_sensitive(
10775 &mut self,
10776 _: &SortLinesCaseSensitive,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10781 }
10782
10783 pub fn sort_lines_by_length(
10784 &mut self,
10785 _: &SortLinesByLength,
10786 window: &mut Window,
10787 cx: &mut Context<Self>,
10788 ) {
10789 self.manipulate_immutable_lines(window, cx, |lines| {
10790 lines.sort_by_key(|&line| line.chars().count())
10791 })
10792 }
10793
10794 pub fn sort_lines_case_insensitive(
10795 &mut self,
10796 _: &SortLinesCaseInsensitive,
10797 window: &mut Window,
10798 cx: &mut Context<Self>,
10799 ) {
10800 self.manipulate_immutable_lines(window, cx, |lines| {
10801 lines.sort_by_key(|line| line.to_lowercase())
10802 })
10803 }
10804
10805 pub fn unique_lines_case_insensitive(
10806 &mut self,
10807 _: &UniqueLinesCaseInsensitive,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 ) {
10811 self.manipulate_immutable_lines(window, cx, |lines| {
10812 let mut seen = HashSet::default();
10813 lines.retain(|line| seen.insert(line.to_lowercase()));
10814 })
10815 }
10816
10817 pub fn unique_lines_case_sensitive(
10818 &mut self,
10819 _: &UniqueLinesCaseSensitive,
10820 window: &mut Window,
10821 cx: &mut Context<Self>,
10822 ) {
10823 self.manipulate_immutable_lines(window, cx, |lines| {
10824 let mut seen = HashSet::default();
10825 lines.retain(|line| seen.insert(*line));
10826 })
10827 }
10828
10829 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10830 let snapshot = self.buffer.read(cx).snapshot(cx);
10831 for selection in self.selections.disjoint_anchors_arc().iter() {
10832 if snapshot
10833 .language_at(selection.start)
10834 .and_then(|lang| lang.config().wrap_characters.as_ref())
10835 .is_some()
10836 {
10837 return true;
10838 }
10839 }
10840 false
10841 }
10842
10843 fn wrap_selections_in_tag(
10844 &mut self,
10845 _: &WrapSelectionsInTag,
10846 window: &mut Window,
10847 cx: &mut Context<Self>,
10848 ) {
10849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10850
10851 let snapshot = self.buffer.read(cx).snapshot(cx);
10852
10853 let mut edits = Vec::new();
10854 let mut boundaries = Vec::new();
10855
10856 for selection in self
10857 .selections
10858 .all_adjusted(&self.display_snapshot(cx))
10859 .iter()
10860 {
10861 let Some(wrap_config) = snapshot
10862 .language_at(selection.start)
10863 .and_then(|lang| lang.config().wrap_characters.clone())
10864 else {
10865 continue;
10866 };
10867
10868 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10869 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10870
10871 let start_before = snapshot.anchor_before(selection.start);
10872 let end_after = snapshot.anchor_after(selection.end);
10873
10874 edits.push((start_before..start_before, open_tag));
10875 edits.push((end_after..end_after, close_tag));
10876
10877 boundaries.push((
10878 start_before,
10879 end_after,
10880 wrap_config.start_prefix.len(),
10881 wrap_config.end_suffix.len(),
10882 ));
10883 }
10884
10885 if edits.is_empty() {
10886 return;
10887 }
10888
10889 self.transact(window, cx, |this, window, cx| {
10890 let buffer = this.buffer.update(cx, |buffer, cx| {
10891 buffer.edit(edits, None, cx);
10892 buffer.snapshot(cx)
10893 });
10894
10895 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10896 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10897 boundaries.into_iter()
10898 {
10899 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10900 let close_offset = end_after
10901 .to_offset(&buffer)
10902 .saturating_sub_usize(end_suffix_len);
10903 new_selections.push(open_offset..open_offset);
10904 new_selections.push(close_offset..close_offset);
10905 }
10906
10907 this.change_selections(Default::default(), window, cx, |s| {
10908 s.select_ranges(new_selections);
10909 });
10910
10911 this.request_autoscroll(Autoscroll::fit(), cx);
10912 });
10913 }
10914
10915 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10916 let Some(project) = self.project.clone() else {
10917 return;
10918 };
10919 self.reload(project, window, cx)
10920 .detach_and_notify_err(window, cx);
10921 }
10922
10923 pub fn restore_file(
10924 &mut self,
10925 _: &::git::RestoreFile,
10926 window: &mut Window,
10927 cx: &mut Context<Self>,
10928 ) {
10929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10930 let mut buffer_ids = HashSet::default();
10931 let snapshot = self.buffer().read(cx).snapshot(cx);
10932 for selection in self
10933 .selections
10934 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10935 {
10936 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10937 }
10938
10939 let buffer = self.buffer().read(cx);
10940 let ranges = buffer_ids
10941 .into_iter()
10942 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10943 .collect::<Vec<_>>();
10944
10945 self.restore_hunks_in_ranges(ranges, window, cx);
10946 }
10947
10948 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10950 let selections = self
10951 .selections
10952 .all(&self.display_snapshot(cx))
10953 .into_iter()
10954 .map(|s| s.range())
10955 .collect();
10956 self.restore_hunks_in_ranges(selections, window, cx);
10957 }
10958
10959 pub fn restore_hunks_in_ranges(
10960 &mut self,
10961 ranges: Vec<Range<Point>>,
10962 window: &mut Window,
10963 cx: &mut Context<Editor>,
10964 ) {
10965 let mut revert_changes = HashMap::default();
10966 let chunk_by = self
10967 .snapshot(window, cx)
10968 .hunks_for_ranges(ranges)
10969 .into_iter()
10970 .chunk_by(|hunk| hunk.buffer_id);
10971 for (buffer_id, hunks) in &chunk_by {
10972 let hunks = hunks.collect::<Vec<_>>();
10973 for hunk in &hunks {
10974 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10975 }
10976 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10977 }
10978 drop(chunk_by);
10979 if !revert_changes.is_empty() {
10980 self.transact(window, cx, |editor, window, cx| {
10981 editor.restore(revert_changes, window, cx);
10982 });
10983 }
10984 }
10985
10986 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10987 if let Some(status) = self
10988 .addons
10989 .iter()
10990 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10991 {
10992 return Some(status);
10993 }
10994 self.project
10995 .as_ref()?
10996 .read(cx)
10997 .status_for_buffer_id(buffer_id, cx)
10998 }
10999
11000 pub fn open_active_item_in_terminal(
11001 &mut self,
11002 _: &OpenInTerminal,
11003 window: &mut Window,
11004 cx: &mut Context<Self>,
11005 ) {
11006 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11007 let project_path = buffer.read(cx).project_path(cx)?;
11008 let project = self.project()?.read(cx);
11009 let entry = project.entry_for_path(&project_path, cx)?;
11010 let parent = match &entry.canonical_path {
11011 Some(canonical_path) => canonical_path.to_path_buf(),
11012 None => project.absolute_path(&project_path, cx)?,
11013 }
11014 .parent()?
11015 .to_path_buf();
11016 Some(parent)
11017 }) {
11018 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11019 }
11020 }
11021
11022 fn set_breakpoint_context_menu(
11023 &mut self,
11024 display_row: DisplayRow,
11025 position: Option<Anchor>,
11026 clicked_point: gpui::Point<Pixels>,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 ) {
11030 let source = self
11031 .buffer
11032 .read(cx)
11033 .snapshot(cx)
11034 .anchor_before(Point::new(display_row.0, 0u32));
11035
11036 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11037
11038 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11039 self,
11040 source,
11041 clicked_point,
11042 context_menu,
11043 window,
11044 cx,
11045 );
11046 }
11047
11048 fn add_edit_breakpoint_block(
11049 &mut self,
11050 anchor: Anchor,
11051 breakpoint: &Breakpoint,
11052 edit_action: BreakpointPromptEditAction,
11053 window: &mut Window,
11054 cx: &mut Context<Self>,
11055 ) {
11056 let weak_editor = cx.weak_entity();
11057 let bp_prompt = cx.new(|cx| {
11058 BreakpointPromptEditor::new(
11059 weak_editor,
11060 anchor,
11061 breakpoint.clone(),
11062 edit_action,
11063 window,
11064 cx,
11065 )
11066 });
11067
11068 let height = bp_prompt.update(cx, |this, cx| {
11069 this.prompt
11070 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11071 });
11072 let cloned_prompt = bp_prompt.clone();
11073 let blocks = vec![BlockProperties {
11074 style: BlockStyle::Sticky,
11075 placement: BlockPlacement::Above(anchor),
11076 height: Some(height),
11077 render: Arc::new(move |cx| {
11078 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11079 cloned_prompt.clone().into_any_element()
11080 }),
11081 priority: 0,
11082 }];
11083
11084 let focus_handle = bp_prompt.focus_handle(cx);
11085 window.focus(&focus_handle);
11086
11087 let block_ids = self.insert_blocks(blocks, None, cx);
11088 bp_prompt.update(cx, |prompt, _| {
11089 prompt.add_block_ids(block_ids);
11090 });
11091 }
11092
11093 pub(crate) fn breakpoint_at_row(
11094 &self,
11095 row: u32,
11096 window: &mut Window,
11097 cx: &mut Context<Self>,
11098 ) -> Option<(Anchor, Breakpoint)> {
11099 let snapshot = self.snapshot(window, cx);
11100 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11101
11102 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11103 }
11104
11105 pub(crate) fn breakpoint_at_anchor(
11106 &self,
11107 breakpoint_position: Anchor,
11108 snapshot: &EditorSnapshot,
11109 cx: &mut Context<Self>,
11110 ) -> Option<(Anchor, Breakpoint)> {
11111 let buffer = self
11112 .buffer
11113 .read(cx)
11114 .buffer_for_anchor(breakpoint_position, cx)?;
11115
11116 let enclosing_excerpt = breakpoint_position.excerpt_id;
11117 let buffer_snapshot = buffer.read(cx).snapshot();
11118
11119 let row = buffer_snapshot
11120 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11121 .row;
11122
11123 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11124 let anchor_end = snapshot
11125 .buffer_snapshot()
11126 .anchor_after(Point::new(row, line_len));
11127
11128 self.breakpoint_store
11129 .as_ref()?
11130 .read_with(cx, |breakpoint_store, cx| {
11131 breakpoint_store
11132 .breakpoints(
11133 &buffer,
11134 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11135 &buffer_snapshot,
11136 cx,
11137 )
11138 .next()
11139 .and_then(|(bp, _)| {
11140 let breakpoint_row = buffer_snapshot
11141 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11142 .row;
11143
11144 if breakpoint_row == row {
11145 snapshot
11146 .buffer_snapshot()
11147 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11148 .map(|position| (position, bp.bp.clone()))
11149 } else {
11150 None
11151 }
11152 })
11153 })
11154 }
11155
11156 pub fn edit_log_breakpoint(
11157 &mut self,
11158 _: &EditLogBreakpoint,
11159 window: &mut Window,
11160 cx: &mut Context<Self>,
11161 ) {
11162 if self.breakpoint_store.is_none() {
11163 return;
11164 }
11165
11166 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11167 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11168 message: None,
11169 state: BreakpointState::Enabled,
11170 condition: None,
11171 hit_condition: None,
11172 });
11173
11174 self.add_edit_breakpoint_block(
11175 anchor,
11176 &breakpoint,
11177 BreakpointPromptEditAction::Log,
11178 window,
11179 cx,
11180 );
11181 }
11182 }
11183
11184 fn breakpoints_at_cursors(
11185 &self,
11186 window: &mut Window,
11187 cx: &mut Context<Self>,
11188 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11189 let snapshot = self.snapshot(window, cx);
11190 let cursors = self
11191 .selections
11192 .disjoint_anchors_arc()
11193 .iter()
11194 .map(|selection| {
11195 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11196
11197 let breakpoint_position = self
11198 .breakpoint_at_row(cursor_position.row, window, cx)
11199 .map(|bp| bp.0)
11200 .unwrap_or_else(|| {
11201 snapshot
11202 .display_snapshot
11203 .buffer_snapshot()
11204 .anchor_after(Point::new(cursor_position.row, 0))
11205 });
11206
11207 let breakpoint = self
11208 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11209 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11210
11211 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11212 })
11213 // 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.
11214 .collect::<HashMap<Anchor, _>>();
11215
11216 cursors.into_iter().collect()
11217 }
11218
11219 pub fn enable_breakpoint(
11220 &mut self,
11221 _: &crate::actions::EnableBreakpoint,
11222 window: &mut Window,
11223 cx: &mut Context<Self>,
11224 ) {
11225 if self.breakpoint_store.is_none() {
11226 return;
11227 }
11228
11229 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11230 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11231 continue;
11232 };
11233 self.edit_breakpoint_at_anchor(
11234 anchor,
11235 breakpoint,
11236 BreakpointEditAction::InvertState,
11237 cx,
11238 );
11239 }
11240 }
11241
11242 pub fn disable_breakpoint(
11243 &mut self,
11244 _: &crate::actions::DisableBreakpoint,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 if self.breakpoint_store.is_none() {
11249 return;
11250 }
11251
11252 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11253 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11254 continue;
11255 };
11256 self.edit_breakpoint_at_anchor(
11257 anchor,
11258 breakpoint,
11259 BreakpointEditAction::InvertState,
11260 cx,
11261 );
11262 }
11263 }
11264
11265 pub fn toggle_breakpoint(
11266 &mut self,
11267 _: &crate::actions::ToggleBreakpoint,
11268 window: &mut Window,
11269 cx: &mut Context<Self>,
11270 ) {
11271 if self.breakpoint_store.is_none() {
11272 return;
11273 }
11274
11275 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11276 if let Some(breakpoint) = breakpoint {
11277 self.edit_breakpoint_at_anchor(
11278 anchor,
11279 breakpoint,
11280 BreakpointEditAction::Toggle,
11281 cx,
11282 );
11283 } else {
11284 self.edit_breakpoint_at_anchor(
11285 anchor,
11286 Breakpoint::new_standard(),
11287 BreakpointEditAction::Toggle,
11288 cx,
11289 );
11290 }
11291 }
11292 }
11293
11294 pub fn edit_breakpoint_at_anchor(
11295 &mut self,
11296 breakpoint_position: Anchor,
11297 breakpoint: Breakpoint,
11298 edit_action: BreakpointEditAction,
11299 cx: &mut Context<Self>,
11300 ) {
11301 let Some(breakpoint_store) = &self.breakpoint_store else {
11302 return;
11303 };
11304
11305 let Some(buffer) = self
11306 .buffer
11307 .read(cx)
11308 .buffer_for_anchor(breakpoint_position, cx)
11309 else {
11310 return;
11311 };
11312
11313 breakpoint_store.update(cx, |breakpoint_store, cx| {
11314 breakpoint_store.toggle_breakpoint(
11315 buffer,
11316 BreakpointWithPosition {
11317 position: breakpoint_position.text_anchor,
11318 bp: breakpoint,
11319 },
11320 edit_action,
11321 cx,
11322 );
11323 });
11324
11325 cx.notify();
11326 }
11327
11328 #[cfg(any(test, feature = "test-support"))]
11329 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11330 self.breakpoint_store.clone()
11331 }
11332
11333 pub fn prepare_restore_change(
11334 &self,
11335 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11336 hunk: &MultiBufferDiffHunk,
11337 cx: &mut App,
11338 ) -> Option<()> {
11339 if hunk.is_created_file() {
11340 return None;
11341 }
11342 let buffer = self.buffer.read(cx);
11343 let diff = buffer.diff_for(hunk.buffer_id)?;
11344 let buffer = buffer.buffer(hunk.buffer_id)?;
11345 let buffer = buffer.read(cx);
11346 let original_text = diff
11347 .read(cx)
11348 .base_text()
11349 .as_rope()
11350 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11351 let buffer_snapshot = buffer.snapshot();
11352 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11353 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11354 probe
11355 .0
11356 .start
11357 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11358 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11359 }) {
11360 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11361 Some(())
11362 } else {
11363 None
11364 }
11365 }
11366
11367 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11368 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11369 }
11370
11371 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11372 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11373 }
11374
11375 fn manipulate_lines<M>(
11376 &mut self,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 mut manipulate: M,
11380 ) where
11381 M: FnMut(&str) -> LineManipulationResult,
11382 {
11383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11384
11385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11386 let buffer = self.buffer.read(cx).snapshot(cx);
11387
11388 let mut edits = Vec::new();
11389
11390 let selections = self.selections.all::<Point>(&display_map);
11391 let mut selections = selections.iter().peekable();
11392 let mut contiguous_row_selections = Vec::new();
11393 let mut new_selections = Vec::new();
11394 let mut added_lines = 0;
11395 let mut removed_lines = 0;
11396
11397 while let Some(selection) = selections.next() {
11398 let (start_row, end_row) = consume_contiguous_rows(
11399 &mut contiguous_row_selections,
11400 selection,
11401 &display_map,
11402 &mut selections,
11403 );
11404
11405 let start_point = Point::new(start_row.0, 0);
11406 let end_point = Point::new(
11407 end_row.previous_row().0,
11408 buffer.line_len(end_row.previous_row()),
11409 );
11410 let text = buffer
11411 .text_for_range(start_point..end_point)
11412 .collect::<String>();
11413
11414 let LineManipulationResult {
11415 new_text,
11416 line_count_before,
11417 line_count_after,
11418 } = manipulate(&text);
11419
11420 edits.push((start_point..end_point, new_text));
11421
11422 // Selections must change based on added and removed line count
11423 let start_row =
11424 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11425 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11426 new_selections.push(Selection {
11427 id: selection.id,
11428 start: start_row,
11429 end: end_row,
11430 goal: SelectionGoal::None,
11431 reversed: selection.reversed,
11432 });
11433
11434 if line_count_after > line_count_before {
11435 added_lines += line_count_after - line_count_before;
11436 } else if line_count_before > line_count_after {
11437 removed_lines += line_count_before - line_count_after;
11438 }
11439 }
11440
11441 self.transact(window, cx, |this, window, cx| {
11442 let buffer = this.buffer.update(cx, |buffer, cx| {
11443 buffer.edit(edits, None, cx);
11444 buffer.snapshot(cx)
11445 });
11446
11447 // Recalculate offsets on newly edited buffer
11448 let new_selections = new_selections
11449 .iter()
11450 .map(|s| {
11451 let start_point = Point::new(s.start.0, 0);
11452 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11453 Selection {
11454 id: s.id,
11455 start: buffer.point_to_offset(start_point),
11456 end: buffer.point_to_offset(end_point),
11457 goal: s.goal,
11458 reversed: s.reversed,
11459 }
11460 })
11461 .collect();
11462
11463 this.change_selections(Default::default(), window, cx, |s| {
11464 s.select(new_selections);
11465 });
11466
11467 this.request_autoscroll(Autoscroll::fit(), cx);
11468 });
11469 }
11470
11471 fn manipulate_immutable_lines<Fn>(
11472 &mut self,
11473 window: &mut Window,
11474 cx: &mut Context<Self>,
11475 mut callback: Fn,
11476 ) where
11477 Fn: FnMut(&mut Vec<&str>),
11478 {
11479 self.manipulate_lines(window, cx, |text| {
11480 let mut lines: Vec<&str> = text.split('\n').collect();
11481 let line_count_before = lines.len();
11482
11483 callback(&mut lines);
11484
11485 LineManipulationResult {
11486 new_text: lines.join("\n"),
11487 line_count_before,
11488 line_count_after: lines.len(),
11489 }
11490 });
11491 }
11492
11493 fn manipulate_mutable_lines<Fn>(
11494 &mut self,
11495 window: &mut Window,
11496 cx: &mut Context<Self>,
11497 mut callback: Fn,
11498 ) where
11499 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11500 {
11501 self.manipulate_lines(window, cx, |text| {
11502 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11503 let line_count_before = lines.len();
11504
11505 callback(&mut lines);
11506
11507 LineManipulationResult {
11508 new_text: lines.join("\n"),
11509 line_count_before,
11510 line_count_after: lines.len(),
11511 }
11512 });
11513 }
11514
11515 pub fn convert_indentation_to_spaces(
11516 &mut self,
11517 _: &ConvertIndentationToSpaces,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 let settings = self.buffer.read(cx).language_settings(cx);
11522 let tab_size = settings.tab_size.get() as usize;
11523
11524 self.manipulate_mutable_lines(window, cx, |lines| {
11525 // Allocates a reasonably sized scratch buffer once for the whole loop
11526 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11527 // Avoids recomputing spaces that could be inserted many times
11528 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11529 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11530 .collect();
11531
11532 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11533 let mut chars = line.as_ref().chars();
11534 let mut col = 0;
11535 let mut changed = false;
11536
11537 for ch in chars.by_ref() {
11538 match ch {
11539 ' ' => {
11540 reindented_line.push(' ');
11541 col += 1;
11542 }
11543 '\t' => {
11544 // \t are converted to spaces depending on the current column
11545 let spaces_len = tab_size - (col % tab_size);
11546 reindented_line.extend(&space_cache[spaces_len - 1]);
11547 col += spaces_len;
11548 changed = true;
11549 }
11550 _ => {
11551 // If we dont append before break, the character is consumed
11552 reindented_line.push(ch);
11553 break;
11554 }
11555 }
11556 }
11557
11558 if !changed {
11559 reindented_line.clear();
11560 continue;
11561 }
11562 // Append the rest of the line and replace old reference with new one
11563 reindented_line.extend(chars);
11564 *line = Cow::Owned(reindented_line.clone());
11565 reindented_line.clear();
11566 }
11567 });
11568 }
11569
11570 pub fn convert_indentation_to_tabs(
11571 &mut self,
11572 _: &ConvertIndentationToTabs,
11573 window: &mut Window,
11574 cx: &mut Context<Self>,
11575 ) {
11576 let settings = self.buffer.read(cx).language_settings(cx);
11577 let tab_size = settings.tab_size.get() as usize;
11578
11579 self.manipulate_mutable_lines(window, cx, |lines| {
11580 // Allocates a reasonably sized buffer once for the whole loop
11581 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11582 // Avoids recomputing spaces that could be inserted many times
11583 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11584 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11585 .collect();
11586
11587 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11588 let mut chars = line.chars();
11589 let mut spaces_count = 0;
11590 let mut first_non_indent_char = None;
11591 let mut changed = false;
11592
11593 for ch in chars.by_ref() {
11594 match ch {
11595 ' ' => {
11596 // Keep track of spaces. Append \t when we reach tab_size
11597 spaces_count += 1;
11598 changed = true;
11599 if spaces_count == tab_size {
11600 reindented_line.push('\t');
11601 spaces_count = 0;
11602 }
11603 }
11604 '\t' => {
11605 reindented_line.push('\t');
11606 spaces_count = 0;
11607 }
11608 _ => {
11609 // Dont append it yet, we might have remaining spaces
11610 first_non_indent_char = Some(ch);
11611 break;
11612 }
11613 }
11614 }
11615
11616 if !changed {
11617 reindented_line.clear();
11618 continue;
11619 }
11620 // Remaining spaces that didn't make a full tab stop
11621 if spaces_count > 0 {
11622 reindented_line.extend(&space_cache[spaces_count - 1]);
11623 }
11624 // If we consume an extra character that was not indentation, add it back
11625 if let Some(extra_char) = first_non_indent_char {
11626 reindented_line.push(extra_char);
11627 }
11628 // Append the rest of the line and replace old reference with new one
11629 reindented_line.extend(chars);
11630 *line = Cow::Owned(reindented_line.clone());
11631 reindented_line.clear();
11632 }
11633 });
11634 }
11635
11636 pub fn convert_to_upper_case(
11637 &mut self,
11638 _: &ConvertToUpperCase,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 self.manipulate_text(window, cx, |text| text.to_uppercase())
11643 }
11644
11645 pub fn convert_to_lower_case(
11646 &mut self,
11647 _: &ConvertToLowerCase,
11648 window: &mut Window,
11649 cx: &mut Context<Self>,
11650 ) {
11651 self.manipulate_text(window, cx, |text| text.to_lowercase())
11652 }
11653
11654 pub fn convert_to_title_case(
11655 &mut self,
11656 _: &ConvertToTitleCase,
11657 window: &mut Window,
11658 cx: &mut Context<Self>,
11659 ) {
11660 self.manipulate_text(window, cx, |text| {
11661 text.split('\n')
11662 .map(|line| line.to_case(Case::Title))
11663 .join("\n")
11664 })
11665 }
11666
11667 pub fn convert_to_snake_case(
11668 &mut self,
11669 _: &ConvertToSnakeCase,
11670 window: &mut Window,
11671 cx: &mut Context<Self>,
11672 ) {
11673 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11674 }
11675
11676 pub fn convert_to_kebab_case(
11677 &mut self,
11678 _: &ConvertToKebabCase,
11679 window: &mut Window,
11680 cx: &mut Context<Self>,
11681 ) {
11682 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11683 }
11684
11685 pub fn convert_to_upper_camel_case(
11686 &mut self,
11687 _: &ConvertToUpperCamelCase,
11688 window: &mut Window,
11689 cx: &mut Context<Self>,
11690 ) {
11691 self.manipulate_text(window, cx, |text| {
11692 text.split('\n')
11693 .map(|line| line.to_case(Case::UpperCamel))
11694 .join("\n")
11695 })
11696 }
11697
11698 pub fn convert_to_lower_camel_case(
11699 &mut self,
11700 _: &ConvertToLowerCamelCase,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11705 }
11706
11707 pub fn convert_to_opposite_case(
11708 &mut self,
11709 _: &ConvertToOppositeCase,
11710 window: &mut Window,
11711 cx: &mut Context<Self>,
11712 ) {
11713 self.manipulate_text(window, cx, |text| {
11714 text.chars()
11715 .fold(String::with_capacity(text.len()), |mut t, c| {
11716 if c.is_uppercase() {
11717 t.extend(c.to_lowercase());
11718 } else {
11719 t.extend(c.to_uppercase());
11720 }
11721 t
11722 })
11723 })
11724 }
11725
11726 pub fn convert_to_sentence_case(
11727 &mut self,
11728 _: &ConvertToSentenceCase,
11729 window: &mut Window,
11730 cx: &mut Context<Self>,
11731 ) {
11732 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11733 }
11734
11735 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11736 self.manipulate_text(window, cx, |text| {
11737 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11738 if has_upper_case_characters {
11739 text.to_lowercase()
11740 } else {
11741 text.to_uppercase()
11742 }
11743 })
11744 }
11745
11746 pub fn convert_to_rot13(
11747 &mut self,
11748 _: &ConvertToRot13,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 self.manipulate_text(window, cx, |text| {
11753 text.chars()
11754 .map(|c| match c {
11755 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11756 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11757 _ => c,
11758 })
11759 .collect()
11760 })
11761 }
11762
11763 pub fn convert_to_rot47(
11764 &mut self,
11765 _: &ConvertToRot47,
11766 window: &mut Window,
11767 cx: &mut Context<Self>,
11768 ) {
11769 self.manipulate_text(window, cx, |text| {
11770 text.chars()
11771 .map(|c| {
11772 let code_point = c as u32;
11773 if code_point >= 33 && code_point <= 126 {
11774 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11775 }
11776 c
11777 })
11778 .collect()
11779 })
11780 }
11781
11782 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11783 where
11784 Fn: FnMut(&str) -> String,
11785 {
11786 let buffer = self.buffer.read(cx).snapshot(cx);
11787
11788 let mut new_selections = Vec::new();
11789 let mut edits = Vec::new();
11790 let mut selection_adjustment = 0isize;
11791
11792 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11793 let selection_is_empty = selection.is_empty();
11794
11795 let (start, end) = if selection_is_empty {
11796 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11797 (word_range.start, word_range.end)
11798 } else {
11799 (
11800 buffer.point_to_offset(selection.start),
11801 buffer.point_to_offset(selection.end),
11802 )
11803 };
11804
11805 let text = buffer.text_for_range(start..end).collect::<String>();
11806 let old_length = text.len() as isize;
11807 let text = callback(&text);
11808
11809 new_selections.push(Selection {
11810 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11811 end: MultiBufferOffset(
11812 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11813 ),
11814 goal: SelectionGoal::None,
11815 id: selection.id,
11816 reversed: selection.reversed,
11817 });
11818
11819 selection_adjustment += old_length - text.len() as isize;
11820
11821 edits.push((start..end, text));
11822 }
11823
11824 self.transact(window, cx, |this, window, cx| {
11825 this.buffer.update(cx, |buffer, cx| {
11826 buffer.edit(edits, None, cx);
11827 });
11828
11829 this.change_selections(Default::default(), window, cx, |s| {
11830 s.select(new_selections);
11831 });
11832
11833 this.request_autoscroll(Autoscroll::fit(), cx);
11834 });
11835 }
11836
11837 pub fn move_selection_on_drop(
11838 &mut self,
11839 selection: &Selection<Anchor>,
11840 target: DisplayPoint,
11841 is_cut: bool,
11842 window: &mut Window,
11843 cx: &mut Context<Self>,
11844 ) {
11845 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11846 let buffer = display_map.buffer_snapshot();
11847 let mut edits = Vec::new();
11848 let insert_point = display_map
11849 .clip_point(target, Bias::Left)
11850 .to_point(&display_map);
11851 let text = buffer
11852 .text_for_range(selection.start..selection.end)
11853 .collect::<String>();
11854 if is_cut {
11855 edits.push(((selection.start..selection.end), String::new()));
11856 }
11857 let insert_anchor = buffer.anchor_before(insert_point);
11858 edits.push(((insert_anchor..insert_anchor), text));
11859 let last_edit_start = insert_anchor.bias_left(buffer);
11860 let last_edit_end = insert_anchor.bias_right(buffer);
11861 self.transact(window, cx, |this, window, cx| {
11862 this.buffer.update(cx, |buffer, cx| {
11863 buffer.edit(edits, None, cx);
11864 });
11865 this.change_selections(Default::default(), window, cx, |s| {
11866 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11867 });
11868 });
11869 }
11870
11871 pub fn clear_selection_drag_state(&mut self) {
11872 self.selection_drag_state = SelectionDragState::None;
11873 }
11874
11875 pub fn duplicate(
11876 &mut self,
11877 upwards: bool,
11878 whole_lines: bool,
11879 window: &mut Window,
11880 cx: &mut Context<Self>,
11881 ) {
11882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11883
11884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11885 let buffer = display_map.buffer_snapshot();
11886 let selections = self.selections.all::<Point>(&display_map);
11887
11888 let mut edits = Vec::new();
11889 let mut selections_iter = selections.iter().peekable();
11890 while let Some(selection) = selections_iter.next() {
11891 let mut rows = selection.spanned_rows(false, &display_map);
11892 // duplicate line-wise
11893 if whole_lines || selection.start == selection.end {
11894 // Avoid duplicating the same lines twice.
11895 while let Some(next_selection) = selections_iter.peek() {
11896 let next_rows = next_selection.spanned_rows(false, &display_map);
11897 if next_rows.start < rows.end {
11898 rows.end = next_rows.end;
11899 selections_iter.next().unwrap();
11900 } else {
11901 break;
11902 }
11903 }
11904
11905 // Copy the text from the selected row region and splice it either at the start
11906 // or end of the region.
11907 let start = Point::new(rows.start.0, 0);
11908 let end = Point::new(
11909 rows.end.previous_row().0,
11910 buffer.line_len(rows.end.previous_row()),
11911 );
11912
11913 let mut text = buffer.text_for_range(start..end).collect::<String>();
11914
11915 let insert_location = if upwards {
11916 // When duplicating upward, we need to insert before the current line.
11917 // If we're on the last line and it doesn't end with a newline,
11918 // we need to add a newline before the duplicated content.
11919 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11920 && buffer.max_point().column > 0
11921 && !text.ends_with('\n');
11922
11923 if needs_leading_newline {
11924 text.insert(0, '\n');
11925 end
11926 } else {
11927 text.push('\n');
11928 Point::new(rows.start.0, 0)
11929 }
11930 } else {
11931 text.push('\n');
11932 start
11933 };
11934 edits.push((insert_location..insert_location, text));
11935 } else {
11936 // duplicate character-wise
11937 let start = selection.start;
11938 let end = selection.end;
11939 let text = buffer.text_for_range(start..end).collect::<String>();
11940 edits.push((selection.end..selection.end, text));
11941 }
11942 }
11943
11944 self.transact(window, cx, |this, window, cx| {
11945 this.buffer.update(cx, |buffer, cx| {
11946 buffer.edit(edits, None, cx);
11947 });
11948
11949 // When duplicating upward with whole lines, move the cursor to the duplicated line
11950 if upwards && whole_lines {
11951 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11952
11953 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11954 let mut new_ranges = Vec::new();
11955 let selections = s.all::<Point>(&display_map);
11956 let mut selections_iter = selections.iter().peekable();
11957
11958 while let Some(first_selection) = selections_iter.next() {
11959 // Group contiguous selections together to find the total row span
11960 let mut group_selections = vec![first_selection];
11961 let mut rows = first_selection.spanned_rows(false, &display_map);
11962
11963 while let Some(next_selection) = selections_iter.peek() {
11964 let next_rows = next_selection.spanned_rows(false, &display_map);
11965 if next_rows.start < rows.end {
11966 rows.end = next_rows.end;
11967 group_selections.push(selections_iter.next().unwrap());
11968 } else {
11969 break;
11970 }
11971 }
11972
11973 let row_count = rows.end.0 - rows.start.0;
11974
11975 // Move all selections in this group up by the total number of duplicated rows
11976 for selection in group_selections {
11977 let new_start = Point::new(
11978 selection.start.row.saturating_sub(row_count),
11979 selection.start.column,
11980 );
11981
11982 let new_end = Point::new(
11983 selection.end.row.saturating_sub(row_count),
11984 selection.end.column,
11985 );
11986
11987 new_ranges.push(new_start..new_end);
11988 }
11989 }
11990
11991 s.select_ranges(new_ranges);
11992 });
11993 }
11994
11995 this.request_autoscroll(Autoscroll::fit(), cx);
11996 });
11997 }
11998
11999 pub fn duplicate_line_up(
12000 &mut self,
12001 _: &DuplicateLineUp,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.duplicate(true, true, window, cx);
12006 }
12007
12008 pub fn duplicate_line_down(
12009 &mut self,
12010 _: &DuplicateLineDown,
12011 window: &mut Window,
12012 cx: &mut Context<Self>,
12013 ) {
12014 self.duplicate(false, true, window, cx);
12015 }
12016
12017 pub fn duplicate_selection(
12018 &mut self,
12019 _: &DuplicateSelection,
12020 window: &mut Window,
12021 cx: &mut Context<Self>,
12022 ) {
12023 self.duplicate(false, false, window, cx);
12024 }
12025
12026 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12028 if self.mode.is_single_line() {
12029 cx.propagate();
12030 return;
12031 }
12032
12033 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12034 let buffer = self.buffer.read(cx).snapshot(cx);
12035
12036 let mut edits = Vec::new();
12037 let mut unfold_ranges = Vec::new();
12038 let mut refold_creases = Vec::new();
12039
12040 let selections = self.selections.all::<Point>(&display_map);
12041 let mut selections = selections.iter().peekable();
12042 let mut contiguous_row_selections = Vec::new();
12043 let mut new_selections = Vec::new();
12044
12045 while let Some(selection) = selections.next() {
12046 // Find all the selections that span a contiguous row range
12047 let (start_row, end_row) = consume_contiguous_rows(
12048 &mut contiguous_row_selections,
12049 selection,
12050 &display_map,
12051 &mut selections,
12052 );
12053
12054 // Move the text spanned by the row range to be before the line preceding the row range
12055 if start_row.0 > 0 {
12056 let range_to_move = Point::new(
12057 start_row.previous_row().0,
12058 buffer.line_len(start_row.previous_row()),
12059 )
12060 ..Point::new(
12061 end_row.previous_row().0,
12062 buffer.line_len(end_row.previous_row()),
12063 );
12064 let insertion_point = display_map
12065 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12066 .0;
12067
12068 // Don't move lines across excerpts
12069 if buffer
12070 .excerpt_containing(insertion_point..range_to_move.end)
12071 .is_some()
12072 {
12073 let text = buffer
12074 .text_for_range(range_to_move.clone())
12075 .flat_map(|s| s.chars())
12076 .skip(1)
12077 .chain(['\n'])
12078 .collect::<String>();
12079
12080 edits.push((
12081 buffer.anchor_after(range_to_move.start)
12082 ..buffer.anchor_before(range_to_move.end),
12083 String::new(),
12084 ));
12085 let insertion_anchor = buffer.anchor_after(insertion_point);
12086 edits.push((insertion_anchor..insertion_anchor, text));
12087
12088 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12089
12090 // Move selections up
12091 new_selections.extend(contiguous_row_selections.drain(..).map(
12092 |mut selection| {
12093 selection.start.row -= row_delta;
12094 selection.end.row -= row_delta;
12095 selection
12096 },
12097 ));
12098
12099 // Move folds up
12100 unfold_ranges.push(range_to_move.clone());
12101 for fold in display_map.folds_in_range(
12102 buffer.anchor_before(range_to_move.start)
12103 ..buffer.anchor_after(range_to_move.end),
12104 ) {
12105 let mut start = fold.range.start.to_point(&buffer);
12106 let mut end = fold.range.end.to_point(&buffer);
12107 start.row -= row_delta;
12108 end.row -= row_delta;
12109 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12110 }
12111 }
12112 }
12113
12114 // If we didn't move line(s), preserve the existing selections
12115 new_selections.append(&mut contiguous_row_selections);
12116 }
12117
12118 self.transact(window, cx, |this, window, cx| {
12119 this.unfold_ranges(&unfold_ranges, true, true, cx);
12120 this.buffer.update(cx, |buffer, cx| {
12121 for (range, text) in edits {
12122 buffer.edit([(range, text)], None, cx);
12123 }
12124 });
12125 this.fold_creases(refold_creases, true, window, cx);
12126 this.change_selections(Default::default(), window, cx, |s| {
12127 s.select(new_selections);
12128 })
12129 });
12130 }
12131
12132 pub fn move_line_down(
12133 &mut self,
12134 _: &MoveLineDown,
12135 window: &mut Window,
12136 cx: &mut Context<Self>,
12137 ) {
12138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12139 if self.mode.is_single_line() {
12140 cx.propagate();
12141 return;
12142 }
12143
12144 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12145 let buffer = self.buffer.read(cx).snapshot(cx);
12146
12147 let mut edits = Vec::new();
12148 let mut unfold_ranges = Vec::new();
12149 let mut refold_creases = Vec::new();
12150
12151 let selections = self.selections.all::<Point>(&display_map);
12152 let mut selections = selections.iter().peekable();
12153 let mut contiguous_row_selections = Vec::new();
12154 let mut new_selections = Vec::new();
12155
12156 while let Some(selection) = selections.next() {
12157 // Find all the selections that span a contiguous row range
12158 let (start_row, end_row) = consume_contiguous_rows(
12159 &mut contiguous_row_selections,
12160 selection,
12161 &display_map,
12162 &mut selections,
12163 );
12164
12165 // Move the text spanned by the row range to be after the last line of the row range
12166 if end_row.0 <= buffer.max_point().row {
12167 let range_to_move =
12168 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12169 let insertion_point = display_map
12170 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12171 .0;
12172
12173 // Don't move lines across excerpt boundaries
12174 if buffer
12175 .excerpt_containing(range_to_move.start..insertion_point)
12176 .is_some()
12177 {
12178 let mut text = String::from("\n");
12179 text.extend(buffer.text_for_range(range_to_move.clone()));
12180 text.pop(); // Drop trailing newline
12181 edits.push((
12182 buffer.anchor_after(range_to_move.start)
12183 ..buffer.anchor_before(range_to_move.end),
12184 String::new(),
12185 ));
12186 let insertion_anchor = buffer.anchor_after(insertion_point);
12187 edits.push((insertion_anchor..insertion_anchor, text));
12188
12189 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12190
12191 // Move selections down
12192 new_selections.extend(contiguous_row_selections.drain(..).map(
12193 |mut selection| {
12194 selection.start.row += row_delta;
12195 selection.end.row += row_delta;
12196 selection
12197 },
12198 ));
12199
12200 // Move folds down
12201 unfold_ranges.push(range_to_move.clone());
12202 for fold in display_map.folds_in_range(
12203 buffer.anchor_before(range_to_move.start)
12204 ..buffer.anchor_after(range_to_move.end),
12205 ) {
12206 let mut start = fold.range.start.to_point(&buffer);
12207 let mut end = fold.range.end.to_point(&buffer);
12208 start.row += row_delta;
12209 end.row += row_delta;
12210 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12211 }
12212 }
12213 }
12214
12215 // If we didn't move line(s), preserve the existing selections
12216 new_selections.append(&mut contiguous_row_selections);
12217 }
12218
12219 self.transact(window, cx, |this, window, cx| {
12220 this.unfold_ranges(&unfold_ranges, true, true, cx);
12221 this.buffer.update(cx, |buffer, cx| {
12222 for (range, text) in edits {
12223 buffer.edit([(range, text)], None, cx);
12224 }
12225 });
12226 this.fold_creases(refold_creases, true, window, cx);
12227 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12228 });
12229 }
12230
12231 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12233 let text_layout_details = &self.text_layout_details(window);
12234 self.transact(window, cx, |this, window, cx| {
12235 let edits = this.change_selections(Default::default(), window, cx, |s| {
12236 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12237 s.move_with(|display_map, selection| {
12238 if !selection.is_empty() {
12239 return;
12240 }
12241
12242 let mut head = selection.head();
12243 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12244 if head.column() == display_map.line_len(head.row()) {
12245 transpose_offset = display_map
12246 .buffer_snapshot()
12247 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12248 }
12249
12250 if transpose_offset == MultiBufferOffset(0) {
12251 return;
12252 }
12253
12254 *head.column_mut() += 1;
12255 head = display_map.clip_point(head, Bias::Right);
12256 let goal = SelectionGoal::HorizontalPosition(
12257 display_map
12258 .x_for_display_point(head, text_layout_details)
12259 .into(),
12260 );
12261 selection.collapse_to(head, goal);
12262
12263 let transpose_start = display_map
12264 .buffer_snapshot()
12265 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12266 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12267 let transpose_end = display_map
12268 .buffer_snapshot()
12269 .clip_offset(transpose_offset + 1usize, Bias::Right);
12270 if let Some(ch) = display_map
12271 .buffer_snapshot()
12272 .chars_at(transpose_start)
12273 .next()
12274 {
12275 edits.push((transpose_start..transpose_offset, String::new()));
12276 edits.push((transpose_end..transpose_end, ch.to_string()));
12277 }
12278 }
12279 });
12280 edits
12281 });
12282 this.buffer
12283 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12284 let selections = this
12285 .selections
12286 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12287 this.change_selections(Default::default(), window, cx, |s| {
12288 s.select(selections);
12289 });
12290 });
12291 }
12292
12293 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12295 if self.mode.is_single_line() {
12296 cx.propagate();
12297 return;
12298 }
12299
12300 self.rewrap_impl(RewrapOptions::default(), cx)
12301 }
12302
12303 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12304 let buffer = self.buffer.read(cx).snapshot(cx);
12305 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12306
12307 #[derive(Clone, Debug, PartialEq)]
12308 enum CommentFormat {
12309 /// single line comment, with prefix for line
12310 Line(String),
12311 /// single line within a block comment, with prefix for line
12312 BlockLine(String),
12313 /// a single line of a block comment that includes the initial delimiter
12314 BlockCommentWithStart(BlockCommentConfig),
12315 /// a single line of a block comment that includes the ending delimiter
12316 BlockCommentWithEnd(BlockCommentConfig),
12317 }
12318
12319 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12320 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12321 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12322 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12323 .peekable();
12324
12325 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12326 row
12327 } else {
12328 return Vec::new();
12329 };
12330
12331 let language_settings = buffer.language_settings_at(selection.head(), cx);
12332 let language_scope = buffer.language_scope_at(selection.head());
12333
12334 let indent_and_prefix_for_row =
12335 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12336 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12337 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12338 &language_scope
12339 {
12340 let indent_end = Point::new(row, indent.len);
12341 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12342 let line_text_after_indent = buffer
12343 .text_for_range(indent_end..line_end)
12344 .collect::<String>();
12345
12346 let is_within_comment_override = buffer
12347 .language_scope_at(indent_end)
12348 .is_some_and(|scope| scope.override_name() == Some("comment"));
12349 let comment_delimiters = if is_within_comment_override {
12350 // we are within a comment syntax node, but we don't
12351 // yet know what kind of comment: block, doc or line
12352 match (
12353 language_scope.documentation_comment(),
12354 language_scope.block_comment(),
12355 ) {
12356 (Some(config), _) | (_, Some(config))
12357 if buffer.contains_str_at(indent_end, &config.start) =>
12358 {
12359 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12360 }
12361 (Some(config), _) | (_, Some(config))
12362 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12363 {
12364 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12365 }
12366 (Some(config), _) | (_, Some(config))
12367 if buffer.contains_str_at(indent_end, &config.prefix) =>
12368 {
12369 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12370 }
12371 (_, _) => language_scope
12372 .line_comment_prefixes()
12373 .iter()
12374 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12375 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12376 }
12377 } else {
12378 // we not in an overridden comment node, but we may
12379 // be within a non-overridden line comment node
12380 language_scope
12381 .line_comment_prefixes()
12382 .iter()
12383 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12384 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12385 };
12386
12387 let rewrap_prefix = language_scope
12388 .rewrap_prefixes()
12389 .iter()
12390 .find_map(|prefix_regex| {
12391 prefix_regex.find(&line_text_after_indent).map(|mat| {
12392 if mat.start() == 0 {
12393 Some(mat.as_str().to_string())
12394 } else {
12395 None
12396 }
12397 })
12398 })
12399 .flatten();
12400 (comment_delimiters, rewrap_prefix)
12401 } else {
12402 (None, None)
12403 };
12404 (indent, comment_prefix, rewrap_prefix)
12405 };
12406
12407 let mut ranges = Vec::new();
12408 let from_empty_selection = selection.is_empty();
12409
12410 let mut current_range_start = first_row;
12411 let mut prev_row = first_row;
12412 let (
12413 mut current_range_indent,
12414 mut current_range_comment_delimiters,
12415 mut current_range_rewrap_prefix,
12416 ) = indent_and_prefix_for_row(first_row);
12417
12418 for row in non_blank_rows_iter.skip(1) {
12419 let has_paragraph_break = row > prev_row + 1;
12420
12421 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12422 indent_and_prefix_for_row(row);
12423
12424 let has_indent_change = row_indent != current_range_indent;
12425 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12426
12427 let has_boundary_change = has_comment_change
12428 || row_rewrap_prefix.is_some()
12429 || (has_indent_change && current_range_comment_delimiters.is_some());
12430
12431 if has_paragraph_break || has_boundary_change {
12432 ranges.push((
12433 language_settings.clone(),
12434 Point::new(current_range_start, 0)
12435 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12436 current_range_indent,
12437 current_range_comment_delimiters.clone(),
12438 current_range_rewrap_prefix.clone(),
12439 from_empty_selection,
12440 ));
12441 current_range_start = row;
12442 current_range_indent = row_indent;
12443 current_range_comment_delimiters = row_comment_delimiters;
12444 current_range_rewrap_prefix = row_rewrap_prefix;
12445 }
12446 prev_row = row;
12447 }
12448
12449 ranges.push((
12450 language_settings.clone(),
12451 Point::new(current_range_start, 0)
12452 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12453 current_range_indent,
12454 current_range_comment_delimiters,
12455 current_range_rewrap_prefix,
12456 from_empty_selection,
12457 ));
12458
12459 ranges
12460 });
12461
12462 let mut edits = Vec::new();
12463 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12464
12465 for (
12466 language_settings,
12467 wrap_range,
12468 mut indent_size,
12469 comment_prefix,
12470 rewrap_prefix,
12471 from_empty_selection,
12472 ) in wrap_ranges
12473 {
12474 let mut start_row = wrap_range.start.row;
12475 let mut end_row = wrap_range.end.row;
12476
12477 // Skip selections that overlap with a range that has already been rewrapped.
12478 let selection_range = start_row..end_row;
12479 if rewrapped_row_ranges
12480 .iter()
12481 .any(|range| range.overlaps(&selection_range))
12482 {
12483 continue;
12484 }
12485
12486 let tab_size = language_settings.tab_size;
12487
12488 let (line_prefix, inside_comment) = match &comment_prefix {
12489 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12490 (Some(prefix.as_str()), true)
12491 }
12492 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12493 (Some(prefix.as_ref()), true)
12494 }
12495 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12496 start: _,
12497 end: _,
12498 prefix,
12499 tab_size,
12500 })) => {
12501 indent_size.len += tab_size;
12502 (Some(prefix.as_ref()), true)
12503 }
12504 None => (None, false),
12505 };
12506 let indent_prefix = indent_size.chars().collect::<String>();
12507 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12508
12509 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12510 RewrapBehavior::InComments => inside_comment,
12511 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12512 RewrapBehavior::Anywhere => true,
12513 };
12514
12515 let should_rewrap = options.override_language_settings
12516 || allow_rewrap_based_on_language
12517 || self.hard_wrap.is_some();
12518 if !should_rewrap {
12519 continue;
12520 }
12521
12522 if from_empty_selection {
12523 'expand_upwards: while start_row > 0 {
12524 let prev_row = start_row - 1;
12525 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12526 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12527 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12528 {
12529 start_row = prev_row;
12530 } else {
12531 break 'expand_upwards;
12532 }
12533 }
12534
12535 'expand_downwards: while end_row < buffer.max_point().row {
12536 let next_row = end_row + 1;
12537 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12538 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12539 && !buffer.is_line_blank(MultiBufferRow(next_row))
12540 {
12541 end_row = next_row;
12542 } else {
12543 break 'expand_downwards;
12544 }
12545 }
12546 }
12547
12548 let start = Point::new(start_row, 0);
12549 let start_offset = ToOffset::to_offset(&start, &buffer);
12550 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12551 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12552 let mut first_line_delimiter = None;
12553 let mut last_line_delimiter = None;
12554 let Some(lines_without_prefixes) = selection_text
12555 .lines()
12556 .enumerate()
12557 .map(|(ix, line)| {
12558 let line_trimmed = line.trim_start();
12559 if rewrap_prefix.is_some() && ix > 0 {
12560 Ok(line_trimmed)
12561 } else if let Some(
12562 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12563 start,
12564 prefix,
12565 end,
12566 tab_size,
12567 })
12568 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12569 start,
12570 prefix,
12571 end,
12572 tab_size,
12573 }),
12574 ) = &comment_prefix
12575 {
12576 let line_trimmed = line_trimmed
12577 .strip_prefix(start.as_ref())
12578 .map(|s| {
12579 let mut indent_size = indent_size;
12580 indent_size.len -= tab_size;
12581 let indent_prefix: String = indent_size.chars().collect();
12582 first_line_delimiter = Some((indent_prefix, start));
12583 s.trim_start()
12584 })
12585 .unwrap_or(line_trimmed);
12586 let line_trimmed = line_trimmed
12587 .strip_suffix(end.as_ref())
12588 .map(|s| {
12589 last_line_delimiter = Some(end);
12590 s.trim_end()
12591 })
12592 .unwrap_or(line_trimmed);
12593 let line_trimmed = line_trimmed
12594 .strip_prefix(prefix.as_ref())
12595 .unwrap_or(line_trimmed);
12596 Ok(line_trimmed)
12597 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12598 line_trimmed.strip_prefix(prefix).with_context(|| {
12599 format!("line did not start with prefix {prefix:?}: {line:?}")
12600 })
12601 } else {
12602 line_trimmed
12603 .strip_prefix(&line_prefix.trim_start())
12604 .with_context(|| {
12605 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12606 })
12607 }
12608 })
12609 .collect::<Result<Vec<_>, _>>()
12610 .log_err()
12611 else {
12612 continue;
12613 };
12614
12615 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12616 buffer
12617 .language_settings_at(Point::new(start_row, 0), cx)
12618 .preferred_line_length as usize
12619 });
12620
12621 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12622 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12623 } else {
12624 line_prefix.clone()
12625 };
12626
12627 let wrapped_text = {
12628 let mut wrapped_text = wrap_with_prefix(
12629 line_prefix,
12630 subsequent_lines_prefix,
12631 lines_without_prefixes.join("\n"),
12632 wrap_column,
12633 tab_size,
12634 options.preserve_existing_whitespace,
12635 );
12636
12637 if let Some((indent, delimiter)) = first_line_delimiter {
12638 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12639 }
12640 if let Some(last_line) = last_line_delimiter {
12641 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12642 }
12643
12644 wrapped_text
12645 };
12646
12647 // TODO: should always use char-based diff while still supporting cursor behavior that
12648 // matches vim.
12649 let mut diff_options = DiffOptions::default();
12650 if options.override_language_settings {
12651 diff_options.max_word_diff_len = 0;
12652 diff_options.max_word_diff_line_count = 0;
12653 } else {
12654 diff_options.max_word_diff_len = usize::MAX;
12655 diff_options.max_word_diff_line_count = usize::MAX;
12656 }
12657
12658 for (old_range, new_text) in
12659 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12660 {
12661 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12662 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12663 edits.push((edit_start..edit_end, new_text));
12664 }
12665
12666 rewrapped_row_ranges.push(start_row..=end_row);
12667 }
12668
12669 self.buffer
12670 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12671 }
12672
12673 pub fn cut_common(
12674 &mut self,
12675 cut_no_selection_line: bool,
12676 window: &mut Window,
12677 cx: &mut Context<Self>,
12678 ) -> ClipboardItem {
12679 let mut text = String::new();
12680 let buffer = self.buffer.read(cx).snapshot(cx);
12681 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12682 let mut clipboard_selections = Vec::with_capacity(selections.len());
12683 {
12684 let max_point = buffer.max_point();
12685 let mut is_first = true;
12686 let mut prev_selection_was_entire_line = false;
12687 for selection in &mut selections {
12688 let is_entire_line =
12689 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12690 if is_entire_line {
12691 selection.start = Point::new(selection.start.row, 0);
12692 if !selection.is_empty() && selection.end.column == 0 {
12693 selection.end = cmp::min(max_point, selection.end);
12694 } else {
12695 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12696 }
12697 selection.goal = SelectionGoal::None;
12698 }
12699 if is_first {
12700 is_first = false;
12701 } else if !prev_selection_was_entire_line {
12702 text += "\n";
12703 }
12704 prev_selection_was_entire_line = is_entire_line;
12705 let mut len = 0;
12706 for chunk in buffer.text_for_range(selection.start..selection.end) {
12707 text.push_str(chunk);
12708 len += chunk.len();
12709 }
12710 clipboard_selections.push(ClipboardSelection {
12711 len,
12712 is_entire_line,
12713 first_line_indent: buffer
12714 .indent_size_for_line(MultiBufferRow(selection.start.row))
12715 .len,
12716 });
12717 }
12718 }
12719
12720 self.transact(window, cx, |this, window, cx| {
12721 this.change_selections(Default::default(), window, cx, |s| {
12722 s.select(selections);
12723 });
12724 this.insert("", window, cx);
12725 });
12726 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12727 }
12728
12729 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12731 let item = self.cut_common(true, window, cx);
12732 cx.write_to_clipboard(item);
12733 }
12734
12735 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12737 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12738 s.move_with(|snapshot, sel| {
12739 if sel.is_empty() {
12740 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12741 }
12742 if sel.is_empty() {
12743 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12744 }
12745 });
12746 });
12747 let item = self.cut_common(false, window, cx);
12748 cx.set_global(KillRing(item))
12749 }
12750
12751 pub fn kill_ring_yank(
12752 &mut self,
12753 _: &KillRingYank,
12754 window: &mut Window,
12755 cx: &mut Context<Self>,
12756 ) {
12757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12758 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12759 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12760 (kill_ring.text().to_string(), kill_ring.metadata_json())
12761 } else {
12762 return;
12763 }
12764 } else {
12765 return;
12766 };
12767 self.do_paste(&text, metadata, false, window, cx);
12768 }
12769
12770 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12771 self.do_copy(true, cx);
12772 }
12773
12774 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12775 self.do_copy(false, cx);
12776 }
12777
12778 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12779 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12780 let buffer = self.buffer.read(cx).read(cx);
12781 let mut text = String::new();
12782
12783 let mut clipboard_selections = Vec::with_capacity(selections.len());
12784 {
12785 let max_point = buffer.max_point();
12786 let mut is_first = true;
12787 let mut prev_selection_was_entire_line = false;
12788 for selection in &selections {
12789 let mut start = selection.start;
12790 let mut end = selection.end;
12791 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12792 let mut add_trailing_newline = false;
12793 if is_entire_line {
12794 start = Point::new(start.row, 0);
12795 let next_line_start = Point::new(end.row + 1, 0);
12796 if next_line_start <= max_point {
12797 end = next_line_start;
12798 } else {
12799 // We're on the last line without a trailing newline.
12800 // Copy to the end of the line and add a newline afterwards.
12801 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12802 add_trailing_newline = true;
12803 }
12804 }
12805
12806 let mut trimmed_selections = Vec::new();
12807 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12808 let row = MultiBufferRow(start.row);
12809 let first_indent = buffer.indent_size_for_line(row);
12810 if first_indent.len == 0 || start.column > first_indent.len {
12811 trimmed_selections.push(start..end);
12812 } else {
12813 trimmed_selections.push(
12814 Point::new(row.0, first_indent.len)
12815 ..Point::new(row.0, buffer.line_len(row)),
12816 );
12817 for row in start.row + 1..=end.row {
12818 let mut line_len = buffer.line_len(MultiBufferRow(row));
12819 if row == end.row {
12820 line_len = end.column;
12821 }
12822 if line_len == 0 {
12823 trimmed_selections
12824 .push(Point::new(row, 0)..Point::new(row, line_len));
12825 continue;
12826 }
12827 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12828 if row_indent_size.len >= first_indent.len {
12829 trimmed_selections.push(
12830 Point::new(row, first_indent.len)..Point::new(row, line_len),
12831 );
12832 } else {
12833 trimmed_selections.clear();
12834 trimmed_selections.push(start..end);
12835 break;
12836 }
12837 }
12838 }
12839 } else {
12840 trimmed_selections.push(start..end);
12841 }
12842
12843 for trimmed_range in trimmed_selections {
12844 if is_first {
12845 is_first = false;
12846 } else if !prev_selection_was_entire_line {
12847 text += "\n";
12848 }
12849 prev_selection_was_entire_line = is_entire_line;
12850 let mut len = 0;
12851 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12852 text.push_str(chunk);
12853 len += chunk.len();
12854 }
12855 if add_trailing_newline {
12856 text.push('\n');
12857 len += 1;
12858 }
12859 clipboard_selections.push(ClipboardSelection {
12860 len,
12861 is_entire_line,
12862 first_line_indent: buffer
12863 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12864 .len,
12865 });
12866 }
12867 }
12868 }
12869
12870 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12871 text,
12872 clipboard_selections,
12873 ));
12874 }
12875
12876 pub fn do_paste(
12877 &mut self,
12878 text: &String,
12879 clipboard_selections: Option<Vec<ClipboardSelection>>,
12880 handle_entire_lines: bool,
12881 window: &mut Window,
12882 cx: &mut Context<Self>,
12883 ) {
12884 if self.read_only(cx) {
12885 return;
12886 }
12887
12888 let clipboard_text = Cow::Borrowed(text.as_str());
12889
12890 self.transact(window, cx, |this, window, cx| {
12891 let had_active_edit_prediction = this.has_active_edit_prediction();
12892 let display_map = this.display_snapshot(cx);
12893 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12894 let cursor_offset = this
12895 .selections
12896 .last::<MultiBufferOffset>(&display_map)
12897 .head();
12898
12899 if let Some(mut clipboard_selections) = clipboard_selections {
12900 let all_selections_were_entire_line =
12901 clipboard_selections.iter().all(|s| s.is_entire_line);
12902 let first_selection_indent_column =
12903 clipboard_selections.first().map(|s| s.first_line_indent);
12904 if clipboard_selections.len() != old_selections.len() {
12905 clipboard_selections.drain(..);
12906 }
12907 let mut auto_indent_on_paste = true;
12908
12909 this.buffer.update(cx, |buffer, cx| {
12910 let snapshot = buffer.read(cx);
12911 auto_indent_on_paste = snapshot
12912 .language_settings_at(cursor_offset, cx)
12913 .auto_indent_on_paste;
12914
12915 let mut start_offset = 0;
12916 let mut edits = Vec::new();
12917 let mut original_indent_columns = Vec::new();
12918 for (ix, selection) in old_selections.iter().enumerate() {
12919 let to_insert;
12920 let entire_line;
12921 let original_indent_column;
12922 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12923 let end_offset = start_offset + clipboard_selection.len;
12924 to_insert = &clipboard_text[start_offset..end_offset];
12925 entire_line = clipboard_selection.is_entire_line;
12926 start_offset = if entire_line {
12927 end_offset
12928 } else {
12929 end_offset + 1
12930 };
12931 original_indent_column = Some(clipboard_selection.first_line_indent);
12932 } else {
12933 to_insert = &*clipboard_text;
12934 entire_line = all_selections_were_entire_line;
12935 original_indent_column = first_selection_indent_column
12936 }
12937
12938 let (range, to_insert) =
12939 if selection.is_empty() && handle_entire_lines && entire_line {
12940 // If the corresponding selection was empty when this slice of the
12941 // clipboard text was written, then the entire line containing the
12942 // selection was copied. If this selection is also currently empty,
12943 // then paste the line before the current line of the buffer.
12944 let column = selection.start.to_point(&snapshot).column as usize;
12945 let line_start = selection.start - column;
12946 (line_start..line_start, Cow::Borrowed(to_insert))
12947 } else {
12948 let language = snapshot.language_at(selection.head());
12949 let range = selection.range();
12950 if let Some(language) = language
12951 && language.name() == "Markdown".into()
12952 {
12953 edit_for_markdown_paste(
12954 &snapshot,
12955 range,
12956 to_insert,
12957 url::Url::parse(to_insert).ok(),
12958 )
12959 } else {
12960 (range, Cow::Borrowed(to_insert))
12961 }
12962 };
12963
12964 edits.push((range, to_insert));
12965 original_indent_columns.push(original_indent_column);
12966 }
12967 drop(snapshot);
12968
12969 buffer.edit(
12970 edits,
12971 if auto_indent_on_paste {
12972 Some(AutoindentMode::Block {
12973 original_indent_columns,
12974 })
12975 } else {
12976 None
12977 },
12978 cx,
12979 );
12980 });
12981
12982 let selections = this
12983 .selections
12984 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12985 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12986 } else {
12987 let url = url::Url::parse(&clipboard_text).ok();
12988
12989 let auto_indent_mode = if !clipboard_text.is_empty() {
12990 Some(AutoindentMode::Block {
12991 original_indent_columns: Vec::new(),
12992 })
12993 } else {
12994 None
12995 };
12996
12997 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12998 let snapshot = buffer.snapshot(cx);
12999
13000 let anchors = old_selections
13001 .iter()
13002 .map(|s| {
13003 let anchor = snapshot.anchor_after(s.head());
13004 s.map(|_| anchor)
13005 })
13006 .collect::<Vec<_>>();
13007
13008 let mut edits = Vec::new();
13009
13010 for selection in old_selections.iter() {
13011 let language = snapshot.language_at(selection.head());
13012 let range = selection.range();
13013
13014 let (edit_range, edit_text) = if let Some(language) = language
13015 && language.name() == "Markdown".into()
13016 {
13017 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13018 } else {
13019 (range, clipboard_text.clone())
13020 };
13021
13022 edits.push((edit_range, edit_text));
13023 }
13024
13025 drop(snapshot);
13026 buffer.edit(edits, auto_indent_mode, cx);
13027
13028 anchors
13029 });
13030
13031 this.change_selections(Default::default(), window, cx, |s| {
13032 s.select_anchors(selection_anchors);
13033 });
13034 }
13035
13036 // 🤔 | .. | show_in_menu |
13037 // | .. | true true
13038 // | had_edit_prediction | false true
13039
13040 let trigger_in_words =
13041 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13042
13043 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13044 });
13045 }
13046
13047 pub fn diff_clipboard_with_selection(
13048 &mut self,
13049 _: &DiffClipboardWithSelection,
13050 window: &mut Window,
13051 cx: &mut Context<Self>,
13052 ) {
13053 let selections = self
13054 .selections
13055 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13056
13057 if selections.is_empty() {
13058 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13059 return;
13060 };
13061
13062 let clipboard_text = match cx.read_from_clipboard() {
13063 Some(item) => match item.entries().first() {
13064 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13065 _ => None,
13066 },
13067 None => None,
13068 };
13069
13070 let Some(clipboard_text) = clipboard_text else {
13071 log::warn!("Clipboard doesn't contain text.");
13072 return;
13073 };
13074
13075 window.dispatch_action(
13076 Box::new(DiffClipboardWithSelectionData {
13077 clipboard_text,
13078 editor: cx.entity(),
13079 }),
13080 cx,
13081 );
13082 }
13083
13084 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13086 if let Some(item) = cx.read_from_clipboard() {
13087 let entries = item.entries();
13088
13089 match entries.first() {
13090 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13091 // of all the pasted entries.
13092 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13093 .do_paste(
13094 clipboard_string.text(),
13095 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13096 true,
13097 window,
13098 cx,
13099 ),
13100 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13101 }
13102 }
13103 }
13104
13105 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13106 if self.read_only(cx) {
13107 return;
13108 }
13109
13110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13111
13112 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13113 if let Some((selections, _)) =
13114 self.selection_history.transaction(transaction_id).cloned()
13115 {
13116 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13117 s.select_anchors(selections.to_vec());
13118 });
13119 } else {
13120 log::error!(
13121 "No entry in selection_history found for undo. \
13122 This may correspond to a bug where undo does not update the selection. \
13123 If this is occurring, please add details to \
13124 https://github.com/zed-industries/zed/issues/22692"
13125 );
13126 }
13127 self.request_autoscroll(Autoscroll::fit(), cx);
13128 self.unmark_text(window, cx);
13129 self.refresh_edit_prediction(true, false, window, cx);
13130 cx.emit(EditorEvent::Edited { transaction_id });
13131 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13132 }
13133 }
13134
13135 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13136 if self.read_only(cx) {
13137 return;
13138 }
13139
13140 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13141
13142 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13143 if let Some((_, Some(selections))) =
13144 self.selection_history.transaction(transaction_id).cloned()
13145 {
13146 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13147 s.select_anchors(selections.to_vec());
13148 });
13149 } else {
13150 log::error!(
13151 "No entry in selection_history found for redo. \
13152 This may correspond to a bug where undo does not update the selection. \
13153 If this is occurring, please add details to \
13154 https://github.com/zed-industries/zed/issues/22692"
13155 );
13156 }
13157 self.request_autoscroll(Autoscroll::fit(), cx);
13158 self.unmark_text(window, cx);
13159 self.refresh_edit_prediction(true, false, window, cx);
13160 cx.emit(EditorEvent::Edited { transaction_id });
13161 }
13162 }
13163
13164 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13165 self.buffer
13166 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13167 }
13168
13169 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13170 self.buffer
13171 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13172 }
13173
13174 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13176 self.change_selections(Default::default(), window, cx, |s| {
13177 s.move_with(|map, selection| {
13178 let cursor = if selection.is_empty() {
13179 movement::left(map, selection.start)
13180 } else {
13181 selection.start
13182 };
13183 selection.collapse_to(cursor, SelectionGoal::None);
13184 });
13185 })
13186 }
13187
13188 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13190 self.change_selections(Default::default(), window, cx, |s| {
13191 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13192 })
13193 }
13194
13195 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13196 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13197 self.change_selections(Default::default(), window, cx, |s| {
13198 s.move_with(|map, selection| {
13199 let cursor = if selection.is_empty() {
13200 movement::right(map, selection.end)
13201 } else {
13202 selection.end
13203 };
13204 selection.collapse_to(cursor, SelectionGoal::None)
13205 });
13206 })
13207 }
13208
13209 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13211 self.change_selections(Default::default(), window, cx, |s| {
13212 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13213 });
13214 }
13215
13216 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13217 if self.take_rename(true, window, cx).is_some() {
13218 return;
13219 }
13220
13221 if self.mode.is_single_line() {
13222 cx.propagate();
13223 return;
13224 }
13225
13226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13227
13228 let text_layout_details = &self.text_layout_details(window);
13229 let selection_count = self.selections.count();
13230 let first_selection = self.selections.first_anchor();
13231
13232 self.change_selections(Default::default(), window, cx, |s| {
13233 s.move_with(|map, selection| {
13234 if !selection.is_empty() {
13235 selection.goal = SelectionGoal::None;
13236 }
13237 let (cursor, goal) = movement::up(
13238 map,
13239 selection.start,
13240 selection.goal,
13241 false,
13242 text_layout_details,
13243 );
13244 selection.collapse_to(cursor, goal);
13245 });
13246 });
13247
13248 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13249 {
13250 cx.propagate();
13251 }
13252 }
13253
13254 pub fn move_up_by_lines(
13255 &mut self,
13256 action: &MoveUpByLines,
13257 window: &mut Window,
13258 cx: &mut Context<Self>,
13259 ) {
13260 if self.take_rename(true, window, cx).is_some() {
13261 return;
13262 }
13263
13264 if self.mode.is_single_line() {
13265 cx.propagate();
13266 return;
13267 }
13268
13269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13270
13271 let text_layout_details = &self.text_layout_details(window);
13272
13273 self.change_selections(Default::default(), window, cx, |s| {
13274 s.move_with(|map, selection| {
13275 if !selection.is_empty() {
13276 selection.goal = SelectionGoal::None;
13277 }
13278 let (cursor, goal) = movement::up_by_rows(
13279 map,
13280 selection.start,
13281 action.lines,
13282 selection.goal,
13283 false,
13284 text_layout_details,
13285 );
13286 selection.collapse_to(cursor, goal);
13287 });
13288 })
13289 }
13290
13291 pub fn move_down_by_lines(
13292 &mut self,
13293 action: &MoveDownByLines,
13294 window: &mut Window,
13295 cx: &mut Context<Self>,
13296 ) {
13297 if self.take_rename(true, window, cx).is_some() {
13298 return;
13299 }
13300
13301 if self.mode.is_single_line() {
13302 cx.propagate();
13303 return;
13304 }
13305
13306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13307
13308 let text_layout_details = &self.text_layout_details(window);
13309
13310 self.change_selections(Default::default(), window, cx, |s| {
13311 s.move_with(|map, selection| {
13312 if !selection.is_empty() {
13313 selection.goal = SelectionGoal::None;
13314 }
13315 let (cursor, goal) = movement::down_by_rows(
13316 map,
13317 selection.start,
13318 action.lines,
13319 selection.goal,
13320 false,
13321 text_layout_details,
13322 );
13323 selection.collapse_to(cursor, goal);
13324 });
13325 })
13326 }
13327
13328 pub fn select_down_by_lines(
13329 &mut self,
13330 action: &SelectDownByLines,
13331 window: &mut Window,
13332 cx: &mut Context<Self>,
13333 ) {
13334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13335 let text_layout_details = &self.text_layout_details(window);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_heads_with(|map, head, goal| {
13338 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13339 })
13340 })
13341 }
13342
13343 pub fn select_up_by_lines(
13344 &mut self,
13345 action: &SelectUpByLines,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13350 let text_layout_details = &self.text_layout_details(window);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_heads_with(|map, head, goal| {
13353 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13354 })
13355 })
13356 }
13357
13358 pub fn select_page_up(
13359 &mut self,
13360 _: &SelectPageUp,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 let Some(row_count) = self.visible_row_count() else {
13365 return;
13366 };
13367
13368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13369
13370 let text_layout_details = &self.text_layout_details(window);
13371
13372 self.change_selections(Default::default(), window, cx, |s| {
13373 s.move_heads_with(|map, head, goal| {
13374 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13375 })
13376 })
13377 }
13378
13379 pub fn move_page_up(
13380 &mut self,
13381 action: &MovePageUp,
13382 window: &mut Window,
13383 cx: &mut Context<Self>,
13384 ) {
13385 if self.take_rename(true, window, cx).is_some() {
13386 return;
13387 }
13388
13389 if self
13390 .context_menu
13391 .borrow_mut()
13392 .as_mut()
13393 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13394 .unwrap_or(false)
13395 {
13396 return;
13397 }
13398
13399 if matches!(self.mode, EditorMode::SingleLine) {
13400 cx.propagate();
13401 return;
13402 }
13403
13404 let Some(row_count) = self.visible_row_count() else {
13405 return;
13406 };
13407
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409
13410 let effects = if action.center_cursor {
13411 SelectionEffects::scroll(Autoscroll::center())
13412 } else {
13413 SelectionEffects::default()
13414 };
13415
13416 let text_layout_details = &self.text_layout_details(window);
13417
13418 self.change_selections(effects, window, cx, |s| {
13419 s.move_with(|map, selection| {
13420 if !selection.is_empty() {
13421 selection.goal = SelectionGoal::None;
13422 }
13423 let (cursor, goal) = movement::up_by_rows(
13424 map,
13425 selection.end,
13426 row_count,
13427 selection.goal,
13428 false,
13429 text_layout_details,
13430 );
13431 selection.collapse_to(cursor, goal);
13432 });
13433 });
13434 }
13435
13436 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438 let text_layout_details = &self.text_layout_details(window);
13439 self.change_selections(Default::default(), window, cx, |s| {
13440 s.move_heads_with(|map, head, goal| {
13441 movement::up(map, head, goal, false, text_layout_details)
13442 })
13443 })
13444 }
13445
13446 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13447 self.take_rename(true, window, cx);
13448
13449 if self.mode.is_single_line() {
13450 cx.propagate();
13451 return;
13452 }
13453
13454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13455
13456 let text_layout_details = &self.text_layout_details(window);
13457 let selection_count = self.selections.count();
13458 let first_selection = self.selections.first_anchor();
13459
13460 self.change_selections(Default::default(), window, cx, |s| {
13461 s.move_with(|map, selection| {
13462 if !selection.is_empty() {
13463 selection.goal = SelectionGoal::None;
13464 }
13465 let (cursor, goal) = movement::down(
13466 map,
13467 selection.end,
13468 selection.goal,
13469 false,
13470 text_layout_details,
13471 );
13472 selection.collapse_to(cursor, goal);
13473 });
13474 });
13475
13476 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13477 {
13478 cx.propagate();
13479 }
13480 }
13481
13482 pub fn select_page_down(
13483 &mut self,
13484 _: &SelectPageDown,
13485 window: &mut Window,
13486 cx: &mut Context<Self>,
13487 ) {
13488 let Some(row_count) = self.visible_row_count() else {
13489 return;
13490 };
13491
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493
13494 let text_layout_details = &self.text_layout_details(window);
13495
13496 self.change_selections(Default::default(), window, cx, |s| {
13497 s.move_heads_with(|map, head, goal| {
13498 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13499 })
13500 })
13501 }
13502
13503 pub fn move_page_down(
13504 &mut self,
13505 action: &MovePageDown,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) {
13509 if self.take_rename(true, window, cx).is_some() {
13510 return;
13511 }
13512
13513 if self
13514 .context_menu
13515 .borrow_mut()
13516 .as_mut()
13517 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13518 .unwrap_or(false)
13519 {
13520 return;
13521 }
13522
13523 if matches!(self.mode, EditorMode::SingleLine) {
13524 cx.propagate();
13525 return;
13526 }
13527
13528 let Some(row_count) = self.visible_row_count() else {
13529 return;
13530 };
13531
13532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13533
13534 let effects = if action.center_cursor {
13535 SelectionEffects::scroll(Autoscroll::center())
13536 } else {
13537 SelectionEffects::default()
13538 };
13539
13540 let text_layout_details = &self.text_layout_details(window);
13541 self.change_selections(effects, window, cx, |s| {
13542 s.move_with(|map, selection| {
13543 if !selection.is_empty() {
13544 selection.goal = SelectionGoal::None;
13545 }
13546 let (cursor, goal) = movement::down_by_rows(
13547 map,
13548 selection.end,
13549 row_count,
13550 selection.goal,
13551 false,
13552 text_layout_details,
13553 );
13554 selection.collapse_to(cursor, goal);
13555 });
13556 });
13557 }
13558
13559 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13561 let text_layout_details = &self.text_layout_details(window);
13562 self.change_selections(Default::default(), window, cx, |s| {
13563 s.move_heads_with(|map, head, goal| {
13564 movement::down(map, head, goal, false, text_layout_details)
13565 })
13566 });
13567 }
13568
13569 pub fn context_menu_first(
13570 &mut self,
13571 _: &ContextMenuFirst,
13572 window: &mut Window,
13573 cx: &mut Context<Self>,
13574 ) {
13575 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13576 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13577 }
13578 }
13579
13580 pub fn context_menu_prev(
13581 &mut self,
13582 _: &ContextMenuPrevious,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13587 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13588 }
13589 }
13590
13591 pub fn context_menu_next(
13592 &mut self,
13593 _: &ContextMenuNext,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13598 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13599 }
13600 }
13601
13602 pub fn context_menu_last(
13603 &mut self,
13604 _: &ContextMenuLast,
13605 window: &mut Window,
13606 cx: &mut Context<Self>,
13607 ) {
13608 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13609 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13610 }
13611 }
13612
13613 pub fn signature_help_prev(
13614 &mut self,
13615 _: &SignatureHelpPrevious,
13616 _: &mut Window,
13617 cx: &mut Context<Self>,
13618 ) {
13619 if let Some(popover) = self.signature_help_state.popover_mut() {
13620 if popover.current_signature == 0 {
13621 popover.current_signature = popover.signatures.len() - 1;
13622 } else {
13623 popover.current_signature -= 1;
13624 }
13625 cx.notify();
13626 }
13627 }
13628
13629 pub fn signature_help_next(
13630 &mut self,
13631 _: &SignatureHelpNext,
13632 _: &mut Window,
13633 cx: &mut Context<Self>,
13634 ) {
13635 if let Some(popover) = self.signature_help_state.popover_mut() {
13636 if popover.current_signature + 1 == popover.signatures.len() {
13637 popover.current_signature = 0;
13638 } else {
13639 popover.current_signature += 1;
13640 }
13641 cx.notify();
13642 }
13643 }
13644
13645 pub fn move_to_previous_word_start(
13646 &mut self,
13647 _: &MoveToPreviousWordStart,
13648 window: &mut Window,
13649 cx: &mut Context<Self>,
13650 ) {
13651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13652 self.change_selections(Default::default(), window, cx, |s| {
13653 s.move_cursors_with(|map, head, _| {
13654 (
13655 movement::previous_word_start(map, head),
13656 SelectionGoal::None,
13657 )
13658 });
13659 })
13660 }
13661
13662 pub fn move_to_previous_subword_start(
13663 &mut self,
13664 _: &MoveToPreviousSubwordStart,
13665 window: &mut Window,
13666 cx: &mut Context<Self>,
13667 ) {
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_cursors_with(|map, head, _| {
13671 (
13672 movement::previous_subword_start(map, head),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn select_to_previous_word_start(
13680 &mut self,
13681 _: &SelectToPreviousWordStart,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686 self.change_selections(Default::default(), window, cx, |s| {
13687 s.move_heads_with(|map, head, _| {
13688 (
13689 movement::previous_word_start(map, head),
13690 SelectionGoal::None,
13691 )
13692 });
13693 })
13694 }
13695
13696 pub fn select_to_previous_subword_start(
13697 &mut self,
13698 _: &SelectToPreviousSubwordStart,
13699 window: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13703 self.change_selections(Default::default(), window, cx, |s| {
13704 s.move_heads_with(|map, head, _| {
13705 (
13706 movement::previous_subword_start(map, head),
13707 SelectionGoal::None,
13708 )
13709 });
13710 })
13711 }
13712
13713 pub fn delete_to_previous_word_start(
13714 &mut self,
13715 action: &DeleteToPreviousWordStart,
13716 window: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13720 self.transact(window, cx, |this, window, cx| {
13721 this.select_autoclose_pair(window, cx);
13722 this.change_selections(Default::default(), window, cx, |s| {
13723 s.move_with(|map, selection| {
13724 if selection.is_empty() {
13725 let mut cursor = if action.ignore_newlines {
13726 movement::previous_word_start(map, selection.head())
13727 } else {
13728 movement::previous_word_start_or_newline(map, selection.head())
13729 };
13730 cursor = movement::adjust_greedy_deletion(
13731 map,
13732 selection.head(),
13733 cursor,
13734 action.ignore_brackets,
13735 );
13736 selection.set_head(cursor, SelectionGoal::None);
13737 }
13738 });
13739 });
13740 this.insert("", window, cx);
13741 });
13742 }
13743
13744 pub fn delete_to_previous_subword_start(
13745 &mut self,
13746 _: &DeleteToPreviousSubwordStart,
13747 window: &mut Window,
13748 cx: &mut Context<Self>,
13749 ) {
13750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13751 self.transact(window, cx, |this, window, cx| {
13752 this.select_autoclose_pair(window, cx);
13753 this.change_selections(Default::default(), window, cx, |s| {
13754 s.move_with(|map, selection| {
13755 if selection.is_empty() {
13756 let mut cursor = movement::previous_subword_start(map, selection.head());
13757 cursor =
13758 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13759 selection.set_head(cursor, SelectionGoal::None);
13760 }
13761 });
13762 });
13763 this.insert("", window, cx);
13764 });
13765 }
13766
13767 pub fn move_to_next_word_end(
13768 &mut self,
13769 _: &MoveToNextWordEnd,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_cursors_with(|map, head, _| {
13776 (movement::next_word_end(map, head), SelectionGoal::None)
13777 });
13778 })
13779 }
13780
13781 pub fn move_to_next_subword_end(
13782 &mut self,
13783 _: &MoveToNextSubwordEnd,
13784 window: &mut Window,
13785 cx: &mut Context<Self>,
13786 ) {
13787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13788 self.change_selections(Default::default(), window, cx, |s| {
13789 s.move_cursors_with(|map, head, _| {
13790 (movement::next_subword_end(map, head), SelectionGoal::None)
13791 });
13792 })
13793 }
13794
13795 pub fn select_to_next_word_end(
13796 &mut self,
13797 _: &SelectToNextWordEnd,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13802 self.change_selections(Default::default(), window, cx, |s| {
13803 s.move_heads_with(|map, head, _| {
13804 (movement::next_word_end(map, head), SelectionGoal::None)
13805 });
13806 })
13807 }
13808
13809 pub fn select_to_next_subword_end(
13810 &mut self,
13811 _: &SelectToNextSubwordEnd,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) {
13815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13816 self.change_selections(Default::default(), window, cx, |s| {
13817 s.move_heads_with(|map, head, _| {
13818 (movement::next_subword_end(map, head), SelectionGoal::None)
13819 });
13820 })
13821 }
13822
13823 pub fn delete_to_next_word_end(
13824 &mut self,
13825 action: &DeleteToNextWordEnd,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) {
13829 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13830 self.transact(window, cx, |this, window, cx| {
13831 this.change_selections(Default::default(), window, cx, |s| {
13832 s.move_with(|map, selection| {
13833 if selection.is_empty() {
13834 let mut cursor = if action.ignore_newlines {
13835 movement::next_word_end(map, selection.head())
13836 } else {
13837 movement::next_word_end_or_newline(map, selection.head())
13838 };
13839 cursor = movement::adjust_greedy_deletion(
13840 map,
13841 selection.head(),
13842 cursor,
13843 action.ignore_brackets,
13844 );
13845 selection.set_head(cursor, SelectionGoal::None);
13846 }
13847 });
13848 });
13849 this.insert("", window, cx);
13850 });
13851 }
13852
13853 pub fn delete_to_next_subword_end(
13854 &mut self,
13855 _: &DeleteToNextSubwordEnd,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13860 self.transact(window, cx, |this, window, cx| {
13861 this.change_selections(Default::default(), window, cx, |s| {
13862 s.move_with(|map, selection| {
13863 if selection.is_empty() {
13864 let mut cursor = movement::next_subword_end(map, selection.head());
13865 cursor =
13866 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13867 selection.set_head(cursor, SelectionGoal::None);
13868 }
13869 });
13870 });
13871 this.insert("", window, cx);
13872 });
13873 }
13874
13875 pub fn move_to_beginning_of_line(
13876 &mut self,
13877 action: &MoveToBeginningOfLine,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) {
13881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13882 self.change_selections(Default::default(), window, cx, |s| {
13883 s.move_cursors_with(|map, head, _| {
13884 (
13885 movement::indented_line_beginning(
13886 map,
13887 head,
13888 action.stop_at_soft_wraps,
13889 action.stop_at_indent,
13890 ),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn select_to_beginning_of_line(
13898 &mut self,
13899 action: &SelectToBeginningOfLine,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13904 self.change_selections(Default::default(), window, cx, |s| {
13905 s.move_heads_with(|map, head, _| {
13906 (
13907 movement::indented_line_beginning(
13908 map,
13909 head,
13910 action.stop_at_soft_wraps,
13911 action.stop_at_indent,
13912 ),
13913 SelectionGoal::None,
13914 )
13915 });
13916 });
13917 }
13918
13919 pub fn delete_to_beginning_of_line(
13920 &mut self,
13921 action: &DeleteToBeginningOfLine,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13926 self.transact(window, cx, |this, window, cx| {
13927 this.change_selections(Default::default(), window, cx, |s| {
13928 s.move_with(|_, selection| {
13929 selection.reversed = true;
13930 });
13931 });
13932
13933 this.select_to_beginning_of_line(
13934 &SelectToBeginningOfLine {
13935 stop_at_soft_wraps: false,
13936 stop_at_indent: action.stop_at_indent,
13937 },
13938 window,
13939 cx,
13940 );
13941 this.backspace(&Backspace, window, cx);
13942 });
13943 }
13944
13945 pub fn move_to_end_of_line(
13946 &mut self,
13947 action: &MoveToEndOfLine,
13948 window: &mut Window,
13949 cx: &mut Context<Self>,
13950 ) {
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 self.change_selections(Default::default(), window, cx, |s| {
13953 s.move_cursors_with(|map, head, _| {
13954 (
13955 movement::line_end(map, head, action.stop_at_soft_wraps),
13956 SelectionGoal::None,
13957 )
13958 });
13959 })
13960 }
13961
13962 pub fn select_to_end_of_line(
13963 &mut self,
13964 action: &SelectToEndOfLine,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 self.change_selections(Default::default(), window, cx, |s| {
13970 s.move_heads_with(|map, head, _| {
13971 (
13972 movement::line_end(map, head, action.stop_at_soft_wraps),
13973 SelectionGoal::None,
13974 )
13975 });
13976 })
13977 }
13978
13979 pub fn delete_to_end_of_line(
13980 &mut self,
13981 _: &DeleteToEndOfLine,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13986 self.transact(window, cx, |this, window, cx| {
13987 this.select_to_end_of_line(
13988 &SelectToEndOfLine {
13989 stop_at_soft_wraps: false,
13990 },
13991 window,
13992 cx,
13993 );
13994 this.delete(&Delete, window, cx);
13995 });
13996 }
13997
13998 pub fn cut_to_end_of_line(
13999 &mut self,
14000 action: &CutToEndOfLine,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14005 self.transact(window, cx, |this, window, cx| {
14006 this.select_to_end_of_line(
14007 &SelectToEndOfLine {
14008 stop_at_soft_wraps: false,
14009 },
14010 window,
14011 cx,
14012 );
14013 if !action.stop_at_newlines {
14014 this.change_selections(Default::default(), window, cx, |s| {
14015 s.move_with(|_, sel| {
14016 if sel.is_empty() {
14017 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14018 }
14019 });
14020 });
14021 }
14022 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14023 let item = this.cut_common(false, window, cx);
14024 cx.write_to_clipboard(item);
14025 });
14026 }
14027
14028 pub fn move_to_start_of_paragraph(
14029 &mut self,
14030 _: &MoveToStartOfParagraph,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 if matches!(self.mode, EditorMode::SingleLine) {
14035 cx.propagate();
14036 return;
14037 }
14038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14039 self.change_selections(Default::default(), window, cx, |s| {
14040 s.move_with(|map, selection| {
14041 selection.collapse_to(
14042 movement::start_of_paragraph(map, selection.head(), 1),
14043 SelectionGoal::None,
14044 )
14045 });
14046 })
14047 }
14048
14049 pub fn move_to_end_of_paragraph(
14050 &mut self,
14051 _: &MoveToEndOfParagraph,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 if matches!(self.mode, EditorMode::SingleLine) {
14056 cx.propagate();
14057 return;
14058 }
14059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14060 self.change_selections(Default::default(), window, cx, |s| {
14061 s.move_with(|map, selection| {
14062 selection.collapse_to(
14063 movement::end_of_paragraph(map, selection.head(), 1),
14064 SelectionGoal::None,
14065 )
14066 });
14067 })
14068 }
14069
14070 pub fn select_to_start_of_paragraph(
14071 &mut self,
14072 _: &SelectToStartOfParagraph,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 if matches!(self.mode, EditorMode::SingleLine) {
14077 cx.propagate();
14078 return;
14079 }
14080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14081 self.change_selections(Default::default(), window, cx, |s| {
14082 s.move_heads_with(|map, head, _| {
14083 (
14084 movement::start_of_paragraph(map, head, 1),
14085 SelectionGoal::None,
14086 )
14087 });
14088 })
14089 }
14090
14091 pub fn select_to_end_of_paragraph(
14092 &mut self,
14093 _: &SelectToEndOfParagraph,
14094 window: &mut Window,
14095 cx: &mut Context<Self>,
14096 ) {
14097 if matches!(self.mode, EditorMode::SingleLine) {
14098 cx.propagate();
14099 return;
14100 }
14101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14102 self.change_selections(Default::default(), window, cx, |s| {
14103 s.move_heads_with(|map, head, _| {
14104 (
14105 movement::end_of_paragraph(map, head, 1),
14106 SelectionGoal::None,
14107 )
14108 });
14109 })
14110 }
14111
14112 pub fn move_to_start_of_excerpt(
14113 &mut self,
14114 _: &MoveToStartOfExcerpt,
14115 window: &mut Window,
14116 cx: &mut Context<Self>,
14117 ) {
14118 if matches!(self.mode, EditorMode::SingleLine) {
14119 cx.propagate();
14120 return;
14121 }
14122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14123 self.change_selections(Default::default(), window, cx, |s| {
14124 s.move_with(|map, selection| {
14125 selection.collapse_to(
14126 movement::start_of_excerpt(
14127 map,
14128 selection.head(),
14129 workspace::searchable::Direction::Prev,
14130 ),
14131 SelectionGoal::None,
14132 )
14133 });
14134 })
14135 }
14136
14137 pub fn move_to_start_of_next_excerpt(
14138 &mut self,
14139 _: &MoveToStartOfNextExcerpt,
14140 window: &mut Window,
14141 cx: &mut Context<Self>,
14142 ) {
14143 if matches!(self.mode, EditorMode::SingleLine) {
14144 cx.propagate();
14145 return;
14146 }
14147
14148 self.change_selections(Default::default(), window, cx, |s| {
14149 s.move_with(|map, selection| {
14150 selection.collapse_to(
14151 movement::start_of_excerpt(
14152 map,
14153 selection.head(),
14154 workspace::searchable::Direction::Next,
14155 ),
14156 SelectionGoal::None,
14157 )
14158 });
14159 })
14160 }
14161
14162 pub fn move_to_end_of_excerpt(
14163 &mut self,
14164 _: &MoveToEndOfExcerpt,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) {
14168 if matches!(self.mode, EditorMode::SingleLine) {
14169 cx.propagate();
14170 return;
14171 }
14172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.move_with(|map, selection| {
14175 selection.collapse_to(
14176 movement::end_of_excerpt(
14177 map,
14178 selection.head(),
14179 workspace::searchable::Direction::Next,
14180 ),
14181 SelectionGoal::None,
14182 )
14183 });
14184 })
14185 }
14186
14187 pub fn move_to_end_of_previous_excerpt(
14188 &mut self,
14189 _: &MoveToEndOfPreviousExcerpt,
14190 window: &mut Window,
14191 cx: &mut Context<Self>,
14192 ) {
14193 if matches!(self.mode, EditorMode::SingleLine) {
14194 cx.propagate();
14195 return;
14196 }
14197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14198 self.change_selections(Default::default(), window, cx, |s| {
14199 s.move_with(|map, selection| {
14200 selection.collapse_to(
14201 movement::end_of_excerpt(
14202 map,
14203 selection.head(),
14204 workspace::searchable::Direction::Prev,
14205 ),
14206 SelectionGoal::None,
14207 )
14208 });
14209 })
14210 }
14211
14212 pub fn select_to_start_of_excerpt(
14213 &mut self,
14214 _: &SelectToStartOfExcerpt,
14215 window: &mut Window,
14216 cx: &mut Context<Self>,
14217 ) {
14218 if matches!(self.mode, EditorMode::SingleLine) {
14219 cx.propagate();
14220 return;
14221 }
14222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14223 self.change_selections(Default::default(), window, cx, |s| {
14224 s.move_heads_with(|map, head, _| {
14225 (
14226 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14227 SelectionGoal::None,
14228 )
14229 });
14230 })
14231 }
14232
14233 pub fn select_to_start_of_next_excerpt(
14234 &mut self,
14235 _: &SelectToStartOfNextExcerpt,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 if matches!(self.mode, EditorMode::SingleLine) {
14240 cx.propagate();
14241 return;
14242 }
14243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14244 self.change_selections(Default::default(), window, cx, |s| {
14245 s.move_heads_with(|map, head, _| {
14246 (
14247 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14248 SelectionGoal::None,
14249 )
14250 });
14251 })
14252 }
14253
14254 pub fn select_to_end_of_excerpt(
14255 &mut self,
14256 _: &SelectToEndOfExcerpt,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 if matches!(self.mode, EditorMode::SingleLine) {
14261 cx.propagate();
14262 return;
14263 }
14264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14265 self.change_selections(Default::default(), window, cx, |s| {
14266 s.move_heads_with(|map, head, _| {
14267 (
14268 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14269 SelectionGoal::None,
14270 )
14271 });
14272 })
14273 }
14274
14275 pub fn select_to_end_of_previous_excerpt(
14276 &mut self,
14277 _: &SelectToEndOfPreviousExcerpt,
14278 window: &mut Window,
14279 cx: &mut Context<Self>,
14280 ) {
14281 if matches!(self.mode, EditorMode::SingleLine) {
14282 cx.propagate();
14283 return;
14284 }
14285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14286 self.change_selections(Default::default(), window, cx, |s| {
14287 s.move_heads_with(|map, head, _| {
14288 (
14289 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14290 SelectionGoal::None,
14291 )
14292 });
14293 })
14294 }
14295
14296 pub fn move_to_beginning(
14297 &mut self,
14298 _: &MoveToBeginning,
14299 window: &mut Window,
14300 cx: &mut Context<Self>,
14301 ) {
14302 if matches!(self.mode, EditorMode::SingleLine) {
14303 cx.propagate();
14304 return;
14305 }
14306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14307 self.change_selections(Default::default(), window, cx, |s| {
14308 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14309 });
14310 }
14311
14312 pub fn select_to_beginning(
14313 &mut self,
14314 _: &SelectToBeginning,
14315 window: &mut Window,
14316 cx: &mut Context<Self>,
14317 ) {
14318 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14319 selection.set_head(Point::zero(), SelectionGoal::None);
14320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14321 self.change_selections(Default::default(), window, cx, |s| {
14322 s.select(vec![selection]);
14323 });
14324 }
14325
14326 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14327 if matches!(self.mode, EditorMode::SingleLine) {
14328 cx.propagate();
14329 return;
14330 }
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14332 let cursor = self.buffer.read(cx).read(cx).len();
14333 self.change_selections(Default::default(), window, cx, |s| {
14334 s.select_ranges(vec![cursor..cursor])
14335 });
14336 }
14337
14338 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14339 self.nav_history = nav_history;
14340 }
14341
14342 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14343 self.nav_history.as_ref()
14344 }
14345
14346 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14347 self.push_to_nav_history(
14348 self.selections.newest_anchor().head(),
14349 None,
14350 false,
14351 true,
14352 cx,
14353 );
14354 }
14355
14356 fn push_to_nav_history(
14357 &mut self,
14358 cursor_anchor: Anchor,
14359 new_position: Option<Point>,
14360 is_deactivate: bool,
14361 always: bool,
14362 cx: &mut Context<Self>,
14363 ) {
14364 if let Some(nav_history) = self.nav_history.as_mut() {
14365 let buffer = self.buffer.read(cx).read(cx);
14366 let cursor_position = cursor_anchor.to_point(&buffer);
14367 let scroll_state = self.scroll_manager.anchor();
14368 let scroll_top_row = scroll_state.top_row(&buffer);
14369 drop(buffer);
14370
14371 if let Some(new_position) = new_position {
14372 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14373 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14374 return;
14375 }
14376 }
14377
14378 nav_history.push(
14379 Some(NavigationData {
14380 cursor_anchor,
14381 cursor_position,
14382 scroll_anchor: scroll_state,
14383 scroll_top_row,
14384 }),
14385 cx,
14386 );
14387 cx.emit(EditorEvent::PushedToNavHistory {
14388 anchor: cursor_anchor,
14389 is_deactivate,
14390 })
14391 }
14392 }
14393
14394 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14396 let buffer = self.buffer.read(cx).snapshot(cx);
14397 let mut selection = self
14398 .selections
14399 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14400 selection.set_head(buffer.len(), SelectionGoal::None);
14401 self.change_selections(Default::default(), window, cx, |s| {
14402 s.select(vec![selection]);
14403 });
14404 }
14405
14406 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14409 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14410 });
14411 }
14412
14413 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14416 let mut selections = self.selections.all::<Point>(&display_map);
14417 let max_point = display_map.buffer_snapshot().max_point();
14418 for selection in &mut selections {
14419 let rows = selection.spanned_rows(true, &display_map);
14420 selection.start = Point::new(rows.start.0, 0);
14421 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14422 selection.reversed = false;
14423 }
14424 self.change_selections(Default::default(), window, cx, |s| {
14425 s.select(selections);
14426 });
14427 }
14428
14429 pub fn split_selection_into_lines(
14430 &mut self,
14431 action: &SplitSelectionIntoLines,
14432 window: &mut Window,
14433 cx: &mut Context<Self>,
14434 ) {
14435 let selections = self
14436 .selections
14437 .all::<Point>(&self.display_snapshot(cx))
14438 .into_iter()
14439 .map(|selection| selection.start..selection.end)
14440 .collect::<Vec<_>>();
14441 self.unfold_ranges(&selections, true, true, cx);
14442
14443 let mut new_selection_ranges = Vec::new();
14444 {
14445 let buffer = self.buffer.read(cx).read(cx);
14446 for selection in selections {
14447 for row in selection.start.row..selection.end.row {
14448 let line_start = Point::new(row, 0);
14449 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14450
14451 if action.keep_selections {
14452 // Keep the selection range for each line
14453 let selection_start = if row == selection.start.row {
14454 selection.start
14455 } else {
14456 line_start
14457 };
14458 new_selection_ranges.push(selection_start..line_end);
14459 } else {
14460 // Collapse to cursor at end of line
14461 new_selection_ranges.push(line_end..line_end);
14462 }
14463 }
14464
14465 let is_multiline_selection = selection.start.row != selection.end.row;
14466 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14467 // so this action feels more ergonomic when paired with other selection operations
14468 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14469 if !should_skip_last {
14470 if action.keep_selections {
14471 if is_multiline_selection {
14472 let line_start = Point::new(selection.end.row, 0);
14473 new_selection_ranges.push(line_start..selection.end);
14474 } else {
14475 new_selection_ranges.push(selection.start..selection.end);
14476 }
14477 } else {
14478 new_selection_ranges.push(selection.end..selection.end);
14479 }
14480 }
14481 }
14482 }
14483 self.change_selections(Default::default(), window, cx, |s| {
14484 s.select_ranges(new_selection_ranges);
14485 });
14486 }
14487
14488 pub fn add_selection_above(
14489 &mut self,
14490 action: &AddSelectionAbove,
14491 window: &mut Window,
14492 cx: &mut Context<Self>,
14493 ) {
14494 self.add_selection(true, action.skip_soft_wrap, window, cx);
14495 }
14496
14497 pub fn add_selection_below(
14498 &mut self,
14499 action: &AddSelectionBelow,
14500 window: &mut Window,
14501 cx: &mut Context<Self>,
14502 ) {
14503 self.add_selection(false, action.skip_soft_wrap, window, cx);
14504 }
14505
14506 fn add_selection(
14507 &mut self,
14508 above: bool,
14509 skip_soft_wrap: bool,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14514
14515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14516 let all_selections = self.selections.all::<Point>(&display_map);
14517 let text_layout_details = self.text_layout_details(window);
14518
14519 let (mut columnar_selections, new_selections_to_columnarize) = {
14520 if let Some(state) = self.add_selections_state.as_ref() {
14521 let columnar_selection_ids: HashSet<_> = state
14522 .groups
14523 .iter()
14524 .flat_map(|group| group.stack.iter())
14525 .copied()
14526 .collect();
14527
14528 all_selections
14529 .into_iter()
14530 .partition(|s| columnar_selection_ids.contains(&s.id))
14531 } else {
14532 (Vec::new(), all_selections)
14533 }
14534 };
14535
14536 let mut state = self
14537 .add_selections_state
14538 .take()
14539 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14540
14541 for selection in new_selections_to_columnarize {
14542 let range = selection.display_range(&display_map).sorted();
14543 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14544 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14545 let positions = start_x.min(end_x)..start_x.max(end_x);
14546 let mut stack = Vec::new();
14547 for row in range.start.row().0..=range.end.row().0 {
14548 if let Some(selection) = self.selections.build_columnar_selection(
14549 &display_map,
14550 DisplayRow(row),
14551 &positions,
14552 selection.reversed,
14553 &text_layout_details,
14554 ) {
14555 stack.push(selection.id);
14556 columnar_selections.push(selection);
14557 }
14558 }
14559 if !stack.is_empty() {
14560 if above {
14561 stack.reverse();
14562 }
14563 state.groups.push(AddSelectionsGroup { above, stack });
14564 }
14565 }
14566
14567 let mut final_selections = Vec::new();
14568 let end_row = if above {
14569 DisplayRow(0)
14570 } else {
14571 display_map.max_point().row()
14572 };
14573
14574 let mut last_added_item_per_group = HashMap::default();
14575 for group in state.groups.iter_mut() {
14576 if let Some(last_id) = group.stack.last() {
14577 last_added_item_per_group.insert(*last_id, group);
14578 }
14579 }
14580
14581 for selection in columnar_selections {
14582 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14583 if above == group.above {
14584 let range = selection.display_range(&display_map).sorted();
14585 debug_assert_eq!(range.start.row(), range.end.row());
14586 let mut row = range.start.row();
14587 let positions =
14588 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14589 Pixels::from(start)..Pixels::from(end)
14590 } else {
14591 let start_x =
14592 display_map.x_for_display_point(range.start, &text_layout_details);
14593 let end_x =
14594 display_map.x_for_display_point(range.end, &text_layout_details);
14595 start_x.min(end_x)..start_x.max(end_x)
14596 };
14597
14598 let mut maybe_new_selection = None;
14599 let direction = if above { -1 } else { 1 };
14600
14601 while row != end_row {
14602 if skip_soft_wrap {
14603 row = display_map
14604 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14605 .row();
14606 } else if above {
14607 row.0 -= 1;
14608 } else {
14609 row.0 += 1;
14610 }
14611
14612 if let Some(new_selection) = self.selections.build_columnar_selection(
14613 &display_map,
14614 row,
14615 &positions,
14616 selection.reversed,
14617 &text_layout_details,
14618 ) {
14619 maybe_new_selection = Some(new_selection);
14620 break;
14621 }
14622 }
14623
14624 if let Some(new_selection) = maybe_new_selection {
14625 group.stack.push(new_selection.id);
14626 if above {
14627 final_selections.push(new_selection);
14628 final_selections.push(selection);
14629 } else {
14630 final_selections.push(selection);
14631 final_selections.push(new_selection);
14632 }
14633 } else {
14634 final_selections.push(selection);
14635 }
14636 } else {
14637 group.stack.pop();
14638 }
14639 } else {
14640 final_selections.push(selection);
14641 }
14642 }
14643
14644 self.change_selections(Default::default(), window, cx, |s| {
14645 s.select(final_selections);
14646 });
14647
14648 let final_selection_ids: HashSet<_> = self
14649 .selections
14650 .all::<Point>(&display_map)
14651 .iter()
14652 .map(|s| s.id)
14653 .collect();
14654 state.groups.retain_mut(|group| {
14655 // selections might get merged above so we remove invalid items from stacks
14656 group.stack.retain(|id| final_selection_ids.contains(id));
14657
14658 // single selection in stack can be treated as initial state
14659 group.stack.len() > 1
14660 });
14661
14662 if !state.groups.is_empty() {
14663 self.add_selections_state = Some(state);
14664 }
14665 }
14666
14667 fn select_match_ranges(
14668 &mut self,
14669 range: Range<MultiBufferOffset>,
14670 reversed: bool,
14671 replace_newest: bool,
14672 auto_scroll: Option<Autoscroll>,
14673 window: &mut Window,
14674 cx: &mut Context<Editor>,
14675 ) {
14676 self.unfold_ranges(
14677 std::slice::from_ref(&range),
14678 false,
14679 auto_scroll.is_some(),
14680 cx,
14681 );
14682 let effects = if let Some(scroll) = auto_scroll {
14683 SelectionEffects::scroll(scroll)
14684 } else {
14685 SelectionEffects::no_scroll()
14686 };
14687 self.change_selections(effects, window, cx, |s| {
14688 if replace_newest {
14689 s.delete(s.newest_anchor().id);
14690 }
14691 if reversed {
14692 s.insert_range(range.end..range.start);
14693 } else {
14694 s.insert_range(range);
14695 }
14696 });
14697 }
14698
14699 pub fn select_next_match_internal(
14700 &mut self,
14701 display_map: &DisplaySnapshot,
14702 replace_newest: bool,
14703 autoscroll: Option<Autoscroll>,
14704 window: &mut Window,
14705 cx: &mut Context<Self>,
14706 ) -> Result<()> {
14707 let buffer = display_map.buffer_snapshot();
14708 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14709 if let Some(mut select_next_state) = self.select_next_state.take() {
14710 let query = &select_next_state.query;
14711 if !select_next_state.done {
14712 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14713 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14714 let mut next_selected_range = None;
14715
14716 let bytes_after_last_selection =
14717 buffer.bytes_in_range(last_selection.end..buffer.len());
14718 let bytes_before_first_selection =
14719 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14720 let query_matches = query
14721 .stream_find_iter(bytes_after_last_selection)
14722 .map(|result| (last_selection.end, result))
14723 .chain(
14724 query
14725 .stream_find_iter(bytes_before_first_selection)
14726 .map(|result| (MultiBufferOffset(0), result)),
14727 );
14728
14729 for (start_offset, query_match) in query_matches {
14730 let query_match = query_match.unwrap(); // can only fail due to I/O
14731 let offset_range =
14732 start_offset + query_match.start()..start_offset + query_match.end();
14733
14734 if !select_next_state.wordwise
14735 || (!buffer.is_inside_word(offset_range.start, None)
14736 && !buffer.is_inside_word(offset_range.end, None))
14737 {
14738 let idx = selections
14739 .partition_point(|selection| selection.end <= offset_range.start);
14740 let overlaps = selections
14741 .get(idx)
14742 .map_or(false, |selection| selection.start < offset_range.end);
14743
14744 if !overlaps {
14745 next_selected_range = Some(offset_range);
14746 break;
14747 }
14748 }
14749 }
14750
14751 if let Some(next_selected_range) = next_selected_range {
14752 self.select_match_ranges(
14753 next_selected_range,
14754 last_selection.reversed,
14755 replace_newest,
14756 autoscroll,
14757 window,
14758 cx,
14759 );
14760 } else {
14761 select_next_state.done = true;
14762 }
14763 }
14764
14765 self.select_next_state = Some(select_next_state);
14766 } else {
14767 let mut only_carets = true;
14768 let mut same_text_selected = true;
14769 let mut selected_text = None;
14770
14771 let mut selections_iter = selections.iter().peekable();
14772 while let Some(selection) = selections_iter.next() {
14773 if selection.start != selection.end {
14774 only_carets = false;
14775 }
14776
14777 if same_text_selected {
14778 if selected_text.is_none() {
14779 selected_text =
14780 Some(buffer.text_for_range(selection.range()).collect::<String>());
14781 }
14782
14783 if let Some(next_selection) = selections_iter.peek() {
14784 if next_selection.len() == selection.len() {
14785 let next_selected_text = buffer
14786 .text_for_range(next_selection.range())
14787 .collect::<String>();
14788 if Some(next_selected_text) != selected_text {
14789 same_text_selected = false;
14790 selected_text = None;
14791 }
14792 } else {
14793 same_text_selected = false;
14794 selected_text = None;
14795 }
14796 }
14797 }
14798 }
14799
14800 if only_carets {
14801 for selection in &mut selections {
14802 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14803 selection.start = word_range.start;
14804 selection.end = word_range.end;
14805 selection.goal = SelectionGoal::None;
14806 selection.reversed = false;
14807 self.select_match_ranges(
14808 selection.start..selection.end,
14809 selection.reversed,
14810 replace_newest,
14811 autoscroll,
14812 window,
14813 cx,
14814 );
14815 }
14816
14817 if selections.len() == 1 {
14818 let selection = selections
14819 .last()
14820 .expect("ensured that there's only one selection");
14821 let query = buffer
14822 .text_for_range(selection.start..selection.end)
14823 .collect::<String>();
14824 let is_empty = query.is_empty();
14825 let select_state = SelectNextState {
14826 query: self.build_query(&[query], cx)?,
14827 wordwise: true,
14828 done: is_empty,
14829 };
14830 self.select_next_state = Some(select_state);
14831 } else {
14832 self.select_next_state = None;
14833 }
14834 } else if let Some(selected_text) = selected_text {
14835 self.select_next_state = Some(SelectNextState {
14836 query: self.build_query(&[selected_text], cx)?,
14837 wordwise: false,
14838 done: false,
14839 });
14840 self.select_next_match_internal(
14841 display_map,
14842 replace_newest,
14843 autoscroll,
14844 window,
14845 cx,
14846 )?;
14847 }
14848 }
14849 Ok(())
14850 }
14851
14852 pub fn select_all_matches(
14853 &mut self,
14854 _action: &SelectAllMatches,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) -> Result<()> {
14858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14859
14860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14861
14862 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14863 let Some(select_next_state) = self.select_next_state.as_mut() else {
14864 return Ok(());
14865 };
14866 if select_next_state.done {
14867 return Ok(());
14868 }
14869
14870 let mut new_selections = Vec::new();
14871
14872 let reversed = self
14873 .selections
14874 .oldest::<MultiBufferOffset>(&display_map)
14875 .reversed;
14876 let buffer = display_map.buffer_snapshot();
14877 let query_matches = select_next_state
14878 .query
14879 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14880
14881 for query_match in query_matches.into_iter() {
14882 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14883 let offset_range = if reversed {
14884 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14885 } else {
14886 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14887 };
14888
14889 if !select_next_state.wordwise
14890 || (!buffer.is_inside_word(offset_range.start, None)
14891 && !buffer.is_inside_word(offset_range.end, None))
14892 {
14893 new_selections.push(offset_range.start..offset_range.end);
14894 }
14895 }
14896
14897 select_next_state.done = true;
14898
14899 if new_selections.is_empty() {
14900 log::error!("bug: new_selections is empty in select_all_matches");
14901 return Ok(());
14902 }
14903
14904 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14905 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14906 selections.select_ranges(new_selections)
14907 });
14908
14909 Ok(())
14910 }
14911
14912 pub fn select_next(
14913 &mut self,
14914 action: &SelectNext,
14915 window: &mut Window,
14916 cx: &mut Context<Self>,
14917 ) -> Result<()> {
14918 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14919 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14920 self.select_next_match_internal(
14921 &display_map,
14922 action.replace_newest,
14923 Some(Autoscroll::newest()),
14924 window,
14925 cx,
14926 )?;
14927 Ok(())
14928 }
14929
14930 pub fn select_previous(
14931 &mut self,
14932 action: &SelectPrevious,
14933 window: &mut Window,
14934 cx: &mut Context<Self>,
14935 ) -> Result<()> {
14936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14938 let buffer = display_map.buffer_snapshot();
14939 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14940 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14941 let query = &select_prev_state.query;
14942 if !select_prev_state.done {
14943 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14944 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14945 let mut next_selected_range = None;
14946 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14947 let bytes_before_last_selection =
14948 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14949 let bytes_after_first_selection =
14950 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14951 let query_matches = query
14952 .stream_find_iter(bytes_before_last_selection)
14953 .map(|result| (last_selection.start, result))
14954 .chain(
14955 query
14956 .stream_find_iter(bytes_after_first_selection)
14957 .map(|result| (buffer.len(), result)),
14958 );
14959 for (end_offset, query_match) in query_matches {
14960 let query_match = query_match.unwrap(); // can only fail due to I/O
14961 let offset_range =
14962 end_offset - query_match.end()..end_offset - query_match.start();
14963
14964 if !select_prev_state.wordwise
14965 || (!buffer.is_inside_word(offset_range.start, None)
14966 && !buffer.is_inside_word(offset_range.end, None))
14967 {
14968 next_selected_range = Some(offset_range);
14969 break;
14970 }
14971 }
14972
14973 if let Some(next_selected_range) = next_selected_range {
14974 self.select_match_ranges(
14975 next_selected_range,
14976 last_selection.reversed,
14977 action.replace_newest,
14978 Some(Autoscroll::newest()),
14979 window,
14980 cx,
14981 );
14982 } else {
14983 select_prev_state.done = true;
14984 }
14985 }
14986
14987 self.select_prev_state = Some(select_prev_state);
14988 } else {
14989 let mut only_carets = true;
14990 let mut same_text_selected = true;
14991 let mut selected_text = None;
14992
14993 let mut selections_iter = selections.iter().peekable();
14994 while let Some(selection) = selections_iter.next() {
14995 if selection.start != selection.end {
14996 only_carets = false;
14997 }
14998
14999 if same_text_selected {
15000 if selected_text.is_none() {
15001 selected_text =
15002 Some(buffer.text_for_range(selection.range()).collect::<String>());
15003 }
15004
15005 if let Some(next_selection) = selections_iter.peek() {
15006 if next_selection.len() == selection.len() {
15007 let next_selected_text = buffer
15008 .text_for_range(next_selection.range())
15009 .collect::<String>();
15010 if Some(next_selected_text) != selected_text {
15011 same_text_selected = false;
15012 selected_text = None;
15013 }
15014 } else {
15015 same_text_selected = false;
15016 selected_text = None;
15017 }
15018 }
15019 }
15020 }
15021
15022 if only_carets {
15023 for selection in &mut selections {
15024 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15025 selection.start = word_range.start;
15026 selection.end = word_range.end;
15027 selection.goal = SelectionGoal::None;
15028 selection.reversed = false;
15029 self.select_match_ranges(
15030 selection.start..selection.end,
15031 selection.reversed,
15032 action.replace_newest,
15033 Some(Autoscroll::newest()),
15034 window,
15035 cx,
15036 );
15037 }
15038 if selections.len() == 1 {
15039 let selection = selections
15040 .last()
15041 .expect("ensured that there's only one selection");
15042 let query = buffer
15043 .text_for_range(selection.start..selection.end)
15044 .collect::<String>();
15045 let is_empty = query.is_empty();
15046 let select_state = SelectNextState {
15047 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15048 wordwise: true,
15049 done: is_empty,
15050 };
15051 self.select_prev_state = Some(select_state);
15052 } else {
15053 self.select_prev_state = None;
15054 }
15055 } else if let Some(selected_text) = selected_text {
15056 self.select_prev_state = Some(SelectNextState {
15057 query: self
15058 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15059 wordwise: false,
15060 done: false,
15061 });
15062 self.select_previous(action, window, cx)?;
15063 }
15064 }
15065 Ok(())
15066 }
15067
15068 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15069 /// setting the case sensitivity based on the global
15070 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15071 /// editor's settings.
15072 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15073 where
15074 I: IntoIterator<Item = P>,
15075 P: AsRef<[u8]>,
15076 {
15077 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15078 || EditorSettings::get_global(cx).search.case_sensitive,
15079 |value| value,
15080 );
15081
15082 let mut builder = AhoCorasickBuilder::new();
15083 builder.ascii_case_insensitive(!case_sensitive);
15084 builder.build(patterns)
15085 }
15086
15087 pub fn find_next_match(
15088 &mut self,
15089 _: &FindNextMatch,
15090 window: &mut Window,
15091 cx: &mut Context<Self>,
15092 ) -> Result<()> {
15093 let selections = self.selections.disjoint_anchors_arc();
15094 match selections.first() {
15095 Some(first) if selections.len() >= 2 => {
15096 self.change_selections(Default::default(), window, cx, |s| {
15097 s.select_ranges([first.range()]);
15098 });
15099 }
15100 _ => self.select_next(
15101 &SelectNext {
15102 replace_newest: true,
15103 },
15104 window,
15105 cx,
15106 )?,
15107 }
15108 Ok(())
15109 }
15110
15111 pub fn find_previous_match(
15112 &mut self,
15113 _: &FindPreviousMatch,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) -> Result<()> {
15117 let selections = self.selections.disjoint_anchors_arc();
15118 match selections.last() {
15119 Some(last) if selections.len() >= 2 => {
15120 self.change_selections(Default::default(), window, cx, |s| {
15121 s.select_ranges([last.range()]);
15122 });
15123 }
15124 _ => self.select_previous(
15125 &SelectPrevious {
15126 replace_newest: true,
15127 },
15128 window,
15129 cx,
15130 )?,
15131 }
15132 Ok(())
15133 }
15134
15135 pub fn toggle_comments(
15136 &mut self,
15137 action: &ToggleComments,
15138 window: &mut Window,
15139 cx: &mut Context<Self>,
15140 ) {
15141 if self.read_only(cx) {
15142 return;
15143 }
15144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15145 let text_layout_details = &self.text_layout_details(window);
15146 self.transact(window, cx, |this, window, cx| {
15147 let mut selections = this
15148 .selections
15149 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15150 let mut edits = Vec::new();
15151 let mut selection_edit_ranges = Vec::new();
15152 let mut last_toggled_row = None;
15153 let snapshot = this.buffer.read(cx).read(cx);
15154 let empty_str: Arc<str> = Arc::default();
15155 let mut suffixes_inserted = Vec::new();
15156 let ignore_indent = action.ignore_indent;
15157
15158 fn comment_prefix_range(
15159 snapshot: &MultiBufferSnapshot,
15160 row: MultiBufferRow,
15161 comment_prefix: &str,
15162 comment_prefix_whitespace: &str,
15163 ignore_indent: bool,
15164 ) -> Range<Point> {
15165 let indent_size = if ignore_indent {
15166 0
15167 } else {
15168 snapshot.indent_size_for_line(row).len
15169 };
15170
15171 let start = Point::new(row.0, indent_size);
15172
15173 let mut line_bytes = snapshot
15174 .bytes_in_range(start..snapshot.max_point())
15175 .flatten()
15176 .copied();
15177
15178 // If this line currently begins with the line comment prefix, then record
15179 // the range containing the prefix.
15180 if line_bytes
15181 .by_ref()
15182 .take(comment_prefix.len())
15183 .eq(comment_prefix.bytes())
15184 {
15185 // Include any whitespace that matches the comment prefix.
15186 let matching_whitespace_len = line_bytes
15187 .zip(comment_prefix_whitespace.bytes())
15188 .take_while(|(a, b)| a == b)
15189 .count() as u32;
15190 let end = Point::new(
15191 start.row,
15192 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15193 );
15194 start..end
15195 } else {
15196 start..start
15197 }
15198 }
15199
15200 fn comment_suffix_range(
15201 snapshot: &MultiBufferSnapshot,
15202 row: MultiBufferRow,
15203 comment_suffix: &str,
15204 comment_suffix_has_leading_space: bool,
15205 ) -> Range<Point> {
15206 let end = Point::new(row.0, snapshot.line_len(row));
15207 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15208
15209 let mut line_end_bytes = snapshot
15210 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15211 .flatten()
15212 .copied();
15213
15214 let leading_space_len = if suffix_start_column > 0
15215 && line_end_bytes.next() == Some(b' ')
15216 && comment_suffix_has_leading_space
15217 {
15218 1
15219 } else {
15220 0
15221 };
15222
15223 // If this line currently begins with the line comment prefix, then record
15224 // the range containing the prefix.
15225 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15226 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15227 start..end
15228 } else {
15229 end..end
15230 }
15231 }
15232
15233 // TODO: Handle selections that cross excerpts
15234 for selection in &mut selections {
15235 let start_column = snapshot
15236 .indent_size_for_line(MultiBufferRow(selection.start.row))
15237 .len;
15238 let language = if let Some(language) =
15239 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15240 {
15241 language
15242 } else {
15243 continue;
15244 };
15245
15246 selection_edit_ranges.clear();
15247
15248 // If multiple selections contain a given row, avoid processing that
15249 // row more than once.
15250 let mut start_row = MultiBufferRow(selection.start.row);
15251 if last_toggled_row == Some(start_row) {
15252 start_row = start_row.next_row();
15253 }
15254 let end_row =
15255 if selection.end.row > selection.start.row && selection.end.column == 0 {
15256 MultiBufferRow(selection.end.row - 1)
15257 } else {
15258 MultiBufferRow(selection.end.row)
15259 };
15260 last_toggled_row = Some(end_row);
15261
15262 if start_row > end_row {
15263 continue;
15264 }
15265
15266 // If the language has line comments, toggle those.
15267 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15268
15269 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15270 if ignore_indent {
15271 full_comment_prefixes = full_comment_prefixes
15272 .into_iter()
15273 .map(|s| Arc::from(s.trim_end()))
15274 .collect();
15275 }
15276
15277 if !full_comment_prefixes.is_empty() {
15278 let first_prefix = full_comment_prefixes
15279 .first()
15280 .expect("prefixes is non-empty");
15281 let prefix_trimmed_lengths = full_comment_prefixes
15282 .iter()
15283 .map(|p| p.trim_end_matches(' ').len())
15284 .collect::<SmallVec<[usize; 4]>>();
15285
15286 let mut all_selection_lines_are_comments = true;
15287
15288 for row in start_row.0..=end_row.0 {
15289 let row = MultiBufferRow(row);
15290 if start_row < end_row && snapshot.is_line_blank(row) {
15291 continue;
15292 }
15293
15294 let prefix_range = full_comment_prefixes
15295 .iter()
15296 .zip(prefix_trimmed_lengths.iter().copied())
15297 .map(|(prefix, trimmed_prefix_len)| {
15298 comment_prefix_range(
15299 snapshot.deref(),
15300 row,
15301 &prefix[..trimmed_prefix_len],
15302 &prefix[trimmed_prefix_len..],
15303 ignore_indent,
15304 )
15305 })
15306 .max_by_key(|range| range.end.column - range.start.column)
15307 .expect("prefixes is non-empty");
15308
15309 if prefix_range.is_empty() {
15310 all_selection_lines_are_comments = false;
15311 }
15312
15313 selection_edit_ranges.push(prefix_range);
15314 }
15315
15316 if all_selection_lines_are_comments {
15317 edits.extend(
15318 selection_edit_ranges
15319 .iter()
15320 .cloned()
15321 .map(|range| (range, empty_str.clone())),
15322 );
15323 } else {
15324 let min_column = selection_edit_ranges
15325 .iter()
15326 .map(|range| range.start.column)
15327 .min()
15328 .unwrap_or(0);
15329 edits.extend(selection_edit_ranges.iter().map(|range| {
15330 let position = Point::new(range.start.row, min_column);
15331 (position..position, first_prefix.clone())
15332 }));
15333 }
15334 } else if let Some(BlockCommentConfig {
15335 start: full_comment_prefix,
15336 end: comment_suffix,
15337 ..
15338 }) = language.block_comment()
15339 {
15340 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15341 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15342 let prefix_range = comment_prefix_range(
15343 snapshot.deref(),
15344 start_row,
15345 comment_prefix,
15346 comment_prefix_whitespace,
15347 ignore_indent,
15348 );
15349 let suffix_range = comment_suffix_range(
15350 snapshot.deref(),
15351 end_row,
15352 comment_suffix.trim_start_matches(' '),
15353 comment_suffix.starts_with(' '),
15354 );
15355
15356 if prefix_range.is_empty() || suffix_range.is_empty() {
15357 edits.push((
15358 prefix_range.start..prefix_range.start,
15359 full_comment_prefix.clone(),
15360 ));
15361 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15362 suffixes_inserted.push((end_row, comment_suffix.len()));
15363 } else {
15364 edits.push((prefix_range, empty_str.clone()));
15365 edits.push((suffix_range, empty_str.clone()));
15366 }
15367 } else {
15368 continue;
15369 }
15370 }
15371
15372 drop(snapshot);
15373 this.buffer.update(cx, |buffer, cx| {
15374 buffer.edit(edits, None, cx);
15375 });
15376
15377 // Adjust selections so that they end before any comment suffixes that
15378 // were inserted.
15379 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15380 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15381 let snapshot = this.buffer.read(cx).read(cx);
15382 for selection in &mut selections {
15383 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15384 match row.cmp(&MultiBufferRow(selection.end.row)) {
15385 Ordering::Less => {
15386 suffixes_inserted.next();
15387 continue;
15388 }
15389 Ordering::Greater => break,
15390 Ordering::Equal => {
15391 if selection.end.column == snapshot.line_len(row) {
15392 if selection.is_empty() {
15393 selection.start.column -= suffix_len as u32;
15394 }
15395 selection.end.column -= suffix_len as u32;
15396 }
15397 break;
15398 }
15399 }
15400 }
15401 }
15402
15403 drop(snapshot);
15404 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15405
15406 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15407 let selections_on_single_row = selections.windows(2).all(|selections| {
15408 selections[0].start.row == selections[1].start.row
15409 && selections[0].end.row == selections[1].end.row
15410 && selections[0].start.row == selections[0].end.row
15411 });
15412 let selections_selecting = selections
15413 .iter()
15414 .any(|selection| selection.start != selection.end);
15415 let advance_downwards = action.advance_downwards
15416 && selections_on_single_row
15417 && !selections_selecting
15418 && !matches!(this.mode, EditorMode::SingleLine);
15419
15420 if advance_downwards {
15421 let snapshot = this.buffer.read(cx).snapshot(cx);
15422
15423 this.change_selections(Default::default(), window, cx, |s| {
15424 s.move_cursors_with(|display_snapshot, display_point, _| {
15425 let mut point = display_point.to_point(display_snapshot);
15426 point.row += 1;
15427 point = snapshot.clip_point(point, Bias::Left);
15428 let display_point = point.to_display_point(display_snapshot);
15429 let goal = SelectionGoal::HorizontalPosition(
15430 display_snapshot
15431 .x_for_display_point(display_point, text_layout_details)
15432 .into(),
15433 );
15434 (display_point, goal)
15435 })
15436 });
15437 }
15438 });
15439 }
15440
15441 pub fn select_enclosing_symbol(
15442 &mut self,
15443 _: &SelectEnclosingSymbol,
15444 window: &mut Window,
15445 cx: &mut Context<Self>,
15446 ) {
15447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15448
15449 let buffer = self.buffer.read(cx).snapshot(cx);
15450 let old_selections = self
15451 .selections
15452 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15453 .into_boxed_slice();
15454
15455 fn update_selection(
15456 selection: &Selection<MultiBufferOffset>,
15457 buffer_snap: &MultiBufferSnapshot,
15458 ) -> Option<Selection<MultiBufferOffset>> {
15459 let cursor = selection.head();
15460 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15461 for symbol in symbols.iter().rev() {
15462 let start = symbol.range.start.to_offset(buffer_snap);
15463 let end = symbol.range.end.to_offset(buffer_snap);
15464 let new_range = start..end;
15465 if start < selection.start || end > selection.end {
15466 return Some(Selection {
15467 id: selection.id,
15468 start: new_range.start,
15469 end: new_range.end,
15470 goal: SelectionGoal::None,
15471 reversed: selection.reversed,
15472 });
15473 }
15474 }
15475 None
15476 }
15477
15478 let mut selected_larger_symbol = false;
15479 let new_selections = old_selections
15480 .iter()
15481 .map(|selection| match update_selection(selection, &buffer) {
15482 Some(new_selection) => {
15483 if new_selection.range() != selection.range() {
15484 selected_larger_symbol = true;
15485 }
15486 new_selection
15487 }
15488 None => selection.clone(),
15489 })
15490 .collect::<Vec<_>>();
15491
15492 if selected_larger_symbol {
15493 self.change_selections(Default::default(), window, cx, |s| {
15494 s.select(new_selections);
15495 });
15496 }
15497 }
15498
15499 pub fn select_larger_syntax_node(
15500 &mut self,
15501 _: &SelectLargerSyntaxNode,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) {
15505 let Some(visible_row_count) = self.visible_row_count() else {
15506 return;
15507 };
15508 let old_selections: Box<[_]> = self
15509 .selections
15510 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15511 .into();
15512 if old_selections.is_empty() {
15513 return;
15514 }
15515
15516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15517
15518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15519 let buffer = self.buffer.read(cx).snapshot(cx);
15520
15521 let mut selected_larger_node = false;
15522 let mut new_selections = old_selections
15523 .iter()
15524 .map(|selection| {
15525 let old_range = selection.start..selection.end;
15526
15527 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15528 // manually select word at selection
15529 if ["string_content", "inline"].contains(&node.kind()) {
15530 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15531 // ignore if word is already selected
15532 if !word_range.is_empty() && old_range != word_range {
15533 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15534 // only select word if start and end point belongs to same word
15535 if word_range == last_word_range {
15536 selected_larger_node = true;
15537 return Selection {
15538 id: selection.id,
15539 start: word_range.start,
15540 end: word_range.end,
15541 goal: SelectionGoal::None,
15542 reversed: selection.reversed,
15543 };
15544 }
15545 }
15546 }
15547 }
15548
15549 let mut new_range = old_range.clone();
15550 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15551 new_range = range;
15552 if !node.is_named() {
15553 continue;
15554 }
15555 if !display_map.intersects_fold(new_range.start)
15556 && !display_map.intersects_fold(new_range.end)
15557 {
15558 break;
15559 }
15560 }
15561
15562 selected_larger_node |= new_range != old_range;
15563 Selection {
15564 id: selection.id,
15565 start: new_range.start,
15566 end: new_range.end,
15567 goal: SelectionGoal::None,
15568 reversed: selection.reversed,
15569 }
15570 })
15571 .collect::<Vec<_>>();
15572
15573 if !selected_larger_node {
15574 return; // don't put this call in the history
15575 }
15576
15577 // scroll based on transformation done to the last selection created by the user
15578 let (last_old, last_new) = old_selections
15579 .last()
15580 .zip(new_selections.last().cloned())
15581 .expect("old_selections isn't empty");
15582
15583 // revert selection
15584 let is_selection_reversed = {
15585 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15586 new_selections.last_mut().expect("checked above").reversed =
15587 should_newest_selection_be_reversed;
15588 should_newest_selection_be_reversed
15589 };
15590
15591 if selected_larger_node {
15592 self.select_syntax_node_history.disable_clearing = true;
15593 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15594 s.select(new_selections.clone());
15595 });
15596 self.select_syntax_node_history.disable_clearing = false;
15597 }
15598
15599 let start_row = last_new.start.to_display_point(&display_map).row().0;
15600 let end_row = last_new.end.to_display_point(&display_map).row().0;
15601 let selection_height = end_row - start_row + 1;
15602 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15603
15604 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15605 let scroll_behavior = if fits_on_the_screen {
15606 self.request_autoscroll(Autoscroll::fit(), cx);
15607 SelectSyntaxNodeScrollBehavior::FitSelection
15608 } else if is_selection_reversed {
15609 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15610 SelectSyntaxNodeScrollBehavior::CursorTop
15611 } else {
15612 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15613 SelectSyntaxNodeScrollBehavior::CursorBottom
15614 };
15615
15616 self.select_syntax_node_history.push((
15617 old_selections,
15618 scroll_behavior,
15619 is_selection_reversed,
15620 ));
15621 }
15622
15623 pub fn select_smaller_syntax_node(
15624 &mut self,
15625 _: &SelectSmallerSyntaxNode,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) {
15629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15630
15631 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15632 self.select_syntax_node_history.pop()
15633 {
15634 if let Some(selection) = selections.last_mut() {
15635 selection.reversed = is_selection_reversed;
15636 }
15637
15638 self.select_syntax_node_history.disable_clearing = true;
15639 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15640 s.select(selections.to_vec());
15641 });
15642 self.select_syntax_node_history.disable_clearing = false;
15643
15644 match scroll_behavior {
15645 SelectSyntaxNodeScrollBehavior::CursorTop => {
15646 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15647 }
15648 SelectSyntaxNodeScrollBehavior::FitSelection => {
15649 self.request_autoscroll(Autoscroll::fit(), cx);
15650 }
15651 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15652 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15653 }
15654 }
15655 }
15656 }
15657
15658 pub fn unwrap_syntax_node(
15659 &mut self,
15660 _: &UnwrapSyntaxNode,
15661 window: &mut Window,
15662 cx: &mut Context<Self>,
15663 ) {
15664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15665
15666 let buffer = self.buffer.read(cx).snapshot(cx);
15667 let selections = self
15668 .selections
15669 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15670 .into_iter()
15671 // subtracting the offset requires sorting
15672 .sorted_by_key(|i| i.start);
15673
15674 let full_edits = selections
15675 .into_iter()
15676 .filter_map(|selection| {
15677 let child = if selection.is_empty()
15678 && let Some((_, ancestor_range)) =
15679 buffer.syntax_ancestor(selection.start..selection.end)
15680 {
15681 ancestor_range
15682 } else {
15683 selection.range()
15684 };
15685
15686 let mut parent = child.clone();
15687 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15688 parent = ancestor_range;
15689 if parent.start < child.start || parent.end > child.end {
15690 break;
15691 }
15692 }
15693
15694 if parent == child {
15695 return None;
15696 }
15697 let text = buffer.text_for_range(child).collect::<String>();
15698 Some((selection.id, parent, text))
15699 })
15700 .collect::<Vec<_>>();
15701 if full_edits.is_empty() {
15702 return;
15703 }
15704
15705 self.transact(window, cx, |this, window, cx| {
15706 this.buffer.update(cx, |buffer, cx| {
15707 buffer.edit(
15708 full_edits
15709 .iter()
15710 .map(|(_, p, t)| (p.clone(), t.clone()))
15711 .collect::<Vec<_>>(),
15712 None,
15713 cx,
15714 );
15715 });
15716 this.change_selections(Default::default(), window, cx, |s| {
15717 let mut offset = 0;
15718 let mut selections = vec![];
15719 for (id, parent, text) in full_edits {
15720 let start = parent.start - offset;
15721 offset += (parent.end - parent.start) - text.len();
15722 selections.push(Selection {
15723 id,
15724 start,
15725 end: start + text.len(),
15726 reversed: false,
15727 goal: Default::default(),
15728 });
15729 }
15730 s.select(selections);
15731 });
15732 });
15733 }
15734
15735 pub fn select_next_syntax_node(
15736 &mut self,
15737 _: &SelectNextSyntaxNode,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) {
15741 let old_selections: Box<[_]> = self
15742 .selections
15743 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15744 .into();
15745 if old_selections.is_empty() {
15746 return;
15747 }
15748
15749 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15750
15751 let buffer = self.buffer.read(cx).snapshot(cx);
15752 let mut selected_sibling = false;
15753
15754 let new_selections = old_selections
15755 .iter()
15756 .map(|selection| {
15757 let old_range = selection.start..selection.end;
15758
15759 let old_range =
15760 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15761 let excerpt = buffer.excerpt_containing(old_range.clone());
15762
15763 if let Some(mut excerpt) = excerpt
15764 && let Some(node) = excerpt
15765 .buffer()
15766 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15767 {
15768 let new_range = excerpt.map_range_from_buffer(
15769 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15770 );
15771 selected_sibling = true;
15772 Selection {
15773 id: selection.id,
15774 start: new_range.start,
15775 end: new_range.end,
15776 goal: SelectionGoal::None,
15777 reversed: selection.reversed,
15778 }
15779 } else {
15780 selection.clone()
15781 }
15782 })
15783 .collect::<Vec<_>>();
15784
15785 if selected_sibling {
15786 self.change_selections(
15787 SelectionEffects::scroll(Autoscroll::fit()),
15788 window,
15789 cx,
15790 |s| {
15791 s.select(new_selections);
15792 },
15793 );
15794 }
15795 }
15796
15797 pub fn select_prev_syntax_node(
15798 &mut self,
15799 _: &SelectPreviousSyntaxNode,
15800 window: &mut Window,
15801 cx: &mut Context<Self>,
15802 ) {
15803 let old_selections: Box<[_]> = self
15804 .selections
15805 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15806 .into();
15807 if old_selections.is_empty() {
15808 return;
15809 }
15810
15811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15812
15813 let buffer = self.buffer.read(cx).snapshot(cx);
15814 let mut selected_sibling = false;
15815
15816 let new_selections = old_selections
15817 .iter()
15818 .map(|selection| {
15819 let old_range = selection.start..selection.end;
15820 let old_range =
15821 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15822 let excerpt = buffer.excerpt_containing(old_range.clone());
15823
15824 if let Some(mut excerpt) = excerpt
15825 && let Some(node) = excerpt
15826 .buffer()
15827 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15828 {
15829 let new_range = excerpt.map_range_from_buffer(
15830 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15831 );
15832 selected_sibling = true;
15833 Selection {
15834 id: selection.id,
15835 start: new_range.start,
15836 end: new_range.end,
15837 goal: SelectionGoal::None,
15838 reversed: selection.reversed,
15839 }
15840 } else {
15841 selection.clone()
15842 }
15843 })
15844 .collect::<Vec<_>>();
15845
15846 if selected_sibling {
15847 self.change_selections(
15848 SelectionEffects::scroll(Autoscroll::fit()),
15849 window,
15850 cx,
15851 |s| {
15852 s.select(new_selections);
15853 },
15854 );
15855 }
15856 }
15857
15858 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15859 if !EditorSettings::get_global(cx).gutter.runnables {
15860 self.clear_tasks();
15861 return Task::ready(());
15862 }
15863 let project = self.project().map(Entity::downgrade);
15864 let task_sources = self.lsp_task_sources(cx);
15865 let multi_buffer = self.buffer.downgrade();
15866 cx.spawn_in(window, async move |editor, cx| {
15867 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15868 let Some(project) = project.and_then(|p| p.upgrade()) else {
15869 return;
15870 };
15871 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15872 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15873 }) else {
15874 return;
15875 };
15876
15877 let hide_runnables = project
15878 .update(cx, |project, _| project.is_via_collab())
15879 .unwrap_or(true);
15880 if hide_runnables {
15881 return;
15882 }
15883 let new_rows =
15884 cx.background_spawn({
15885 let snapshot = display_snapshot.clone();
15886 async move {
15887 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15888 }
15889 })
15890 .await;
15891 let Ok(lsp_tasks) =
15892 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15893 else {
15894 return;
15895 };
15896 let lsp_tasks = lsp_tasks.await;
15897
15898 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15899 lsp_tasks
15900 .into_iter()
15901 .flat_map(|(kind, tasks)| {
15902 tasks.into_iter().filter_map(move |(location, task)| {
15903 Some((kind.clone(), location?, task))
15904 })
15905 })
15906 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15907 let buffer = location.target.buffer;
15908 let buffer_snapshot = buffer.read(cx).snapshot();
15909 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15910 |(excerpt_id, snapshot, _)| {
15911 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15912 display_snapshot
15913 .buffer_snapshot()
15914 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15915 } else {
15916 None
15917 }
15918 },
15919 );
15920 if let Some(offset) = offset {
15921 let task_buffer_range =
15922 location.target.range.to_point(&buffer_snapshot);
15923 let context_buffer_range =
15924 task_buffer_range.to_offset(&buffer_snapshot);
15925 let context_range = BufferOffset(context_buffer_range.start)
15926 ..BufferOffset(context_buffer_range.end);
15927
15928 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15929 .or_insert_with(|| RunnableTasks {
15930 templates: Vec::new(),
15931 offset,
15932 column: task_buffer_range.start.column,
15933 extra_variables: HashMap::default(),
15934 context_range,
15935 })
15936 .templates
15937 .push((kind, task.original_task().clone()));
15938 }
15939
15940 acc
15941 })
15942 }) else {
15943 return;
15944 };
15945
15946 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15947 buffer.language_settings(cx).tasks.prefer_lsp
15948 }) else {
15949 return;
15950 };
15951
15952 let rows = Self::runnable_rows(
15953 project,
15954 display_snapshot,
15955 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15956 new_rows,
15957 cx.clone(),
15958 )
15959 .await;
15960 editor
15961 .update(cx, |editor, _| {
15962 editor.clear_tasks();
15963 for (key, mut value) in rows {
15964 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15965 value.templates.extend(lsp_tasks.templates);
15966 }
15967
15968 editor.insert_tasks(key, value);
15969 }
15970 for (key, value) in lsp_tasks_by_rows {
15971 editor.insert_tasks(key, value);
15972 }
15973 })
15974 .ok();
15975 })
15976 }
15977 fn fetch_runnable_ranges(
15978 snapshot: &DisplaySnapshot,
15979 range: Range<Anchor>,
15980 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15981 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15982 }
15983
15984 fn runnable_rows(
15985 project: Entity<Project>,
15986 snapshot: DisplaySnapshot,
15987 prefer_lsp: bool,
15988 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
15989 cx: AsyncWindowContext,
15990 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15991 cx.spawn(async move |cx| {
15992 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15993 for (run_range, mut runnable) in runnable_ranges {
15994 let Some(tasks) = cx
15995 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15996 .ok()
15997 else {
15998 continue;
15999 };
16000 let mut tasks = tasks.await;
16001
16002 if prefer_lsp {
16003 tasks.retain(|(task_kind, _)| {
16004 !matches!(task_kind, TaskSourceKind::Language { .. })
16005 });
16006 }
16007 if tasks.is_empty() {
16008 continue;
16009 }
16010
16011 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16012 let Some(row) = snapshot
16013 .buffer_snapshot()
16014 .buffer_line_for_row(MultiBufferRow(point.row))
16015 .map(|(_, range)| range.start.row)
16016 else {
16017 continue;
16018 };
16019
16020 let context_range =
16021 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16022 runnable_rows.push((
16023 (runnable.buffer_id, row),
16024 RunnableTasks {
16025 templates: tasks,
16026 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16027 context_range,
16028 column: point.column,
16029 extra_variables: runnable.extra_captures,
16030 },
16031 ));
16032 }
16033 runnable_rows
16034 })
16035 }
16036
16037 fn templates_with_tags(
16038 project: &Entity<Project>,
16039 runnable: &mut Runnable,
16040 cx: &mut App,
16041 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16042 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16043 let (worktree_id, file) = project
16044 .buffer_for_id(runnable.buffer, cx)
16045 .and_then(|buffer| buffer.read(cx).file())
16046 .map(|file| (file.worktree_id(cx), file.clone()))
16047 .unzip();
16048
16049 (
16050 project.task_store().read(cx).task_inventory().cloned(),
16051 worktree_id,
16052 file,
16053 )
16054 });
16055
16056 let tags = mem::take(&mut runnable.tags);
16057 let language = runnable.language.clone();
16058 cx.spawn(async move |cx| {
16059 let mut templates_with_tags = Vec::new();
16060 if let Some(inventory) = inventory {
16061 for RunnableTag(tag) in tags {
16062 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16063 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16064 }) else {
16065 return templates_with_tags;
16066 };
16067 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16068 move |(_, template)| {
16069 template.tags.iter().any(|source_tag| source_tag == &tag)
16070 },
16071 ));
16072 }
16073 }
16074 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16075
16076 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16077 // Strongest source wins; if we have worktree tag binding, prefer that to
16078 // global and language bindings;
16079 // if we have a global binding, prefer that to language binding.
16080 let first_mismatch = templates_with_tags
16081 .iter()
16082 .position(|(tag_source, _)| tag_source != leading_tag_source);
16083 if let Some(index) = first_mismatch {
16084 templates_with_tags.truncate(index);
16085 }
16086 }
16087
16088 templates_with_tags
16089 })
16090 }
16091
16092 pub fn move_to_enclosing_bracket(
16093 &mut self,
16094 _: &MoveToEnclosingBracket,
16095 window: &mut Window,
16096 cx: &mut Context<Self>,
16097 ) {
16098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16099 self.change_selections(Default::default(), window, cx, |s| {
16100 s.move_offsets_with(|snapshot, selection| {
16101 let Some(enclosing_bracket_ranges) =
16102 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16103 else {
16104 return;
16105 };
16106
16107 let mut best_length = usize::MAX;
16108 let mut best_inside = false;
16109 let mut best_in_bracket_range = false;
16110 let mut best_destination = None;
16111 for (open, close) in enclosing_bracket_ranges {
16112 let close = close.to_inclusive();
16113 let length = *close.end() - open.start;
16114 let inside = selection.start >= open.end && selection.end <= *close.start();
16115 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16116 || close.contains(&selection.head());
16117
16118 // If best is next to a bracket and current isn't, skip
16119 if !in_bracket_range && best_in_bracket_range {
16120 continue;
16121 }
16122
16123 // Prefer smaller lengths unless best is inside and current isn't
16124 if length > best_length && (best_inside || !inside) {
16125 continue;
16126 }
16127
16128 best_length = length;
16129 best_inside = inside;
16130 best_in_bracket_range = in_bracket_range;
16131 best_destination = Some(
16132 if close.contains(&selection.start) && close.contains(&selection.end) {
16133 if inside { open.end } else { open.start }
16134 } else if inside {
16135 *close.start()
16136 } else {
16137 *close.end()
16138 },
16139 );
16140 }
16141
16142 if let Some(destination) = best_destination {
16143 selection.collapse_to(destination, SelectionGoal::None);
16144 }
16145 })
16146 });
16147 }
16148
16149 pub fn undo_selection(
16150 &mut self,
16151 _: &UndoSelection,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16156 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16157 self.selection_history.mode = SelectionHistoryMode::Undoing;
16158 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16159 this.end_selection(window, cx);
16160 this.change_selections(
16161 SelectionEffects::scroll(Autoscroll::newest()),
16162 window,
16163 cx,
16164 |s| s.select_anchors(entry.selections.to_vec()),
16165 );
16166 });
16167 self.selection_history.mode = SelectionHistoryMode::Normal;
16168
16169 self.select_next_state = entry.select_next_state;
16170 self.select_prev_state = entry.select_prev_state;
16171 self.add_selections_state = entry.add_selections_state;
16172 }
16173 }
16174
16175 pub fn redo_selection(
16176 &mut self,
16177 _: &RedoSelection,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) {
16181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16182 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16183 self.selection_history.mode = SelectionHistoryMode::Redoing;
16184 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16185 this.end_selection(window, cx);
16186 this.change_selections(
16187 SelectionEffects::scroll(Autoscroll::newest()),
16188 window,
16189 cx,
16190 |s| s.select_anchors(entry.selections.to_vec()),
16191 );
16192 });
16193 self.selection_history.mode = SelectionHistoryMode::Normal;
16194
16195 self.select_next_state = entry.select_next_state;
16196 self.select_prev_state = entry.select_prev_state;
16197 self.add_selections_state = entry.add_selections_state;
16198 }
16199 }
16200
16201 pub fn expand_excerpts(
16202 &mut self,
16203 action: &ExpandExcerpts,
16204 _: &mut Window,
16205 cx: &mut Context<Self>,
16206 ) {
16207 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16208 }
16209
16210 pub fn expand_excerpts_down(
16211 &mut self,
16212 action: &ExpandExcerptsDown,
16213 _: &mut Window,
16214 cx: &mut Context<Self>,
16215 ) {
16216 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16217 }
16218
16219 pub fn expand_excerpts_up(
16220 &mut self,
16221 action: &ExpandExcerptsUp,
16222 _: &mut Window,
16223 cx: &mut Context<Self>,
16224 ) {
16225 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16226 }
16227
16228 pub fn expand_excerpts_for_direction(
16229 &mut self,
16230 lines: u32,
16231 direction: ExpandExcerptDirection,
16232
16233 cx: &mut Context<Self>,
16234 ) {
16235 let selections = self.selections.disjoint_anchors_arc();
16236
16237 let lines = if lines == 0 {
16238 EditorSettings::get_global(cx).expand_excerpt_lines
16239 } else {
16240 lines
16241 };
16242
16243 self.buffer.update(cx, |buffer, cx| {
16244 let snapshot = buffer.snapshot(cx);
16245 let mut excerpt_ids = selections
16246 .iter()
16247 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16248 .collect::<Vec<_>>();
16249 excerpt_ids.sort();
16250 excerpt_ids.dedup();
16251 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16252 })
16253 }
16254
16255 pub fn expand_excerpt(
16256 &mut self,
16257 excerpt: ExcerptId,
16258 direction: ExpandExcerptDirection,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) {
16262 let current_scroll_position = self.scroll_position(cx);
16263 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16264 let mut scroll = None;
16265
16266 if direction == ExpandExcerptDirection::Down {
16267 let multi_buffer = self.buffer.read(cx);
16268 let snapshot = multi_buffer.snapshot(cx);
16269 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16270 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16271 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16272 {
16273 let buffer_snapshot = buffer.read(cx).snapshot();
16274 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16275 let last_row = buffer_snapshot.max_point().row;
16276 let lines_below = last_row.saturating_sub(excerpt_end_row);
16277 if lines_below >= lines_to_expand {
16278 scroll = Some(
16279 current_scroll_position
16280 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16281 );
16282 }
16283 }
16284 }
16285 if direction == ExpandExcerptDirection::Up
16286 && self
16287 .buffer
16288 .read(cx)
16289 .snapshot(cx)
16290 .excerpt_before(excerpt)
16291 .is_none()
16292 {
16293 scroll = Some(current_scroll_position);
16294 }
16295
16296 self.buffer.update(cx, |buffer, cx| {
16297 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16298 });
16299
16300 if let Some(new_scroll_position) = scroll {
16301 self.set_scroll_position(new_scroll_position, window, cx);
16302 }
16303 }
16304
16305 pub fn go_to_singleton_buffer_point(
16306 &mut self,
16307 point: Point,
16308 window: &mut Window,
16309 cx: &mut Context<Self>,
16310 ) {
16311 self.go_to_singleton_buffer_range(point..point, window, cx);
16312 }
16313
16314 pub fn go_to_singleton_buffer_range(
16315 &mut self,
16316 range: Range<Point>,
16317 window: &mut Window,
16318 cx: &mut Context<Self>,
16319 ) {
16320 let multibuffer = self.buffer().read(cx);
16321 let Some(buffer) = multibuffer.as_singleton() else {
16322 return;
16323 };
16324 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16325 return;
16326 };
16327 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16328 return;
16329 };
16330 self.change_selections(
16331 SelectionEffects::default().nav_history(true),
16332 window,
16333 cx,
16334 |s| s.select_anchor_ranges([start..end]),
16335 );
16336 }
16337
16338 pub fn go_to_diagnostic(
16339 &mut self,
16340 action: &GoToDiagnostic,
16341 window: &mut Window,
16342 cx: &mut Context<Self>,
16343 ) {
16344 if !self.diagnostics_enabled() {
16345 return;
16346 }
16347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16348 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16349 }
16350
16351 pub fn go_to_prev_diagnostic(
16352 &mut self,
16353 action: &GoToPreviousDiagnostic,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) {
16357 if !self.diagnostics_enabled() {
16358 return;
16359 }
16360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16361 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16362 }
16363
16364 pub fn go_to_diagnostic_impl(
16365 &mut self,
16366 direction: Direction,
16367 severity: GoToDiagnosticSeverityFilter,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) {
16371 let buffer = self.buffer.read(cx).snapshot(cx);
16372 let selection = self
16373 .selections
16374 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16375
16376 let mut active_group_id = None;
16377 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16378 && active_group.active_range.start.to_offset(&buffer) == selection.start
16379 {
16380 active_group_id = Some(active_group.group_id);
16381 }
16382
16383 fn filtered<'a>(
16384 severity: GoToDiagnosticSeverityFilter,
16385 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16386 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16387 diagnostics
16388 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16389 .filter(|entry| entry.range.start != entry.range.end)
16390 .filter(|entry| !entry.diagnostic.is_unnecessary)
16391 }
16392
16393 let before = filtered(
16394 severity,
16395 buffer
16396 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16397 .filter(|entry| entry.range.start <= selection.start),
16398 );
16399 let after = filtered(
16400 severity,
16401 buffer
16402 .diagnostics_in_range(selection.start..buffer.len())
16403 .filter(|entry| entry.range.start >= selection.start),
16404 );
16405
16406 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16407 if direction == Direction::Prev {
16408 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16409 {
16410 for diagnostic in prev_diagnostics.into_iter().rev() {
16411 if diagnostic.range.start != selection.start
16412 || active_group_id
16413 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16414 {
16415 found = Some(diagnostic);
16416 break 'outer;
16417 }
16418 }
16419 }
16420 } else {
16421 for diagnostic in after.chain(before) {
16422 if diagnostic.range.start != selection.start
16423 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16424 {
16425 found = Some(diagnostic);
16426 break;
16427 }
16428 }
16429 }
16430 let Some(next_diagnostic) = found else {
16431 return;
16432 };
16433
16434 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16435 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16436 return;
16437 };
16438 let snapshot = self.snapshot(window, cx);
16439 if snapshot.intersects_fold(next_diagnostic.range.start) {
16440 self.unfold_ranges(
16441 std::slice::from_ref(&next_diagnostic.range),
16442 true,
16443 false,
16444 cx,
16445 );
16446 }
16447 self.change_selections(Default::default(), window, cx, |s| {
16448 s.select_ranges(vec![
16449 next_diagnostic.range.start..next_diagnostic.range.start,
16450 ])
16451 });
16452 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16453 self.refresh_edit_prediction(false, true, window, cx);
16454 }
16455
16456 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16458 let snapshot = self.snapshot(window, cx);
16459 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16460 self.go_to_hunk_before_or_after_position(
16461 &snapshot,
16462 selection.head(),
16463 Direction::Next,
16464 window,
16465 cx,
16466 );
16467 }
16468
16469 pub fn go_to_hunk_before_or_after_position(
16470 &mut self,
16471 snapshot: &EditorSnapshot,
16472 position: Point,
16473 direction: Direction,
16474 window: &mut Window,
16475 cx: &mut Context<Editor>,
16476 ) {
16477 let row = if direction == Direction::Next {
16478 self.hunk_after_position(snapshot, position)
16479 .map(|hunk| hunk.row_range.start)
16480 } else {
16481 self.hunk_before_position(snapshot, position)
16482 };
16483
16484 if let Some(row) = row {
16485 let destination = Point::new(row.0, 0);
16486 let autoscroll = Autoscroll::center();
16487
16488 self.unfold_ranges(&[destination..destination], false, false, cx);
16489 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16490 s.select_ranges([destination..destination]);
16491 });
16492 }
16493 }
16494
16495 fn hunk_after_position(
16496 &mut self,
16497 snapshot: &EditorSnapshot,
16498 position: Point,
16499 ) -> Option<MultiBufferDiffHunk> {
16500 snapshot
16501 .buffer_snapshot()
16502 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16503 .find(|hunk| hunk.row_range.start.0 > position.row)
16504 .or_else(|| {
16505 snapshot
16506 .buffer_snapshot()
16507 .diff_hunks_in_range(Point::zero()..position)
16508 .find(|hunk| hunk.row_range.end.0 < position.row)
16509 })
16510 }
16511
16512 fn go_to_prev_hunk(
16513 &mut self,
16514 _: &GoToPreviousHunk,
16515 window: &mut Window,
16516 cx: &mut Context<Self>,
16517 ) {
16518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16519 let snapshot = self.snapshot(window, cx);
16520 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16521 self.go_to_hunk_before_or_after_position(
16522 &snapshot,
16523 selection.head(),
16524 Direction::Prev,
16525 window,
16526 cx,
16527 );
16528 }
16529
16530 fn hunk_before_position(
16531 &mut self,
16532 snapshot: &EditorSnapshot,
16533 position: Point,
16534 ) -> Option<MultiBufferRow> {
16535 snapshot
16536 .buffer_snapshot()
16537 .diff_hunk_before(position)
16538 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16539 }
16540
16541 fn go_to_next_change(
16542 &mut self,
16543 _: &GoToNextChange,
16544 window: &mut Window,
16545 cx: &mut Context<Self>,
16546 ) {
16547 if let Some(selections) = self
16548 .change_list
16549 .next_change(1, Direction::Next)
16550 .map(|s| s.to_vec())
16551 {
16552 self.change_selections(Default::default(), window, cx, |s| {
16553 let map = s.display_snapshot();
16554 s.select_display_ranges(selections.iter().map(|a| {
16555 let point = a.to_display_point(&map);
16556 point..point
16557 }))
16558 })
16559 }
16560 }
16561
16562 fn go_to_previous_change(
16563 &mut self,
16564 _: &GoToPreviousChange,
16565 window: &mut Window,
16566 cx: &mut Context<Self>,
16567 ) {
16568 if let Some(selections) = self
16569 .change_list
16570 .next_change(1, Direction::Prev)
16571 .map(|s| s.to_vec())
16572 {
16573 self.change_selections(Default::default(), window, cx, |s| {
16574 let map = s.display_snapshot();
16575 s.select_display_ranges(selections.iter().map(|a| {
16576 let point = a.to_display_point(&map);
16577 point..point
16578 }))
16579 })
16580 }
16581 }
16582
16583 pub fn go_to_next_document_highlight(
16584 &mut self,
16585 _: &GoToNextDocumentHighlight,
16586 window: &mut Window,
16587 cx: &mut Context<Self>,
16588 ) {
16589 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16590 }
16591
16592 pub fn go_to_prev_document_highlight(
16593 &mut self,
16594 _: &GoToPreviousDocumentHighlight,
16595 window: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) {
16598 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16599 }
16600
16601 pub fn go_to_document_highlight_before_or_after_position(
16602 &mut self,
16603 direction: Direction,
16604 window: &mut Window,
16605 cx: &mut Context<Editor>,
16606 ) {
16607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16608 let snapshot = self.snapshot(window, cx);
16609 let buffer = &snapshot.buffer_snapshot();
16610 let position = self
16611 .selections
16612 .newest::<Point>(&snapshot.display_snapshot)
16613 .head();
16614 let anchor_position = buffer.anchor_after(position);
16615
16616 // Get all document highlights (both read and write)
16617 let mut all_highlights = Vec::new();
16618
16619 if let Some((_, read_highlights)) = self
16620 .background_highlights
16621 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16622 {
16623 all_highlights.extend(read_highlights.iter());
16624 }
16625
16626 if let Some((_, write_highlights)) = self
16627 .background_highlights
16628 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16629 {
16630 all_highlights.extend(write_highlights.iter());
16631 }
16632
16633 if all_highlights.is_empty() {
16634 return;
16635 }
16636
16637 // Sort highlights by position
16638 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16639
16640 let target_highlight = match direction {
16641 Direction::Next => {
16642 // Find the first highlight after the current position
16643 all_highlights
16644 .iter()
16645 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16646 }
16647 Direction::Prev => {
16648 // Find the last highlight before the current position
16649 all_highlights
16650 .iter()
16651 .rev()
16652 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16653 }
16654 };
16655
16656 if let Some(highlight) = target_highlight {
16657 let destination = highlight.start.to_point(buffer);
16658 let autoscroll = Autoscroll::center();
16659
16660 self.unfold_ranges(&[destination..destination], false, false, cx);
16661 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16662 s.select_ranges([destination..destination]);
16663 });
16664 }
16665 }
16666
16667 fn go_to_line<T: 'static>(
16668 &mut self,
16669 position: Anchor,
16670 highlight_color: Option<Hsla>,
16671 window: &mut Window,
16672 cx: &mut Context<Self>,
16673 ) {
16674 let snapshot = self.snapshot(window, cx).display_snapshot;
16675 let position = position.to_point(&snapshot.buffer_snapshot());
16676 let start = snapshot
16677 .buffer_snapshot()
16678 .clip_point(Point::new(position.row, 0), Bias::Left);
16679 let end = start + Point::new(1, 0);
16680 let start = snapshot.buffer_snapshot().anchor_before(start);
16681 let end = snapshot.buffer_snapshot().anchor_before(end);
16682
16683 self.highlight_rows::<T>(
16684 start..end,
16685 highlight_color
16686 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16687 Default::default(),
16688 cx,
16689 );
16690
16691 if self.buffer.read(cx).is_singleton() {
16692 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16693 }
16694 }
16695
16696 pub fn go_to_definition(
16697 &mut self,
16698 _: &GoToDefinition,
16699 window: &mut Window,
16700 cx: &mut Context<Self>,
16701 ) -> Task<Result<Navigated>> {
16702 let definition =
16703 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16704 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16705 cx.spawn_in(window, async move |editor, cx| {
16706 if definition.await? == Navigated::Yes {
16707 return Ok(Navigated::Yes);
16708 }
16709 match fallback_strategy {
16710 GoToDefinitionFallback::None => Ok(Navigated::No),
16711 GoToDefinitionFallback::FindAllReferences => {
16712 match editor.update_in(cx, |editor, window, cx| {
16713 editor.find_all_references(&FindAllReferences, window, cx)
16714 })? {
16715 Some(references) => references.await,
16716 None => Ok(Navigated::No),
16717 }
16718 }
16719 }
16720 })
16721 }
16722
16723 pub fn go_to_declaration(
16724 &mut self,
16725 _: &GoToDeclaration,
16726 window: &mut Window,
16727 cx: &mut Context<Self>,
16728 ) -> Task<Result<Navigated>> {
16729 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16730 }
16731
16732 pub fn go_to_declaration_split(
16733 &mut self,
16734 _: &GoToDeclaration,
16735 window: &mut Window,
16736 cx: &mut Context<Self>,
16737 ) -> Task<Result<Navigated>> {
16738 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16739 }
16740
16741 pub fn go_to_implementation(
16742 &mut self,
16743 _: &GoToImplementation,
16744 window: &mut Window,
16745 cx: &mut Context<Self>,
16746 ) -> Task<Result<Navigated>> {
16747 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16748 }
16749
16750 pub fn go_to_implementation_split(
16751 &mut self,
16752 _: &GoToImplementationSplit,
16753 window: &mut Window,
16754 cx: &mut Context<Self>,
16755 ) -> Task<Result<Navigated>> {
16756 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16757 }
16758
16759 pub fn go_to_type_definition(
16760 &mut self,
16761 _: &GoToTypeDefinition,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) -> Task<Result<Navigated>> {
16765 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16766 }
16767
16768 pub fn go_to_definition_split(
16769 &mut self,
16770 _: &GoToDefinitionSplit,
16771 window: &mut Window,
16772 cx: &mut Context<Self>,
16773 ) -> Task<Result<Navigated>> {
16774 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16775 }
16776
16777 pub fn go_to_type_definition_split(
16778 &mut self,
16779 _: &GoToTypeDefinitionSplit,
16780 window: &mut Window,
16781 cx: &mut Context<Self>,
16782 ) -> Task<Result<Navigated>> {
16783 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16784 }
16785
16786 fn go_to_definition_of_kind(
16787 &mut self,
16788 kind: GotoDefinitionKind,
16789 split: bool,
16790 window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) -> Task<Result<Navigated>> {
16793 let Some(provider) = self.semantics_provider.clone() else {
16794 return Task::ready(Ok(Navigated::No));
16795 };
16796 let head = self
16797 .selections
16798 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16799 .head();
16800 let buffer = self.buffer.read(cx);
16801 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16802 return Task::ready(Ok(Navigated::No));
16803 };
16804 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16805 return Task::ready(Ok(Navigated::No));
16806 };
16807
16808 cx.spawn_in(window, async move |editor, cx| {
16809 let Some(definitions) = definitions.await? else {
16810 return Ok(Navigated::No);
16811 };
16812 let navigated = editor
16813 .update_in(cx, |editor, window, cx| {
16814 editor.navigate_to_hover_links(
16815 Some(kind),
16816 definitions
16817 .into_iter()
16818 .filter(|location| {
16819 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16820 })
16821 .map(HoverLink::Text)
16822 .collect::<Vec<_>>(),
16823 split,
16824 window,
16825 cx,
16826 )
16827 })?
16828 .await?;
16829 anyhow::Ok(navigated)
16830 })
16831 }
16832
16833 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16834 let selection = self.selections.newest_anchor();
16835 let head = selection.head();
16836 let tail = selection.tail();
16837
16838 let Some((buffer, start_position)) =
16839 self.buffer.read(cx).text_anchor_for_position(head, cx)
16840 else {
16841 return;
16842 };
16843
16844 let end_position = if head != tail {
16845 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16846 return;
16847 };
16848 Some(pos)
16849 } else {
16850 None
16851 };
16852
16853 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16854 let url = if let Some(end_pos) = end_position {
16855 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16856 } else {
16857 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16858 };
16859
16860 if let Some(url) = url {
16861 cx.update(|window, cx| {
16862 if parse_zed_link(&url, cx).is_some() {
16863 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16864 } else {
16865 cx.open_url(&url);
16866 }
16867 })?;
16868 }
16869
16870 anyhow::Ok(())
16871 });
16872
16873 url_finder.detach();
16874 }
16875
16876 pub fn open_selected_filename(
16877 &mut self,
16878 _: &OpenSelectedFilename,
16879 window: &mut Window,
16880 cx: &mut Context<Self>,
16881 ) {
16882 let Some(workspace) = self.workspace() else {
16883 return;
16884 };
16885
16886 let position = self.selections.newest_anchor().head();
16887
16888 let Some((buffer, buffer_position)) =
16889 self.buffer.read(cx).text_anchor_for_position(position, cx)
16890 else {
16891 return;
16892 };
16893
16894 let project = self.project.clone();
16895
16896 cx.spawn_in(window, async move |_, cx| {
16897 let result = find_file(&buffer, project, buffer_position, cx).await;
16898
16899 if let Some((_, path)) = result {
16900 workspace
16901 .update_in(cx, |workspace, window, cx| {
16902 workspace.open_resolved_path(path, window, cx)
16903 })?
16904 .await?;
16905 }
16906 anyhow::Ok(())
16907 })
16908 .detach();
16909 }
16910
16911 pub(crate) fn navigate_to_hover_links(
16912 &mut self,
16913 kind: Option<GotoDefinitionKind>,
16914 definitions: Vec<HoverLink>,
16915 split: bool,
16916 window: &mut Window,
16917 cx: &mut Context<Editor>,
16918 ) -> Task<Result<Navigated>> {
16919 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16920 let mut first_url_or_file = None;
16921 let definitions: Vec<_> = definitions
16922 .into_iter()
16923 .filter_map(|def| match def {
16924 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16925 HoverLink::InlayHint(lsp_location, server_id) => {
16926 let computation =
16927 self.compute_target_location(lsp_location, server_id, window, cx);
16928 Some(cx.background_spawn(computation))
16929 }
16930 HoverLink::Url(url) => {
16931 first_url_or_file = Some(Either::Left(url));
16932 None
16933 }
16934 HoverLink::File(path) => {
16935 first_url_or_file = Some(Either::Right(path));
16936 None
16937 }
16938 })
16939 .collect();
16940
16941 let workspace = self.workspace();
16942
16943 cx.spawn_in(window, async move |editor, cx| {
16944 let locations: Vec<Location> = future::join_all(definitions)
16945 .await
16946 .into_iter()
16947 .filter_map(|location| location.transpose())
16948 .collect::<Result<_>>()
16949 .context("location tasks")?;
16950 let mut locations = cx.update(|_, cx| {
16951 locations
16952 .into_iter()
16953 .map(|location| {
16954 let buffer = location.buffer.read(cx);
16955 (location.buffer, location.range.to_point(buffer))
16956 })
16957 .into_group_map()
16958 })?;
16959 let mut num_locations = 0;
16960 for ranges in locations.values_mut() {
16961 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16962 ranges.dedup();
16963 num_locations += ranges.len();
16964 }
16965
16966 if num_locations > 1 {
16967 let Some(workspace) = workspace else {
16968 return Ok(Navigated::No);
16969 };
16970
16971 let tab_kind = match kind {
16972 Some(GotoDefinitionKind::Implementation) => "Implementations",
16973 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16974 Some(GotoDefinitionKind::Declaration) => "Declarations",
16975 Some(GotoDefinitionKind::Type) => "Types",
16976 };
16977 let title = editor
16978 .update_in(cx, |_, _, cx| {
16979 let target = locations
16980 .iter()
16981 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16982 .map(|(buffer, location)| {
16983 buffer
16984 .read(cx)
16985 .text_for_range(location.clone())
16986 .collect::<String>()
16987 })
16988 .filter(|text| !text.contains('\n'))
16989 .unique()
16990 .take(3)
16991 .join(", ");
16992 if target.is_empty() {
16993 tab_kind.to_owned()
16994 } else {
16995 format!("{tab_kind} for {target}")
16996 }
16997 })
16998 .context("buffer title")?;
16999
17000 let opened = workspace
17001 .update_in(cx, |workspace, window, cx| {
17002 Self::open_locations_in_multibuffer(
17003 workspace,
17004 locations,
17005 title,
17006 split,
17007 MultibufferSelectionMode::First,
17008 window,
17009 cx,
17010 )
17011 })
17012 .is_ok();
17013
17014 anyhow::Ok(Navigated::from_bool(opened))
17015 } else if num_locations == 0 {
17016 // If there is one url or file, open it directly
17017 match first_url_or_file {
17018 Some(Either::Left(url)) => {
17019 cx.update(|_, cx| cx.open_url(&url))?;
17020 Ok(Navigated::Yes)
17021 }
17022 Some(Either::Right(path)) => {
17023 let Some(workspace) = workspace else {
17024 return Ok(Navigated::No);
17025 };
17026
17027 workspace
17028 .update_in(cx, |workspace, window, cx| {
17029 workspace.open_resolved_path(path, window, cx)
17030 })?
17031 .await?;
17032 Ok(Navigated::Yes)
17033 }
17034 None => Ok(Navigated::No),
17035 }
17036 } else {
17037 let Some(workspace) = workspace else {
17038 return Ok(Navigated::No);
17039 };
17040
17041 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17042 let target_range = target_ranges.first().unwrap().clone();
17043
17044 editor.update_in(cx, |editor, window, cx| {
17045 let range = target_range.to_point(target_buffer.read(cx));
17046 let range = editor.range_for_match(&range);
17047 let range = collapse_multiline_range(range);
17048
17049 if !split
17050 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17051 {
17052 editor.go_to_singleton_buffer_range(range, window, cx);
17053 } else {
17054 let pane = workspace.read(cx).active_pane().clone();
17055 window.defer(cx, move |window, cx| {
17056 let target_editor: Entity<Self> =
17057 workspace.update(cx, |workspace, cx| {
17058 let pane = if split {
17059 workspace.adjacent_pane(window, cx)
17060 } else {
17061 workspace.active_pane().clone()
17062 };
17063
17064 workspace.open_project_item(
17065 pane,
17066 target_buffer.clone(),
17067 true,
17068 true,
17069 window,
17070 cx,
17071 )
17072 });
17073 target_editor.update(cx, |target_editor, cx| {
17074 // When selecting a definition in a different buffer, disable the nav history
17075 // to avoid creating a history entry at the previous cursor location.
17076 pane.update(cx, |pane, _| pane.disable_history());
17077 target_editor.go_to_singleton_buffer_range(range, window, cx);
17078 pane.update(cx, |pane, _| pane.enable_history());
17079 });
17080 });
17081 }
17082 Navigated::Yes
17083 })
17084 }
17085 })
17086 }
17087
17088 fn compute_target_location(
17089 &self,
17090 lsp_location: lsp::Location,
17091 server_id: LanguageServerId,
17092 window: &mut Window,
17093 cx: &mut Context<Self>,
17094 ) -> Task<anyhow::Result<Option<Location>>> {
17095 let Some(project) = self.project.clone() else {
17096 return Task::ready(Ok(None));
17097 };
17098
17099 cx.spawn_in(window, async move |editor, cx| {
17100 let location_task = editor.update(cx, |_, cx| {
17101 project.update(cx, |project, cx| {
17102 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17103 })
17104 })?;
17105 let location = Some({
17106 let target_buffer_handle = location_task.await.context("open local buffer")?;
17107 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17108 let target_start = target_buffer
17109 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17110 let target_end = target_buffer
17111 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17112 target_buffer.anchor_after(target_start)
17113 ..target_buffer.anchor_before(target_end)
17114 })?;
17115 Location {
17116 buffer: target_buffer_handle,
17117 range,
17118 }
17119 });
17120 Ok(location)
17121 })
17122 }
17123
17124 fn go_to_next_reference(
17125 &mut self,
17126 _: &GoToNextReference,
17127 window: &mut Window,
17128 cx: &mut Context<Self>,
17129 ) {
17130 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17131 if let Some(task) = task {
17132 task.detach();
17133 };
17134 }
17135
17136 fn go_to_prev_reference(
17137 &mut self,
17138 _: &GoToPreviousReference,
17139 window: &mut Window,
17140 cx: &mut Context<Self>,
17141 ) {
17142 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17143 if let Some(task) = task {
17144 task.detach();
17145 };
17146 }
17147
17148 pub fn go_to_reference_before_or_after_position(
17149 &mut self,
17150 direction: Direction,
17151 count: usize,
17152 window: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) -> Option<Task<Result<()>>> {
17155 let selection = self.selections.newest_anchor();
17156 let head = selection.head();
17157
17158 let multi_buffer = self.buffer.read(cx);
17159
17160 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17161 let workspace = self.workspace()?;
17162 let project = workspace.read(cx).project().clone();
17163 let references =
17164 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17165 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17166 let Some(locations) = references.await? else {
17167 return Ok(());
17168 };
17169
17170 if locations.is_empty() {
17171 // totally normal - the cursor may be on something which is not
17172 // a symbol (e.g. a keyword)
17173 log::info!("no references found under cursor");
17174 return Ok(());
17175 }
17176
17177 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17178
17179 let (locations, current_location_index) =
17180 multi_buffer.update(cx, |multi_buffer, cx| {
17181 let mut locations = locations
17182 .into_iter()
17183 .filter_map(|loc| {
17184 let start = multi_buffer.buffer_anchor_to_anchor(
17185 &loc.buffer,
17186 loc.range.start,
17187 cx,
17188 )?;
17189 let end = multi_buffer.buffer_anchor_to_anchor(
17190 &loc.buffer,
17191 loc.range.end,
17192 cx,
17193 )?;
17194 Some(start..end)
17195 })
17196 .collect::<Vec<_>>();
17197
17198 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17199 // There is an O(n) implementation, but given this list will be
17200 // small (usually <100 items), the extra O(log(n)) factor isn't
17201 // worth the (surprisingly large amount of) extra complexity.
17202 locations
17203 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17204
17205 let head_offset = head.to_offset(&multi_buffer_snapshot);
17206
17207 let current_location_index = locations.iter().position(|loc| {
17208 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17209 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17210 });
17211
17212 (locations, current_location_index)
17213 })?;
17214
17215 let Some(current_location_index) = current_location_index else {
17216 // This indicates something has gone wrong, because we already
17217 // handle the "no references" case above
17218 log::error!(
17219 "failed to find current reference under cursor. Total references: {}",
17220 locations.len()
17221 );
17222 return Ok(());
17223 };
17224
17225 let destination_location_index = match direction {
17226 Direction::Next => (current_location_index + count) % locations.len(),
17227 Direction::Prev => {
17228 (current_location_index + locations.len() - count % locations.len())
17229 % locations.len()
17230 }
17231 };
17232
17233 // TODO(cameron): is this needed?
17234 // the thinking is to avoid "jumping to the current location" (avoid
17235 // polluting "jumplist" in vim terms)
17236 if current_location_index == destination_location_index {
17237 return Ok(());
17238 }
17239
17240 let Range { start, end } = locations[destination_location_index];
17241
17242 editor.update_in(cx, |editor, window, cx| {
17243 let effects = SelectionEffects::default();
17244
17245 editor.unfold_ranges(&[start..end], false, false, cx);
17246 editor.change_selections(effects, window, cx, |s| {
17247 s.select_ranges([start..start]);
17248 });
17249 })?;
17250
17251 Ok(())
17252 }))
17253 }
17254
17255 pub fn find_all_references(
17256 &mut self,
17257 _: &FindAllReferences,
17258 window: &mut Window,
17259 cx: &mut Context<Self>,
17260 ) -> Option<Task<Result<Navigated>>> {
17261 let selection = self
17262 .selections
17263 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17264 let multi_buffer = self.buffer.read(cx);
17265 let head = selection.head();
17266
17267 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17268 let head_anchor = multi_buffer_snapshot.anchor_at(
17269 head,
17270 if head < selection.tail() {
17271 Bias::Right
17272 } else {
17273 Bias::Left
17274 },
17275 );
17276
17277 match self
17278 .find_all_references_task_sources
17279 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17280 {
17281 Ok(_) => {
17282 log::info!(
17283 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17284 );
17285 return None;
17286 }
17287 Err(i) => {
17288 self.find_all_references_task_sources.insert(i, head_anchor);
17289 }
17290 }
17291
17292 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17293 let workspace = self.workspace()?;
17294 let project = workspace.read(cx).project().clone();
17295 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17296 Some(cx.spawn_in(window, async move |editor, cx| {
17297 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17298 if let Ok(i) = editor
17299 .find_all_references_task_sources
17300 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17301 {
17302 editor.find_all_references_task_sources.remove(i);
17303 }
17304 });
17305
17306 let Some(locations) = references.await? else {
17307 return anyhow::Ok(Navigated::No);
17308 };
17309 let mut locations = cx.update(|_, cx| {
17310 locations
17311 .into_iter()
17312 .map(|location| {
17313 let buffer = location.buffer.read(cx);
17314 (location.buffer, location.range.to_point(buffer))
17315 })
17316 .into_group_map()
17317 })?;
17318 if locations.is_empty() {
17319 return anyhow::Ok(Navigated::No);
17320 }
17321 for ranges in locations.values_mut() {
17322 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17323 ranges.dedup();
17324 }
17325
17326 workspace.update_in(cx, |workspace, window, cx| {
17327 let target = locations
17328 .iter()
17329 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17330 .map(|(buffer, location)| {
17331 buffer
17332 .read(cx)
17333 .text_for_range(location.clone())
17334 .collect::<String>()
17335 })
17336 .filter(|text| !text.contains('\n'))
17337 .unique()
17338 .take(3)
17339 .join(", ");
17340 let title = if target.is_empty() {
17341 "References".to_owned()
17342 } else {
17343 format!("References to {target}")
17344 };
17345 Self::open_locations_in_multibuffer(
17346 workspace,
17347 locations,
17348 title,
17349 false,
17350 MultibufferSelectionMode::First,
17351 window,
17352 cx,
17353 );
17354 Navigated::Yes
17355 })
17356 }))
17357 }
17358
17359 /// Opens a multibuffer with the given project locations in it
17360 pub fn open_locations_in_multibuffer(
17361 workspace: &mut Workspace,
17362 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17363 title: String,
17364 split: bool,
17365 multibuffer_selection_mode: MultibufferSelectionMode,
17366 window: &mut Window,
17367 cx: &mut Context<Workspace>,
17368 ) {
17369 if locations.is_empty() {
17370 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17371 return;
17372 }
17373
17374 let capability = workspace.project().read(cx).capability();
17375 let mut ranges = <Vec<Range<Anchor>>>::new();
17376
17377 // a key to find existing multibuffer editors with the same set of locations
17378 // to prevent us from opening more and more multibuffer tabs for searches and the like
17379 let mut key = (title.clone(), vec![]);
17380 let excerpt_buffer = cx.new(|cx| {
17381 let key = &mut key.1;
17382 let mut multibuffer = MultiBuffer::new(capability);
17383 for (buffer, mut ranges_for_buffer) in locations {
17384 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17385 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17386 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17387 PathKey::for_buffer(&buffer, cx),
17388 buffer.clone(),
17389 ranges_for_buffer,
17390 multibuffer_context_lines(cx),
17391 cx,
17392 );
17393 ranges.extend(new_ranges)
17394 }
17395
17396 multibuffer.with_title(title)
17397 });
17398 let existing = workspace.active_pane().update(cx, |pane, cx| {
17399 pane.items()
17400 .filter_map(|item| item.downcast::<Editor>())
17401 .find(|editor| {
17402 editor
17403 .read(cx)
17404 .lookup_key
17405 .as_ref()
17406 .and_then(|it| {
17407 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17408 })
17409 .is_some_and(|it| *it == key)
17410 })
17411 });
17412 let editor = existing.unwrap_or_else(|| {
17413 cx.new(|cx| {
17414 let mut editor = Editor::for_multibuffer(
17415 excerpt_buffer,
17416 Some(workspace.project().clone()),
17417 window,
17418 cx,
17419 );
17420 editor.lookup_key = Some(Box::new(key));
17421 editor
17422 })
17423 });
17424 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17425 MultibufferSelectionMode::First => {
17426 if let Some(first_range) = ranges.first() {
17427 editor.change_selections(
17428 SelectionEffects::no_scroll(),
17429 window,
17430 cx,
17431 |selections| {
17432 selections.clear_disjoint();
17433 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17434 },
17435 );
17436 }
17437 editor.highlight_background::<Self>(
17438 &ranges,
17439 |theme| theme.colors().editor_highlighted_line_background,
17440 cx,
17441 );
17442 }
17443 MultibufferSelectionMode::All => {
17444 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17445 selections.clear_disjoint();
17446 selections.select_anchor_ranges(ranges);
17447 });
17448 }
17449 });
17450
17451 let item = Box::new(editor);
17452 let item_id = item.item_id();
17453
17454 if split {
17455 let pane = workspace.adjacent_pane(window, cx);
17456 workspace.add_item(pane, item, None, true, true, window, cx);
17457 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17458 let (preview_item_id, preview_item_idx) =
17459 workspace.active_pane().read_with(cx, |pane, _| {
17460 (pane.preview_item_id(), pane.preview_item_idx())
17461 });
17462
17463 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17464
17465 if let Some(preview_item_id) = preview_item_id {
17466 workspace.active_pane().update(cx, |pane, cx| {
17467 pane.remove_item(preview_item_id, false, false, window, cx);
17468 });
17469 }
17470 } else {
17471 workspace.add_item_to_active_pane(item, None, true, window, cx);
17472 }
17473 workspace.active_pane().update(cx, |pane, cx| {
17474 pane.set_preview_item_id(Some(item_id), cx);
17475 });
17476 }
17477
17478 pub fn rename(
17479 &mut self,
17480 _: &Rename,
17481 window: &mut Window,
17482 cx: &mut Context<Self>,
17483 ) -> Option<Task<Result<()>>> {
17484 use language::ToOffset as _;
17485
17486 let provider = self.semantics_provider.clone()?;
17487 let selection = self.selections.newest_anchor().clone();
17488 let (cursor_buffer, cursor_buffer_position) = self
17489 .buffer
17490 .read(cx)
17491 .text_anchor_for_position(selection.head(), cx)?;
17492 let (tail_buffer, cursor_buffer_position_end) = self
17493 .buffer
17494 .read(cx)
17495 .text_anchor_for_position(selection.tail(), cx)?;
17496 if tail_buffer != cursor_buffer {
17497 return None;
17498 }
17499
17500 let snapshot = cursor_buffer.read(cx).snapshot();
17501 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17502 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17503 let prepare_rename = provider
17504 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17505 .unwrap_or_else(|| Task::ready(Ok(None)));
17506 drop(snapshot);
17507
17508 Some(cx.spawn_in(window, async move |this, cx| {
17509 let rename_range = if let Some(range) = prepare_rename.await? {
17510 Some(range)
17511 } else {
17512 this.update(cx, |this, cx| {
17513 let buffer = this.buffer.read(cx).snapshot(cx);
17514 let mut buffer_highlights = this
17515 .document_highlights_for_position(selection.head(), &buffer)
17516 .filter(|highlight| {
17517 highlight.start.excerpt_id == selection.head().excerpt_id
17518 && highlight.end.excerpt_id == selection.head().excerpt_id
17519 });
17520 buffer_highlights
17521 .next()
17522 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17523 })?
17524 };
17525 if let Some(rename_range) = rename_range {
17526 this.update_in(cx, |this, window, cx| {
17527 let snapshot = cursor_buffer.read(cx).snapshot();
17528 let rename_buffer_range = rename_range.to_offset(&snapshot);
17529 let cursor_offset_in_rename_range =
17530 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17531 let cursor_offset_in_rename_range_end =
17532 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17533
17534 this.take_rename(false, window, cx);
17535 let buffer = this.buffer.read(cx).read(cx);
17536 let cursor_offset = selection.head().to_offset(&buffer);
17537 let rename_start =
17538 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17539 let rename_end = rename_start + rename_buffer_range.len();
17540 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17541 let mut old_highlight_id = None;
17542 let old_name: Arc<str> = buffer
17543 .chunks(rename_start..rename_end, true)
17544 .map(|chunk| {
17545 if old_highlight_id.is_none() {
17546 old_highlight_id = chunk.syntax_highlight_id;
17547 }
17548 chunk.text
17549 })
17550 .collect::<String>()
17551 .into();
17552
17553 drop(buffer);
17554
17555 // Position the selection in the rename editor so that it matches the current selection.
17556 this.show_local_selections = false;
17557 let rename_editor = cx.new(|cx| {
17558 let mut editor = Editor::single_line(window, cx);
17559 editor.buffer.update(cx, |buffer, cx| {
17560 buffer.edit(
17561 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17562 None,
17563 cx,
17564 )
17565 });
17566 let cursor_offset_in_rename_range =
17567 MultiBufferOffset(cursor_offset_in_rename_range);
17568 let cursor_offset_in_rename_range_end =
17569 MultiBufferOffset(cursor_offset_in_rename_range_end);
17570 let rename_selection_range = match cursor_offset_in_rename_range
17571 .cmp(&cursor_offset_in_rename_range_end)
17572 {
17573 Ordering::Equal => {
17574 editor.select_all(&SelectAll, window, cx);
17575 return editor;
17576 }
17577 Ordering::Less => {
17578 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17579 }
17580 Ordering::Greater => {
17581 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17582 }
17583 };
17584 if rename_selection_range.end.0 > old_name.len() {
17585 editor.select_all(&SelectAll, window, cx);
17586 } else {
17587 editor.change_selections(Default::default(), window, cx, |s| {
17588 s.select_ranges([rename_selection_range]);
17589 });
17590 }
17591 editor
17592 });
17593 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17594 if e == &EditorEvent::Focused {
17595 cx.emit(EditorEvent::FocusedIn)
17596 }
17597 })
17598 .detach();
17599
17600 let write_highlights =
17601 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17602 let read_highlights =
17603 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17604 let ranges = write_highlights
17605 .iter()
17606 .flat_map(|(_, ranges)| ranges.iter())
17607 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17608 .cloned()
17609 .collect();
17610
17611 this.highlight_text::<Rename>(
17612 ranges,
17613 HighlightStyle {
17614 fade_out: Some(0.6),
17615 ..Default::default()
17616 },
17617 cx,
17618 );
17619 let rename_focus_handle = rename_editor.focus_handle(cx);
17620 window.focus(&rename_focus_handle);
17621 let block_id = this.insert_blocks(
17622 [BlockProperties {
17623 style: BlockStyle::Flex,
17624 placement: BlockPlacement::Below(range.start),
17625 height: Some(1),
17626 render: Arc::new({
17627 let rename_editor = rename_editor.clone();
17628 move |cx: &mut BlockContext| {
17629 let mut text_style = cx.editor_style.text.clone();
17630 if let Some(highlight_style) = old_highlight_id
17631 .and_then(|h| h.style(&cx.editor_style.syntax))
17632 {
17633 text_style = text_style.highlight(highlight_style);
17634 }
17635 div()
17636 .block_mouse_except_scroll()
17637 .pl(cx.anchor_x)
17638 .child(EditorElement::new(
17639 &rename_editor,
17640 EditorStyle {
17641 background: cx.theme().system().transparent,
17642 local_player: cx.editor_style.local_player,
17643 text: text_style,
17644 scrollbar_width: cx.editor_style.scrollbar_width,
17645 syntax: cx.editor_style.syntax.clone(),
17646 status: cx.editor_style.status.clone(),
17647 inlay_hints_style: HighlightStyle {
17648 font_weight: Some(FontWeight::BOLD),
17649 ..make_inlay_hints_style(cx.app)
17650 },
17651 edit_prediction_styles: make_suggestion_styles(
17652 cx.app,
17653 ),
17654 ..EditorStyle::default()
17655 },
17656 ))
17657 .into_any_element()
17658 }
17659 }),
17660 priority: 0,
17661 }],
17662 Some(Autoscroll::fit()),
17663 cx,
17664 )[0];
17665 this.pending_rename = Some(RenameState {
17666 range,
17667 old_name,
17668 editor: rename_editor,
17669 block_id,
17670 });
17671 })?;
17672 }
17673
17674 Ok(())
17675 }))
17676 }
17677
17678 pub fn confirm_rename(
17679 &mut self,
17680 _: &ConfirmRename,
17681 window: &mut Window,
17682 cx: &mut Context<Self>,
17683 ) -> Option<Task<Result<()>>> {
17684 let rename = self.take_rename(false, window, cx)?;
17685 let workspace = self.workspace()?.downgrade();
17686 let (buffer, start) = self
17687 .buffer
17688 .read(cx)
17689 .text_anchor_for_position(rename.range.start, cx)?;
17690 let (end_buffer, _) = self
17691 .buffer
17692 .read(cx)
17693 .text_anchor_for_position(rename.range.end, cx)?;
17694 if buffer != end_buffer {
17695 return None;
17696 }
17697
17698 let old_name = rename.old_name;
17699 let new_name = rename.editor.read(cx).text(cx);
17700
17701 let rename = self.semantics_provider.as_ref()?.perform_rename(
17702 &buffer,
17703 start,
17704 new_name.clone(),
17705 cx,
17706 )?;
17707
17708 Some(cx.spawn_in(window, async move |editor, cx| {
17709 let project_transaction = rename.await?;
17710 Self::open_project_transaction(
17711 &editor,
17712 workspace,
17713 project_transaction,
17714 format!("Rename: {} → {}", old_name, new_name),
17715 cx,
17716 )
17717 .await?;
17718
17719 editor.update(cx, |editor, cx| {
17720 editor.refresh_document_highlights(cx);
17721 })?;
17722 Ok(())
17723 }))
17724 }
17725
17726 fn take_rename(
17727 &mut self,
17728 moving_cursor: bool,
17729 window: &mut Window,
17730 cx: &mut Context<Self>,
17731 ) -> Option<RenameState> {
17732 let rename = self.pending_rename.take()?;
17733 if rename.editor.focus_handle(cx).is_focused(window) {
17734 window.focus(&self.focus_handle);
17735 }
17736
17737 self.remove_blocks(
17738 [rename.block_id].into_iter().collect(),
17739 Some(Autoscroll::fit()),
17740 cx,
17741 );
17742 self.clear_highlights::<Rename>(cx);
17743 self.show_local_selections = true;
17744
17745 if moving_cursor {
17746 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17747 editor
17748 .selections
17749 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17750 .head()
17751 });
17752
17753 // Update the selection to match the position of the selection inside
17754 // the rename editor.
17755 let snapshot = self.buffer.read(cx).read(cx);
17756 let rename_range = rename.range.to_offset(&snapshot);
17757 let cursor_in_editor = snapshot
17758 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17759 .min(rename_range.end);
17760 drop(snapshot);
17761
17762 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17763 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17764 });
17765 } else {
17766 self.refresh_document_highlights(cx);
17767 }
17768
17769 Some(rename)
17770 }
17771
17772 pub fn pending_rename(&self) -> Option<&RenameState> {
17773 self.pending_rename.as_ref()
17774 }
17775
17776 fn format(
17777 &mut self,
17778 _: &Format,
17779 window: &mut Window,
17780 cx: &mut Context<Self>,
17781 ) -> Option<Task<Result<()>>> {
17782 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17783
17784 let project = match &self.project {
17785 Some(project) => project.clone(),
17786 None => return None,
17787 };
17788
17789 Some(self.perform_format(
17790 project,
17791 FormatTrigger::Manual,
17792 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17793 window,
17794 cx,
17795 ))
17796 }
17797
17798 fn format_selections(
17799 &mut self,
17800 _: &FormatSelections,
17801 window: &mut Window,
17802 cx: &mut Context<Self>,
17803 ) -> Option<Task<Result<()>>> {
17804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17805
17806 let project = match &self.project {
17807 Some(project) => project.clone(),
17808 None => return None,
17809 };
17810
17811 let ranges = self
17812 .selections
17813 .all_adjusted(&self.display_snapshot(cx))
17814 .into_iter()
17815 .map(|selection| selection.range())
17816 .collect_vec();
17817
17818 Some(self.perform_format(
17819 project,
17820 FormatTrigger::Manual,
17821 FormatTarget::Ranges(ranges),
17822 window,
17823 cx,
17824 ))
17825 }
17826
17827 fn perform_format(
17828 &mut self,
17829 project: Entity<Project>,
17830 trigger: FormatTrigger,
17831 target: FormatTarget,
17832 window: &mut Window,
17833 cx: &mut Context<Self>,
17834 ) -> Task<Result<()>> {
17835 let buffer = self.buffer.clone();
17836 let (buffers, target) = match target {
17837 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17838 FormatTarget::Ranges(selection_ranges) => {
17839 let multi_buffer = buffer.read(cx);
17840 let snapshot = multi_buffer.read(cx);
17841 let mut buffers = HashSet::default();
17842 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17843 BTreeMap::new();
17844 for selection_range in selection_ranges {
17845 for (buffer, buffer_range, _) in
17846 snapshot.range_to_buffer_ranges(selection_range)
17847 {
17848 let buffer_id = buffer.remote_id();
17849 let start = buffer.anchor_before(buffer_range.start);
17850 let end = buffer.anchor_after(buffer_range.end);
17851 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17852 buffer_id_to_ranges
17853 .entry(buffer_id)
17854 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17855 .or_insert_with(|| vec![start..end]);
17856 }
17857 }
17858 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17859 }
17860 };
17861
17862 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17863 let selections_prev = transaction_id_prev
17864 .and_then(|transaction_id_prev| {
17865 // default to selections as they were after the last edit, if we have them,
17866 // instead of how they are now.
17867 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17868 // will take you back to where you made the last edit, instead of staying where you scrolled
17869 self.selection_history
17870 .transaction(transaction_id_prev)
17871 .map(|t| t.0.clone())
17872 })
17873 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17874
17875 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17876 let format = project.update(cx, |project, cx| {
17877 project.format(buffers, target, true, trigger, cx)
17878 });
17879
17880 cx.spawn_in(window, async move |editor, cx| {
17881 let transaction = futures::select_biased! {
17882 transaction = format.log_err().fuse() => transaction,
17883 () = timeout => {
17884 log::warn!("timed out waiting for formatting");
17885 None
17886 }
17887 };
17888
17889 buffer
17890 .update(cx, |buffer, cx| {
17891 if let Some(transaction) = transaction
17892 && !buffer.is_singleton()
17893 {
17894 buffer.push_transaction(&transaction.0, cx);
17895 }
17896 cx.notify();
17897 })
17898 .ok();
17899
17900 if let Some(transaction_id_now) =
17901 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17902 {
17903 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17904 if has_new_transaction {
17905 _ = editor.update(cx, |editor, _| {
17906 editor
17907 .selection_history
17908 .insert_transaction(transaction_id_now, selections_prev);
17909 });
17910 }
17911 }
17912
17913 Ok(())
17914 })
17915 }
17916
17917 fn organize_imports(
17918 &mut self,
17919 _: &OrganizeImports,
17920 window: &mut Window,
17921 cx: &mut Context<Self>,
17922 ) -> Option<Task<Result<()>>> {
17923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17924 let project = match &self.project {
17925 Some(project) => project.clone(),
17926 None => return None,
17927 };
17928 Some(self.perform_code_action_kind(
17929 project,
17930 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17931 window,
17932 cx,
17933 ))
17934 }
17935
17936 fn perform_code_action_kind(
17937 &mut self,
17938 project: Entity<Project>,
17939 kind: CodeActionKind,
17940 window: &mut Window,
17941 cx: &mut Context<Self>,
17942 ) -> Task<Result<()>> {
17943 let buffer = self.buffer.clone();
17944 let buffers = buffer.read(cx).all_buffers();
17945 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17946 let apply_action = project.update(cx, |project, cx| {
17947 project.apply_code_action_kind(buffers, kind, true, cx)
17948 });
17949 cx.spawn_in(window, async move |_, cx| {
17950 let transaction = futures::select_biased! {
17951 () = timeout => {
17952 log::warn!("timed out waiting for executing code action");
17953 None
17954 }
17955 transaction = apply_action.log_err().fuse() => transaction,
17956 };
17957 buffer
17958 .update(cx, |buffer, cx| {
17959 // check if we need this
17960 if let Some(transaction) = transaction
17961 && !buffer.is_singleton()
17962 {
17963 buffer.push_transaction(&transaction.0, cx);
17964 }
17965 cx.notify();
17966 })
17967 .ok();
17968 Ok(())
17969 })
17970 }
17971
17972 pub fn restart_language_server(
17973 &mut self,
17974 _: &RestartLanguageServer,
17975 _: &mut Window,
17976 cx: &mut Context<Self>,
17977 ) {
17978 if let Some(project) = self.project.clone() {
17979 self.buffer.update(cx, |multi_buffer, cx| {
17980 project.update(cx, |project, cx| {
17981 project.restart_language_servers_for_buffers(
17982 multi_buffer.all_buffers().into_iter().collect(),
17983 HashSet::default(),
17984 cx,
17985 );
17986 });
17987 })
17988 }
17989 }
17990
17991 pub fn stop_language_server(
17992 &mut self,
17993 _: &StopLanguageServer,
17994 _: &mut Window,
17995 cx: &mut Context<Self>,
17996 ) {
17997 if let Some(project) = self.project.clone() {
17998 self.buffer.update(cx, |multi_buffer, cx| {
17999 project.update(cx, |project, cx| {
18000 project.stop_language_servers_for_buffers(
18001 multi_buffer.all_buffers().into_iter().collect(),
18002 HashSet::default(),
18003 cx,
18004 );
18005 });
18006 });
18007 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18008 }
18009 }
18010
18011 fn cancel_language_server_work(
18012 workspace: &mut Workspace,
18013 _: &actions::CancelLanguageServerWork,
18014 _: &mut Window,
18015 cx: &mut Context<Workspace>,
18016 ) {
18017 let project = workspace.project();
18018 let buffers = workspace
18019 .active_item(cx)
18020 .and_then(|item| item.act_as::<Editor>(cx))
18021 .map_or(HashSet::default(), |editor| {
18022 editor.read(cx).buffer.read(cx).all_buffers()
18023 });
18024 project.update(cx, |project, cx| {
18025 project.cancel_language_server_work_for_buffers(buffers, cx);
18026 });
18027 }
18028
18029 fn show_character_palette(
18030 &mut self,
18031 _: &ShowCharacterPalette,
18032 window: &mut Window,
18033 _: &mut Context<Self>,
18034 ) {
18035 window.show_character_palette();
18036 }
18037
18038 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18039 if !self.diagnostics_enabled() {
18040 return;
18041 }
18042
18043 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18044 let buffer = self.buffer.read(cx).snapshot(cx);
18045 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18046 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18047 let is_valid = buffer
18048 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18049 .any(|entry| {
18050 entry.diagnostic.is_primary
18051 && !entry.range.is_empty()
18052 && entry.range.start == primary_range_start
18053 && entry.diagnostic.message == active_diagnostics.active_message
18054 });
18055
18056 if !is_valid {
18057 self.dismiss_diagnostics(cx);
18058 }
18059 }
18060 }
18061
18062 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18063 match &self.active_diagnostics {
18064 ActiveDiagnostic::Group(group) => Some(group),
18065 _ => None,
18066 }
18067 }
18068
18069 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18070 if !self.diagnostics_enabled() {
18071 return;
18072 }
18073 self.dismiss_diagnostics(cx);
18074 self.active_diagnostics = ActiveDiagnostic::All;
18075 }
18076
18077 fn activate_diagnostics(
18078 &mut self,
18079 buffer_id: BufferId,
18080 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18085 return;
18086 }
18087 self.dismiss_diagnostics(cx);
18088 let snapshot = self.snapshot(window, cx);
18089 let buffer = self.buffer.read(cx).snapshot(cx);
18090 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18091 return;
18092 };
18093
18094 let diagnostic_group = buffer
18095 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18096 .collect::<Vec<_>>();
18097
18098 let language_registry = self
18099 .project()
18100 .map(|project| project.read(cx).languages().clone());
18101
18102 let blocks = renderer.render_group(
18103 diagnostic_group,
18104 buffer_id,
18105 snapshot,
18106 cx.weak_entity(),
18107 language_registry,
18108 cx,
18109 );
18110
18111 let blocks = self.display_map.update(cx, |display_map, cx| {
18112 display_map.insert_blocks(blocks, cx).into_iter().collect()
18113 });
18114 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18115 active_range: buffer.anchor_before(diagnostic.range.start)
18116 ..buffer.anchor_after(diagnostic.range.end),
18117 active_message: diagnostic.diagnostic.message.clone(),
18118 group_id: diagnostic.diagnostic.group_id,
18119 blocks,
18120 });
18121 cx.notify();
18122 }
18123
18124 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18125 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18126 return;
18127 };
18128
18129 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18130 if let ActiveDiagnostic::Group(group) = prev {
18131 self.display_map.update(cx, |display_map, cx| {
18132 display_map.remove_blocks(group.blocks, cx);
18133 });
18134 cx.notify();
18135 }
18136 }
18137
18138 /// Disable inline diagnostics rendering for this editor.
18139 pub fn disable_inline_diagnostics(&mut self) {
18140 self.inline_diagnostics_enabled = false;
18141 self.inline_diagnostics_update = Task::ready(());
18142 self.inline_diagnostics.clear();
18143 }
18144
18145 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18146 self.diagnostics_enabled = false;
18147 self.dismiss_diagnostics(cx);
18148 self.inline_diagnostics_update = Task::ready(());
18149 self.inline_diagnostics.clear();
18150 }
18151
18152 pub fn disable_word_completions(&mut self) {
18153 self.word_completions_enabled = false;
18154 }
18155
18156 pub fn diagnostics_enabled(&self) -> bool {
18157 self.diagnostics_enabled && self.mode.is_full()
18158 }
18159
18160 pub fn inline_diagnostics_enabled(&self) -> bool {
18161 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18162 }
18163
18164 pub fn show_inline_diagnostics(&self) -> bool {
18165 self.show_inline_diagnostics
18166 }
18167
18168 pub fn toggle_inline_diagnostics(
18169 &mut self,
18170 _: &ToggleInlineDiagnostics,
18171 window: &mut Window,
18172 cx: &mut Context<Editor>,
18173 ) {
18174 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18175 self.refresh_inline_diagnostics(false, window, cx);
18176 }
18177
18178 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18179 self.diagnostics_max_severity = severity;
18180 self.display_map.update(cx, |display_map, _| {
18181 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18182 });
18183 }
18184
18185 pub fn toggle_diagnostics(
18186 &mut self,
18187 _: &ToggleDiagnostics,
18188 window: &mut Window,
18189 cx: &mut Context<Editor>,
18190 ) {
18191 if !self.diagnostics_enabled() {
18192 return;
18193 }
18194
18195 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18196 EditorSettings::get_global(cx)
18197 .diagnostics_max_severity
18198 .filter(|severity| severity != &DiagnosticSeverity::Off)
18199 .unwrap_or(DiagnosticSeverity::Hint)
18200 } else {
18201 DiagnosticSeverity::Off
18202 };
18203 self.set_max_diagnostics_severity(new_severity, cx);
18204 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18205 self.active_diagnostics = ActiveDiagnostic::None;
18206 self.inline_diagnostics_update = Task::ready(());
18207 self.inline_diagnostics.clear();
18208 } else {
18209 self.refresh_inline_diagnostics(false, window, cx);
18210 }
18211
18212 cx.notify();
18213 }
18214
18215 pub fn toggle_minimap(
18216 &mut self,
18217 _: &ToggleMinimap,
18218 window: &mut Window,
18219 cx: &mut Context<Editor>,
18220 ) {
18221 if self.supports_minimap(cx) {
18222 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18223 }
18224 }
18225
18226 fn refresh_inline_diagnostics(
18227 &mut self,
18228 debounce: bool,
18229 window: &mut Window,
18230 cx: &mut Context<Self>,
18231 ) {
18232 let max_severity = ProjectSettings::get_global(cx)
18233 .diagnostics
18234 .inline
18235 .max_severity
18236 .unwrap_or(self.diagnostics_max_severity);
18237
18238 if !self.inline_diagnostics_enabled()
18239 || !self.diagnostics_enabled()
18240 || !self.show_inline_diagnostics
18241 || max_severity == DiagnosticSeverity::Off
18242 {
18243 self.inline_diagnostics_update = Task::ready(());
18244 self.inline_diagnostics.clear();
18245 return;
18246 }
18247
18248 let debounce_ms = ProjectSettings::get_global(cx)
18249 .diagnostics
18250 .inline
18251 .update_debounce_ms;
18252 let debounce = if debounce && debounce_ms > 0 {
18253 Some(Duration::from_millis(debounce_ms))
18254 } else {
18255 None
18256 };
18257 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18258 if let Some(debounce) = debounce {
18259 cx.background_executor().timer(debounce).await;
18260 }
18261 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18262 editor
18263 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18264 .ok()
18265 }) else {
18266 return;
18267 };
18268
18269 let new_inline_diagnostics = cx
18270 .background_spawn(async move {
18271 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18272 for diagnostic_entry in
18273 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18274 {
18275 let message = diagnostic_entry
18276 .diagnostic
18277 .message
18278 .split_once('\n')
18279 .map(|(line, _)| line)
18280 .map(SharedString::new)
18281 .unwrap_or_else(|| {
18282 SharedString::new(&*diagnostic_entry.diagnostic.message)
18283 });
18284 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18285 let (Ok(i) | Err(i)) = inline_diagnostics
18286 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18287 inline_diagnostics.insert(
18288 i,
18289 (
18290 start_anchor,
18291 InlineDiagnostic {
18292 message,
18293 group_id: diagnostic_entry.diagnostic.group_id,
18294 start: diagnostic_entry.range.start.to_point(&snapshot),
18295 is_primary: diagnostic_entry.diagnostic.is_primary,
18296 severity: diagnostic_entry.diagnostic.severity,
18297 },
18298 ),
18299 );
18300 }
18301 inline_diagnostics
18302 })
18303 .await;
18304
18305 editor
18306 .update(cx, |editor, cx| {
18307 editor.inline_diagnostics = new_inline_diagnostics;
18308 cx.notify();
18309 })
18310 .ok();
18311 });
18312 }
18313
18314 fn pull_diagnostics(
18315 &mut self,
18316 buffer_id: Option<BufferId>,
18317 window: &Window,
18318 cx: &mut Context<Self>,
18319 ) -> Option<()> {
18320 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18321 return None;
18322 }
18323 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18324 .diagnostics
18325 .lsp_pull_diagnostics;
18326 if !pull_diagnostics_settings.enabled {
18327 return None;
18328 }
18329 let project = self.project()?.downgrade();
18330 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18331 let mut buffers = self.buffer.read(cx).all_buffers();
18332 buffers.retain(|buffer| {
18333 let buffer_id_to_retain = buffer.read(cx).remote_id();
18334 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18335 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18336 });
18337 if buffers.is_empty() {
18338 self.pull_diagnostics_task = Task::ready(());
18339 return None;
18340 }
18341
18342 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18343 cx.background_executor().timer(debounce).await;
18344
18345 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18346 buffers
18347 .into_iter()
18348 .filter_map(|buffer| {
18349 project
18350 .update(cx, |project, cx| {
18351 project.lsp_store().update(cx, |lsp_store, cx| {
18352 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18353 })
18354 })
18355 .ok()
18356 })
18357 .collect::<FuturesUnordered<_>>()
18358 }) else {
18359 return;
18360 };
18361
18362 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18363 match pull_task {
18364 Ok(()) => {
18365 if editor
18366 .update_in(cx, |editor, window, cx| {
18367 editor.update_diagnostics_state(window, cx);
18368 })
18369 .is_err()
18370 {
18371 return;
18372 }
18373 }
18374 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18375 }
18376 }
18377 });
18378
18379 Some(())
18380 }
18381
18382 pub fn set_selections_from_remote(
18383 &mut self,
18384 selections: Vec<Selection<Anchor>>,
18385 pending_selection: Option<Selection<Anchor>>,
18386 window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) {
18389 let old_cursor_position = self.selections.newest_anchor().head();
18390 self.selections
18391 .change_with(&self.display_snapshot(cx), |s| {
18392 s.select_anchors(selections);
18393 if let Some(pending_selection) = pending_selection {
18394 s.set_pending(pending_selection, SelectMode::Character);
18395 } else {
18396 s.clear_pending();
18397 }
18398 });
18399 self.selections_did_change(
18400 false,
18401 &old_cursor_position,
18402 SelectionEffects::default(),
18403 window,
18404 cx,
18405 );
18406 }
18407
18408 pub fn transact(
18409 &mut self,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18413 ) -> Option<TransactionId> {
18414 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18415 this.start_transaction_at(Instant::now(), window, cx);
18416 update(this, window, cx);
18417 this.end_transaction_at(Instant::now(), cx)
18418 })
18419 }
18420
18421 pub fn start_transaction_at(
18422 &mut self,
18423 now: Instant,
18424 window: &mut Window,
18425 cx: &mut Context<Self>,
18426 ) -> Option<TransactionId> {
18427 self.end_selection(window, cx);
18428 if let Some(tx_id) = self
18429 .buffer
18430 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18431 {
18432 self.selection_history
18433 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18434 cx.emit(EditorEvent::TransactionBegun {
18435 transaction_id: tx_id,
18436 });
18437 Some(tx_id)
18438 } else {
18439 None
18440 }
18441 }
18442
18443 pub fn end_transaction_at(
18444 &mut self,
18445 now: Instant,
18446 cx: &mut Context<Self>,
18447 ) -> Option<TransactionId> {
18448 if let Some(transaction_id) = self
18449 .buffer
18450 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18451 {
18452 if let Some((_, end_selections)) =
18453 self.selection_history.transaction_mut(transaction_id)
18454 {
18455 *end_selections = Some(self.selections.disjoint_anchors_arc());
18456 } else {
18457 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18458 }
18459
18460 cx.emit(EditorEvent::Edited { transaction_id });
18461 Some(transaction_id)
18462 } else {
18463 None
18464 }
18465 }
18466
18467 pub fn modify_transaction_selection_history(
18468 &mut self,
18469 transaction_id: TransactionId,
18470 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18471 ) -> bool {
18472 self.selection_history
18473 .transaction_mut(transaction_id)
18474 .map(modify)
18475 .is_some()
18476 }
18477
18478 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18479 if self.selection_mark_mode {
18480 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18481 s.move_with(|_, sel| {
18482 sel.collapse_to(sel.head(), SelectionGoal::None);
18483 });
18484 })
18485 }
18486 self.selection_mark_mode = true;
18487 cx.notify();
18488 }
18489
18490 pub fn swap_selection_ends(
18491 &mut self,
18492 _: &actions::SwapSelectionEnds,
18493 window: &mut Window,
18494 cx: &mut Context<Self>,
18495 ) {
18496 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18497 s.move_with(|_, sel| {
18498 if sel.start != sel.end {
18499 sel.reversed = !sel.reversed
18500 }
18501 });
18502 });
18503 self.request_autoscroll(Autoscroll::newest(), cx);
18504 cx.notify();
18505 }
18506
18507 pub fn toggle_focus(
18508 workspace: &mut Workspace,
18509 _: &actions::ToggleFocus,
18510 window: &mut Window,
18511 cx: &mut Context<Workspace>,
18512 ) {
18513 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18514 return;
18515 };
18516 workspace.activate_item(&item, true, true, window, cx);
18517 }
18518
18519 pub fn toggle_fold(
18520 &mut self,
18521 _: &actions::ToggleFold,
18522 window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18527 let selection = self.selections.newest::<Point>(&display_map);
18528
18529 let range = if selection.is_empty() {
18530 let point = selection.head().to_display_point(&display_map);
18531 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18532 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18533 .to_point(&display_map);
18534 start..end
18535 } else {
18536 selection.range()
18537 };
18538 if display_map.folds_in_range(range).next().is_some() {
18539 self.unfold_lines(&Default::default(), window, cx)
18540 } else {
18541 self.fold(&Default::default(), window, cx)
18542 }
18543 } else {
18544 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18545 let buffer_ids: HashSet<_> = self
18546 .selections
18547 .disjoint_anchor_ranges()
18548 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18549 .collect();
18550
18551 let should_unfold = buffer_ids
18552 .iter()
18553 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18554
18555 for buffer_id in buffer_ids {
18556 if should_unfold {
18557 self.unfold_buffer(buffer_id, cx);
18558 } else {
18559 self.fold_buffer(buffer_id, cx);
18560 }
18561 }
18562 }
18563 }
18564
18565 pub fn toggle_fold_recursive(
18566 &mut self,
18567 _: &actions::ToggleFoldRecursive,
18568 window: &mut Window,
18569 cx: &mut Context<Self>,
18570 ) {
18571 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18572
18573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18574 let range = if selection.is_empty() {
18575 let point = selection.head().to_display_point(&display_map);
18576 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18577 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18578 .to_point(&display_map);
18579 start..end
18580 } else {
18581 selection.range()
18582 };
18583 if display_map.folds_in_range(range).next().is_some() {
18584 self.unfold_recursive(&Default::default(), window, cx)
18585 } else {
18586 self.fold_recursive(&Default::default(), window, cx)
18587 }
18588 }
18589
18590 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18591 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18592 let mut to_fold = Vec::new();
18593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18594 let selections = self.selections.all_adjusted(&display_map);
18595
18596 for selection in selections {
18597 let range = selection.range().sorted();
18598 let buffer_start_row = range.start.row;
18599
18600 if range.start.row != range.end.row {
18601 let mut found = false;
18602 let mut row = range.start.row;
18603 while row <= range.end.row {
18604 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18605 {
18606 found = true;
18607 row = crease.range().end.row + 1;
18608 to_fold.push(crease);
18609 } else {
18610 row += 1
18611 }
18612 }
18613 if found {
18614 continue;
18615 }
18616 }
18617
18618 for row in (0..=range.start.row).rev() {
18619 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18620 && crease.range().end.row >= buffer_start_row
18621 {
18622 to_fold.push(crease);
18623 if row <= range.start.row {
18624 break;
18625 }
18626 }
18627 }
18628 }
18629
18630 self.fold_creases(to_fold, true, window, cx);
18631 } else {
18632 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18633 let buffer_ids = self
18634 .selections
18635 .disjoint_anchor_ranges()
18636 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18637 .collect::<HashSet<_>>();
18638 for buffer_id in buffer_ids {
18639 self.fold_buffer(buffer_id, cx);
18640 }
18641 }
18642 }
18643
18644 pub fn toggle_fold_all(
18645 &mut self,
18646 _: &actions::ToggleFoldAll,
18647 window: &mut Window,
18648 cx: &mut Context<Self>,
18649 ) {
18650 if self.buffer.read(cx).is_singleton() {
18651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18652 let has_folds = display_map
18653 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18654 .next()
18655 .is_some();
18656
18657 if has_folds {
18658 self.unfold_all(&actions::UnfoldAll, window, cx);
18659 } else {
18660 self.fold_all(&actions::FoldAll, window, cx);
18661 }
18662 } else {
18663 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18664 let should_unfold = buffer_ids
18665 .iter()
18666 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18667
18668 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18669 editor
18670 .update_in(cx, |editor, _, cx| {
18671 for buffer_id in buffer_ids {
18672 if should_unfold {
18673 editor.unfold_buffer(buffer_id, cx);
18674 } else {
18675 editor.fold_buffer(buffer_id, cx);
18676 }
18677 }
18678 })
18679 .ok();
18680 });
18681 }
18682 }
18683
18684 fn fold_at_level(
18685 &mut self,
18686 fold_at: &FoldAtLevel,
18687 window: &mut Window,
18688 cx: &mut Context<Self>,
18689 ) {
18690 if !self.buffer.read(cx).is_singleton() {
18691 return;
18692 }
18693
18694 let fold_at_level = fold_at.0;
18695 let snapshot = self.buffer.read(cx).snapshot(cx);
18696 let mut to_fold = Vec::new();
18697 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18698
18699 let row_ranges_to_keep: Vec<Range<u32>> = self
18700 .selections
18701 .all::<Point>(&self.display_snapshot(cx))
18702 .into_iter()
18703 .map(|sel| sel.start.row..sel.end.row)
18704 .collect();
18705
18706 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18707 while start_row < end_row {
18708 match self
18709 .snapshot(window, cx)
18710 .crease_for_buffer_row(MultiBufferRow(start_row))
18711 {
18712 Some(crease) => {
18713 let nested_start_row = crease.range().start.row + 1;
18714 let nested_end_row = crease.range().end.row;
18715
18716 if current_level < fold_at_level {
18717 stack.push((nested_start_row, nested_end_row, current_level + 1));
18718 } else if current_level == fold_at_level {
18719 // Fold iff there is no selection completely contained within the fold region
18720 if !row_ranges_to_keep.iter().any(|selection| {
18721 selection.end >= nested_start_row
18722 && selection.start <= nested_end_row
18723 }) {
18724 to_fold.push(crease);
18725 }
18726 }
18727
18728 start_row = nested_end_row + 1;
18729 }
18730 None => start_row += 1,
18731 }
18732 }
18733 }
18734
18735 self.fold_creases(to_fold, true, window, cx);
18736 }
18737
18738 pub fn fold_at_level_1(
18739 &mut self,
18740 _: &actions::FoldAtLevel1,
18741 window: &mut Window,
18742 cx: &mut Context<Self>,
18743 ) {
18744 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18745 }
18746
18747 pub fn fold_at_level_2(
18748 &mut self,
18749 _: &actions::FoldAtLevel2,
18750 window: &mut Window,
18751 cx: &mut Context<Self>,
18752 ) {
18753 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18754 }
18755
18756 pub fn fold_at_level_3(
18757 &mut self,
18758 _: &actions::FoldAtLevel3,
18759 window: &mut Window,
18760 cx: &mut Context<Self>,
18761 ) {
18762 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18763 }
18764
18765 pub fn fold_at_level_4(
18766 &mut self,
18767 _: &actions::FoldAtLevel4,
18768 window: &mut Window,
18769 cx: &mut Context<Self>,
18770 ) {
18771 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18772 }
18773
18774 pub fn fold_at_level_5(
18775 &mut self,
18776 _: &actions::FoldAtLevel5,
18777 window: &mut Window,
18778 cx: &mut Context<Self>,
18779 ) {
18780 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18781 }
18782
18783 pub fn fold_at_level_6(
18784 &mut self,
18785 _: &actions::FoldAtLevel6,
18786 window: &mut Window,
18787 cx: &mut Context<Self>,
18788 ) {
18789 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18790 }
18791
18792 pub fn fold_at_level_7(
18793 &mut self,
18794 _: &actions::FoldAtLevel7,
18795 window: &mut Window,
18796 cx: &mut Context<Self>,
18797 ) {
18798 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18799 }
18800
18801 pub fn fold_at_level_8(
18802 &mut self,
18803 _: &actions::FoldAtLevel8,
18804 window: &mut Window,
18805 cx: &mut Context<Self>,
18806 ) {
18807 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18808 }
18809
18810 pub fn fold_at_level_9(
18811 &mut self,
18812 _: &actions::FoldAtLevel9,
18813 window: &mut Window,
18814 cx: &mut Context<Self>,
18815 ) {
18816 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18817 }
18818
18819 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18820 if self.buffer.read(cx).is_singleton() {
18821 let mut fold_ranges = Vec::new();
18822 let snapshot = self.buffer.read(cx).snapshot(cx);
18823
18824 for row in 0..snapshot.max_row().0 {
18825 if let Some(foldable_range) = self
18826 .snapshot(window, cx)
18827 .crease_for_buffer_row(MultiBufferRow(row))
18828 {
18829 fold_ranges.push(foldable_range);
18830 }
18831 }
18832
18833 self.fold_creases(fold_ranges, true, window, cx);
18834 } else {
18835 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18836 editor
18837 .update_in(cx, |editor, _, cx| {
18838 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18839 editor.fold_buffer(buffer_id, cx);
18840 }
18841 })
18842 .ok();
18843 });
18844 }
18845 }
18846
18847 pub fn fold_function_bodies(
18848 &mut self,
18849 _: &actions::FoldFunctionBodies,
18850 window: &mut Window,
18851 cx: &mut Context<Self>,
18852 ) {
18853 let snapshot = self.buffer.read(cx).snapshot(cx);
18854
18855 let ranges = snapshot
18856 .text_object_ranges(
18857 MultiBufferOffset(0)..snapshot.len(),
18858 TreeSitterOptions::default(),
18859 )
18860 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18861 .collect::<Vec<_>>();
18862
18863 let creases = ranges
18864 .into_iter()
18865 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18866 .collect();
18867
18868 self.fold_creases(creases, true, window, cx);
18869 }
18870
18871 pub fn fold_recursive(
18872 &mut self,
18873 _: &actions::FoldRecursive,
18874 window: &mut Window,
18875 cx: &mut Context<Self>,
18876 ) {
18877 let mut to_fold = Vec::new();
18878 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18879 let selections = self.selections.all_adjusted(&display_map);
18880
18881 for selection in selections {
18882 let range = selection.range().sorted();
18883 let buffer_start_row = range.start.row;
18884
18885 if range.start.row != range.end.row {
18886 let mut found = false;
18887 for row in range.start.row..=range.end.row {
18888 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18889 found = true;
18890 to_fold.push(crease);
18891 }
18892 }
18893 if found {
18894 continue;
18895 }
18896 }
18897
18898 for row in (0..=range.start.row).rev() {
18899 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18900 if crease.range().end.row >= buffer_start_row {
18901 to_fold.push(crease);
18902 } else {
18903 break;
18904 }
18905 }
18906 }
18907 }
18908
18909 self.fold_creases(to_fold, true, window, cx);
18910 }
18911
18912 pub fn fold_at(
18913 &mut self,
18914 buffer_row: MultiBufferRow,
18915 window: &mut Window,
18916 cx: &mut Context<Self>,
18917 ) {
18918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18919
18920 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18921 let autoscroll = self
18922 .selections
18923 .all::<Point>(&display_map)
18924 .iter()
18925 .any(|selection| crease.range().overlaps(&selection.range()));
18926
18927 self.fold_creases(vec![crease], autoscroll, window, cx);
18928 }
18929 }
18930
18931 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18932 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18934 let buffer = display_map.buffer_snapshot();
18935 let selections = self.selections.all::<Point>(&display_map);
18936 let ranges = selections
18937 .iter()
18938 .map(|s| {
18939 let range = s.display_range(&display_map).sorted();
18940 let mut start = range.start.to_point(&display_map);
18941 let mut end = range.end.to_point(&display_map);
18942 start.column = 0;
18943 end.column = buffer.line_len(MultiBufferRow(end.row));
18944 start..end
18945 })
18946 .collect::<Vec<_>>();
18947
18948 self.unfold_ranges(&ranges, true, true, cx);
18949 } else {
18950 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18951 let buffer_ids = self
18952 .selections
18953 .disjoint_anchor_ranges()
18954 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18955 .collect::<HashSet<_>>();
18956 for buffer_id in buffer_ids {
18957 self.unfold_buffer(buffer_id, cx);
18958 }
18959 }
18960 }
18961
18962 pub fn unfold_recursive(
18963 &mut self,
18964 _: &UnfoldRecursive,
18965 _window: &mut Window,
18966 cx: &mut Context<Self>,
18967 ) {
18968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18969 let selections = self.selections.all::<Point>(&display_map);
18970 let ranges = selections
18971 .iter()
18972 .map(|s| {
18973 let mut range = s.display_range(&display_map).sorted();
18974 *range.start.column_mut() = 0;
18975 *range.end.column_mut() = display_map.line_len(range.end.row());
18976 let start = range.start.to_point(&display_map);
18977 let end = range.end.to_point(&display_map);
18978 start..end
18979 })
18980 .collect::<Vec<_>>();
18981
18982 self.unfold_ranges(&ranges, true, true, cx);
18983 }
18984
18985 pub fn unfold_at(
18986 &mut self,
18987 buffer_row: MultiBufferRow,
18988 _window: &mut Window,
18989 cx: &mut Context<Self>,
18990 ) {
18991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18992
18993 let intersection_range = Point::new(buffer_row.0, 0)
18994 ..Point::new(
18995 buffer_row.0,
18996 display_map.buffer_snapshot().line_len(buffer_row),
18997 );
18998
18999 let autoscroll = self
19000 .selections
19001 .all::<Point>(&display_map)
19002 .iter()
19003 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19004
19005 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19006 }
19007
19008 pub fn unfold_all(
19009 &mut self,
19010 _: &actions::UnfoldAll,
19011 _window: &mut Window,
19012 cx: &mut Context<Self>,
19013 ) {
19014 if self.buffer.read(cx).is_singleton() {
19015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19016 self.unfold_ranges(
19017 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19018 true,
19019 true,
19020 cx,
19021 );
19022 } else {
19023 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19024 editor
19025 .update(cx, |editor, cx| {
19026 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19027 editor.unfold_buffer(buffer_id, cx);
19028 }
19029 })
19030 .ok();
19031 });
19032 }
19033 }
19034
19035 pub fn fold_selected_ranges(
19036 &mut self,
19037 _: &FoldSelectedRanges,
19038 window: &mut Window,
19039 cx: &mut Context<Self>,
19040 ) {
19041 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19042 let selections = self.selections.all_adjusted(&display_map);
19043 let ranges = selections
19044 .into_iter()
19045 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19046 .collect::<Vec<_>>();
19047 self.fold_creases(ranges, true, window, cx);
19048 }
19049
19050 pub fn fold_ranges<T: ToOffset + Clone>(
19051 &mut self,
19052 ranges: Vec<Range<T>>,
19053 auto_scroll: bool,
19054 window: &mut Window,
19055 cx: &mut Context<Self>,
19056 ) {
19057 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19058 let ranges = ranges
19059 .into_iter()
19060 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19061 .collect::<Vec<_>>();
19062 self.fold_creases(ranges, auto_scroll, window, cx);
19063 }
19064
19065 pub fn fold_creases<T: ToOffset + Clone>(
19066 &mut self,
19067 creases: Vec<Crease<T>>,
19068 auto_scroll: bool,
19069 _window: &mut Window,
19070 cx: &mut Context<Self>,
19071 ) {
19072 if creases.is_empty() {
19073 return;
19074 }
19075
19076 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19077
19078 if auto_scroll {
19079 self.request_autoscroll(Autoscroll::fit(), cx);
19080 }
19081
19082 cx.notify();
19083
19084 self.scrollbar_marker_state.dirty = true;
19085 self.folds_did_change(cx);
19086 }
19087
19088 /// Removes any folds whose ranges intersect any of the given ranges.
19089 pub fn unfold_ranges<T: ToOffset + Clone>(
19090 &mut self,
19091 ranges: &[Range<T>],
19092 inclusive: bool,
19093 auto_scroll: bool,
19094 cx: &mut Context<Self>,
19095 ) {
19096 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19097 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19098 });
19099 self.folds_did_change(cx);
19100 }
19101
19102 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19103 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19104 return;
19105 }
19106
19107 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19108 self.display_map.update(cx, |display_map, cx| {
19109 display_map.fold_buffers([buffer_id], cx)
19110 });
19111
19112 let snapshot = self.display_snapshot(cx);
19113 self.selections.change_with(&snapshot, |selections| {
19114 selections.remove_selections_from_buffer(buffer_id);
19115 });
19116
19117 cx.emit(EditorEvent::BufferFoldToggled {
19118 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19119 folded: true,
19120 });
19121 cx.notify();
19122 }
19123
19124 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19125 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19126 return;
19127 }
19128 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19129 self.display_map.update(cx, |display_map, cx| {
19130 display_map.unfold_buffers([buffer_id], cx);
19131 });
19132 cx.emit(EditorEvent::BufferFoldToggled {
19133 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19134 folded: false,
19135 });
19136 cx.notify();
19137 }
19138
19139 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19140 self.display_map.read(cx).is_buffer_folded(buffer)
19141 }
19142
19143 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19144 self.display_map.read(cx).folded_buffers()
19145 }
19146
19147 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19148 self.display_map.update(cx, |display_map, cx| {
19149 display_map.disable_header_for_buffer(buffer_id, cx);
19150 });
19151 cx.notify();
19152 }
19153
19154 /// Removes any folds with the given ranges.
19155 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19156 &mut self,
19157 ranges: &[Range<T>],
19158 type_id: TypeId,
19159 auto_scroll: bool,
19160 cx: &mut Context<Self>,
19161 ) {
19162 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19163 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19164 });
19165 self.folds_did_change(cx);
19166 }
19167
19168 fn remove_folds_with<T: ToOffset + Clone>(
19169 &mut self,
19170 ranges: &[Range<T>],
19171 auto_scroll: bool,
19172 cx: &mut Context<Self>,
19173 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19174 ) {
19175 if ranges.is_empty() {
19176 return;
19177 }
19178
19179 let mut buffers_affected = HashSet::default();
19180 let multi_buffer = self.buffer().read(cx);
19181 for range in ranges {
19182 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19183 buffers_affected.insert(buffer.read(cx).remote_id());
19184 };
19185 }
19186
19187 self.display_map.update(cx, update);
19188
19189 if auto_scroll {
19190 self.request_autoscroll(Autoscroll::fit(), cx);
19191 }
19192
19193 cx.notify();
19194 self.scrollbar_marker_state.dirty = true;
19195 self.active_indent_guides_state.dirty = true;
19196 }
19197
19198 pub fn update_renderer_widths(
19199 &mut self,
19200 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19201 cx: &mut Context<Self>,
19202 ) -> bool {
19203 self.display_map
19204 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19205 }
19206
19207 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19208 self.display_map.read(cx).fold_placeholder.clone()
19209 }
19210
19211 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19212 self.use_base_text_line_numbers = show;
19213 }
19214
19215 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19216 self.buffer.update(cx, |buffer, cx| {
19217 buffer.set_all_diff_hunks_expanded(cx);
19218 });
19219 }
19220
19221 pub fn expand_all_diff_hunks(
19222 &mut self,
19223 _: &ExpandAllDiffHunks,
19224 _window: &mut Window,
19225 cx: &mut Context<Self>,
19226 ) {
19227 self.buffer.update(cx, |buffer, cx| {
19228 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19229 });
19230 }
19231
19232 pub fn collapse_all_diff_hunks(
19233 &mut self,
19234 _: &CollapseAllDiffHunks,
19235 _window: &mut Window,
19236 cx: &mut Context<Self>,
19237 ) {
19238 self.buffer.update(cx, |buffer, cx| {
19239 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19240 });
19241 }
19242
19243 pub fn toggle_selected_diff_hunks(
19244 &mut self,
19245 _: &ToggleSelectedDiffHunks,
19246 _window: &mut Window,
19247 cx: &mut Context<Self>,
19248 ) {
19249 let ranges: Vec<_> = self
19250 .selections
19251 .disjoint_anchors()
19252 .iter()
19253 .map(|s| s.range())
19254 .collect();
19255 self.toggle_diff_hunks_in_ranges(ranges, cx);
19256 }
19257
19258 pub fn diff_hunks_in_ranges<'a>(
19259 &'a self,
19260 ranges: &'a [Range<Anchor>],
19261 buffer: &'a MultiBufferSnapshot,
19262 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19263 ranges.iter().flat_map(move |range| {
19264 let end_excerpt_id = range.end.excerpt_id;
19265 let range = range.to_point(buffer);
19266 let mut peek_end = range.end;
19267 if range.end.row < buffer.max_row().0 {
19268 peek_end = Point::new(range.end.row + 1, 0);
19269 }
19270 buffer
19271 .diff_hunks_in_range(range.start..peek_end)
19272 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19273 })
19274 }
19275
19276 pub fn has_stageable_diff_hunks_in_ranges(
19277 &self,
19278 ranges: &[Range<Anchor>],
19279 snapshot: &MultiBufferSnapshot,
19280 ) -> bool {
19281 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19282 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19283 }
19284
19285 pub fn toggle_staged_selected_diff_hunks(
19286 &mut self,
19287 _: &::git::ToggleStaged,
19288 _: &mut Window,
19289 cx: &mut Context<Self>,
19290 ) {
19291 let snapshot = self.buffer.read(cx).snapshot(cx);
19292 let ranges: Vec<_> = self
19293 .selections
19294 .disjoint_anchors()
19295 .iter()
19296 .map(|s| s.range())
19297 .collect();
19298 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19299 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19300 }
19301
19302 pub fn set_render_diff_hunk_controls(
19303 &mut self,
19304 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19305 cx: &mut Context<Self>,
19306 ) {
19307 self.render_diff_hunk_controls = render_diff_hunk_controls;
19308 cx.notify();
19309 }
19310
19311 pub fn stage_and_next(
19312 &mut self,
19313 _: &::git::StageAndNext,
19314 window: &mut Window,
19315 cx: &mut Context<Self>,
19316 ) {
19317 self.do_stage_or_unstage_and_next(true, window, cx);
19318 }
19319
19320 pub fn unstage_and_next(
19321 &mut self,
19322 _: &::git::UnstageAndNext,
19323 window: &mut Window,
19324 cx: &mut Context<Self>,
19325 ) {
19326 self.do_stage_or_unstage_and_next(false, window, cx);
19327 }
19328
19329 pub fn stage_or_unstage_diff_hunks(
19330 &mut self,
19331 stage: bool,
19332 ranges: Vec<Range<Anchor>>,
19333 cx: &mut Context<Self>,
19334 ) {
19335 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19336 cx.spawn(async move |this, cx| {
19337 task.await?;
19338 this.update(cx, |this, cx| {
19339 let snapshot = this.buffer.read(cx).snapshot(cx);
19340 let chunk_by = this
19341 .diff_hunks_in_ranges(&ranges, &snapshot)
19342 .chunk_by(|hunk| hunk.buffer_id);
19343 for (buffer_id, hunks) in &chunk_by {
19344 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19345 }
19346 })
19347 })
19348 .detach_and_log_err(cx);
19349 }
19350
19351 fn save_buffers_for_ranges_if_needed(
19352 &mut self,
19353 ranges: &[Range<Anchor>],
19354 cx: &mut Context<Editor>,
19355 ) -> Task<Result<()>> {
19356 let multibuffer = self.buffer.read(cx);
19357 let snapshot = multibuffer.read(cx);
19358 let buffer_ids: HashSet<_> = ranges
19359 .iter()
19360 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19361 .collect();
19362 drop(snapshot);
19363
19364 let mut buffers = HashSet::default();
19365 for buffer_id in buffer_ids {
19366 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19367 let buffer = buffer_entity.read(cx);
19368 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19369 {
19370 buffers.insert(buffer_entity);
19371 }
19372 }
19373 }
19374
19375 if let Some(project) = &self.project {
19376 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19377 } else {
19378 Task::ready(Ok(()))
19379 }
19380 }
19381
19382 fn do_stage_or_unstage_and_next(
19383 &mut self,
19384 stage: bool,
19385 window: &mut Window,
19386 cx: &mut Context<Self>,
19387 ) {
19388 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19389
19390 if ranges.iter().any(|range| range.start != range.end) {
19391 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19392 return;
19393 }
19394
19395 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19396 let snapshot = self.snapshot(window, cx);
19397 let position = self
19398 .selections
19399 .newest::<Point>(&snapshot.display_snapshot)
19400 .head();
19401 let mut row = snapshot
19402 .buffer_snapshot()
19403 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19404 .find(|hunk| hunk.row_range.start.0 > position.row)
19405 .map(|hunk| hunk.row_range.start);
19406
19407 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19408 // Outside of the project diff editor, wrap around to the beginning.
19409 if !all_diff_hunks_expanded {
19410 row = row.or_else(|| {
19411 snapshot
19412 .buffer_snapshot()
19413 .diff_hunks_in_range(Point::zero()..position)
19414 .find(|hunk| hunk.row_range.end.0 < position.row)
19415 .map(|hunk| hunk.row_range.start)
19416 });
19417 }
19418
19419 if let Some(row) = row {
19420 let destination = Point::new(row.0, 0);
19421 let autoscroll = Autoscroll::center();
19422
19423 self.unfold_ranges(&[destination..destination], false, false, cx);
19424 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19425 s.select_ranges([destination..destination]);
19426 });
19427 }
19428 }
19429
19430 fn do_stage_or_unstage(
19431 &self,
19432 stage: bool,
19433 buffer_id: BufferId,
19434 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19435 cx: &mut App,
19436 ) -> Option<()> {
19437 let project = self.project()?;
19438 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19439 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19440 let buffer_snapshot = buffer.read(cx).snapshot();
19441 let file_exists = buffer_snapshot
19442 .file()
19443 .is_some_and(|file| file.disk_state().exists());
19444 diff.update(cx, |diff, cx| {
19445 diff.stage_or_unstage_hunks(
19446 stage,
19447 &hunks
19448 .map(|hunk| buffer_diff::DiffHunk {
19449 buffer_range: hunk.buffer_range,
19450 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19451 ..hunk.diff_base_byte_range.end.0,
19452 secondary_status: hunk.secondary_status,
19453 range: Point::zero()..Point::zero(), // unused
19454 })
19455 .collect::<Vec<_>>(),
19456 &buffer_snapshot,
19457 file_exists,
19458 cx,
19459 )
19460 });
19461 None
19462 }
19463
19464 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19465 let ranges: Vec<_> = self
19466 .selections
19467 .disjoint_anchors()
19468 .iter()
19469 .map(|s| s.range())
19470 .collect();
19471 self.buffer
19472 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19473 }
19474
19475 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19476 self.buffer.update(cx, |buffer, cx| {
19477 let ranges = vec![Anchor::min()..Anchor::max()];
19478 if !buffer.all_diff_hunks_expanded()
19479 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19480 {
19481 buffer.collapse_diff_hunks(ranges, cx);
19482 true
19483 } else {
19484 false
19485 }
19486 })
19487 }
19488
19489 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19490 if self.buffer.read(cx).all_diff_hunks_expanded() {
19491 return true;
19492 }
19493 let ranges = vec![Anchor::min()..Anchor::max()];
19494 self.buffer
19495 .read(cx)
19496 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19497 }
19498
19499 fn toggle_diff_hunks_in_ranges(
19500 &mut self,
19501 ranges: Vec<Range<Anchor>>,
19502 cx: &mut Context<Editor>,
19503 ) {
19504 self.buffer.update(cx, |buffer, cx| {
19505 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19506 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19507 })
19508 }
19509
19510 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19511 self.buffer.update(cx, |buffer, cx| {
19512 let snapshot = buffer.snapshot(cx);
19513 let excerpt_id = range.end.excerpt_id;
19514 let point_range = range.to_point(&snapshot);
19515 let expand = !buffer.single_hunk_is_expanded(range, cx);
19516 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19517 })
19518 }
19519
19520 pub(crate) fn apply_all_diff_hunks(
19521 &mut self,
19522 _: &ApplyAllDiffHunks,
19523 window: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19527
19528 let buffers = self.buffer.read(cx).all_buffers();
19529 for branch_buffer in buffers {
19530 branch_buffer.update(cx, |branch_buffer, cx| {
19531 branch_buffer.merge_into_base(Vec::new(), cx);
19532 });
19533 }
19534
19535 if let Some(project) = self.project.clone() {
19536 self.save(
19537 SaveOptions {
19538 format: true,
19539 autosave: false,
19540 },
19541 project,
19542 window,
19543 cx,
19544 )
19545 .detach_and_log_err(cx);
19546 }
19547 }
19548
19549 pub(crate) fn apply_selected_diff_hunks(
19550 &mut self,
19551 _: &ApplyDiffHunk,
19552 window: &mut Window,
19553 cx: &mut Context<Self>,
19554 ) {
19555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19556 let snapshot = self.snapshot(window, cx);
19557 let hunks = snapshot.hunks_for_ranges(
19558 self.selections
19559 .all(&snapshot.display_snapshot)
19560 .into_iter()
19561 .map(|selection| selection.range()),
19562 );
19563 let mut ranges_by_buffer = HashMap::default();
19564 self.transact(window, cx, |editor, _window, cx| {
19565 for hunk in hunks {
19566 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19567 ranges_by_buffer
19568 .entry(buffer.clone())
19569 .or_insert_with(Vec::new)
19570 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19571 }
19572 }
19573
19574 for (buffer, ranges) in ranges_by_buffer {
19575 buffer.update(cx, |buffer, cx| {
19576 buffer.merge_into_base(ranges, cx);
19577 });
19578 }
19579 });
19580
19581 if let Some(project) = self.project.clone() {
19582 self.save(
19583 SaveOptions {
19584 format: true,
19585 autosave: false,
19586 },
19587 project,
19588 window,
19589 cx,
19590 )
19591 .detach_and_log_err(cx);
19592 }
19593 }
19594
19595 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19596 if hovered != self.gutter_hovered {
19597 self.gutter_hovered = hovered;
19598 cx.notify();
19599 }
19600 }
19601
19602 pub fn insert_blocks(
19603 &mut self,
19604 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19605 autoscroll: Option<Autoscroll>,
19606 cx: &mut Context<Self>,
19607 ) -> Vec<CustomBlockId> {
19608 let blocks = self
19609 .display_map
19610 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19611 if let Some(autoscroll) = autoscroll {
19612 self.request_autoscroll(autoscroll, cx);
19613 }
19614 cx.notify();
19615 blocks
19616 }
19617
19618 pub fn resize_blocks(
19619 &mut self,
19620 heights: HashMap<CustomBlockId, u32>,
19621 autoscroll: Option<Autoscroll>,
19622 cx: &mut Context<Self>,
19623 ) {
19624 self.display_map
19625 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19626 if let Some(autoscroll) = autoscroll {
19627 self.request_autoscroll(autoscroll, cx);
19628 }
19629 cx.notify();
19630 }
19631
19632 pub fn replace_blocks(
19633 &mut self,
19634 renderers: HashMap<CustomBlockId, RenderBlock>,
19635 autoscroll: Option<Autoscroll>,
19636 cx: &mut Context<Self>,
19637 ) {
19638 self.display_map
19639 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19640 if let Some(autoscroll) = autoscroll {
19641 self.request_autoscroll(autoscroll, cx);
19642 }
19643 cx.notify();
19644 }
19645
19646 pub fn remove_blocks(
19647 &mut self,
19648 block_ids: HashSet<CustomBlockId>,
19649 autoscroll: Option<Autoscroll>,
19650 cx: &mut Context<Self>,
19651 ) {
19652 self.display_map.update(cx, |display_map, cx| {
19653 display_map.remove_blocks(block_ids, cx)
19654 });
19655 if let Some(autoscroll) = autoscroll {
19656 self.request_autoscroll(autoscroll, cx);
19657 }
19658 cx.notify();
19659 }
19660
19661 pub fn row_for_block(
19662 &self,
19663 block_id: CustomBlockId,
19664 cx: &mut Context<Self>,
19665 ) -> Option<DisplayRow> {
19666 self.display_map
19667 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19668 }
19669
19670 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19671 self.focused_block = Some(focused_block);
19672 }
19673
19674 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19675 self.focused_block.take()
19676 }
19677
19678 pub fn insert_creases(
19679 &mut self,
19680 creases: impl IntoIterator<Item = Crease<Anchor>>,
19681 cx: &mut Context<Self>,
19682 ) -> Vec<CreaseId> {
19683 self.display_map
19684 .update(cx, |map, cx| map.insert_creases(creases, cx))
19685 }
19686
19687 pub fn remove_creases(
19688 &mut self,
19689 ids: impl IntoIterator<Item = CreaseId>,
19690 cx: &mut Context<Self>,
19691 ) -> Vec<(CreaseId, Range<Anchor>)> {
19692 self.display_map
19693 .update(cx, |map, cx| map.remove_creases(ids, cx))
19694 }
19695
19696 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19697 self.display_map
19698 .update(cx, |map, cx| map.snapshot(cx))
19699 .longest_row()
19700 }
19701
19702 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19703 self.display_map
19704 .update(cx, |map, cx| map.snapshot(cx))
19705 .max_point()
19706 }
19707
19708 pub fn text(&self, cx: &App) -> String {
19709 self.buffer.read(cx).read(cx).text()
19710 }
19711
19712 pub fn is_empty(&self, cx: &App) -> bool {
19713 self.buffer.read(cx).read(cx).is_empty()
19714 }
19715
19716 pub fn text_option(&self, cx: &App) -> Option<String> {
19717 let text = self.text(cx);
19718 let text = text.trim();
19719
19720 if text.is_empty() {
19721 return None;
19722 }
19723
19724 Some(text.to_string())
19725 }
19726
19727 pub fn set_text(
19728 &mut self,
19729 text: impl Into<Arc<str>>,
19730 window: &mut Window,
19731 cx: &mut Context<Self>,
19732 ) {
19733 self.transact(window, cx, |this, _, cx| {
19734 this.buffer
19735 .read(cx)
19736 .as_singleton()
19737 .expect("you can only call set_text on editors for singleton buffers")
19738 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19739 });
19740 }
19741
19742 pub fn display_text(&self, cx: &mut App) -> String {
19743 self.display_map
19744 .update(cx, |map, cx| map.snapshot(cx))
19745 .text()
19746 }
19747
19748 fn create_minimap(
19749 &self,
19750 minimap_settings: MinimapSettings,
19751 window: &mut Window,
19752 cx: &mut Context<Self>,
19753 ) -> Option<Entity<Self>> {
19754 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19755 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19756 }
19757
19758 fn initialize_new_minimap(
19759 &self,
19760 minimap_settings: MinimapSettings,
19761 window: &mut Window,
19762 cx: &mut Context<Self>,
19763 ) -> Entity<Self> {
19764 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19765
19766 let mut minimap = Editor::new_internal(
19767 EditorMode::Minimap {
19768 parent: cx.weak_entity(),
19769 },
19770 self.buffer.clone(),
19771 None,
19772 Some(self.display_map.clone()),
19773 window,
19774 cx,
19775 );
19776 minimap.scroll_manager.clone_state(&self.scroll_manager);
19777 minimap.set_text_style_refinement(TextStyleRefinement {
19778 font_size: Some(MINIMAP_FONT_SIZE),
19779 font_weight: Some(MINIMAP_FONT_WEIGHT),
19780 ..Default::default()
19781 });
19782 minimap.update_minimap_configuration(minimap_settings, cx);
19783 cx.new(|_| minimap)
19784 }
19785
19786 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19787 let current_line_highlight = minimap_settings
19788 .current_line_highlight
19789 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19790 self.set_current_line_highlight(Some(current_line_highlight));
19791 }
19792
19793 pub fn minimap(&self) -> Option<&Entity<Self>> {
19794 self.minimap
19795 .as_ref()
19796 .filter(|_| self.minimap_visibility.visible())
19797 }
19798
19799 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19800 let mut wrap_guides = smallvec![];
19801
19802 if self.show_wrap_guides == Some(false) {
19803 return wrap_guides;
19804 }
19805
19806 let settings = self.buffer.read(cx).language_settings(cx);
19807 if settings.show_wrap_guides {
19808 match self.soft_wrap_mode(cx) {
19809 SoftWrap::Column(soft_wrap) => {
19810 wrap_guides.push((soft_wrap as usize, true));
19811 }
19812 SoftWrap::Bounded(soft_wrap) => {
19813 wrap_guides.push((soft_wrap as usize, true));
19814 }
19815 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19816 }
19817 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19818 }
19819
19820 wrap_guides
19821 }
19822
19823 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19824 let settings = self.buffer.read(cx).language_settings(cx);
19825 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19826 match mode {
19827 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19828 SoftWrap::None
19829 }
19830 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19831 language_settings::SoftWrap::PreferredLineLength => {
19832 SoftWrap::Column(settings.preferred_line_length)
19833 }
19834 language_settings::SoftWrap::Bounded => {
19835 SoftWrap::Bounded(settings.preferred_line_length)
19836 }
19837 }
19838 }
19839
19840 pub fn set_soft_wrap_mode(
19841 &mut self,
19842 mode: language_settings::SoftWrap,
19843
19844 cx: &mut Context<Self>,
19845 ) {
19846 self.soft_wrap_mode_override = Some(mode);
19847 cx.notify();
19848 }
19849
19850 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19851 self.hard_wrap = hard_wrap;
19852 cx.notify();
19853 }
19854
19855 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19856 self.text_style_refinement = Some(style);
19857 }
19858
19859 /// called by the Element so we know what style we were most recently rendered with.
19860 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19861 // We intentionally do not inform the display map about the minimap style
19862 // so that wrapping is not recalculated and stays consistent for the editor
19863 // and its linked minimap.
19864 if !self.mode.is_minimap() {
19865 let font = style.text.font();
19866 let font_size = style.text.font_size.to_pixels(window.rem_size());
19867 let display_map = self
19868 .placeholder_display_map
19869 .as_ref()
19870 .filter(|_| self.is_empty(cx))
19871 .unwrap_or(&self.display_map);
19872
19873 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19874 }
19875 self.style = Some(style);
19876 }
19877
19878 pub fn style(&self) -> Option<&EditorStyle> {
19879 self.style.as_ref()
19880 }
19881
19882 // Called by the element. This method is not designed to be called outside of the editor
19883 // element's layout code because it does not notify when rewrapping is computed synchronously.
19884 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19885 if self.is_empty(cx) {
19886 self.placeholder_display_map
19887 .as_ref()
19888 .map_or(false, |display_map| {
19889 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19890 })
19891 } else {
19892 self.display_map
19893 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19894 }
19895 }
19896
19897 pub fn set_soft_wrap(&mut self) {
19898 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19899 }
19900
19901 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19902 if self.soft_wrap_mode_override.is_some() {
19903 self.soft_wrap_mode_override.take();
19904 } else {
19905 let soft_wrap = match self.soft_wrap_mode(cx) {
19906 SoftWrap::GitDiff => return,
19907 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19908 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19909 language_settings::SoftWrap::None
19910 }
19911 };
19912 self.soft_wrap_mode_override = Some(soft_wrap);
19913 }
19914 cx.notify();
19915 }
19916
19917 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19918 let Some(workspace) = self.workspace() else {
19919 return;
19920 };
19921 let fs = workspace.read(cx).app_state().fs.clone();
19922 let current_show = TabBarSettings::get_global(cx).show;
19923 update_settings_file(fs, cx, move |setting, _| {
19924 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19925 });
19926 }
19927
19928 pub fn toggle_indent_guides(
19929 &mut self,
19930 _: &ToggleIndentGuides,
19931 _: &mut Window,
19932 cx: &mut Context<Self>,
19933 ) {
19934 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19935 self.buffer
19936 .read(cx)
19937 .language_settings(cx)
19938 .indent_guides
19939 .enabled
19940 });
19941 self.show_indent_guides = Some(!currently_enabled);
19942 cx.notify();
19943 }
19944
19945 fn should_show_indent_guides(&self) -> Option<bool> {
19946 self.show_indent_guides
19947 }
19948
19949 pub fn toggle_line_numbers(
19950 &mut self,
19951 _: &ToggleLineNumbers,
19952 _: &mut Window,
19953 cx: &mut Context<Self>,
19954 ) {
19955 let mut editor_settings = EditorSettings::get_global(cx).clone();
19956 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19957 EditorSettings::override_global(editor_settings, cx);
19958 }
19959
19960 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19961 if let Some(show_line_numbers) = self.show_line_numbers {
19962 return show_line_numbers;
19963 }
19964 EditorSettings::get_global(cx).gutter.line_numbers
19965 }
19966
19967 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19968 match (
19969 self.use_relative_line_numbers,
19970 EditorSettings::get_global(cx).relative_line_numbers,
19971 ) {
19972 (None, setting) => setting,
19973 (Some(false), _) => RelativeLineNumbers::Disabled,
19974 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19975 (Some(true), _) => RelativeLineNumbers::Enabled,
19976 }
19977 }
19978
19979 pub fn toggle_relative_line_numbers(
19980 &mut self,
19981 _: &ToggleRelativeLineNumbers,
19982 _: &mut Window,
19983 cx: &mut Context<Self>,
19984 ) {
19985 let is_relative = self.relative_line_numbers(cx);
19986 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19987 }
19988
19989 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19990 self.use_relative_line_numbers = is_relative;
19991 cx.notify();
19992 }
19993
19994 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19995 self.show_gutter = show_gutter;
19996 cx.notify();
19997 }
19998
19999 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20000 self.show_scrollbars = ScrollbarAxes {
20001 horizontal: show,
20002 vertical: show,
20003 };
20004 cx.notify();
20005 }
20006
20007 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20008 self.show_scrollbars.vertical = show;
20009 cx.notify();
20010 }
20011
20012 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20013 self.show_scrollbars.horizontal = show;
20014 cx.notify();
20015 }
20016
20017 pub fn set_minimap_visibility(
20018 &mut self,
20019 minimap_visibility: MinimapVisibility,
20020 window: &mut Window,
20021 cx: &mut Context<Self>,
20022 ) {
20023 if self.minimap_visibility != minimap_visibility {
20024 if minimap_visibility.visible() && self.minimap.is_none() {
20025 let minimap_settings = EditorSettings::get_global(cx).minimap;
20026 self.minimap =
20027 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20028 }
20029 self.minimap_visibility = minimap_visibility;
20030 cx.notify();
20031 }
20032 }
20033
20034 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20035 self.set_show_scrollbars(false, cx);
20036 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20037 }
20038
20039 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20040 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20041 }
20042
20043 /// Normally the text in full mode and auto height editors is padded on the
20044 /// left side by roughly half a character width for improved hit testing.
20045 ///
20046 /// Use this method to disable this for cases where this is not wanted (e.g.
20047 /// if you want to align the editor text with some other text above or below)
20048 /// or if you want to add this padding to single-line editors.
20049 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20050 self.offset_content = offset_content;
20051 cx.notify();
20052 }
20053
20054 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20055 self.show_line_numbers = Some(show_line_numbers);
20056 cx.notify();
20057 }
20058
20059 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20060 self.disable_expand_excerpt_buttons = true;
20061 cx.notify();
20062 }
20063
20064 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20065 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20066 cx.notify();
20067 }
20068
20069 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20070 self.show_code_actions = Some(show_code_actions);
20071 cx.notify();
20072 }
20073
20074 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20075 self.show_runnables = Some(show_runnables);
20076 cx.notify();
20077 }
20078
20079 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20080 self.show_breakpoints = Some(show_breakpoints);
20081 cx.notify();
20082 }
20083
20084 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20085 if self.display_map.read(cx).masked != masked {
20086 self.display_map.update(cx, |map, _| map.masked = masked);
20087 }
20088 cx.notify()
20089 }
20090
20091 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20092 self.show_wrap_guides = Some(show_wrap_guides);
20093 cx.notify();
20094 }
20095
20096 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20097 self.show_indent_guides = Some(show_indent_guides);
20098 cx.notify();
20099 }
20100
20101 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20102 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20103 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20104 && let Some(dir) = file.abs_path(cx).parent()
20105 {
20106 return Some(dir.to_owned());
20107 }
20108 }
20109
20110 None
20111 }
20112
20113 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20114 self.active_excerpt(cx)?
20115 .1
20116 .read(cx)
20117 .file()
20118 .and_then(|f| f.as_local())
20119 }
20120
20121 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20122 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20123 let buffer = buffer.read(cx);
20124 if let Some(project_path) = buffer.project_path(cx) {
20125 let project = self.project()?.read(cx);
20126 project.absolute_path(&project_path, cx)
20127 } else {
20128 buffer
20129 .file()
20130 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20131 }
20132 })
20133 }
20134
20135 pub fn reveal_in_finder(
20136 &mut self,
20137 _: &RevealInFileManager,
20138 _window: &mut Window,
20139 cx: &mut Context<Self>,
20140 ) {
20141 if let Some(target) = self.target_file(cx) {
20142 cx.reveal_path(&target.abs_path(cx));
20143 }
20144 }
20145
20146 pub fn copy_path(
20147 &mut self,
20148 _: &zed_actions::workspace::CopyPath,
20149 _window: &mut Window,
20150 cx: &mut Context<Self>,
20151 ) {
20152 if let Some(path) = self.target_file_abs_path(cx)
20153 && let Some(path) = path.to_str()
20154 {
20155 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20156 } else {
20157 cx.propagate();
20158 }
20159 }
20160
20161 pub fn copy_relative_path(
20162 &mut self,
20163 _: &zed_actions::workspace::CopyRelativePath,
20164 _window: &mut Window,
20165 cx: &mut Context<Self>,
20166 ) {
20167 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20168 let project = self.project()?.read(cx);
20169 let path = buffer.read(cx).file()?.path();
20170 let path = path.display(project.path_style(cx));
20171 Some(path)
20172 }) {
20173 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20174 } else {
20175 cx.propagate();
20176 }
20177 }
20178
20179 /// Returns the project path for the editor's buffer, if any buffer is
20180 /// opened in the editor.
20181 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20182 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20183 buffer.read(cx).project_path(cx)
20184 } else {
20185 None
20186 }
20187 }
20188
20189 // Returns true if the editor handled a go-to-line request
20190 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20191 maybe!({
20192 let breakpoint_store = self.breakpoint_store.as_ref()?;
20193
20194 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20195 else {
20196 self.clear_row_highlights::<ActiveDebugLine>();
20197 return None;
20198 };
20199
20200 let position = active_stack_frame.position;
20201 let buffer_id = position.buffer_id?;
20202 let snapshot = self
20203 .project
20204 .as_ref()?
20205 .read(cx)
20206 .buffer_for_id(buffer_id, cx)?
20207 .read(cx)
20208 .snapshot();
20209
20210 let mut handled = false;
20211 for (id, ExcerptRange { context, .. }) in
20212 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20213 {
20214 if context.start.cmp(&position, &snapshot).is_ge()
20215 || context.end.cmp(&position, &snapshot).is_lt()
20216 {
20217 continue;
20218 }
20219 let snapshot = self.buffer.read(cx).snapshot(cx);
20220 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20221
20222 handled = true;
20223 self.clear_row_highlights::<ActiveDebugLine>();
20224
20225 self.go_to_line::<ActiveDebugLine>(
20226 multibuffer_anchor,
20227 Some(cx.theme().colors().editor_debugger_active_line_background),
20228 window,
20229 cx,
20230 );
20231
20232 cx.notify();
20233 }
20234
20235 handled.then_some(())
20236 })
20237 .is_some()
20238 }
20239
20240 pub fn copy_file_name_without_extension(
20241 &mut self,
20242 _: &CopyFileNameWithoutExtension,
20243 _: &mut Window,
20244 cx: &mut Context<Self>,
20245 ) {
20246 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20247 let file = buffer.read(cx).file()?;
20248 file.path().file_stem()
20249 }) {
20250 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20251 }
20252 }
20253
20254 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20255 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20256 let file = buffer.read(cx).file()?;
20257 Some(file.file_name(cx))
20258 }) {
20259 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20260 }
20261 }
20262
20263 pub fn toggle_git_blame(
20264 &mut self,
20265 _: &::git::Blame,
20266 window: &mut Window,
20267 cx: &mut Context<Self>,
20268 ) {
20269 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20270
20271 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20272 self.start_git_blame(true, window, cx);
20273 }
20274
20275 cx.notify();
20276 }
20277
20278 pub fn toggle_git_blame_inline(
20279 &mut self,
20280 _: &ToggleGitBlameInline,
20281 window: &mut Window,
20282 cx: &mut Context<Self>,
20283 ) {
20284 self.toggle_git_blame_inline_internal(true, window, cx);
20285 cx.notify();
20286 }
20287
20288 pub fn open_git_blame_commit(
20289 &mut self,
20290 _: &OpenGitBlameCommit,
20291 window: &mut Window,
20292 cx: &mut Context<Self>,
20293 ) {
20294 self.open_git_blame_commit_internal(window, cx);
20295 }
20296
20297 fn open_git_blame_commit_internal(
20298 &mut self,
20299 window: &mut Window,
20300 cx: &mut Context<Self>,
20301 ) -> Option<()> {
20302 let blame = self.blame.as_ref()?;
20303 let snapshot = self.snapshot(window, cx);
20304 let cursor = self
20305 .selections
20306 .newest::<Point>(&snapshot.display_snapshot)
20307 .head();
20308 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20309 let (_, blame_entry) = blame
20310 .update(cx, |blame, cx| {
20311 blame
20312 .blame_for_rows(
20313 &[RowInfo {
20314 buffer_id: Some(buffer.remote_id()),
20315 buffer_row: Some(point.row),
20316 ..Default::default()
20317 }],
20318 cx,
20319 )
20320 .next()
20321 })
20322 .flatten()?;
20323 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20324 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20325 let workspace = self.workspace()?.downgrade();
20326 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20327 None
20328 }
20329
20330 pub fn git_blame_inline_enabled(&self) -> bool {
20331 self.git_blame_inline_enabled
20332 }
20333
20334 pub fn toggle_selection_menu(
20335 &mut self,
20336 _: &ToggleSelectionMenu,
20337 _: &mut Window,
20338 cx: &mut Context<Self>,
20339 ) {
20340 self.show_selection_menu = self
20341 .show_selection_menu
20342 .map(|show_selections_menu| !show_selections_menu)
20343 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20344
20345 cx.notify();
20346 }
20347
20348 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20349 self.show_selection_menu
20350 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20351 }
20352
20353 fn start_git_blame(
20354 &mut self,
20355 user_triggered: bool,
20356 window: &mut Window,
20357 cx: &mut Context<Self>,
20358 ) {
20359 if let Some(project) = self.project() {
20360 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20361 && buffer.read(cx).file().is_none()
20362 {
20363 return;
20364 }
20365
20366 let focused = self.focus_handle(cx).contains_focused(window, cx);
20367
20368 let project = project.clone();
20369 let blame = cx
20370 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20371 self.blame_subscription =
20372 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20373 self.blame = Some(blame);
20374 }
20375 }
20376
20377 fn toggle_git_blame_inline_internal(
20378 &mut self,
20379 user_triggered: bool,
20380 window: &mut Window,
20381 cx: &mut Context<Self>,
20382 ) {
20383 if self.git_blame_inline_enabled {
20384 self.git_blame_inline_enabled = false;
20385 self.show_git_blame_inline = false;
20386 self.show_git_blame_inline_delay_task.take();
20387 } else {
20388 self.git_blame_inline_enabled = true;
20389 self.start_git_blame_inline(user_triggered, window, cx);
20390 }
20391
20392 cx.notify();
20393 }
20394
20395 fn start_git_blame_inline(
20396 &mut self,
20397 user_triggered: bool,
20398 window: &mut Window,
20399 cx: &mut Context<Self>,
20400 ) {
20401 self.start_git_blame(user_triggered, window, cx);
20402
20403 if ProjectSettings::get_global(cx)
20404 .git
20405 .inline_blame_delay()
20406 .is_some()
20407 {
20408 self.start_inline_blame_timer(window, cx);
20409 } else {
20410 self.show_git_blame_inline = true
20411 }
20412 }
20413
20414 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20415 self.blame.as_ref()
20416 }
20417
20418 pub fn show_git_blame_gutter(&self) -> bool {
20419 self.show_git_blame_gutter
20420 }
20421
20422 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20423 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20424 }
20425
20426 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20427 self.show_git_blame_inline
20428 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20429 && !self.newest_selection_head_on_empty_line(cx)
20430 && self.has_blame_entries(cx)
20431 }
20432
20433 fn has_blame_entries(&self, cx: &App) -> bool {
20434 self.blame()
20435 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20436 }
20437
20438 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20439 let cursor_anchor = self.selections.newest_anchor().head();
20440
20441 let snapshot = self.buffer.read(cx).snapshot(cx);
20442 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20443
20444 snapshot.line_len(buffer_row) == 0
20445 }
20446
20447 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20448 let buffer_and_selection = maybe!({
20449 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20450 let selection_range = selection.range();
20451
20452 let multi_buffer = self.buffer().read(cx);
20453 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20454 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20455
20456 let (buffer, range, _) = if selection.reversed {
20457 buffer_ranges.first()
20458 } else {
20459 buffer_ranges.last()
20460 }?;
20461
20462 let selection = text::ToPoint::to_point(&range.start, buffer).row
20463 ..text::ToPoint::to_point(&range.end, buffer).row;
20464 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20465 });
20466
20467 let Some((buffer, selection)) = buffer_and_selection else {
20468 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20469 };
20470
20471 let Some(project) = self.project() else {
20472 return Task::ready(Err(anyhow!("editor does not have project")));
20473 };
20474
20475 project.update(cx, |project, cx| {
20476 project.get_permalink_to_line(&buffer, selection, cx)
20477 })
20478 }
20479
20480 pub fn copy_permalink_to_line(
20481 &mut self,
20482 _: &CopyPermalinkToLine,
20483 window: &mut Window,
20484 cx: &mut Context<Self>,
20485 ) {
20486 let permalink_task = self.get_permalink_to_line(cx);
20487 let workspace = self.workspace();
20488
20489 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20490 Ok(permalink) => {
20491 cx.update(|_, cx| {
20492 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20493 })
20494 .ok();
20495 }
20496 Err(err) => {
20497 let message = format!("Failed to copy permalink: {err}");
20498
20499 anyhow::Result::<()>::Err(err).log_err();
20500
20501 if let Some(workspace) = workspace {
20502 workspace
20503 .update_in(cx, |workspace, _, cx| {
20504 struct CopyPermalinkToLine;
20505
20506 workspace.show_toast(
20507 Toast::new(
20508 NotificationId::unique::<CopyPermalinkToLine>(),
20509 message,
20510 ),
20511 cx,
20512 )
20513 })
20514 .ok();
20515 }
20516 }
20517 })
20518 .detach();
20519 }
20520
20521 pub fn copy_file_location(
20522 &mut self,
20523 _: &CopyFileLocation,
20524 _: &mut Window,
20525 cx: &mut Context<Self>,
20526 ) {
20527 let selection = self
20528 .selections
20529 .newest::<Point>(&self.display_snapshot(cx))
20530 .start
20531 .row
20532 + 1;
20533 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20534 let project = self.project()?.read(cx);
20535 let file = buffer.read(cx).file()?;
20536 let path = file.path().display(project.path_style(cx));
20537
20538 Some(format!("{path}:{selection}"))
20539 }) {
20540 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20541 }
20542 }
20543
20544 pub fn open_permalink_to_line(
20545 &mut self,
20546 _: &OpenPermalinkToLine,
20547 window: &mut Window,
20548 cx: &mut Context<Self>,
20549 ) {
20550 let permalink_task = self.get_permalink_to_line(cx);
20551 let workspace = self.workspace();
20552
20553 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20554 Ok(permalink) => {
20555 cx.update(|_, cx| {
20556 cx.open_url(permalink.as_ref());
20557 })
20558 .ok();
20559 }
20560 Err(err) => {
20561 let message = format!("Failed to open permalink: {err}");
20562
20563 anyhow::Result::<()>::Err(err).log_err();
20564
20565 if let Some(workspace) = workspace {
20566 workspace
20567 .update(cx, |workspace, cx| {
20568 struct OpenPermalinkToLine;
20569
20570 workspace.show_toast(
20571 Toast::new(
20572 NotificationId::unique::<OpenPermalinkToLine>(),
20573 message,
20574 ),
20575 cx,
20576 )
20577 })
20578 .ok();
20579 }
20580 }
20581 })
20582 .detach();
20583 }
20584
20585 pub fn insert_uuid_v4(
20586 &mut self,
20587 _: &InsertUuidV4,
20588 window: &mut Window,
20589 cx: &mut Context<Self>,
20590 ) {
20591 self.insert_uuid(UuidVersion::V4, window, cx);
20592 }
20593
20594 pub fn insert_uuid_v7(
20595 &mut self,
20596 _: &InsertUuidV7,
20597 window: &mut Window,
20598 cx: &mut Context<Self>,
20599 ) {
20600 self.insert_uuid(UuidVersion::V7, window, cx);
20601 }
20602
20603 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20604 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20605 self.transact(window, cx, |this, window, cx| {
20606 let edits = this
20607 .selections
20608 .all::<Point>(&this.display_snapshot(cx))
20609 .into_iter()
20610 .map(|selection| {
20611 let uuid = match version {
20612 UuidVersion::V4 => uuid::Uuid::new_v4(),
20613 UuidVersion::V7 => uuid::Uuid::now_v7(),
20614 };
20615
20616 (selection.range(), uuid.to_string())
20617 });
20618 this.edit(edits, cx);
20619 this.refresh_edit_prediction(true, false, window, cx);
20620 });
20621 }
20622
20623 pub fn open_selections_in_multibuffer(
20624 &mut self,
20625 _: &OpenSelectionsInMultibuffer,
20626 window: &mut Window,
20627 cx: &mut Context<Self>,
20628 ) {
20629 let multibuffer = self.buffer.read(cx);
20630
20631 let Some(buffer) = multibuffer.as_singleton() else {
20632 return;
20633 };
20634
20635 let Some(workspace) = self.workspace() else {
20636 return;
20637 };
20638
20639 let title = multibuffer.title(cx).to_string();
20640
20641 let locations = self
20642 .selections
20643 .all_anchors(&self.display_snapshot(cx))
20644 .iter()
20645 .map(|selection| {
20646 (
20647 buffer.clone(),
20648 (selection.start.text_anchor..selection.end.text_anchor)
20649 .to_point(buffer.read(cx)),
20650 )
20651 })
20652 .into_group_map();
20653
20654 cx.spawn_in(window, async move |_, cx| {
20655 workspace.update_in(cx, |workspace, window, cx| {
20656 Self::open_locations_in_multibuffer(
20657 workspace,
20658 locations,
20659 format!("Selections for '{title}'"),
20660 false,
20661 MultibufferSelectionMode::All,
20662 window,
20663 cx,
20664 );
20665 })
20666 })
20667 .detach();
20668 }
20669
20670 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20671 /// last highlight added will be used.
20672 ///
20673 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20674 pub fn highlight_rows<T: 'static>(
20675 &mut self,
20676 range: Range<Anchor>,
20677 color: Hsla,
20678 options: RowHighlightOptions,
20679 cx: &mut Context<Self>,
20680 ) {
20681 let snapshot = self.buffer().read(cx).snapshot(cx);
20682 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20683 let ix = row_highlights.binary_search_by(|highlight| {
20684 Ordering::Equal
20685 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20686 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20687 });
20688
20689 if let Err(mut ix) = ix {
20690 let index = post_inc(&mut self.highlight_order);
20691
20692 // If this range intersects with the preceding highlight, then merge it with
20693 // the preceding highlight. Otherwise insert a new highlight.
20694 let mut merged = false;
20695 if ix > 0 {
20696 let prev_highlight = &mut row_highlights[ix - 1];
20697 if prev_highlight
20698 .range
20699 .end
20700 .cmp(&range.start, &snapshot)
20701 .is_ge()
20702 {
20703 ix -= 1;
20704 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20705 prev_highlight.range.end = range.end;
20706 }
20707 merged = true;
20708 prev_highlight.index = index;
20709 prev_highlight.color = color;
20710 prev_highlight.options = options;
20711 }
20712 }
20713
20714 if !merged {
20715 row_highlights.insert(
20716 ix,
20717 RowHighlight {
20718 range,
20719 index,
20720 color,
20721 options,
20722 type_id: TypeId::of::<T>(),
20723 },
20724 );
20725 }
20726
20727 // If any of the following highlights intersect with this one, merge them.
20728 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20729 let highlight = &row_highlights[ix];
20730 if next_highlight
20731 .range
20732 .start
20733 .cmp(&highlight.range.end, &snapshot)
20734 .is_le()
20735 {
20736 if next_highlight
20737 .range
20738 .end
20739 .cmp(&highlight.range.end, &snapshot)
20740 .is_gt()
20741 {
20742 row_highlights[ix].range.end = next_highlight.range.end;
20743 }
20744 row_highlights.remove(ix + 1);
20745 } else {
20746 break;
20747 }
20748 }
20749 }
20750 }
20751
20752 /// Remove any highlighted row ranges of the given type that intersect the
20753 /// given ranges.
20754 pub fn remove_highlighted_rows<T: 'static>(
20755 &mut self,
20756 ranges_to_remove: Vec<Range<Anchor>>,
20757 cx: &mut Context<Self>,
20758 ) {
20759 let snapshot = self.buffer().read(cx).snapshot(cx);
20760 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20761 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20762 row_highlights.retain(|highlight| {
20763 while let Some(range_to_remove) = ranges_to_remove.peek() {
20764 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20765 Ordering::Less | Ordering::Equal => {
20766 ranges_to_remove.next();
20767 }
20768 Ordering::Greater => {
20769 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20770 Ordering::Less | Ordering::Equal => {
20771 return false;
20772 }
20773 Ordering::Greater => break,
20774 }
20775 }
20776 }
20777 }
20778
20779 true
20780 })
20781 }
20782
20783 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20784 pub fn clear_row_highlights<T: 'static>(&mut self) {
20785 self.highlighted_rows.remove(&TypeId::of::<T>());
20786 }
20787
20788 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20789 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20790 self.highlighted_rows
20791 .get(&TypeId::of::<T>())
20792 .map_or(&[] as &[_], |vec| vec.as_slice())
20793 .iter()
20794 .map(|highlight| (highlight.range.clone(), highlight.color))
20795 }
20796
20797 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20798 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20799 /// Allows to ignore certain kinds of highlights.
20800 pub fn highlighted_display_rows(
20801 &self,
20802 window: &mut Window,
20803 cx: &mut App,
20804 ) -> BTreeMap<DisplayRow, LineHighlight> {
20805 let snapshot = self.snapshot(window, cx);
20806 let mut used_highlight_orders = HashMap::default();
20807 self.highlighted_rows
20808 .iter()
20809 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20810 .fold(
20811 BTreeMap::<DisplayRow, LineHighlight>::new(),
20812 |mut unique_rows, highlight| {
20813 let start = highlight.range.start.to_display_point(&snapshot);
20814 let end = highlight.range.end.to_display_point(&snapshot);
20815 let start_row = start.row().0;
20816 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20817 {
20818 end.row().0.saturating_sub(1)
20819 } else {
20820 end.row().0
20821 };
20822 for row in start_row..=end_row {
20823 let used_index =
20824 used_highlight_orders.entry(row).or_insert(highlight.index);
20825 if highlight.index >= *used_index {
20826 *used_index = highlight.index;
20827 unique_rows.insert(
20828 DisplayRow(row),
20829 LineHighlight {
20830 include_gutter: highlight.options.include_gutter,
20831 border: None,
20832 background: highlight.color.into(),
20833 type_id: Some(highlight.type_id),
20834 },
20835 );
20836 }
20837 }
20838 unique_rows
20839 },
20840 )
20841 }
20842
20843 pub fn highlighted_display_row_for_autoscroll(
20844 &self,
20845 snapshot: &DisplaySnapshot,
20846 ) -> Option<DisplayRow> {
20847 self.highlighted_rows
20848 .values()
20849 .flat_map(|highlighted_rows| highlighted_rows.iter())
20850 .filter_map(|highlight| {
20851 if highlight.options.autoscroll {
20852 Some(highlight.range.start.to_display_point(snapshot).row())
20853 } else {
20854 None
20855 }
20856 })
20857 .min()
20858 }
20859
20860 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20861 self.highlight_background::<SearchWithinRange>(
20862 ranges,
20863 |colors| colors.colors().editor_document_highlight_read_background,
20864 cx,
20865 )
20866 }
20867
20868 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20869 self.breadcrumb_header = Some(new_header);
20870 }
20871
20872 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20873 self.clear_background_highlights::<SearchWithinRange>(cx);
20874 }
20875
20876 pub fn highlight_background<T: 'static>(
20877 &mut self,
20878 ranges: &[Range<Anchor>],
20879 color_fetcher: fn(&Theme) -> Hsla,
20880 cx: &mut Context<Self>,
20881 ) {
20882 self.background_highlights.insert(
20883 HighlightKey::Type(TypeId::of::<T>()),
20884 (color_fetcher, Arc::from(ranges)),
20885 );
20886 self.scrollbar_marker_state.dirty = true;
20887 cx.notify();
20888 }
20889
20890 pub fn highlight_background_key<T: 'static>(
20891 &mut self,
20892 key: usize,
20893 ranges: &[Range<Anchor>],
20894 color_fetcher: fn(&Theme) -> Hsla,
20895 cx: &mut Context<Self>,
20896 ) {
20897 self.background_highlights.insert(
20898 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20899 (color_fetcher, Arc::from(ranges)),
20900 );
20901 self.scrollbar_marker_state.dirty = true;
20902 cx.notify();
20903 }
20904
20905 pub fn clear_background_highlights<T: 'static>(
20906 &mut self,
20907 cx: &mut Context<Self>,
20908 ) -> Option<BackgroundHighlight> {
20909 let text_highlights = self
20910 .background_highlights
20911 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20912 if !text_highlights.1.is_empty() {
20913 self.scrollbar_marker_state.dirty = true;
20914 cx.notify();
20915 }
20916 Some(text_highlights)
20917 }
20918
20919 pub fn highlight_gutter<T: 'static>(
20920 &mut self,
20921 ranges: impl Into<Vec<Range<Anchor>>>,
20922 color_fetcher: fn(&App) -> Hsla,
20923 cx: &mut Context<Self>,
20924 ) {
20925 self.gutter_highlights
20926 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20927 cx.notify();
20928 }
20929
20930 pub fn clear_gutter_highlights<T: 'static>(
20931 &mut self,
20932 cx: &mut Context<Self>,
20933 ) -> Option<GutterHighlight> {
20934 cx.notify();
20935 self.gutter_highlights.remove(&TypeId::of::<T>())
20936 }
20937
20938 pub fn insert_gutter_highlight<T: 'static>(
20939 &mut self,
20940 range: Range<Anchor>,
20941 color_fetcher: fn(&App) -> Hsla,
20942 cx: &mut Context<Self>,
20943 ) {
20944 let snapshot = self.buffer().read(cx).snapshot(cx);
20945 let mut highlights = self
20946 .gutter_highlights
20947 .remove(&TypeId::of::<T>())
20948 .map(|(_, highlights)| highlights)
20949 .unwrap_or_default();
20950 let ix = highlights.binary_search_by(|highlight| {
20951 Ordering::Equal
20952 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20953 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20954 });
20955 if let Err(ix) = ix {
20956 highlights.insert(ix, range);
20957 }
20958 self.gutter_highlights
20959 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20960 }
20961
20962 pub fn remove_gutter_highlights<T: 'static>(
20963 &mut self,
20964 ranges_to_remove: Vec<Range<Anchor>>,
20965 cx: &mut Context<Self>,
20966 ) {
20967 let snapshot = self.buffer().read(cx).snapshot(cx);
20968 let Some((color_fetcher, mut gutter_highlights)) =
20969 self.gutter_highlights.remove(&TypeId::of::<T>())
20970 else {
20971 return;
20972 };
20973 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20974 gutter_highlights.retain(|highlight| {
20975 while let Some(range_to_remove) = ranges_to_remove.peek() {
20976 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20977 Ordering::Less | Ordering::Equal => {
20978 ranges_to_remove.next();
20979 }
20980 Ordering::Greater => {
20981 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20982 Ordering::Less | Ordering::Equal => {
20983 return false;
20984 }
20985 Ordering::Greater => break,
20986 }
20987 }
20988 }
20989 }
20990
20991 true
20992 });
20993 self.gutter_highlights
20994 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20995 }
20996
20997 #[cfg(feature = "test-support")]
20998 pub fn all_text_highlights(
20999 &self,
21000 window: &mut Window,
21001 cx: &mut Context<Self>,
21002 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21003 let snapshot = self.snapshot(window, cx);
21004 self.display_map.update(cx, |display_map, _| {
21005 display_map
21006 .all_text_highlights()
21007 .map(|highlight| {
21008 let (style, ranges) = highlight.as_ref();
21009 (
21010 *style,
21011 ranges
21012 .iter()
21013 .map(|range| range.clone().to_display_points(&snapshot))
21014 .collect(),
21015 )
21016 })
21017 .collect()
21018 })
21019 }
21020
21021 #[cfg(feature = "test-support")]
21022 pub fn all_text_background_highlights(
21023 &self,
21024 window: &mut Window,
21025 cx: &mut Context<Self>,
21026 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21027 let snapshot = self.snapshot(window, cx);
21028 let buffer = &snapshot.buffer_snapshot();
21029 let start = buffer.anchor_before(MultiBufferOffset(0));
21030 let end = buffer.anchor_after(buffer.len());
21031 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21032 }
21033
21034 #[cfg(any(test, feature = "test-support"))]
21035 pub fn sorted_background_highlights_in_range(
21036 &self,
21037 search_range: Range<Anchor>,
21038 display_snapshot: &DisplaySnapshot,
21039 theme: &Theme,
21040 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21041 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21042 res.sort_by(|a, b| {
21043 a.0.start
21044 .cmp(&b.0.start)
21045 .then_with(|| a.0.end.cmp(&b.0.end))
21046 .then_with(|| a.1.cmp(&b.1))
21047 });
21048 res
21049 }
21050
21051 #[cfg(feature = "test-support")]
21052 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21053 let snapshot = self.buffer().read(cx).snapshot(cx);
21054
21055 let highlights = self
21056 .background_highlights
21057 .get(&HighlightKey::Type(TypeId::of::<
21058 items::BufferSearchHighlights,
21059 >()));
21060
21061 if let Some((_color, ranges)) = highlights {
21062 ranges
21063 .iter()
21064 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21065 .collect_vec()
21066 } else {
21067 vec![]
21068 }
21069 }
21070
21071 fn document_highlights_for_position<'a>(
21072 &'a self,
21073 position: Anchor,
21074 buffer: &'a MultiBufferSnapshot,
21075 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21076 let read_highlights = self
21077 .background_highlights
21078 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21079 .map(|h| &h.1);
21080 let write_highlights = self
21081 .background_highlights
21082 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21083 .map(|h| &h.1);
21084 let left_position = position.bias_left(buffer);
21085 let right_position = position.bias_right(buffer);
21086 read_highlights
21087 .into_iter()
21088 .chain(write_highlights)
21089 .flat_map(move |ranges| {
21090 let start_ix = match ranges.binary_search_by(|probe| {
21091 let cmp = probe.end.cmp(&left_position, buffer);
21092 if cmp.is_ge() {
21093 Ordering::Greater
21094 } else {
21095 Ordering::Less
21096 }
21097 }) {
21098 Ok(i) | Err(i) => i,
21099 };
21100
21101 ranges[start_ix..]
21102 .iter()
21103 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21104 })
21105 }
21106
21107 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21108 self.background_highlights
21109 .get(&HighlightKey::Type(TypeId::of::<T>()))
21110 .is_some_and(|(_, highlights)| !highlights.is_empty())
21111 }
21112
21113 /// Returns all background highlights for a given range.
21114 ///
21115 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21116 pub fn background_highlights_in_range(
21117 &self,
21118 search_range: Range<Anchor>,
21119 display_snapshot: &DisplaySnapshot,
21120 theme: &Theme,
21121 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21122 let mut results = Vec::new();
21123 for (color_fetcher, ranges) in self.background_highlights.values() {
21124 let color = color_fetcher(theme);
21125 let start_ix = match ranges.binary_search_by(|probe| {
21126 let cmp = probe
21127 .end
21128 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21129 if cmp.is_gt() {
21130 Ordering::Greater
21131 } else {
21132 Ordering::Less
21133 }
21134 }) {
21135 Ok(i) | Err(i) => i,
21136 };
21137 for range in &ranges[start_ix..] {
21138 if range
21139 .start
21140 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21141 .is_ge()
21142 {
21143 break;
21144 }
21145
21146 let start = range.start.to_display_point(display_snapshot);
21147 let end = range.end.to_display_point(display_snapshot);
21148 results.push((start..end, color))
21149 }
21150 }
21151 results
21152 }
21153
21154 pub fn gutter_highlights_in_range(
21155 &self,
21156 search_range: Range<Anchor>,
21157 display_snapshot: &DisplaySnapshot,
21158 cx: &App,
21159 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21160 let mut results = Vec::new();
21161 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21162 let color = color_fetcher(cx);
21163 let start_ix = match ranges.binary_search_by(|probe| {
21164 let cmp = probe
21165 .end
21166 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21167 if cmp.is_gt() {
21168 Ordering::Greater
21169 } else {
21170 Ordering::Less
21171 }
21172 }) {
21173 Ok(i) | Err(i) => i,
21174 };
21175 for range in &ranges[start_ix..] {
21176 if range
21177 .start
21178 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21179 .is_ge()
21180 {
21181 break;
21182 }
21183
21184 let start = range.start.to_display_point(display_snapshot);
21185 let end = range.end.to_display_point(display_snapshot);
21186 results.push((start..end, color))
21187 }
21188 }
21189 results
21190 }
21191
21192 /// Get the text ranges corresponding to the redaction query
21193 pub fn redacted_ranges(
21194 &self,
21195 search_range: Range<Anchor>,
21196 display_snapshot: &DisplaySnapshot,
21197 cx: &App,
21198 ) -> Vec<Range<DisplayPoint>> {
21199 display_snapshot
21200 .buffer_snapshot()
21201 .redacted_ranges(search_range, |file| {
21202 if let Some(file) = file {
21203 file.is_private()
21204 && EditorSettings::get(
21205 Some(SettingsLocation {
21206 worktree_id: file.worktree_id(cx),
21207 path: file.path().as_ref(),
21208 }),
21209 cx,
21210 )
21211 .redact_private_values
21212 } else {
21213 false
21214 }
21215 })
21216 .map(|range| {
21217 range.start.to_display_point(display_snapshot)
21218 ..range.end.to_display_point(display_snapshot)
21219 })
21220 .collect()
21221 }
21222
21223 pub fn highlight_text_key<T: 'static>(
21224 &mut self,
21225 key: usize,
21226 ranges: Vec<Range<Anchor>>,
21227 style: HighlightStyle,
21228 merge: bool,
21229 cx: &mut Context<Self>,
21230 ) {
21231 self.display_map.update(cx, |map, cx| {
21232 map.highlight_text(
21233 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21234 ranges,
21235 style,
21236 merge,
21237 cx,
21238 );
21239 });
21240 cx.notify();
21241 }
21242
21243 pub fn highlight_text<T: 'static>(
21244 &mut self,
21245 ranges: Vec<Range<Anchor>>,
21246 style: HighlightStyle,
21247 cx: &mut Context<Self>,
21248 ) {
21249 self.display_map.update(cx, |map, cx| {
21250 map.highlight_text(
21251 HighlightKey::Type(TypeId::of::<T>()),
21252 ranges,
21253 style,
21254 false,
21255 cx,
21256 )
21257 });
21258 cx.notify();
21259 }
21260
21261 pub fn text_highlights<'a, T: 'static>(
21262 &'a self,
21263 cx: &'a App,
21264 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21265 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21266 }
21267
21268 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21269 let cleared = self
21270 .display_map
21271 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21272 if cleared {
21273 cx.notify();
21274 }
21275 }
21276
21277 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21278 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21279 && self.focus_handle.is_focused(window)
21280 }
21281
21282 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21283 self.show_cursor_when_unfocused = is_enabled;
21284 cx.notify();
21285 }
21286
21287 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21288 cx.notify();
21289 }
21290
21291 fn on_debug_session_event(
21292 &mut self,
21293 _session: Entity<Session>,
21294 event: &SessionEvent,
21295 cx: &mut Context<Self>,
21296 ) {
21297 if let SessionEvent::InvalidateInlineValue = event {
21298 self.refresh_inline_values(cx);
21299 }
21300 }
21301
21302 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21303 let Some(project) = self.project.clone() else {
21304 return;
21305 };
21306
21307 if !self.inline_value_cache.enabled {
21308 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21309 self.splice_inlays(&inlays, Vec::new(), cx);
21310 return;
21311 }
21312
21313 let current_execution_position = self
21314 .highlighted_rows
21315 .get(&TypeId::of::<ActiveDebugLine>())
21316 .and_then(|lines| lines.last().map(|line| line.range.end));
21317
21318 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21319 let inline_values = editor
21320 .update(cx, |editor, cx| {
21321 let Some(current_execution_position) = current_execution_position else {
21322 return Some(Task::ready(Ok(Vec::new())));
21323 };
21324
21325 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21326 let snapshot = buffer.snapshot(cx);
21327
21328 let excerpt = snapshot.excerpt_containing(
21329 current_execution_position..current_execution_position,
21330 )?;
21331
21332 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21333 })?;
21334
21335 let range =
21336 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21337
21338 project.inline_values(buffer, range, cx)
21339 })
21340 .ok()
21341 .flatten()?
21342 .await
21343 .context("refreshing debugger inlays")
21344 .log_err()?;
21345
21346 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21347
21348 for (buffer_id, inline_value) in inline_values
21349 .into_iter()
21350 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21351 {
21352 buffer_inline_values
21353 .entry(buffer_id)
21354 .or_default()
21355 .push(inline_value);
21356 }
21357
21358 editor
21359 .update(cx, |editor, cx| {
21360 let snapshot = editor.buffer.read(cx).snapshot(cx);
21361 let mut new_inlays = Vec::default();
21362
21363 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21364 let buffer_id = buffer_snapshot.remote_id();
21365 buffer_inline_values
21366 .get(&buffer_id)
21367 .into_iter()
21368 .flatten()
21369 .for_each(|hint| {
21370 let inlay = Inlay::debugger(
21371 post_inc(&mut editor.next_inlay_id),
21372 Anchor::in_buffer(excerpt_id, hint.position),
21373 hint.text(),
21374 );
21375 if !inlay.text().chars().contains(&'\n') {
21376 new_inlays.push(inlay);
21377 }
21378 });
21379 }
21380
21381 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21382 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21383
21384 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21385 })
21386 .ok()?;
21387 Some(())
21388 });
21389 }
21390
21391 fn on_buffer_event(
21392 &mut self,
21393 multibuffer: &Entity<MultiBuffer>,
21394 event: &multi_buffer::Event,
21395 window: &mut Window,
21396 cx: &mut Context<Self>,
21397 ) {
21398 match event {
21399 multi_buffer::Event::Edited { edited_buffer } => {
21400 self.scrollbar_marker_state.dirty = true;
21401 self.active_indent_guides_state.dirty = true;
21402 self.refresh_active_diagnostics(cx);
21403 self.refresh_code_actions(window, cx);
21404 self.refresh_single_line_folds(window, cx);
21405 self.refresh_matching_bracket_highlights(window, cx);
21406 if self.has_active_edit_prediction() {
21407 self.update_visible_edit_prediction(window, cx);
21408 }
21409
21410 if let Some(buffer) = edited_buffer {
21411 if buffer.read(cx).file().is_none() {
21412 cx.emit(EditorEvent::TitleChanged);
21413 }
21414
21415 if self.project.is_some() {
21416 let buffer_id = buffer.read(cx).remote_id();
21417 self.register_buffer(buffer_id, cx);
21418 self.update_lsp_data(Some(buffer_id), window, cx);
21419 self.refresh_inlay_hints(
21420 InlayHintRefreshReason::BufferEdited(buffer_id),
21421 cx,
21422 );
21423 }
21424 }
21425
21426 cx.emit(EditorEvent::BufferEdited);
21427 cx.emit(SearchEvent::MatchesInvalidated);
21428
21429 let Some(project) = &self.project else { return };
21430 let (telemetry, is_via_ssh) = {
21431 let project = project.read(cx);
21432 let telemetry = project.client().telemetry().clone();
21433 let is_via_ssh = project.is_via_remote_server();
21434 (telemetry, is_via_ssh)
21435 };
21436 telemetry.log_edit_event("editor", is_via_ssh);
21437 }
21438 multi_buffer::Event::ExcerptsAdded {
21439 buffer,
21440 predecessor,
21441 excerpts,
21442 } => {
21443 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21444 let buffer_id = buffer.read(cx).remote_id();
21445 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21446 && let Some(project) = &self.project
21447 {
21448 update_uncommitted_diff_for_buffer(
21449 cx.entity(),
21450 project,
21451 [buffer.clone()],
21452 self.buffer.clone(),
21453 cx,
21454 )
21455 .detach();
21456 }
21457 self.update_lsp_data(Some(buffer_id), window, cx);
21458 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21459 self.colorize_brackets(false, cx);
21460 cx.emit(EditorEvent::ExcerptsAdded {
21461 buffer: buffer.clone(),
21462 predecessor: *predecessor,
21463 excerpts: excerpts.clone(),
21464 });
21465 }
21466 multi_buffer::Event::ExcerptsRemoved {
21467 ids,
21468 removed_buffer_ids,
21469 } => {
21470 if let Some(inlay_hints) = &mut self.inlay_hints {
21471 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21472 }
21473 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21474 for buffer_id in removed_buffer_ids {
21475 self.registered_buffers.remove(buffer_id);
21476 }
21477 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21478 cx.emit(EditorEvent::ExcerptsRemoved {
21479 ids: ids.clone(),
21480 removed_buffer_ids: removed_buffer_ids.clone(),
21481 });
21482 }
21483 multi_buffer::Event::ExcerptsEdited {
21484 excerpt_ids,
21485 buffer_ids,
21486 } => {
21487 self.display_map.update(cx, |map, cx| {
21488 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21489 });
21490 cx.emit(EditorEvent::ExcerptsEdited {
21491 ids: excerpt_ids.clone(),
21492 });
21493 }
21494 multi_buffer::Event::ExcerptsExpanded { ids } => {
21495 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21496 self.refresh_document_highlights(cx);
21497 for id in ids {
21498 self.fetched_tree_sitter_chunks.remove(id);
21499 }
21500 self.colorize_brackets(false, cx);
21501 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21502 }
21503 multi_buffer::Event::Reparsed(buffer_id) => {
21504 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21505 self.refresh_selected_text_highlights(true, window, cx);
21506 self.colorize_brackets(true, cx);
21507 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21508
21509 cx.emit(EditorEvent::Reparsed(*buffer_id));
21510 }
21511 multi_buffer::Event::DiffHunksToggled => {
21512 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21513 }
21514 multi_buffer::Event::LanguageChanged(buffer_id) => {
21515 self.registered_buffers.remove(&buffer_id);
21516 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21517 cx.emit(EditorEvent::Reparsed(*buffer_id));
21518 cx.notify();
21519 }
21520 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21521 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21522 multi_buffer::Event::FileHandleChanged
21523 | multi_buffer::Event::Reloaded
21524 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21525 multi_buffer::Event::DiagnosticsUpdated => {
21526 self.update_diagnostics_state(window, cx);
21527 }
21528 _ => {}
21529 };
21530 }
21531
21532 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21533 if !self.diagnostics_enabled() {
21534 return;
21535 }
21536 self.refresh_active_diagnostics(cx);
21537 self.refresh_inline_diagnostics(true, window, cx);
21538 self.scrollbar_marker_state.dirty = true;
21539 cx.notify();
21540 }
21541
21542 pub fn start_temporary_diff_override(&mut self) {
21543 self.load_diff_task.take();
21544 self.temporary_diff_override = true;
21545 }
21546
21547 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21548 self.temporary_diff_override = false;
21549 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21550 self.buffer.update(cx, |buffer, cx| {
21551 buffer.set_all_diff_hunks_collapsed(cx);
21552 });
21553
21554 if let Some(project) = self.project.clone() {
21555 self.load_diff_task = Some(
21556 update_uncommitted_diff_for_buffer(
21557 cx.entity(),
21558 &project,
21559 self.buffer.read(cx).all_buffers(),
21560 self.buffer.clone(),
21561 cx,
21562 )
21563 .shared(),
21564 );
21565 }
21566 }
21567
21568 fn on_display_map_changed(
21569 &mut self,
21570 _: Entity<DisplayMap>,
21571 _: &mut Window,
21572 cx: &mut Context<Self>,
21573 ) {
21574 cx.notify();
21575 }
21576
21577 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21578 if !self.mode.is_full() {
21579 return Vec::new();
21580 }
21581
21582 let theme_settings = theme::ThemeSettings::get_global(cx);
21583
21584 theme_settings
21585 .theme_overrides
21586 .get(cx.theme().name.as_ref())
21587 .map(|theme_style| &theme_style.accents)
21588 .into_iter()
21589 .flatten()
21590 .chain(
21591 theme_settings
21592 .experimental_theme_overrides
21593 .as_ref()
21594 .map(|overrides| &overrides.accents)
21595 .into_iter()
21596 .flatten(),
21597 )
21598 .flat_map(|accent| accent.0.clone())
21599 .collect()
21600 }
21601
21602 fn fetch_applicable_language_settings(
21603 &self,
21604 cx: &App,
21605 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21606 if !self.mode.is_full() {
21607 return HashMap::default();
21608 }
21609
21610 self.buffer().read(cx).all_buffers().into_iter().fold(
21611 HashMap::default(),
21612 |mut acc, buffer| {
21613 let buffer = buffer.read(cx);
21614 let language = buffer.language().map(|language| language.name());
21615 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21616 let file = buffer.file();
21617 v.insert(language_settings(language, file, cx).into_owned());
21618 }
21619 acc
21620 },
21621 )
21622 }
21623
21624 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21625 let new_language_settings = self.fetch_applicable_language_settings(cx);
21626 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21627 self.applicable_language_settings = new_language_settings;
21628
21629 let new_accent_overrides = self.fetch_accent_overrides(cx);
21630 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21631 self.accent_overrides = new_accent_overrides;
21632
21633 if self.diagnostics_enabled() {
21634 let new_severity = EditorSettings::get_global(cx)
21635 .diagnostics_max_severity
21636 .unwrap_or(DiagnosticSeverity::Hint);
21637 self.set_max_diagnostics_severity(new_severity, cx);
21638 }
21639 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21640 self.update_edit_prediction_settings(cx);
21641 self.refresh_edit_prediction(true, false, window, cx);
21642 self.refresh_inline_values(cx);
21643 self.refresh_inlay_hints(
21644 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21645 self.selections.newest_anchor().head(),
21646 &self.buffer.read(cx).snapshot(cx),
21647 cx,
21648 )),
21649 cx,
21650 );
21651
21652 let old_cursor_shape = self.cursor_shape;
21653 let old_show_breadcrumbs = self.show_breadcrumbs;
21654
21655 {
21656 let editor_settings = EditorSettings::get_global(cx);
21657 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21658 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21659 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21660 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21661 }
21662
21663 if old_cursor_shape != self.cursor_shape {
21664 cx.emit(EditorEvent::CursorShapeChanged);
21665 }
21666
21667 if old_show_breadcrumbs != self.show_breadcrumbs {
21668 cx.emit(EditorEvent::BreadcrumbsChanged);
21669 }
21670
21671 let project_settings = ProjectSettings::get_global(cx);
21672 self.buffer_serialization = self
21673 .should_serialize_buffer()
21674 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21675
21676 if self.mode.is_full() {
21677 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21678 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21679 if self.show_inline_diagnostics != show_inline_diagnostics {
21680 self.show_inline_diagnostics = show_inline_diagnostics;
21681 self.refresh_inline_diagnostics(false, window, cx);
21682 }
21683
21684 if self.git_blame_inline_enabled != inline_blame_enabled {
21685 self.toggle_git_blame_inline_internal(false, window, cx);
21686 }
21687
21688 let minimap_settings = EditorSettings::get_global(cx).minimap;
21689 if self.minimap_visibility != MinimapVisibility::Disabled {
21690 if self.minimap_visibility.settings_visibility()
21691 != minimap_settings.minimap_enabled()
21692 {
21693 self.set_minimap_visibility(
21694 MinimapVisibility::for_mode(self.mode(), cx),
21695 window,
21696 cx,
21697 );
21698 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21699 minimap_entity.update(cx, |minimap_editor, cx| {
21700 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21701 })
21702 }
21703 }
21704
21705 if language_settings_changed || accent_overrides_changed {
21706 self.colorize_brackets(true, cx);
21707 }
21708
21709 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21710 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21711 }) {
21712 if !inlay_splice.is_empty() {
21713 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21714 }
21715 self.refresh_colors_for_visible_range(None, window, cx);
21716 }
21717 }
21718
21719 cx.notify();
21720 }
21721
21722 pub fn set_searchable(&mut self, searchable: bool) {
21723 self.searchable = searchable;
21724 }
21725
21726 pub fn searchable(&self) -> bool {
21727 self.searchable
21728 }
21729
21730 pub fn open_excerpts_in_split(
21731 &mut self,
21732 _: &OpenExcerptsSplit,
21733 window: &mut Window,
21734 cx: &mut Context<Self>,
21735 ) {
21736 self.open_excerpts_common(None, true, window, cx)
21737 }
21738
21739 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21740 self.open_excerpts_common(None, false, window, cx)
21741 }
21742
21743 fn open_excerpts_common(
21744 &mut self,
21745 jump_data: Option<JumpData>,
21746 split: bool,
21747 window: &mut Window,
21748 cx: &mut Context<Self>,
21749 ) {
21750 let Some(workspace) = self.workspace() else {
21751 cx.propagate();
21752 return;
21753 };
21754
21755 if self.buffer.read(cx).is_singleton() {
21756 cx.propagate();
21757 return;
21758 }
21759
21760 let mut new_selections_by_buffer = HashMap::default();
21761 match &jump_data {
21762 Some(JumpData::MultiBufferPoint {
21763 excerpt_id,
21764 position,
21765 anchor,
21766 line_offset_from_top,
21767 }) => {
21768 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21769 if let Some(buffer) = multi_buffer_snapshot
21770 .buffer_id_for_excerpt(*excerpt_id)
21771 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21772 {
21773 let buffer_snapshot = buffer.read(cx).snapshot();
21774 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21775 language::ToPoint::to_point(anchor, &buffer_snapshot)
21776 } else {
21777 buffer_snapshot.clip_point(*position, Bias::Left)
21778 };
21779 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21780 new_selections_by_buffer.insert(
21781 buffer,
21782 (
21783 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21784 Some(*line_offset_from_top),
21785 ),
21786 );
21787 }
21788 }
21789 Some(JumpData::MultiBufferRow {
21790 row,
21791 line_offset_from_top,
21792 }) => {
21793 let point = MultiBufferPoint::new(row.0, 0);
21794 if let Some((buffer, buffer_point, _)) =
21795 self.buffer.read(cx).point_to_buffer_point(point, cx)
21796 {
21797 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21798 new_selections_by_buffer
21799 .entry(buffer)
21800 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21801 .0
21802 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21803 }
21804 }
21805 None => {
21806 let selections = self
21807 .selections
21808 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21809 let multi_buffer = self.buffer.read(cx);
21810 for selection in selections {
21811 for (snapshot, range, _, anchor) in multi_buffer
21812 .snapshot(cx)
21813 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21814 {
21815 if let Some(anchor) = anchor {
21816 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21817 else {
21818 continue;
21819 };
21820 let offset = text::ToOffset::to_offset(
21821 &anchor.text_anchor,
21822 &buffer_handle.read(cx).snapshot(),
21823 );
21824 let range = BufferOffset(offset)..BufferOffset(offset);
21825 new_selections_by_buffer
21826 .entry(buffer_handle)
21827 .or_insert((Vec::new(), None))
21828 .0
21829 .push(range)
21830 } else {
21831 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21832 else {
21833 continue;
21834 };
21835 new_selections_by_buffer
21836 .entry(buffer_handle)
21837 .or_insert((Vec::new(), None))
21838 .0
21839 .push(range)
21840 }
21841 }
21842 }
21843 }
21844 }
21845
21846 new_selections_by_buffer
21847 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21848
21849 if new_selections_by_buffer.is_empty() {
21850 return;
21851 }
21852
21853 // We defer the pane interaction because we ourselves are a workspace item
21854 // and activating a new item causes the pane to call a method on us reentrantly,
21855 // which panics if we're on the stack.
21856 window.defer(cx, move |window, cx| {
21857 workspace.update(cx, |workspace, cx| {
21858 let pane = if split {
21859 workspace.adjacent_pane(window, cx)
21860 } else {
21861 workspace.active_pane().clone()
21862 };
21863
21864 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21865 let editor = buffer
21866 .read(cx)
21867 .file()
21868 .is_none()
21869 .then(|| {
21870 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21871 // so `workspace.open_project_item` will never find them, always opening a new editor.
21872 // Instead, we try to activate the existing editor in the pane first.
21873 let (editor, pane_item_index) =
21874 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21875 let editor = item.downcast::<Editor>()?;
21876 let singleton_buffer =
21877 editor.read(cx).buffer().read(cx).as_singleton()?;
21878 if singleton_buffer == buffer {
21879 Some((editor, i))
21880 } else {
21881 None
21882 }
21883 })?;
21884 pane.update(cx, |pane, cx| {
21885 pane.activate_item(pane_item_index, true, true, window, cx)
21886 });
21887 Some(editor)
21888 })
21889 .flatten()
21890 .unwrap_or_else(|| {
21891 workspace.open_project_item::<Self>(
21892 pane.clone(),
21893 buffer,
21894 true,
21895 true,
21896 window,
21897 cx,
21898 )
21899 });
21900
21901 editor.update(cx, |editor, cx| {
21902 let autoscroll = match scroll_offset {
21903 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21904 None => Autoscroll::newest(),
21905 };
21906 let nav_history = editor.nav_history.take();
21907 editor.change_selections(
21908 SelectionEffects::scroll(autoscroll),
21909 window,
21910 cx,
21911 |s| {
21912 s.select_ranges(ranges.into_iter().map(|range| {
21913 // we checked that the editor is a singleton editor so the offsets are valid
21914 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21915 }));
21916 },
21917 );
21918 editor.nav_history = nav_history;
21919 });
21920 }
21921 })
21922 });
21923 }
21924
21925 // For now, don't allow opening excerpts in buffers that aren't backed by
21926 // regular project files.
21927 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21928 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21929 }
21930
21931 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21932 let snapshot = self.buffer.read(cx).read(cx);
21933 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21934 Some(
21935 ranges
21936 .iter()
21937 .map(move |range| {
21938 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21939 })
21940 .collect(),
21941 )
21942 }
21943
21944 fn selection_replacement_ranges(
21945 &self,
21946 range: Range<MultiBufferOffsetUtf16>,
21947 cx: &mut App,
21948 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21949 let selections = self
21950 .selections
21951 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21952 let newest_selection = selections
21953 .iter()
21954 .max_by_key(|selection| selection.id)
21955 .unwrap();
21956 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21957 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21958 let snapshot = self.buffer.read(cx).read(cx);
21959 selections
21960 .into_iter()
21961 .map(|mut selection| {
21962 selection.start.0.0 =
21963 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21964 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21965 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21966 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21967 })
21968 .collect()
21969 }
21970
21971 fn report_editor_event(
21972 &self,
21973 reported_event: ReportEditorEvent,
21974 file_extension: Option<String>,
21975 cx: &App,
21976 ) {
21977 if cfg!(any(test, feature = "test-support")) {
21978 return;
21979 }
21980
21981 let Some(project) = &self.project else { return };
21982
21983 // If None, we are in a file without an extension
21984 let file = self
21985 .buffer
21986 .read(cx)
21987 .as_singleton()
21988 .and_then(|b| b.read(cx).file());
21989 let file_extension = file_extension.or(file
21990 .as_ref()
21991 .and_then(|file| Path::new(file.file_name(cx)).extension())
21992 .and_then(|e| e.to_str())
21993 .map(|a| a.to_string()));
21994
21995 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21996 .map(|vim_mode| vim_mode.0)
21997 .unwrap_or(false);
21998
21999 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22000 let copilot_enabled = edit_predictions_provider
22001 == language::language_settings::EditPredictionProvider::Copilot;
22002 let copilot_enabled_for_language = self
22003 .buffer
22004 .read(cx)
22005 .language_settings(cx)
22006 .show_edit_predictions;
22007
22008 let project = project.read(cx);
22009 let event_type = reported_event.event_type();
22010
22011 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22012 telemetry::event!(
22013 event_type,
22014 type = if auto_saved {"autosave"} else {"manual"},
22015 file_extension,
22016 vim_mode,
22017 copilot_enabled,
22018 copilot_enabled_for_language,
22019 edit_predictions_provider,
22020 is_via_ssh = project.is_via_remote_server(),
22021 );
22022 } else {
22023 telemetry::event!(
22024 event_type,
22025 file_extension,
22026 vim_mode,
22027 copilot_enabled,
22028 copilot_enabled_for_language,
22029 edit_predictions_provider,
22030 is_via_ssh = project.is_via_remote_server(),
22031 );
22032 };
22033 }
22034
22035 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22036 /// with each line being an array of {text, highlight} objects.
22037 fn copy_highlight_json(
22038 &mut self,
22039 _: &CopyHighlightJson,
22040 window: &mut Window,
22041 cx: &mut Context<Self>,
22042 ) {
22043 #[derive(Serialize)]
22044 struct Chunk<'a> {
22045 text: String,
22046 highlight: Option<&'a str>,
22047 }
22048
22049 let snapshot = self.buffer.read(cx).snapshot(cx);
22050 let range = self
22051 .selected_text_range(false, window, cx)
22052 .and_then(|selection| {
22053 if selection.range.is_empty() {
22054 None
22055 } else {
22056 Some(
22057 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22058 selection.range.start,
22059 )))
22060 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22061 selection.range.end,
22062 ))),
22063 )
22064 }
22065 })
22066 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22067
22068 let chunks = snapshot.chunks(range, true);
22069 let mut lines = Vec::new();
22070 let mut line: VecDeque<Chunk> = VecDeque::new();
22071
22072 let Some(style) = self.style.as_ref() else {
22073 return;
22074 };
22075
22076 for chunk in chunks {
22077 let highlight = chunk
22078 .syntax_highlight_id
22079 .and_then(|id| id.name(&style.syntax));
22080 let mut chunk_lines = chunk.text.split('\n').peekable();
22081 while let Some(text) = chunk_lines.next() {
22082 let mut merged_with_last_token = false;
22083 if let Some(last_token) = line.back_mut()
22084 && last_token.highlight == highlight
22085 {
22086 last_token.text.push_str(text);
22087 merged_with_last_token = true;
22088 }
22089
22090 if !merged_with_last_token {
22091 line.push_back(Chunk {
22092 text: text.into(),
22093 highlight,
22094 });
22095 }
22096
22097 if chunk_lines.peek().is_some() {
22098 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22099 line.pop_front();
22100 }
22101 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22102 line.pop_back();
22103 }
22104
22105 lines.push(mem::take(&mut line));
22106 }
22107 }
22108 }
22109
22110 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22111 return;
22112 };
22113 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22114 }
22115
22116 pub fn open_context_menu(
22117 &mut self,
22118 _: &OpenContextMenu,
22119 window: &mut Window,
22120 cx: &mut Context<Self>,
22121 ) {
22122 self.request_autoscroll(Autoscroll::newest(), cx);
22123 let position = self
22124 .selections
22125 .newest_display(&self.display_snapshot(cx))
22126 .start;
22127 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22128 }
22129
22130 pub fn replay_insert_event(
22131 &mut self,
22132 text: &str,
22133 relative_utf16_range: Option<Range<isize>>,
22134 window: &mut Window,
22135 cx: &mut Context<Self>,
22136 ) {
22137 if !self.input_enabled {
22138 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22139 return;
22140 }
22141 if let Some(relative_utf16_range) = relative_utf16_range {
22142 let selections = self
22143 .selections
22144 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22145 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22146 let new_ranges = selections.into_iter().map(|range| {
22147 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22148 range
22149 .head()
22150 .0
22151 .0
22152 .saturating_add_signed(relative_utf16_range.start),
22153 ));
22154 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22155 range
22156 .head()
22157 .0
22158 .0
22159 .saturating_add_signed(relative_utf16_range.end),
22160 ));
22161 start..end
22162 });
22163 s.select_ranges(new_ranges);
22164 });
22165 }
22166
22167 self.handle_input(text, window, cx);
22168 }
22169
22170 pub fn is_focused(&self, window: &Window) -> bool {
22171 self.focus_handle.is_focused(window)
22172 }
22173
22174 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22175 cx.emit(EditorEvent::Focused);
22176
22177 if let Some(descendant) = self
22178 .last_focused_descendant
22179 .take()
22180 .and_then(|descendant| descendant.upgrade())
22181 {
22182 window.focus(&descendant);
22183 } else {
22184 if let Some(blame) = self.blame.as_ref() {
22185 blame.update(cx, GitBlame::focus)
22186 }
22187
22188 self.blink_manager.update(cx, BlinkManager::enable);
22189 self.show_cursor_names(window, cx);
22190 self.buffer.update(cx, |buffer, cx| {
22191 buffer.finalize_last_transaction(cx);
22192 if self.leader_id.is_none() {
22193 buffer.set_active_selections(
22194 &self.selections.disjoint_anchors_arc(),
22195 self.selections.line_mode(),
22196 self.cursor_shape,
22197 cx,
22198 );
22199 }
22200 });
22201
22202 if let Some(position_map) = self.last_position_map.clone() {
22203 EditorElement::mouse_moved(
22204 self,
22205 &MouseMoveEvent {
22206 position: window.mouse_position(),
22207 pressed_button: None,
22208 modifiers: window.modifiers(),
22209 },
22210 &position_map,
22211 window,
22212 cx,
22213 );
22214 }
22215 }
22216 }
22217
22218 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22219 cx.emit(EditorEvent::FocusedIn)
22220 }
22221
22222 fn handle_focus_out(
22223 &mut self,
22224 event: FocusOutEvent,
22225 _window: &mut Window,
22226 cx: &mut Context<Self>,
22227 ) {
22228 if event.blurred != self.focus_handle {
22229 self.last_focused_descendant = Some(event.blurred);
22230 }
22231 self.selection_drag_state = SelectionDragState::None;
22232 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22233 }
22234
22235 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22236 self.blink_manager.update(cx, BlinkManager::disable);
22237 self.buffer
22238 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22239
22240 if let Some(blame) = self.blame.as_ref() {
22241 blame.update(cx, GitBlame::blur)
22242 }
22243 if !self.hover_state.focused(window, cx) {
22244 hide_hover(self, cx);
22245 }
22246 if !self
22247 .context_menu
22248 .borrow()
22249 .as_ref()
22250 .is_some_and(|context_menu| context_menu.focused(window, cx))
22251 {
22252 self.hide_context_menu(window, cx);
22253 }
22254 self.take_active_edit_prediction(cx);
22255 cx.emit(EditorEvent::Blurred);
22256 cx.notify();
22257 }
22258
22259 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22260 let mut pending: String = window
22261 .pending_input_keystrokes()
22262 .into_iter()
22263 .flatten()
22264 .filter_map(|keystroke| keystroke.key_char.clone())
22265 .collect();
22266
22267 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22268 pending = "".to_string();
22269 }
22270
22271 let existing_pending = self
22272 .text_highlights::<PendingInput>(cx)
22273 .map(|(_, ranges)| ranges.to_vec());
22274 if existing_pending.is_none() && pending.is_empty() {
22275 return;
22276 }
22277 let transaction =
22278 self.transact(window, cx, |this, window, cx| {
22279 let selections = this
22280 .selections
22281 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22282 let edits = selections
22283 .iter()
22284 .map(|selection| (selection.end..selection.end, pending.clone()));
22285 this.edit(edits, cx);
22286 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22287 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22288 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22289 }));
22290 });
22291 if let Some(existing_ranges) = existing_pending {
22292 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22293 this.edit(edits, cx);
22294 }
22295 });
22296
22297 let snapshot = self.snapshot(window, cx);
22298 let ranges = self
22299 .selections
22300 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22301 .into_iter()
22302 .map(|selection| {
22303 snapshot.buffer_snapshot().anchor_after(selection.end)
22304 ..snapshot
22305 .buffer_snapshot()
22306 .anchor_before(selection.end + pending.len())
22307 })
22308 .collect();
22309
22310 if pending.is_empty() {
22311 self.clear_highlights::<PendingInput>(cx);
22312 } else {
22313 self.highlight_text::<PendingInput>(
22314 ranges,
22315 HighlightStyle {
22316 underline: Some(UnderlineStyle {
22317 thickness: px(1.),
22318 color: None,
22319 wavy: false,
22320 }),
22321 ..Default::default()
22322 },
22323 cx,
22324 );
22325 }
22326
22327 self.ime_transaction = self.ime_transaction.or(transaction);
22328 if let Some(transaction) = self.ime_transaction {
22329 self.buffer.update(cx, |buffer, cx| {
22330 buffer.group_until_transaction(transaction, cx);
22331 });
22332 }
22333
22334 if self.text_highlights::<PendingInput>(cx).is_none() {
22335 self.ime_transaction.take();
22336 }
22337 }
22338
22339 pub fn register_action_renderer(
22340 &mut self,
22341 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22342 ) -> Subscription {
22343 let id = self.next_editor_action_id.post_inc();
22344 self.editor_actions
22345 .borrow_mut()
22346 .insert(id, Box::new(listener));
22347
22348 let editor_actions = self.editor_actions.clone();
22349 Subscription::new(move || {
22350 editor_actions.borrow_mut().remove(&id);
22351 })
22352 }
22353
22354 pub fn register_action<A: Action>(
22355 &mut self,
22356 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22357 ) -> Subscription {
22358 let id = self.next_editor_action_id.post_inc();
22359 let listener = Arc::new(listener);
22360 self.editor_actions.borrow_mut().insert(
22361 id,
22362 Box::new(move |_, window, _| {
22363 let listener = listener.clone();
22364 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22365 let action = action.downcast_ref().unwrap();
22366 if phase == DispatchPhase::Bubble {
22367 listener(action, window, cx)
22368 }
22369 })
22370 }),
22371 );
22372
22373 let editor_actions = self.editor_actions.clone();
22374 Subscription::new(move || {
22375 editor_actions.borrow_mut().remove(&id);
22376 })
22377 }
22378
22379 pub fn file_header_size(&self) -> u32 {
22380 FILE_HEADER_HEIGHT
22381 }
22382
22383 pub fn restore(
22384 &mut self,
22385 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22386 window: &mut Window,
22387 cx: &mut Context<Self>,
22388 ) {
22389 let workspace = self.workspace();
22390 let project = self.project();
22391 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22392 let mut tasks = Vec::new();
22393 for (buffer_id, changes) in revert_changes {
22394 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22395 buffer.update(cx, |buffer, cx| {
22396 buffer.edit(
22397 changes
22398 .into_iter()
22399 .map(|(range, text)| (range, text.to_string())),
22400 None,
22401 cx,
22402 );
22403 });
22404
22405 if let Some(project) =
22406 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22407 {
22408 project.update(cx, |project, cx| {
22409 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22410 })
22411 }
22412 }
22413 }
22414 tasks
22415 });
22416 cx.spawn_in(window, async move |_, cx| {
22417 for (buffer, task) in save_tasks {
22418 let result = task.await;
22419 if result.is_err() {
22420 let Some(path) = buffer
22421 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22422 .ok()
22423 else {
22424 continue;
22425 };
22426 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22427 let Some(task) = cx
22428 .update_window_entity(workspace, |workspace, window, cx| {
22429 workspace
22430 .open_path_preview(path, None, false, false, false, window, cx)
22431 })
22432 .ok()
22433 else {
22434 continue;
22435 };
22436 task.await.log_err();
22437 }
22438 }
22439 }
22440 })
22441 .detach();
22442 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22443 selections.refresh()
22444 });
22445 }
22446
22447 pub fn to_pixel_point(
22448 &self,
22449 source: multi_buffer::Anchor,
22450 editor_snapshot: &EditorSnapshot,
22451 window: &mut Window,
22452 ) -> Option<gpui::Point<Pixels>> {
22453 let source_point = source.to_display_point(editor_snapshot);
22454 self.display_to_pixel_point(source_point, editor_snapshot, window)
22455 }
22456
22457 pub fn display_to_pixel_point(
22458 &self,
22459 source: DisplayPoint,
22460 editor_snapshot: &EditorSnapshot,
22461 window: &mut Window,
22462 ) -> Option<gpui::Point<Pixels>> {
22463 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22464 let text_layout_details = self.text_layout_details(window);
22465 let scroll_top = text_layout_details
22466 .scroll_anchor
22467 .scroll_position(editor_snapshot)
22468 .y;
22469
22470 if source.row().as_f64() < scroll_top.floor() {
22471 return None;
22472 }
22473 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22474 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22475 Some(gpui::Point::new(source_x, source_y))
22476 }
22477
22478 pub fn has_visible_completions_menu(&self) -> bool {
22479 !self.edit_prediction_preview_is_active()
22480 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22481 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22482 })
22483 }
22484
22485 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22486 if self.mode.is_minimap() {
22487 return;
22488 }
22489 self.addons
22490 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22491 }
22492
22493 pub fn unregister_addon<T: Addon>(&mut self) {
22494 self.addons.remove(&std::any::TypeId::of::<T>());
22495 }
22496
22497 pub fn addon<T: Addon>(&self) -> Option<&T> {
22498 let type_id = std::any::TypeId::of::<T>();
22499 self.addons
22500 .get(&type_id)
22501 .and_then(|item| item.to_any().downcast_ref::<T>())
22502 }
22503
22504 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22505 let type_id = std::any::TypeId::of::<T>();
22506 self.addons
22507 .get_mut(&type_id)
22508 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22509 }
22510
22511 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22512 let text_layout_details = self.text_layout_details(window);
22513 let style = &text_layout_details.editor_style;
22514 let font_id = window.text_system().resolve_font(&style.text.font());
22515 let font_size = style.text.font_size.to_pixels(window.rem_size());
22516 let line_height = style.text.line_height_in_pixels(window.rem_size());
22517 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22518 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22519
22520 CharacterDimensions {
22521 em_width,
22522 em_advance,
22523 line_height,
22524 }
22525 }
22526
22527 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22528 self.load_diff_task.clone()
22529 }
22530
22531 fn read_metadata_from_db(
22532 &mut self,
22533 item_id: u64,
22534 workspace_id: WorkspaceId,
22535 window: &mut Window,
22536 cx: &mut Context<Editor>,
22537 ) {
22538 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22539 && !self.mode.is_minimap()
22540 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22541 {
22542 let buffer_snapshot = OnceCell::new();
22543
22544 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22545 && !folds.is_empty()
22546 {
22547 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22548 self.fold_ranges(
22549 folds
22550 .into_iter()
22551 .map(|(start, end)| {
22552 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22553 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22554 })
22555 .collect(),
22556 false,
22557 window,
22558 cx,
22559 );
22560 }
22561
22562 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22563 && !selections.is_empty()
22564 {
22565 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22566 // skip adding the initial selection to selection history
22567 self.selection_history.mode = SelectionHistoryMode::Skipping;
22568 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22569 s.select_ranges(selections.into_iter().map(|(start, end)| {
22570 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22571 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22572 }));
22573 });
22574 self.selection_history.mode = SelectionHistoryMode::Normal;
22575 };
22576 }
22577
22578 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22579 }
22580
22581 fn update_lsp_data(
22582 &mut self,
22583 for_buffer: Option<BufferId>,
22584 window: &mut Window,
22585 cx: &mut Context<'_, Self>,
22586 ) {
22587 self.pull_diagnostics(for_buffer, window, cx);
22588 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22589 }
22590
22591 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22592 if self.ignore_lsp_data() {
22593 return;
22594 }
22595 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22596 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22597 }
22598 }
22599
22600 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22601 if self.ignore_lsp_data() {
22602 return;
22603 }
22604
22605 if !self.registered_buffers.contains_key(&buffer_id)
22606 && let Some(project) = self.project.as_ref()
22607 {
22608 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22609 project.update(cx, |project, cx| {
22610 self.registered_buffers.insert(
22611 buffer_id,
22612 project.register_buffer_with_language_servers(&buffer, cx),
22613 );
22614 });
22615 } else {
22616 self.registered_buffers.remove(&buffer_id);
22617 }
22618 }
22619 }
22620
22621 fn ignore_lsp_data(&self) -> bool {
22622 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22623 // skip any LSP updates for it.
22624 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22625 }
22626}
22627
22628fn edit_for_markdown_paste<'a>(
22629 buffer: &MultiBufferSnapshot,
22630 range: Range<MultiBufferOffset>,
22631 to_insert: &'a str,
22632 url: Option<url::Url>,
22633) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22634 if url.is_none() {
22635 return (range, Cow::Borrowed(to_insert));
22636 };
22637
22638 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22639
22640 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22641 Cow::Borrowed(to_insert)
22642 } else {
22643 Cow::Owned(format!("[{old_text}]({to_insert})"))
22644 };
22645 (range, new_text)
22646}
22647
22648fn process_completion_for_edit(
22649 completion: &Completion,
22650 intent: CompletionIntent,
22651 buffer: &Entity<Buffer>,
22652 cursor_position: &text::Anchor,
22653 cx: &mut Context<Editor>,
22654) -> CompletionEdit {
22655 let buffer = buffer.read(cx);
22656 let buffer_snapshot = buffer.snapshot();
22657 let (snippet, new_text) = if completion.is_snippet() {
22658 let mut snippet_source = completion.new_text.clone();
22659 // Workaround for typescript language server issues so that methods don't expand within
22660 // strings and functions with type expressions. The previous point is used because the query
22661 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22662 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22663 let previous_point = if previous_point.column > 0 {
22664 cursor_position.to_previous_offset(&buffer_snapshot)
22665 } else {
22666 cursor_position.to_offset(&buffer_snapshot)
22667 };
22668 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22669 && scope.prefers_label_for_snippet_in_completion()
22670 && let Some(label) = completion.label()
22671 && matches!(
22672 completion.kind(),
22673 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22674 )
22675 {
22676 snippet_source = label;
22677 }
22678 match Snippet::parse(&snippet_source).log_err() {
22679 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22680 None => (None, completion.new_text.clone()),
22681 }
22682 } else {
22683 (None, completion.new_text.clone())
22684 };
22685
22686 let mut range_to_replace = {
22687 let replace_range = &completion.replace_range;
22688 if let CompletionSource::Lsp {
22689 insert_range: Some(insert_range),
22690 ..
22691 } = &completion.source
22692 {
22693 debug_assert_eq!(
22694 insert_range.start, replace_range.start,
22695 "insert_range and replace_range should start at the same position"
22696 );
22697 debug_assert!(
22698 insert_range
22699 .start
22700 .cmp(cursor_position, &buffer_snapshot)
22701 .is_le(),
22702 "insert_range should start before or at cursor position"
22703 );
22704 debug_assert!(
22705 replace_range
22706 .start
22707 .cmp(cursor_position, &buffer_snapshot)
22708 .is_le(),
22709 "replace_range should start before or at cursor position"
22710 );
22711
22712 let should_replace = match intent {
22713 CompletionIntent::CompleteWithInsert => false,
22714 CompletionIntent::CompleteWithReplace => true,
22715 CompletionIntent::Complete | CompletionIntent::Compose => {
22716 let insert_mode =
22717 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22718 .completions
22719 .lsp_insert_mode;
22720 match insert_mode {
22721 LspInsertMode::Insert => false,
22722 LspInsertMode::Replace => true,
22723 LspInsertMode::ReplaceSubsequence => {
22724 let mut text_to_replace = buffer.chars_for_range(
22725 buffer.anchor_before(replace_range.start)
22726 ..buffer.anchor_after(replace_range.end),
22727 );
22728 let mut current_needle = text_to_replace.next();
22729 for haystack_ch in completion.label.text.chars() {
22730 if let Some(needle_ch) = current_needle
22731 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22732 {
22733 current_needle = text_to_replace.next();
22734 }
22735 }
22736 current_needle.is_none()
22737 }
22738 LspInsertMode::ReplaceSuffix => {
22739 if replace_range
22740 .end
22741 .cmp(cursor_position, &buffer_snapshot)
22742 .is_gt()
22743 {
22744 let range_after_cursor = *cursor_position..replace_range.end;
22745 let text_after_cursor = buffer
22746 .text_for_range(
22747 buffer.anchor_before(range_after_cursor.start)
22748 ..buffer.anchor_after(range_after_cursor.end),
22749 )
22750 .collect::<String>()
22751 .to_ascii_lowercase();
22752 completion
22753 .label
22754 .text
22755 .to_ascii_lowercase()
22756 .ends_with(&text_after_cursor)
22757 } else {
22758 true
22759 }
22760 }
22761 }
22762 }
22763 };
22764
22765 if should_replace {
22766 replace_range.clone()
22767 } else {
22768 insert_range.clone()
22769 }
22770 } else {
22771 replace_range.clone()
22772 }
22773 };
22774
22775 if range_to_replace
22776 .end
22777 .cmp(cursor_position, &buffer_snapshot)
22778 .is_lt()
22779 {
22780 range_to_replace.end = *cursor_position;
22781 }
22782
22783 let replace_range = range_to_replace.to_offset(buffer);
22784 CompletionEdit {
22785 new_text,
22786 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22787 snippet,
22788 }
22789}
22790
22791struct CompletionEdit {
22792 new_text: String,
22793 replace_range: Range<BufferOffset>,
22794 snippet: Option<Snippet>,
22795}
22796
22797fn insert_extra_newline_brackets(
22798 buffer: &MultiBufferSnapshot,
22799 range: Range<MultiBufferOffset>,
22800 language: &language::LanguageScope,
22801) -> bool {
22802 let leading_whitespace_len = buffer
22803 .reversed_chars_at(range.start)
22804 .take_while(|c| c.is_whitespace() && *c != '\n')
22805 .map(|c| c.len_utf8())
22806 .sum::<usize>();
22807 let trailing_whitespace_len = buffer
22808 .chars_at(range.end)
22809 .take_while(|c| c.is_whitespace() && *c != '\n')
22810 .map(|c| c.len_utf8())
22811 .sum::<usize>();
22812 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22813
22814 language.brackets().any(|(pair, enabled)| {
22815 let pair_start = pair.start.trim_end();
22816 let pair_end = pair.end.trim_start();
22817
22818 enabled
22819 && pair.newline
22820 && buffer.contains_str_at(range.end, pair_end)
22821 && buffer.contains_str_at(
22822 range.start.saturating_sub_usize(pair_start.len()),
22823 pair_start,
22824 )
22825 })
22826}
22827
22828fn insert_extra_newline_tree_sitter(
22829 buffer: &MultiBufferSnapshot,
22830 range: Range<MultiBufferOffset>,
22831) -> bool {
22832 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22833 [(buffer, range, _)] => (*buffer, range.clone()),
22834 _ => return false,
22835 };
22836 let pair = {
22837 let mut result: Option<BracketMatch<usize>> = None;
22838
22839 for pair in buffer
22840 .all_bracket_ranges(range.start.0..range.end.0)
22841 .filter(move |pair| {
22842 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22843 })
22844 {
22845 let len = pair.close_range.end - pair.open_range.start;
22846
22847 if let Some(existing) = &result {
22848 let existing_len = existing.close_range.end - existing.open_range.start;
22849 if len > existing_len {
22850 continue;
22851 }
22852 }
22853
22854 result = Some(pair);
22855 }
22856
22857 result
22858 };
22859 let Some(pair) = pair else {
22860 return false;
22861 };
22862 pair.newline_only
22863 && buffer
22864 .chars_for_range(pair.open_range.end..range.start.0)
22865 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22866 .all(|c| c.is_whitespace() && c != '\n')
22867}
22868
22869fn update_uncommitted_diff_for_buffer(
22870 editor: Entity<Editor>,
22871 project: &Entity<Project>,
22872 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22873 buffer: Entity<MultiBuffer>,
22874 cx: &mut App,
22875) -> Task<()> {
22876 let mut tasks = Vec::new();
22877 project.update(cx, |project, cx| {
22878 for buffer in buffers {
22879 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22880 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22881 }
22882 }
22883 });
22884 cx.spawn(async move |cx| {
22885 let diffs = future::join_all(tasks).await;
22886 if editor
22887 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22888 .unwrap_or(false)
22889 {
22890 return;
22891 }
22892
22893 buffer
22894 .update(cx, |buffer, cx| {
22895 for diff in diffs.into_iter().flatten() {
22896 buffer.add_diff(diff, cx);
22897 }
22898 })
22899 .ok();
22900 })
22901}
22902
22903fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22904 let tab_size = tab_size.get() as usize;
22905 let mut width = offset;
22906
22907 for ch in text.chars() {
22908 width += if ch == '\t' {
22909 tab_size - (width % tab_size)
22910 } else {
22911 1
22912 };
22913 }
22914
22915 width - offset
22916}
22917
22918#[cfg(test)]
22919mod tests {
22920 use super::*;
22921
22922 #[test]
22923 fn test_string_size_with_expanded_tabs() {
22924 let nz = |val| NonZeroU32::new(val).unwrap();
22925 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22926 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22927 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22928 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22929 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22930 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22931 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22932 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22933 }
22934}
22935
22936/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22937struct WordBreakingTokenizer<'a> {
22938 input: &'a str,
22939}
22940
22941impl<'a> WordBreakingTokenizer<'a> {
22942 fn new(input: &'a str) -> Self {
22943 Self { input }
22944 }
22945}
22946
22947fn is_char_ideographic(ch: char) -> bool {
22948 use unicode_script::Script::*;
22949 use unicode_script::UnicodeScript;
22950 matches!(ch.script(), Han | Tangut | Yi)
22951}
22952
22953fn is_grapheme_ideographic(text: &str) -> bool {
22954 text.chars().any(is_char_ideographic)
22955}
22956
22957fn is_grapheme_whitespace(text: &str) -> bool {
22958 text.chars().any(|x| x.is_whitespace())
22959}
22960
22961fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22962 text.chars()
22963 .next()
22964 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22965}
22966
22967#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22968enum WordBreakToken<'a> {
22969 Word { token: &'a str, grapheme_len: usize },
22970 InlineWhitespace { token: &'a str, grapheme_len: usize },
22971 Newline,
22972}
22973
22974impl<'a> Iterator for WordBreakingTokenizer<'a> {
22975 /// Yields a span, the count of graphemes in the token, and whether it was
22976 /// whitespace. Note that it also breaks at word boundaries.
22977 type Item = WordBreakToken<'a>;
22978
22979 fn next(&mut self) -> Option<Self::Item> {
22980 use unicode_segmentation::UnicodeSegmentation;
22981 if self.input.is_empty() {
22982 return None;
22983 }
22984
22985 let mut iter = self.input.graphemes(true).peekable();
22986 let mut offset = 0;
22987 let mut grapheme_len = 0;
22988 if let Some(first_grapheme) = iter.next() {
22989 let is_newline = first_grapheme == "\n";
22990 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22991 offset += first_grapheme.len();
22992 grapheme_len += 1;
22993 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22994 if let Some(grapheme) = iter.peek().copied()
22995 && should_stay_with_preceding_ideograph(grapheme)
22996 {
22997 offset += grapheme.len();
22998 grapheme_len += 1;
22999 }
23000 } else {
23001 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23002 let mut next_word_bound = words.peek().copied();
23003 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23004 next_word_bound = words.next();
23005 }
23006 while let Some(grapheme) = iter.peek().copied() {
23007 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23008 break;
23009 };
23010 if is_grapheme_whitespace(grapheme) != is_whitespace
23011 || (grapheme == "\n") != is_newline
23012 {
23013 break;
23014 };
23015 offset += grapheme.len();
23016 grapheme_len += 1;
23017 iter.next();
23018 }
23019 }
23020 let token = &self.input[..offset];
23021 self.input = &self.input[offset..];
23022 if token == "\n" {
23023 Some(WordBreakToken::Newline)
23024 } else if is_whitespace {
23025 Some(WordBreakToken::InlineWhitespace {
23026 token,
23027 grapheme_len,
23028 })
23029 } else {
23030 Some(WordBreakToken::Word {
23031 token,
23032 grapheme_len,
23033 })
23034 }
23035 } else {
23036 None
23037 }
23038 }
23039}
23040
23041#[test]
23042fn test_word_breaking_tokenizer() {
23043 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23044 ("", &[]),
23045 (" ", &[whitespace(" ", 2)]),
23046 ("Ʒ", &[word("Ʒ", 1)]),
23047 ("Ǽ", &[word("Ǽ", 1)]),
23048 ("⋑", &[word("⋑", 1)]),
23049 ("⋑⋑", &[word("⋑⋑", 2)]),
23050 (
23051 "原理,进而",
23052 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23053 ),
23054 (
23055 "hello world",
23056 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23057 ),
23058 (
23059 "hello, world",
23060 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23061 ),
23062 (
23063 " hello world",
23064 &[
23065 whitespace(" ", 2),
23066 word("hello", 5),
23067 whitespace(" ", 1),
23068 word("world", 5),
23069 ],
23070 ),
23071 (
23072 "这是什么 \n 钢笔",
23073 &[
23074 word("这", 1),
23075 word("是", 1),
23076 word("什", 1),
23077 word("么", 1),
23078 whitespace(" ", 1),
23079 newline(),
23080 whitespace(" ", 1),
23081 word("钢", 1),
23082 word("笔", 1),
23083 ],
23084 ),
23085 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23086 ];
23087
23088 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23089 WordBreakToken::Word {
23090 token,
23091 grapheme_len,
23092 }
23093 }
23094
23095 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23096 WordBreakToken::InlineWhitespace {
23097 token,
23098 grapheme_len,
23099 }
23100 }
23101
23102 fn newline() -> WordBreakToken<'static> {
23103 WordBreakToken::Newline
23104 }
23105
23106 for (input, result) in tests {
23107 assert_eq!(
23108 WordBreakingTokenizer::new(input)
23109 .collect::<Vec<_>>()
23110 .as_slice(),
23111 *result,
23112 );
23113 }
23114}
23115
23116fn wrap_with_prefix(
23117 first_line_prefix: String,
23118 subsequent_lines_prefix: String,
23119 unwrapped_text: String,
23120 wrap_column: usize,
23121 tab_size: NonZeroU32,
23122 preserve_existing_whitespace: bool,
23123) -> String {
23124 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23125 let subsequent_lines_prefix_len =
23126 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23127 let mut wrapped_text = String::new();
23128 let mut current_line = first_line_prefix;
23129 let mut is_first_line = true;
23130
23131 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23132 let mut current_line_len = first_line_prefix_len;
23133 let mut in_whitespace = false;
23134 for token in tokenizer {
23135 let have_preceding_whitespace = in_whitespace;
23136 match token {
23137 WordBreakToken::Word {
23138 token,
23139 grapheme_len,
23140 } => {
23141 in_whitespace = false;
23142 let current_prefix_len = if is_first_line {
23143 first_line_prefix_len
23144 } else {
23145 subsequent_lines_prefix_len
23146 };
23147 if current_line_len + grapheme_len > wrap_column
23148 && current_line_len != current_prefix_len
23149 {
23150 wrapped_text.push_str(current_line.trim_end());
23151 wrapped_text.push('\n');
23152 is_first_line = false;
23153 current_line = subsequent_lines_prefix.clone();
23154 current_line_len = subsequent_lines_prefix_len;
23155 }
23156 current_line.push_str(token);
23157 current_line_len += grapheme_len;
23158 }
23159 WordBreakToken::InlineWhitespace {
23160 mut token,
23161 mut grapheme_len,
23162 } => {
23163 in_whitespace = true;
23164 if have_preceding_whitespace && !preserve_existing_whitespace {
23165 continue;
23166 }
23167 if !preserve_existing_whitespace {
23168 // Keep a single whitespace grapheme as-is
23169 if let Some(first) =
23170 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23171 {
23172 token = first;
23173 } else {
23174 token = " ";
23175 }
23176 grapheme_len = 1;
23177 }
23178 let current_prefix_len = if is_first_line {
23179 first_line_prefix_len
23180 } else {
23181 subsequent_lines_prefix_len
23182 };
23183 if current_line_len + grapheme_len > wrap_column {
23184 wrapped_text.push_str(current_line.trim_end());
23185 wrapped_text.push('\n');
23186 is_first_line = false;
23187 current_line = subsequent_lines_prefix.clone();
23188 current_line_len = subsequent_lines_prefix_len;
23189 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23190 current_line.push_str(token);
23191 current_line_len += grapheme_len;
23192 }
23193 }
23194 WordBreakToken::Newline => {
23195 in_whitespace = true;
23196 let current_prefix_len = if is_first_line {
23197 first_line_prefix_len
23198 } else {
23199 subsequent_lines_prefix_len
23200 };
23201 if preserve_existing_whitespace {
23202 wrapped_text.push_str(current_line.trim_end());
23203 wrapped_text.push('\n');
23204 is_first_line = false;
23205 current_line = subsequent_lines_prefix.clone();
23206 current_line_len = subsequent_lines_prefix_len;
23207 } else if have_preceding_whitespace {
23208 continue;
23209 } else if current_line_len + 1 > wrap_column
23210 && current_line_len != current_prefix_len
23211 {
23212 wrapped_text.push_str(current_line.trim_end());
23213 wrapped_text.push('\n');
23214 is_first_line = false;
23215 current_line = subsequent_lines_prefix.clone();
23216 current_line_len = subsequent_lines_prefix_len;
23217 } else if current_line_len != current_prefix_len {
23218 current_line.push(' ');
23219 current_line_len += 1;
23220 }
23221 }
23222 }
23223 }
23224
23225 if !current_line.is_empty() {
23226 wrapped_text.push_str(¤t_line);
23227 }
23228 wrapped_text
23229}
23230
23231#[test]
23232fn test_wrap_with_prefix() {
23233 assert_eq!(
23234 wrap_with_prefix(
23235 "# ".to_string(),
23236 "# ".to_string(),
23237 "abcdefg".to_string(),
23238 4,
23239 NonZeroU32::new(4).unwrap(),
23240 false,
23241 ),
23242 "# abcdefg"
23243 );
23244 assert_eq!(
23245 wrap_with_prefix(
23246 "".to_string(),
23247 "".to_string(),
23248 "\thello world".to_string(),
23249 8,
23250 NonZeroU32::new(4).unwrap(),
23251 false,
23252 ),
23253 "hello\nworld"
23254 );
23255 assert_eq!(
23256 wrap_with_prefix(
23257 "// ".to_string(),
23258 "// ".to_string(),
23259 "xx \nyy zz aa bb cc".to_string(),
23260 12,
23261 NonZeroU32::new(4).unwrap(),
23262 false,
23263 ),
23264 "// xx yy zz\n// aa bb cc"
23265 );
23266 assert_eq!(
23267 wrap_with_prefix(
23268 String::new(),
23269 String::new(),
23270 "这是什么 \n 钢笔".to_string(),
23271 3,
23272 NonZeroU32::new(4).unwrap(),
23273 false,
23274 ),
23275 "这是什\n么 钢\n笔"
23276 );
23277 assert_eq!(
23278 wrap_with_prefix(
23279 String::new(),
23280 String::new(),
23281 format!("foo{}bar", '\u{2009}'), // thin space
23282 80,
23283 NonZeroU32::new(4).unwrap(),
23284 false,
23285 ),
23286 format!("foo{}bar", '\u{2009}')
23287 );
23288}
23289
23290pub trait CollaborationHub {
23291 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23292 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23293 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23294}
23295
23296impl CollaborationHub for Entity<Project> {
23297 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23298 self.read(cx).collaborators()
23299 }
23300
23301 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23302 self.read(cx).user_store().read(cx).participant_indices()
23303 }
23304
23305 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23306 let this = self.read(cx);
23307 let user_ids = this.collaborators().values().map(|c| c.user_id);
23308 this.user_store().read(cx).participant_names(user_ids, cx)
23309 }
23310}
23311
23312pub trait SemanticsProvider {
23313 fn hover(
23314 &self,
23315 buffer: &Entity<Buffer>,
23316 position: text::Anchor,
23317 cx: &mut App,
23318 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23319
23320 fn inline_values(
23321 &self,
23322 buffer_handle: Entity<Buffer>,
23323 range: Range<text::Anchor>,
23324 cx: &mut App,
23325 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23326
23327 fn applicable_inlay_chunks(
23328 &self,
23329 buffer: &Entity<Buffer>,
23330 ranges: &[Range<text::Anchor>],
23331 cx: &mut App,
23332 ) -> Vec<Range<BufferRow>>;
23333
23334 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23335
23336 fn inlay_hints(
23337 &self,
23338 invalidate: InvalidationStrategy,
23339 buffer: Entity<Buffer>,
23340 ranges: Vec<Range<text::Anchor>>,
23341 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23342 cx: &mut App,
23343 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23344
23345 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23346
23347 fn document_highlights(
23348 &self,
23349 buffer: &Entity<Buffer>,
23350 position: text::Anchor,
23351 cx: &mut App,
23352 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23353
23354 fn definitions(
23355 &self,
23356 buffer: &Entity<Buffer>,
23357 position: text::Anchor,
23358 kind: GotoDefinitionKind,
23359 cx: &mut App,
23360 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23361
23362 fn range_for_rename(
23363 &self,
23364 buffer: &Entity<Buffer>,
23365 position: text::Anchor,
23366 cx: &mut App,
23367 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23368
23369 fn perform_rename(
23370 &self,
23371 buffer: &Entity<Buffer>,
23372 position: text::Anchor,
23373 new_name: String,
23374 cx: &mut App,
23375 ) -> Option<Task<Result<ProjectTransaction>>>;
23376}
23377
23378pub trait CompletionProvider {
23379 fn completions(
23380 &self,
23381 excerpt_id: ExcerptId,
23382 buffer: &Entity<Buffer>,
23383 buffer_position: text::Anchor,
23384 trigger: CompletionContext,
23385 window: &mut Window,
23386 cx: &mut Context<Editor>,
23387 ) -> Task<Result<Vec<CompletionResponse>>>;
23388
23389 fn resolve_completions(
23390 &self,
23391 _buffer: Entity<Buffer>,
23392 _completion_indices: Vec<usize>,
23393 _completions: Rc<RefCell<Box<[Completion]>>>,
23394 _cx: &mut Context<Editor>,
23395 ) -> Task<Result<bool>> {
23396 Task::ready(Ok(false))
23397 }
23398
23399 fn apply_additional_edits_for_completion(
23400 &self,
23401 _buffer: Entity<Buffer>,
23402 _completions: Rc<RefCell<Box<[Completion]>>>,
23403 _completion_index: usize,
23404 _push_to_history: bool,
23405 _cx: &mut Context<Editor>,
23406 ) -> Task<Result<Option<language::Transaction>>> {
23407 Task::ready(Ok(None))
23408 }
23409
23410 fn is_completion_trigger(
23411 &self,
23412 buffer: &Entity<Buffer>,
23413 position: language::Anchor,
23414 text: &str,
23415 trigger_in_words: bool,
23416 menu_is_open: bool,
23417 cx: &mut Context<Editor>,
23418 ) -> bool;
23419
23420 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23421
23422 fn sort_completions(&self) -> bool {
23423 true
23424 }
23425
23426 fn filter_completions(&self) -> bool {
23427 true
23428 }
23429
23430 fn show_snippets(&self) -> bool {
23431 false
23432 }
23433}
23434
23435pub trait CodeActionProvider {
23436 fn id(&self) -> Arc<str>;
23437
23438 fn code_actions(
23439 &self,
23440 buffer: &Entity<Buffer>,
23441 range: Range<text::Anchor>,
23442 window: &mut Window,
23443 cx: &mut App,
23444 ) -> Task<Result<Vec<CodeAction>>>;
23445
23446 fn apply_code_action(
23447 &self,
23448 buffer_handle: Entity<Buffer>,
23449 action: CodeAction,
23450 excerpt_id: ExcerptId,
23451 push_to_history: bool,
23452 window: &mut Window,
23453 cx: &mut App,
23454 ) -> Task<Result<ProjectTransaction>>;
23455}
23456
23457impl CodeActionProvider for Entity<Project> {
23458 fn id(&self) -> Arc<str> {
23459 "project".into()
23460 }
23461
23462 fn code_actions(
23463 &self,
23464 buffer: &Entity<Buffer>,
23465 range: Range<text::Anchor>,
23466 _window: &mut Window,
23467 cx: &mut App,
23468 ) -> Task<Result<Vec<CodeAction>>> {
23469 self.update(cx, |project, cx| {
23470 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23471 let code_actions = project.code_actions(buffer, range, None, cx);
23472 cx.background_spawn(async move {
23473 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23474 Ok(code_lens_actions
23475 .context("code lens fetch")?
23476 .into_iter()
23477 .flatten()
23478 .chain(
23479 code_actions
23480 .context("code action fetch")?
23481 .into_iter()
23482 .flatten(),
23483 )
23484 .collect())
23485 })
23486 })
23487 }
23488
23489 fn apply_code_action(
23490 &self,
23491 buffer_handle: Entity<Buffer>,
23492 action: CodeAction,
23493 _excerpt_id: ExcerptId,
23494 push_to_history: bool,
23495 _window: &mut Window,
23496 cx: &mut App,
23497 ) -> Task<Result<ProjectTransaction>> {
23498 self.update(cx, |project, cx| {
23499 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23500 })
23501 }
23502}
23503
23504fn snippet_completions(
23505 project: &Project,
23506 buffer: &Entity<Buffer>,
23507 buffer_anchor: text::Anchor,
23508 classifier: CharClassifier,
23509 cx: &mut App,
23510) -> Task<Result<CompletionResponse>> {
23511 let languages = buffer.read(cx).languages_at(buffer_anchor);
23512 let snippet_store = project.snippets().read(cx);
23513
23514 let scopes: Vec<_> = languages
23515 .iter()
23516 .filter_map(|language| {
23517 let language_name = language.lsp_id();
23518 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23519
23520 if snippets.is_empty() {
23521 None
23522 } else {
23523 Some((language.default_scope(), snippets))
23524 }
23525 })
23526 .collect();
23527
23528 if scopes.is_empty() {
23529 return Task::ready(Ok(CompletionResponse {
23530 completions: vec![],
23531 display_options: CompletionDisplayOptions::default(),
23532 is_incomplete: false,
23533 }));
23534 }
23535
23536 let snapshot = buffer.read(cx).text_snapshot();
23537 let executor = cx.background_executor().clone();
23538
23539 cx.background_spawn(async move {
23540 let is_word_char = |c| classifier.is_word(c);
23541
23542 let mut is_incomplete = false;
23543 let mut completions: Vec<Completion> = Vec::new();
23544
23545 const MAX_PREFIX_LEN: usize = 128;
23546 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23547 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23548 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23549
23550 let max_buffer_window: String = snapshot
23551 .text_for_range(window_start..buffer_offset)
23552 .collect();
23553
23554 if max_buffer_window.is_empty() {
23555 return Ok(CompletionResponse {
23556 completions: vec![],
23557 display_options: CompletionDisplayOptions::default(),
23558 is_incomplete: true,
23559 });
23560 }
23561
23562 for (_scope, snippets) in scopes.into_iter() {
23563 // Sort snippets by word count to match longer snippet prefixes first.
23564 let mut sorted_snippet_candidates = snippets
23565 .iter()
23566 .enumerate()
23567 .flat_map(|(snippet_ix, snippet)| {
23568 snippet
23569 .prefix
23570 .iter()
23571 .enumerate()
23572 .map(move |(prefix_ix, prefix)| {
23573 let word_count =
23574 snippet_candidate_suffixes(prefix, is_word_char).count();
23575 ((snippet_ix, prefix_ix), prefix, word_count)
23576 })
23577 })
23578 .collect_vec();
23579 sorted_snippet_candidates
23580 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23581
23582 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23583
23584 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23585 .take(
23586 sorted_snippet_candidates
23587 .first()
23588 .map(|(_, _, word_count)| *word_count)
23589 .unwrap_or_default(),
23590 )
23591 .collect_vec();
23592
23593 const MAX_RESULTS: usize = 100;
23594 // Each match also remembers how many characters from the buffer it consumed
23595 let mut matches: Vec<(StringMatch, usize)> = vec![];
23596
23597 let mut snippet_list_cutoff_index = 0;
23598 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23599 let word_count = buffer_index + 1;
23600 // Increase `snippet_list_cutoff_index` until we have all of the
23601 // snippets with sufficiently many words.
23602 while sorted_snippet_candidates
23603 .get(snippet_list_cutoff_index)
23604 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23605 *snippet_word_count >= word_count
23606 })
23607 {
23608 snippet_list_cutoff_index += 1;
23609 }
23610
23611 // Take only the candidates with at least `word_count` many words
23612 let snippet_candidates_at_word_len =
23613 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23614
23615 let candidates = snippet_candidates_at_word_len
23616 .iter()
23617 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23618 .enumerate() // index in `sorted_snippet_candidates`
23619 // First char must match
23620 .filter(|(_ix, prefix)| {
23621 itertools::equal(
23622 prefix
23623 .chars()
23624 .next()
23625 .into_iter()
23626 .flat_map(|c| c.to_lowercase()),
23627 buffer_window
23628 .chars()
23629 .next()
23630 .into_iter()
23631 .flat_map(|c| c.to_lowercase()),
23632 )
23633 })
23634 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23635 .collect::<Vec<StringMatchCandidate>>();
23636
23637 matches.extend(
23638 fuzzy::match_strings(
23639 &candidates,
23640 &buffer_window,
23641 buffer_window.chars().any(|c| c.is_uppercase()),
23642 true,
23643 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23644 &Default::default(),
23645 executor.clone(),
23646 )
23647 .await
23648 .into_iter()
23649 .map(|string_match| (string_match, buffer_window.len())),
23650 );
23651
23652 if matches.len() >= MAX_RESULTS {
23653 break;
23654 }
23655 }
23656
23657 let to_lsp = |point: &text::Anchor| {
23658 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23659 point_to_lsp(end)
23660 };
23661 let lsp_end = to_lsp(&buffer_anchor);
23662
23663 if matches.len() >= MAX_RESULTS {
23664 is_incomplete = true;
23665 }
23666
23667 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23668 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23669 sorted_snippet_candidates[string_match.candidate_id];
23670 let snippet = &snippets[snippet_index];
23671 let start = buffer_offset - buffer_window_len;
23672 let start = snapshot.anchor_before(start);
23673 let range = start..buffer_anchor;
23674 let lsp_start = to_lsp(&start);
23675 let lsp_range = lsp::Range {
23676 start: lsp_start,
23677 end: lsp_end,
23678 };
23679 Completion {
23680 replace_range: range,
23681 new_text: snippet.body.clone(),
23682 source: CompletionSource::Lsp {
23683 insert_range: None,
23684 server_id: LanguageServerId(usize::MAX),
23685 resolved: true,
23686 lsp_completion: Box::new(lsp::CompletionItem {
23687 label: snippet.prefix.first().unwrap().clone(),
23688 kind: Some(CompletionItemKind::SNIPPET),
23689 label_details: snippet.description.as_ref().map(|description| {
23690 lsp::CompletionItemLabelDetails {
23691 detail: Some(description.clone()),
23692 description: None,
23693 }
23694 }),
23695 insert_text_format: Some(InsertTextFormat::SNIPPET),
23696 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23697 lsp::InsertReplaceEdit {
23698 new_text: snippet.body.clone(),
23699 insert: lsp_range,
23700 replace: lsp_range,
23701 },
23702 )),
23703 filter_text: Some(snippet.body.clone()),
23704 sort_text: Some(char::MAX.to_string()),
23705 ..lsp::CompletionItem::default()
23706 }),
23707 lsp_defaults: None,
23708 },
23709 label: CodeLabel {
23710 text: matching_prefix.clone(),
23711 runs: Vec::new(),
23712 filter_range: 0..matching_prefix.len(),
23713 },
23714 icon_path: None,
23715 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23716 single_line: snippet.name.clone().into(),
23717 plain_text: snippet
23718 .description
23719 .clone()
23720 .map(|description| description.into()),
23721 }),
23722 insert_text_mode: None,
23723 confirm: None,
23724 match_start: Some(start),
23725 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23726 }
23727 }));
23728 }
23729
23730 Ok(CompletionResponse {
23731 completions,
23732 display_options: CompletionDisplayOptions::default(),
23733 is_incomplete,
23734 })
23735 })
23736}
23737
23738impl CompletionProvider for Entity<Project> {
23739 fn completions(
23740 &self,
23741 _excerpt_id: ExcerptId,
23742 buffer: &Entity<Buffer>,
23743 buffer_position: text::Anchor,
23744 options: CompletionContext,
23745 _window: &mut Window,
23746 cx: &mut Context<Editor>,
23747 ) -> Task<Result<Vec<CompletionResponse>>> {
23748 self.update(cx, |project, cx| {
23749 let task = project.completions(buffer, buffer_position, options, cx);
23750 cx.background_spawn(task)
23751 })
23752 }
23753
23754 fn resolve_completions(
23755 &self,
23756 buffer: Entity<Buffer>,
23757 completion_indices: Vec<usize>,
23758 completions: Rc<RefCell<Box<[Completion]>>>,
23759 cx: &mut Context<Editor>,
23760 ) -> Task<Result<bool>> {
23761 self.update(cx, |project, cx| {
23762 project.lsp_store().update(cx, |lsp_store, cx| {
23763 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23764 })
23765 })
23766 }
23767
23768 fn apply_additional_edits_for_completion(
23769 &self,
23770 buffer: Entity<Buffer>,
23771 completions: Rc<RefCell<Box<[Completion]>>>,
23772 completion_index: usize,
23773 push_to_history: bool,
23774 cx: &mut Context<Editor>,
23775 ) -> Task<Result<Option<language::Transaction>>> {
23776 self.update(cx, |project, cx| {
23777 project.lsp_store().update(cx, |lsp_store, cx| {
23778 lsp_store.apply_additional_edits_for_completion(
23779 buffer,
23780 completions,
23781 completion_index,
23782 push_to_history,
23783 cx,
23784 )
23785 })
23786 })
23787 }
23788
23789 fn is_completion_trigger(
23790 &self,
23791 buffer: &Entity<Buffer>,
23792 position: language::Anchor,
23793 text: &str,
23794 trigger_in_words: bool,
23795 menu_is_open: bool,
23796 cx: &mut Context<Editor>,
23797 ) -> bool {
23798 let mut chars = text.chars();
23799 let char = if let Some(char) = chars.next() {
23800 char
23801 } else {
23802 return false;
23803 };
23804 if chars.next().is_some() {
23805 return false;
23806 }
23807
23808 let buffer = buffer.read(cx);
23809 let snapshot = buffer.snapshot();
23810 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23811 return false;
23812 }
23813 let classifier = snapshot
23814 .char_classifier_at(position)
23815 .scope_context(Some(CharScopeContext::Completion));
23816 if trigger_in_words && classifier.is_word(char) {
23817 return true;
23818 }
23819
23820 buffer.completion_triggers().contains(text)
23821 }
23822
23823 fn show_snippets(&self) -> bool {
23824 true
23825 }
23826}
23827
23828impl SemanticsProvider for Entity<Project> {
23829 fn hover(
23830 &self,
23831 buffer: &Entity<Buffer>,
23832 position: text::Anchor,
23833 cx: &mut App,
23834 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23835 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23836 }
23837
23838 fn document_highlights(
23839 &self,
23840 buffer: &Entity<Buffer>,
23841 position: text::Anchor,
23842 cx: &mut App,
23843 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23844 Some(self.update(cx, |project, cx| {
23845 project.document_highlights(buffer, position, cx)
23846 }))
23847 }
23848
23849 fn definitions(
23850 &self,
23851 buffer: &Entity<Buffer>,
23852 position: text::Anchor,
23853 kind: GotoDefinitionKind,
23854 cx: &mut App,
23855 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23856 Some(self.update(cx, |project, cx| match kind {
23857 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23858 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23859 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23860 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23861 }))
23862 }
23863
23864 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23865 self.update(cx, |project, cx| {
23866 if project
23867 .active_debug_session(cx)
23868 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23869 {
23870 return true;
23871 }
23872
23873 buffer.update(cx, |buffer, cx| {
23874 project.any_language_server_supports_inlay_hints(buffer, cx)
23875 })
23876 })
23877 }
23878
23879 fn inline_values(
23880 &self,
23881 buffer_handle: Entity<Buffer>,
23882 range: Range<text::Anchor>,
23883 cx: &mut App,
23884 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23885 self.update(cx, |project, cx| {
23886 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23887
23888 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23889 })
23890 }
23891
23892 fn applicable_inlay_chunks(
23893 &self,
23894 buffer: &Entity<Buffer>,
23895 ranges: &[Range<text::Anchor>],
23896 cx: &mut App,
23897 ) -> Vec<Range<BufferRow>> {
23898 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23899 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23900 })
23901 }
23902
23903 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23904 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23905 lsp_store.invalidate_inlay_hints(for_buffers)
23906 });
23907 }
23908
23909 fn inlay_hints(
23910 &self,
23911 invalidate: InvalidationStrategy,
23912 buffer: Entity<Buffer>,
23913 ranges: Vec<Range<text::Anchor>>,
23914 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23915 cx: &mut App,
23916 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23917 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23918 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23919 }))
23920 }
23921
23922 fn range_for_rename(
23923 &self,
23924 buffer: &Entity<Buffer>,
23925 position: text::Anchor,
23926 cx: &mut App,
23927 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23928 Some(self.update(cx, |project, cx| {
23929 let buffer = buffer.clone();
23930 let task = project.prepare_rename(buffer.clone(), position, cx);
23931 cx.spawn(async move |_, cx| {
23932 Ok(match task.await? {
23933 PrepareRenameResponse::Success(range) => Some(range),
23934 PrepareRenameResponse::InvalidPosition => None,
23935 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23936 // Fallback on using TreeSitter info to determine identifier range
23937 buffer.read_with(cx, |buffer, _| {
23938 let snapshot = buffer.snapshot();
23939 let (range, kind) = snapshot.surrounding_word(position, None);
23940 if kind != Some(CharKind::Word) {
23941 return None;
23942 }
23943 Some(
23944 snapshot.anchor_before(range.start)
23945 ..snapshot.anchor_after(range.end),
23946 )
23947 })?
23948 }
23949 })
23950 })
23951 }))
23952 }
23953
23954 fn perform_rename(
23955 &self,
23956 buffer: &Entity<Buffer>,
23957 position: text::Anchor,
23958 new_name: String,
23959 cx: &mut App,
23960 ) -> Option<Task<Result<ProjectTransaction>>> {
23961 Some(self.update(cx, |project, cx| {
23962 project.perform_rename(buffer.clone(), position, new_name, cx)
23963 }))
23964 }
23965}
23966
23967fn consume_contiguous_rows(
23968 contiguous_row_selections: &mut Vec<Selection<Point>>,
23969 selection: &Selection<Point>,
23970 display_map: &DisplaySnapshot,
23971 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23972) -> (MultiBufferRow, MultiBufferRow) {
23973 contiguous_row_selections.push(selection.clone());
23974 let start_row = starting_row(selection, display_map);
23975 let mut end_row = ending_row(selection, display_map);
23976
23977 while let Some(next_selection) = selections.peek() {
23978 if next_selection.start.row <= end_row.0 {
23979 end_row = ending_row(next_selection, display_map);
23980 contiguous_row_selections.push(selections.next().unwrap().clone());
23981 } else {
23982 break;
23983 }
23984 }
23985 (start_row, end_row)
23986}
23987
23988fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23989 if selection.start.column > 0 {
23990 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23991 } else {
23992 MultiBufferRow(selection.start.row)
23993 }
23994}
23995
23996fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23997 if next_selection.end.column > 0 || next_selection.is_empty() {
23998 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23999 } else {
24000 MultiBufferRow(next_selection.end.row)
24001 }
24002}
24003
24004impl EditorSnapshot {
24005 pub fn remote_selections_in_range<'a>(
24006 &'a self,
24007 range: &'a Range<Anchor>,
24008 collaboration_hub: &dyn CollaborationHub,
24009 cx: &'a App,
24010 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24011 let participant_names = collaboration_hub.user_names(cx);
24012 let participant_indices = collaboration_hub.user_participant_indices(cx);
24013 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24014 let collaborators_by_replica_id = collaborators_by_peer_id
24015 .values()
24016 .map(|collaborator| (collaborator.replica_id, collaborator))
24017 .collect::<HashMap<_, _>>();
24018 self.buffer_snapshot()
24019 .selections_in_range(range, false)
24020 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24021 if replica_id == ReplicaId::AGENT {
24022 Some(RemoteSelection {
24023 replica_id,
24024 selection,
24025 cursor_shape,
24026 line_mode,
24027 collaborator_id: CollaboratorId::Agent,
24028 user_name: Some("Agent".into()),
24029 color: cx.theme().players().agent(),
24030 })
24031 } else {
24032 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24033 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24034 let user_name = participant_names.get(&collaborator.user_id).cloned();
24035 Some(RemoteSelection {
24036 replica_id,
24037 selection,
24038 cursor_shape,
24039 line_mode,
24040 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24041 user_name,
24042 color: if let Some(index) = participant_index {
24043 cx.theme().players().color_for_participant(index.0)
24044 } else {
24045 cx.theme().players().absent()
24046 },
24047 })
24048 }
24049 })
24050 }
24051
24052 pub fn hunks_for_ranges(
24053 &self,
24054 ranges: impl IntoIterator<Item = Range<Point>>,
24055 ) -> Vec<MultiBufferDiffHunk> {
24056 let mut hunks = Vec::new();
24057 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24058 HashMap::default();
24059 for query_range in ranges {
24060 let query_rows =
24061 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24062 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24063 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24064 ) {
24065 // Include deleted hunks that are adjacent to the query range, because
24066 // otherwise they would be missed.
24067 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24068 if hunk.status().is_deleted() {
24069 intersects_range |= hunk.row_range.start == query_rows.end;
24070 intersects_range |= hunk.row_range.end == query_rows.start;
24071 }
24072 if intersects_range {
24073 if !processed_buffer_rows
24074 .entry(hunk.buffer_id)
24075 .or_default()
24076 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24077 {
24078 continue;
24079 }
24080 hunks.push(hunk);
24081 }
24082 }
24083 }
24084
24085 hunks
24086 }
24087
24088 fn display_diff_hunks_for_rows<'a>(
24089 &'a self,
24090 display_rows: Range<DisplayRow>,
24091 folded_buffers: &'a HashSet<BufferId>,
24092 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24093 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24094 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24095
24096 self.buffer_snapshot()
24097 .diff_hunks_in_range(buffer_start..buffer_end)
24098 .filter_map(|hunk| {
24099 if folded_buffers.contains(&hunk.buffer_id) {
24100 return None;
24101 }
24102
24103 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24104 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24105
24106 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24107 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24108
24109 let display_hunk = if hunk_display_start.column() != 0 {
24110 DisplayDiffHunk::Folded {
24111 display_row: hunk_display_start.row(),
24112 }
24113 } else {
24114 let mut end_row = hunk_display_end.row();
24115 if hunk_display_end.column() > 0 {
24116 end_row.0 += 1;
24117 }
24118 let is_created_file = hunk.is_created_file();
24119 DisplayDiffHunk::Unfolded {
24120 status: hunk.status(),
24121 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24122 ..hunk.diff_base_byte_range.end.0,
24123 display_row_range: hunk_display_start.row()..end_row,
24124 multi_buffer_range: Anchor::range_in_buffer(
24125 hunk.excerpt_id,
24126 hunk.buffer_range,
24127 ),
24128 is_created_file,
24129 }
24130 };
24131
24132 Some(display_hunk)
24133 })
24134 }
24135
24136 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24137 self.display_snapshot
24138 .buffer_snapshot()
24139 .language_at(position)
24140 }
24141
24142 pub fn is_focused(&self) -> bool {
24143 self.is_focused
24144 }
24145
24146 pub fn placeholder_text(&self) -> Option<String> {
24147 self.placeholder_display_snapshot
24148 .as_ref()
24149 .map(|display_map| display_map.text())
24150 }
24151
24152 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24153 self.scroll_anchor.scroll_position(&self.display_snapshot)
24154 }
24155
24156 fn gutter_dimensions(
24157 &self,
24158 font_id: FontId,
24159 font_size: Pixels,
24160 max_line_number_width: Pixels,
24161 cx: &App,
24162 ) -> Option<GutterDimensions> {
24163 if !self.show_gutter {
24164 return None;
24165 }
24166
24167 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24168 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24169
24170 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24171 matches!(
24172 ProjectSettings::get_global(cx).git.git_gutter,
24173 GitGutterSetting::TrackedFiles
24174 )
24175 });
24176 let gutter_settings = EditorSettings::get_global(cx).gutter;
24177 let show_line_numbers = self
24178 .show_line_numbers
24179 .unwrap_or(gutter_settings.line_numbers);
24180 let line_gutter_width = if show_line_numbers {
24181 // Avoid flicker-like gutter resizes when the line number gains another digit by
24182 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24183 let min_width_for_number_on_gutter =
24184 ch_advance * gutter_settings.min_line_number_digits as f32;
24185 max_line_number_width.max(min_width_for_number_on_gutter)
24186 } else {
24187 0.0.into()
24188 };
24189
24190 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24191 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24192
24193 let git_blame_entries_width =
24194 self.git_blame_gutter_max_author_length
24195 .map(|max_author_length| {
24196 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24197 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24198
24199 /// The number of characters to dedicate to gaps and margins.
24200 const SPACING_WIDTH: usize = 4;
24201
24202 let max_char_count = max_author_length.min(renderer.max_author_length())
24203 + ::git::SHORT_SHA_LENGTH
24204 + MAX_RELATIVE_TIMESTAMP.len()
24205 + SPACING_WIDTH;
24206
24207 ch_advance * max_char_count
24208 });
24209
24210 let is_singleton = self.buffer_snapshot().is_singleton();
24211
24212 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24213 left_padding += if !is_singleton {
24214 ch_width * 4.0
24215 } else if show_runnables || show_breakpoints {
24216 ch_width * 3.0
24217 } else if show_git_gutter && show_line_numbers {
24218 ch_width * 2.0
24219 } else if show_git_gutter || show_line_numbers {
24220 ch_width
24221 } else {
24222 px(0.)
24223 };
24224
24225 let shows_folds = is_singleton && gutter_settings.folds;
24226
24227 let right_padding = if shows_folds && show_line_numbers {
24228 ch_width * 4.0
24229 } else if shows_folds || (!is_singleton && show_line_numbers) {
24230 ch_width * 3.0
24231 } else if show_line_numbers {
24232 ch_width
24233 } else {
24234 px(0.)
24235 };
24236
24237 Some(GutterDimensions {
24238 left_padding,
24239 right_padding,
24240 width: line_gutter_width + left_padding + right_padding,
24241 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24242 git_blame_entries_width,
24243 })
24244 }
24245
24246 pub fn render_crease_toggle(
24247 &self,
24248 buffer_row: MultiBufferRow,
24249 row_contains_cursor: bool,
24250 editor: Entity<Editor>,
24251 window: &mut Window,
24252 cx: &mut App,
24253 ) -> Option<AnyElement> {
24254 let folded = self.is_line_folded(buffer_row);
24255 let mut is_foldable = false;
24256
24257 if let Some(crease) = self
24258 .crease_snapshot
24259 .query_row(buffer_row, self.buffer_snapshot())
24260 {
24261 is_foldable = true;
24262 match crease {
24263 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24264 if let Some(render_toggle) = render_toggle {
24265 let toggle_callback =
24266 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24267 if folded {
24268 editor.update(cx, |editor, cx| {
24269 editor.fold_at(buffer_row, window, cx)
24270 });
24271 } else {
24272 editor.update(cx, |editor, cx| {
24273 editor.unfold_at(buffer_row, window, cx)
24274 });
24275 }
24276 });
24277 return Some((render_toggle)(
24278 buffer_row,
24279 folded,
24280 toggle_callback,
24281 window,
24282 cx,
24283 ));
24284 }
24285 }
24286 }
24287 }
24288
24289 is_foldable |= self.starts_indent(buffer_row);
24290
24291 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24292 Some(
24293 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24294 .toggle_state(folded)
24295 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24296 if folded {
24297 this.unfold_at(buffer_row, window, cx);
24298 } else {
24299 this.fold_at(buffer_row, window, cx);
24300 }
24301 }))
24302 .into_any_element(),
24303 )
24304 } else {
24305 None
24306 }
24307 }
24308
24309 pub fn render_crease_trailer(
24310 &self,
24311 buffer_row: MultiBufferRow,
24312 window: &mut Window,
24313 cx: &mut App,
24314 ) -> Option<AnyElement> {
24315 let folded = self.is_line_folded(buffer_row);
24316 if let Crease::Inline { render_trailer, .. } = self
24317 .crease_snapshot
24318 .query_row(buffer_row, self.buffer_snapshot())?
24319 {
24320 let render_trailer = render_trailer.as_ref()?;
24321 Some(render_trailer(buffer_row, folded, window, cx))
24322 } else {
24323 None
24324 }
24325 }
24326}
24327
24328impl Deref for EditorSnapshot {
24329 type Target = DisplaySnapshot;
24330
24331 fn deref(&self) -> &Self::Target {
24332 &self.display_snapshot
24333 }
24334}
24335
24336#[derive(Clone, Debug, PartialEq, Eq)]
24337pub enum EditorEvent {
24338 InputIgnored {
24339 text: Arc<str>,
24340 },
24341 InputHandled {
24342 utf16_range_to_replace: Option<Range<isize>>,
24343 text: Arc<str>,
24344 },
24345 ExcerptsAdded {
24346 buffer: Entity<Buffer>,
24347 predecessor: ExcerptId,
24348 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24349 },
24350 ExcerptsRemoved {
24351 ids: Vec<ExcerptId>,
24352 removed_buffer_ids: Vec<BufferId>,
24353 },
24354 BufferFoldToggled {
24355 ids: Vec<ExcerptId>,
24356 folded: bool,
24357 },
24358 ExcerptsEdited {
24359 ids: Vec<ExcerptId>,
24360 },
24361 ExcerptsExpanded {
24362 ids: Vec<ExcerptId>,
24363 },
24364 BufferEdited,
24365 Edited {
24366 transaction_id: clock::Lamport,
24367 },
24368 Reparsed(BufferId),
24369 Focused,
24370 FocusedIn,
24371 Blurred,
24372 DirtyChanged,
24373 Saved,
24374 TitleChanged,
24375 SelectionsChanged {
24376 local: bool,
24377 },
24378 ScrollPositionChanged {
24379 local: bool,
24380 autoscroll: bool,
24381 },
24382 TransactionUndone {
24383 transaction_id: clock::Lamport,
24384 },
24385 TransactionBegun {
24386 transaction_id: clock::Lamport,
24387 },
24388 CursorShapeChanged,
24389 BreadcrumbsChanged,
24390 PushedToNavHistory {
24391 anchor: Anchor,
24392 is_deactivate: bool,
24393 },
24394}
24395
24396impl EventEmitter<EditorEvent> for Editor {}
24397
24398impl Focusable for Editor {
24399 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24400 self.focus_handle.clone()
24401 }
24402}
24403
24404impl Render for Editor {
24405 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24406 let settings = ThemeSettings::get_global(cx);
24407
24408 let mut text_style = match self.mode {
24409 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24410 color: cx.theme().colors().editor_foreground,
24411 font_family: settings.ui_font.family.clone(),
24412 font_features: settings.ui_font.features.clone(),
24413 font_fallbacks: settings.ui_font.fallbacks.clone(),
24414 font_size: rems(0.875).into(),
24415 font_weight: settings.ui_font.weight,
24416 line_height: relative(settings.buffer_line_height.value()),
24417 ..Default::default()
24418 },
24419 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24420 color: cx.theme().colors().editor_foreground,
24421 font_family: settings.buffer_font.family.clone(),
24422 font_features: settings.buffer_font.features.clone(),
24423 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24424 font_size: settings.buffer_font_size(cx).into(),
24425 font_weight: settings.buffer_font.weight,
24426 line_height: relative(settings.buffer_line_height.value()),
24427 ..Default::default()
24428 },
24429 };
24430 if let Some(text_style_refinement) = &self.text_style_refinement {
24431 text_style.refine(text_style_refinement)
24432 }
24433
24434 let background = match self.mode {
24435 EditorMode::SingleLine => cx.theme().system().transparent,
24436 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24437 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24438 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24439 };
24440
24441 EditorElement::new(
24442 &cx.entity(),
24443 EditorStyle {
24444 background,
24445 border: cx.theme().colors().border,
24446 local_player: cx.theme().players().local(),
24447 text: text_style,
24448 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24449 syntax: cx.theme().syntax().clone(),
24450 status: cx.theme().status().clone(),
24451 inlay_hints_style: make_inlay_hints_style(cx),
24452 edit_prediction_styles: make_suggestion_styles(cx),
24453 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24454 show_underlines: self.diagnostics_enabled(),
24455 },
24456 )
24457 }
24458}
24459
24460impl EntityInputHandler for Editor {
24461 fn text_for_range(
24462 &mut self,
24463 range_utf16: Range<usize>,
24464 adjusted_range: &mut Option<Range<usize>>,
24465 _: &mut Window,
24466 cx: &mut Context<Self>,
24467 ) -> Option<String> {
24468 let snapshot = self.buffer.read(cx).read(cx);
24469 let start = snapshot.clip_offset_utf16(
24470 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24471 Bias::Left,
24472 );
24473 let end = snapshot.clip_offset_utf16(
24474 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24475 Bias::Right,
24476 );
24477 if (start.0.0..end.0.0) != range_utf16 {
24478 adjusted_range.replace(start.0.0..end.0.0);
24479 }
24480 Some(snapshot.text_for_range(start..end).collect())
24481 }
24482
24483 fn selected_text_range(
24484 &mut self,
24485 ignore_disabled_input: bool,
24486 _: &mut Window,
24487 cx: &mut Context<Self>,
24488 ) -> Option<UTF16Selection> {
24489 // Prevent the IME menu from appearing when holding down an alphabetic key
24490 // while input is disabled.
24491 if !ignore_disabled_input && !self.input_enabled {
24492 return None;
24493 }
24494
24495 let selection = self
24496 .selections
24497 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24498 let range = selection.range();
24499
24500 Some(UTF16Selection {
24501 range: range.start.0.0..range.end.0.0,
24502 reversed: selection.reversed,
24503 })
24504 }
24505
24506 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24507 let snapshot = self.buffer.read(cx).read(cx);
24508 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24509 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24510 }
24511
24512 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24513 self.clear_highlights::<InputComposition>(cx);
24514 self.ime_transaction.take();
24515 }
24516
24517 fn replace_text_in_range(
24518 &mut self,
24519 range_utf16: Option<Range<usize>>,
24520 text: &str,
24521 window: &mut Window,
24522 cx: &mut Context<Self>,
24523 ) {
24524 if !self.input_enabled {
24525 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24526 return;
24527 }
24528
24529 self.transact(window, cx, |this, window, cx| {
24530 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24531 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24532 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24533 Some(this.selection_replacement_ranges(range_utf16, cx))
24534 } else {
24535 this.marked_text_ranges(cx)
24536 };
24537
24538 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24539 let newest_selection_id = this.selections.newest_anchor().id;
24540 this.selections
24541 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24542 .iter()
24543 .zip(ranges_to_replace.iter())
24544 .find_map(|(selection, range)| {
24545 if selection.id == newest_selection_id {
24546 Some(
24547 (range.start.0.0 as isize - selection.head().0.0 as isize)
24548 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24549 )
24550 } else {
24551 None
24552 }
24553 })
24554 });
24555
24556 cx.emit(EditorEvent::InputHandled {
24557 utf16_range_to_replace: range_to_replace,
24558 text: text.into(),
24559 });
24560
24561 if let Some(new_selected_ranges) = new_selected_ranges {
24562 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24563 selections.select_ranges(new_selected_ranges)
24564 });
24565 this.backspace(&Default::default(), window, cx);
24566 }
24567
24568 this.handle_input(text, window, cx);
24569 });
24570
24571 if let Some(transaction) = self.ime_transaction {
24572 self.buffer.update(cx, |buffer, cx| {
24573 buffer.group_until_transaction(transaction, cx);
24574 });
24575 }
24576
24577 self.unmark_text(window, cx);
24578 }
24579
24580 fn replace_and_mark_text_in_range(
24581 &mut self,
24582 range_utf16: Option<Range<usize>>,
24583 text: &str,
24584 new_selected_range_utf16: Option<Range<usize>>,
24585 window: &mut Window,
24586 cx: &mut Context<Self>,
24587 ) {
24588 if !self.input_enabled {
24589 return;
24590 }
24591
24592 let transaction = self.transact(window, cx, |this, window, cx| {
24593 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24594 let snapshot = this.buffer.read(cx).read(cx);
24595 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24596 for marked_range in &mut marked_ranges {
24597 marked_range.end = marked_range.start + relative_range_utf16.end;
24598 marked_range.start += relative_range_utf16.start;
24599 marked_range.start =
24600 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24601 marked_range.end =
24602 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24603 }
24604 }
24605 Some(marked_ranges)
24606 } else if let Some(range_utf16) = range_utf16 {
24607 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24608 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24609 Some(this.selection_replacement_ranges(range_utf16, cx))
24610 } else {
24611 None
24612 };
24613
24614 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24615 let newest_selection_id = this.selections.newest_anchor().id;
24616 this.selections
24617 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24618 .iter()
24619 .zip(ranges_to_replace.iter())
24620 .find_map(|(selection, range)| {
24621 if selection.id == newest_selection_id {
24622 Some(
24623 (range.start.0.0 as isize - selection.head().0.0 as isize)
24624 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24625 )
24626 } else {
24627 None
24628 }
24629 })
24630 });
24631
24632 cx.emit(EditorEvent::InputHandled {
24633 utf16_range_to_replace: range_to_replace,
24634 text: text.into(),
24635 });
24636
24637 if let Some(ranges) = ranges_to_replace {
24638 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24639 s.select_ranges(ranges)
24640 });
24641 }
24642
24643 let marked_ranges = {
24644 let snapshot = this.buffer.read(cx).read(cx);
24645 this.selections
24646 .disjoint_anchors_arc()
24647 .iter()
24648 .map(|selection| {
24649 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24650 })
24651 .collect::<Vec<_>>()
24652 };
24653
24654 if text.is_empty() {
24655 this.unmark_text(window, cx);
24656 } else {
24657 this.highlight_text::<InputComposition>(
24658 marked_ranges.clone(),
24659 HighlightStyle {
24660 underline: Some(UnderlineStyle {
24661 thickness: px(1.),
24662 color: None,
24663 wavy: false,
24664 }),
24665 ..Default::default()
24666 },
24667 cx,
24668 );
24669 }
24670
24671 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24672 let use_autoclose = this.use_autoclose;
24673 let use_auto_surround = this.use_auto_surround;
24674 this.set_use_autoclose(false);
24675 this.set_use_auto_surround(false);
24676 this.handle_input(text, window, cx);
24677 this.set_use_autoclose(use_autoclose);
24678 this.set_use_auto_surround(use_auto_surround);
24679
24680 if let Some(new_selected_range) = new_selected_range_utf16 {
24681 let snapshot = this.buffer.read(cx).read(cx);
24682 let new_selected_ranges = marked_ranges
24683 .into_iter()
24684 .map(|marked_range| {
24685 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24686 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24687 insertion_start.0 + new_selected_range.start,
24688 ));
24689 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24690 insertion_start.0 + new_selected_range.end,
24691 ));
24692 snapshot.clip_offset_utf16(new_start, Bias::Left)
24693 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24694 })
24695 .collect::<Vec<_>>();
24696
24697 drop(snapshot);
24698 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24699 selections.select_ranges(new_selected_ranges)
24700 });
24701 }
24702 });
24703
24704 self.ime_transaction = self.ime_transaction.or(transaction);
24705 if let Some(transaction) = self.ime_transaction {
24706 self.buffer.update(cx, |buffer, cx| {
24707 buffer.group_until_transaction(transaction, cx);
24708 });
24709 }
24710
24711 if self.text_highlights::<InputComposition>(cx).is_none() {
24712 self.ime_transaction.take();
24713 }
24714 }
24715
24716 fn bounds_for_range(
24717 &mut self,
24718 range_utf16: Range<usize>,
24719 element_bounds: gpui::Bounds<Pixels>,
24720 window: &mut Window,
24721 cx: &mut Context<Self>,
24722 ) -> Option<gpui::Bounds<Pixels>> {
24723 let text_layout_details = self.text_layout_details(window);
24724 let CharacterDimensions {
24725 em_width,
24726 em_advance,
24727 line_height,
24728 } = self.character_dimensions(window);
24729
24730 let snapshot = self.snapshot(window, cx);
24731 let scroll_position = snapshot.scroll_position();
24732 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24733
24734 let start =
24735 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24736 let x = Pixels::from(
24737 ScrollOffset::from(
24738 snapshot.x_for_display_point(start, &text_layout_details)
24739 + self.gutter_dimensions.full_width(),
24740 ) - scroll_left,
24741 );
24742 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24743
24744 Some(Bounds {
24745 origin: element_bounds.origin + point(x, y),
24746 size: size(em_width, line_height),
24747 })
24748 }
24749
24750 fn character_index_for_point(
24751 &mut self,
24752 point: gpui::Point<Pixels>,
24753 _window: &mut Window,
24754 _cx: &mut Context<Self>,
24755 ) -> Option<usize> {
24756 let position_map = self.last_position_map.as_ref()?;
24757 if !position_map.text_hitbox.contains(&point) {
24758 return None;
24759 }
24760 let display_point = position_map.point_for_position(point).previous_valid;
24761 let anchor = position_map
24762 .snapshot
24763 .display_point_to_anchor(display_point, Bias::Left);
24764 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24765 Some(utf16_offset.0.0)
24766 }
24767
24768 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24769 self.input_enabled
24770 }
24771}
24772
24773trait SelectionExt {
24774 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24775 fn spanned_rows(
24776 &self,
24777 include_end_if_at_line_start: bool,
24778 map: &DisplaySnapshot,
24779 ) -> Range<MultiBufferRow>;
24780}
24781
24782impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24783 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24784 let start = self
24785 .start
24786 .to_point(map.buffer_snapshot())
24787 .to_display_point(map);
24788 let end = self
24789 .end
24790 .to_point(map.buffer_snapshot())
24791 .to_display_point(map);
24792 if self.reversed {
24793 end..start
24794 } else {
24795 start..end
24796 }
24797 }
24798
24799 fn spanned_rows(
24800 &self,
24801 include_end_if_at_line_start: bool,
24802 map: &DisplaySnapshot,
24803 ) -> Range<MultiBufferRow> {
24804 let start = self.start.to_point(map.buffer_snapshot());
24805 let mut end = self.end.to_point(map.buffer_snapshot());
24806 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24807 end.row -= 1;
24808 }
24809
24810 let buffer_start = map.prev_line_boundary(start).0;
24811 let buffer_end = map.next_line_boundary(end).0;
24812 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24813 }
24814}
24815
24816impl<T: InvalidationRegion> InvalidationStack<T> {
24817 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24818 where
24819 S: Clone + ToOffset,
24820 {
24821 while let Some(region) = self.last() {
24822 let all_selections_inside_invalidation_ranges =
24823 if selections.len() == region.ranges().len() {
24824 selections
24825 .iter()
24826 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24827 .all(|(selection, invalidation_range)| {
24828 let head = selection.head().to_offset(buffer);
24829 invalidation_range.start <= head && invalidation_range.end >= head
24830 })
24831 } else {
24832 false
24833 };
24834
24835 if all_selections_inside_invalidation_ranges {
24836 break;
24837 } else {
24838 self.pop();
24839 }
24840 }
24841 }
24842}
24843
24844impl<T> Default for InvalidationStack<T> {
24845 fn default() -> Self {
24846 Self(Default::default())
24847 }
24848}
24849
24850impl<T> Deref for InvalidationStack<T> {
24851 type Target = Vec<T>;
24852
24853 fn deref(&self) -> &Self::Target {
24854 &self.0
24855 }
24856}
24857
24858impl<T> DerefMut for InvalidationStack<T> {
24859 fn deref_mut(&mut self) -> &mut Self::Target {
24860 &mut self.0
24861 }
24862}
24863
24864impl InvalidationRegion for SnippetState {
24865 fn ranges(&self) -> &[Range<Anchor>] {
24866 &self.ranges[self.active_index]
24867 }
24868}
24869
24870fn edit_prediction_edit_text(
24871 current_snapshot: &BufferSnapshot,
24872 edits: &[(Range<Anchor>, impl AsRef<str>)],
24873 edit_preview: &EditPreview,
24874 include_deletions: bool,
24875 cx: &App,
24876) -> HighlightedText {
24877 let edits = edits
24878 .iter()
24879 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24880 .collect::<Vec<_>>();
24881
24882 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24883}
24884
24885fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24886 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24887 // Just show the raw edit text with basic styling
24888 let mut text = String::new();
24889 let mut highlights = Vec::new();
24890
24891 let insertion_highlight_style = HighlightStyle {
24892 color: Some(cx.theme().colors().text),
24893 ..Default::default()
24894 };
24895
24896 for (_, edit_text) in edits {
24897 let start_offset = text.len();
24898 text.push_str(edit_text);
24899 let end_offset = text.len();
24900
24901 if start_offset < end_offset {
24902 highlights.push((start_offset..end_offset, insertion_highlight_style));
24903 }
24904 }
24905
24906 HighlightedText {
24907 text: text.into(),
24908 highlights,
24909 }
24910}
24911
24912pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24913 match severity {
24914 lsp::DiagnosticSeverity::ERROR => colors.error,
24915 lsp::DiagnosticSeverity::WARNING => colors.warning,
24916 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24917 lsp::DiagnosticSeverity::HINT => colors.info,
24918 _ => colors.ignored,
24919 }
24920}
24921
24922pub fn styled_runs_for_code_label<'a>(
24923 label: &'a CodeLabel,
24924 syntax_theme: &'a theme::SyntaxTheme,
24925) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24926 let fade_out = HighlightStyle {
24927 fade_out: Some(0.35),
24928 ..Default::default()
24929 };
24930
24931 let mut prev_end = label.filter_range.end;
24932 label
24933 .runs
24934 .iter()
24935 .enumerate()
24936 .flat_map(move |(ix, (range, highlight_id))| {
24937 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24938 style
24939 } else {
24940 return Default::default();
24941 };
24942 let muted_style = style.highlight(fade_out);
24943
24944 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24945 if range.start >= label.filter_range.end {
24946 if range.start > prev_end {
24947 runs.push((prev_end..range.start, fade_out));
24948 }
24949 runs.push((range.clone(), muted_style));
24950 } else if range.end <= label.filter_range.end {
24951 runs.push((range.clone(), style));
24952 } else {
24953 runs.push((range.start..label.filter_range.end, style));
24954 runs.push((label.filter_range.end..range.end, muted_style));
24955 }
24956 prev_end = cmp::max(prev_end, range.end);
24957
24958 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24959 runs.push((prev_end..label.text.len(), fade_out));
24960 }
24961
24962 runs
24963 })
24964}
24965
24966pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24967 let mut prev_index = 0;
24968 let mut prev_codepoint: Option<char> = None;
24969 text.char_indices()
24970 .chain([(text.len(), '\0')])
24971 .filter_map(move |(index, codepoint)| {
24972 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24973 let is_boundary = index == text.len()
24974 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24975 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24976 if is_boundary {
24977 let chunk = &text[prev_index..index];
24978 prev_index = index;
24979 Some(chunk)
24980 } else {
24981 None
24982 }
24983 })
24984}
24985
24986/// Given a string of text immediately before the cursor, iterates over possible
24987/// strings a snippet could match to. More precisely: returns an iterator over
24988/// suffixes of `text` created by splitting at word boundaries (before & after
24989/// every non-word character).
24990///
24991/// Shorter suffixes are returned first.
24992pub(crate) fn snippet_candidate_suffixes(
24993 text: &str,
24994 is_word_char: impl Fn(char) -> bool,
24995) -> impl std::iter::Iterator<Item = &str> {
24996 let mut prev_index = text.len();
24997 let mut prev_codepoint = None;
24998 text.char_indices()
24999 .rev()
25000 .chain([(0, '\0')])
25001 .filter_map(move |(index, codepoint)| {
25002 let prev_index = std::mem::replace(&mut prev_index, index);
25003 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25004 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25005 None
25006 } else {
25007 let chunk = &text[prev_index..]; // go to end of string
25008 Some(chunk)
25009 }
25010 })
25011}
25012
25013pub trait RangeToAnchorExt: Sized {
25014 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25015
25016 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25017 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25018 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25019 }
25020}
25021
25022impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25023 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25024 let start_offset = self.start.to_offset(snapshot);
25025 let end_offset = self.end.to_offset(snapshot);
25026 if start_offset == end_offset {
25027 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25028 } else {
25029 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25030 }
25031 }
25032}
25033
25034pub trait RowExt {
25035 fn as_f64(&self) -> f64;
25036
25037 fn next_row(&self) -> Self;
25038
25039 fn previous_row(&self) -> Self;
25040
25041 fn minus(&self, other: Self) -> u32;
25042}
25043
25044impl RowExt for DisplayRow {
25045 fn as_f64(&self) -> f64 {
25046 self.0 as _
25047 }
25048
25049 fn next_row(&self) -> Self {
25050 Self(self.0 + 1)
25051 }
25052
25053 fn previous_row(&self) -> Self {
25054 Self(self.0.saturating_sub(1))
25055 }
25056
25057 fn minus(&self, other: Self) -> u32 {
25058 self.0 - other.0
25059 }
25060}
25061
25062impl RowExt for MultiBufferRow {
25063 fn as_f64(&self) -> f64 {
25064 self.0 as _
25065 }
25066
25067 fn next_row(&self) -> Self {
25068 Self(self.0 + 1)
25069 }
25070
25071 fn previous_row(&self) -> Self {
25072 Self(self.0.saturating_sub(1))
25073 }
25074
25075 fn minus(&self, other: Self) -> u32 {
25076 self.0 - other.0
25077 }
25078}
25079
25080trait RowRangeExt {
25081 type Row;
25082
25083 fn len(&self) -> usize;
25084
25085 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25086}
25087
25088impl RowRangeExt for Range<MultiBufferRow> {
25089 type Row = MultiBufferRow;
25090
25091 fn len(&self) -> usize {
25092 (self.end.0 - self.start.0) as usize
25093 }
25094
25095 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25096 (self.start.0..self.end.0).map(MultiBufferRow)
25097 }
25098}
25099
25100impl RowRangeExt for Range<DisplayRow> {
25101 type Row = DisplayRow;
25102
25103 fn len(&self) -> usize {
25104 (self.end.0 - self.start.0) as usize
25105 }
25106
25107 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25108 (self.start.0..self.end.0).map(DisplayRow)
25109 }
25110}
25111
25112/// If select range has more than one line, we
25113/// just point the cursor to range.start.
25114fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25115 if range.start.row == range.end.row {
25116 range
25117 } else {
25118 range.start..range.start
25119 }
25120}
25121pub struct KillRing(ClipboardItem);
25122impl Global for KillRing {}
25123
25124const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25125
25126enum BreakpointPromptEditAction {
25127 Log,
25128 Condition,
25129 HitCondition,
25130}
25131
25132struct BreakpointPromptEditor {
25133 pub(crate) prompt: Entity<Editor>,
25134 editor: WeakEntity<Editor>,
25135 breakpoint_anchor: Anchor,
25136 breakpoint: Breakpoint,
25137 edit_action: BreakpointPromptEditAction,
25138 block_ids: HashSet<CustomBlockId>,
25139 editor_margins: Arc<Mutex<EditorMargins>>,
25140 _subscriptions: Vec<Subscription>,
25141}
25142
25143impl BreakpointPromptEditor {
25144 const MAX_LINES: u8 = 4;
25145
25146 fn new(
25147 editor: WeakEntity<Editor>,
25148 breakpoint_anchor: Anchor,
25149 breakpoint: Breakpoint,
25150 edit_action: BreakpointPromptEditAction,
25151 window: &mut Window,
25152 cx: &mut Context<Self>,
25153 ) -> Self {
25154 let base_text = match edit_action {
25155 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25156 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25157 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25158 }
25159 .map(|msg| msg.to_string())
25160 .unwrap_or_default();
25161
25162 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25163 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25164
25165 let prompt = cx.new(|cx| {
25166 let mut prompt = Editor::new(
25167 EditorMode::AutoHeight {
25168 min_lines: 1,
25169 max_lines: Some(Self::MAX_LINES as usize),
25170 },
25171 buffer,
25172 None,
25173 window,
25174 cx,
25175 );
25176 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25177 prompt.set_show_cursor_when_unfocused(false, cx);
25178 prompt.set_placeholder_text(
25179 match edit_action {
25180 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25181 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25182 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25183 },
25184 window,
25185 cx,
25186 );
25187
25188 prompt
25189 });
25190
25191 Self {
25192 prompt,
25193 editor,
25194 breakpoint_anchor,
25195 breakpoint,
25196 edit_action,
25197 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25198 block_ids: Default::default(),
25199 _subscriptions: vec![],
25200 }
25201 }
25202
25203 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25204 self.block_ids.extend(block_ids)
25205 }
25206
25207 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25208 if let Some(editor) = self.editor.upgrade() {
25209 let message = self
25210 .prompt
25211 .read(cx)
25212 .buffer
25213 .read(cx)
25214 .as_singleton()
25215 .expect("A multi buffer in breakpoint prompt isn't possible")
25216 .read(cx)
25217 .as_rope()
25218 .to_string();
25219
25220 editor.update(cx, |editor, cx| {
25221 editor.edit_breakpoint_at_anchor(
25222 self.breakpoint_anchor,
25223 self.breakpoint.clone(),
25224 match self.edit_action {
25225 BreakpointPromptEditAction::Log => {
25226 BreakpointEditAction::EditLogMessage(message.into())
25227 }
25228 BreakpointPromptEditAction::Condition => {
25229 BreakpointEditAction::EditCondition(message.into())
25230 }
25231 BreakpointPromptEditAction::HitCondition => {
25232 BreakpointEditAction::EditHitCondition(message.into())
25233 }
25234 },
25235 cx,
25236 );
25237
25238 editor.remove_blocks(self.block_ids.clone(), None, cx);
25239 cx.focus_self(window);
25240 });
25241 }
25242 }
25243
25244 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25245 self.editor
25246 .update(cx, |editor, cx| {
25247 editor.remove_blocks(self.block_ids.clone(), None, cx);
25248 window.focus(&editor.focus_handle);
25249 })
25250 .log_err();
25251 }
25252
25253 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25254 let settings = ThemeSettings::get_global(cx);
25255 let text_style = TextStyle {
25256 color: if self.prompt.read(cx).read_only(cx) {
25257 cx.theme().colors().text_disabled
25258 } else {
25259 cx.theme().colors().text
25260 },
25261 font_family: settings.buffer_font.family.clone(),
25262 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25263 font_size: settings.buffer_font_size(cx).into(),
25264 font_weight: settings.buffer_font.weight,
25265 line_height: relative(settings.buffer_line_height.value()),
25266 ..Default::default()
25267 };
25268 EditorElement::new(
25269 &self.prompt,
25270 EditorStyle {
25271 background: cx.theme().colors().editor_background,
25272 local_player: cx.theme().players().local(),
25273 text: text_style,
25274 ..Default::default()
25275 },
25276 )
25277 }
25278}
25279
25280impl Render for BreakpointPromptEditor {
25281 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25282 let editor_margins = *self.editor_margins.lock();
25283 let gutter_dimensions = editor_margins.gutter;
25284 h_flex()
25285 .key_context("Editor")
25286 .bg(cx.theme().colors().editor_background)
25287 .border_y_1()
25288 .border_color(cx.theme().status().info_border)
25289 .size_full()
25290 .py(window.line_height() / 2.5)
25291 .on_action(cx.listener(Self::confirm))
25292 .on_action(cx.listener(Self::cancel))
25293 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25294 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25295 }
25296}
25297
25298impl Focusable for BreakpointPromptEditor {
25299 fn focus_handle(&self, cx: &App) -> FocusHandle {
25300 self.prompt.focus_handle(cx)
25301 }
25302}
25303
25304fn all_edits_insertions_or_deletions(
25305 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25306 snapshot: &MultiBufferSnapshot,
25307) -> bool {
25308 let mut all_insertions = true;
25309 let mut all_deletions = true;
25310
25311 for (range, new_text) in edits.iter() {
25312 let range_is_empty = range.to_offset(snapshot).is_empty();
25313 let text_is_empty = new_text.is_empty();
25314
25315 if range_is_empty != text_is_empty {
25316 if range_is_empty {
25317 all_deletions = false;
25318 } else {
25319 all_insertions = false;
25320 }
25321 } else {
25322 return false;
25323 }
25324
25325 if !all_insertions && !all_deletions {
25326 return false;
25327 }
25328 }
25329 all_insertions || all_deletions
25330}
25331
25332struct MissingEditPredictionKeybindingTooltip;
25333
25334impl Render for MissingEditPredictionKeybindingTooltip {
25335 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25336 ui::tooltip_container(cx, |container, cx| {
25337 container
25338 .flex_shrink_0()
25339 .max_w_80()
25340 .min_h(rems_from_px(124.))
25341 .justify_between()
25342 .child(
25343 v_flex()
25344 .flex_1()
25345 .text_ui_sm(cx)
25346 .child(Label::new("Conflict with Accept Keybinding"))
25347 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25348 )
25349 .child(
25350 h_flex()
25351 .pb_1()
25352 .gap_1()
25353 .items_end()
25354 .w_full()
25355 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25356 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25357 }))
25358 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25359 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25360 })),
25361 )
25362 })
25363 }
25364}
25365
25366#[derive(Debug, Clone, Copy, PartialEq)]
25367pub struct LineHighlight {
25368 pub background: Background,
25369 pub border: Option<gpui::Hsla>,
25370 pub include_gutter: bool,
25371 pub type_id: Option<TypeId>,
25372}
25373
25374struct LineManipulationResult {
25375 pub new_text: String,
25376 pub line_count_before: usize,
25377 pub line_count_after: usize,
25378}
25379
25380fn render_diff_hunk_controls(
25381 row: u32,
25382 status: &DiffHunkStatus,
25383 hunk_range: Range<Anchor>,
25384 is_created_file: bool,
25385 line_height: Pixels,
25386 editor: &Entity<Editor>,
25387 _window: &mut Window,
25388 cx: &mut App,
25389) -> AnyElement {
25390 h_flex()
25391 .h(line_height)
25392 .mr_1()
25393 .gap_1()
25394 .px_0p5()
25395 .pb_1()
25396 .border_x_1()
25397 .border_b_1()
25398 .border_color(cx.theme().colors().border_variant)
25399 .rounded_b_lg()
25400 .bg(cx.theme().colors().editor_background)
25401 .gap_1()
25402 .block_mouse_except_scroll()
25403 .shadow_md()
25404 .child(if status.has_secondary_hunk() {
25405 Button::new(("stage", row as u64), "Stage")
25406 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25407 .tooltip({
25408 let focus_handle = editor.focus_handle(cx);
25409 move |_window, cx| {
25410 Tooltip::for_action_in(
25411 "Stage Hunk",
25412 &::git::ToggleStaged,
25413 &focus_handle,
25414 cx,
25415 )
25416 }
25417 })
25418 .on_click({
25419 let editor = editor.clone();
25420 move |_event, _window, cx| {
25421 editor.update(cx, |editor, cx| {
25422 editor.stage_or_unstage_diff_hunks(
25423 true,
25424 vec![hunk_range.start..hunk_range.start],
25425 cx,
25426 );
25427 });
25428 }
25429 })
25430 } else {
25431 Button::new(("unstage", row as u64), "Unstage")
25432 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25433 .tooltip({
25434 let focus_handle = editor.focus_handle(cx);
25435 move |_window, cx| {
25436 Tooltip::for_action_in(
25437 "Unstage Hunk",
25438 &::git::ToggleStaged,
25439 &focus_handle,
25440 cx,
25441 )
25442 }
25443 })
25444 .on_click({
25445 let editor = editor.clone();
25446 move |_event, _window, cx| {
25447 editor.update(cx, |editor, cx| {
25448 editor.stage_or_unstage_diff_hunks(
25449 false,
25450 vec![hunk_range.start..hunk_range.start],
25451 cx,
25452 );
25453 });
25454 }
25455 })
25456 })
25457 .child(
25458 Button::new(("restore", row as u64), "Restore")
25459 .tooltip({
25460 let focus_handle = editor.focus_handle(cx);
25461 move |_window, cx| {
25462 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25463 }
25464 })
25465 .on_click({
25466 let editor = editor.clone();
25467 move |_event, window, cx| {
25468 editor.update(cx, |editor, cx| {
25469 let snapshot = editor.snapshot(window, cx);
25470 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25471 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25472 });
25473 }
25474 })
25475 .disabled(is_created_file),
25476 )
25477 .when(
25478 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25479 |el| {
25480 el.child(
25481 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25482 .shape(IconButtonShape::Square)
25483 .icon_size(IconSize::Small)
25484 // .disabled(!has_multiple_hunks)
25485 .tooltip({
25486 let focus_handle = editor.focus_handle(cx);
25487 move |_window, cx| {
25488 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25489 }
25490 })
25491 .on_click({
25492 let editor = editor.clone();
25493 move |_event, window, cx| {
25494 editor.update(cx, |editor, cx| {
25495 let snapshot = editor.snapshot(window, cx);
25496 let position =
25497 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25498 editor.go_to_hunk_before_or_after_position(
25499 &snapshot,
25500 position,
25501 Direction::Next,
25502 window,
25503 cx,
25504 );
25505 editor.expand_selected_diff_hunks(cx);
25506 });
25507 }
25508 }),
25509 )
25510 .child(
25511 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25512 .shape(IconButtonShape::Square)
25513 .icon_size(IconSize::Small)
25514 // .disabled(!has_multiple_hunks)
25515 .tooltip({
25516 let focus_handle = editor.focus_handle(cx);
25517 move |_window, cx| {
25518 Tooltip::for_action_in(
25519 "Previous Hunk",
25520 &GoToPreviousHunk,
25521 &focus_handle,
25522 cx,
25523 )
25524 }
25525 })
25526 .on_click({
25527 let editor = editor.clone();
25528 move |_event, window, cx| {
25529 editor.update(cx, |editor, cx| {
25530 let snapshot = editor.snapshot(window, cx);
25531 let point =
25532 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25533 editor.go_to_hunk_before_or_after_position(
25534 &snapshot,
25535 point,
25536 Direction::Prev,
25537 window,
25538 cx,
25539 );
25540 editor.expand_selected_diff_hunks(cx);
25541 });
25542 }
25543 }),
25544 )
25545 },
25546 )
25547 .into_any_element()
25548}
25549
25550pub fn multibuffer_context_lines(cx: &App) -> u32 {
25551 EditorSettings::try_get(cx)
25552 .map(|settings| settings.excerpt_context_lines)
25553 .unwrap_or(2)
25554 .min(32)
25555}