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 lsp_related_only: bool,
5312 cx: &mut Context<Editor>,
5313 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5314 let project = self.project().cloned();
5315 let multi_buffer = self.buffer().read(cx);
5316 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5317 let multi_buffer_visible_start = self
5318 .scroll_manager
5319 .anchor()
5320 .anchor
5321 .to_point(&multi_buffer_snapshot);
5322 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5323 multi_buffer_visible_start
5324 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5325 Bias::Left,
5326 );
5327 multi_buffer_snapshot
5328 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5329 .into_iter()
5330 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5331 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5332 if !lsp_related_only {
5333 return Some((
5334 excerpt_id,
5335 (
5336 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5337 buffer.version().clone(),
5338 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5339 ),
5340 ));
5341 }
5342
5343 let project = project.as_ref()?.read(cx);
5344 let buffer_file = project::File::from_dyn(buffer.file())?;
5345 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5346 let worktree_entry = buffer_worktree
5347 .read(cx)
5348 .entry_for_id(buffer_file.project_entry_id()?)?;
5349 if worktree_entry.is_ignored {
5350 None
5351 } else {
5352 Some((
5353 excerpt_id,
5354 (
5355 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5356 buffer.version().clone(),
5357 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5358 ),
5359 ))
5360 }
5361 })
5362 .collect()
5363 }
5364
5365 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5366 TextLayoutDetails {
5367 text_system: window.text_system().clone(),
5368 editor_style: self.style.clone().unwrap(),
5369 rem_size: window.rem_size(),
5370 scroll_anchor: self.scroll_manager.anchor(),
5371 visible_rows: self.visible_line_count(),
5372 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5373 }
5374 }
5375
5376 fn trigger_on_type_formatting(
5377 &self,
5378 input: String,
5379 window: &mut Window,
5380 cx: &mut Context<Self>,
5381 ) -> Option<Task<Result<()>>> {
5382 if input.len() != 1 {
5383 return None;
5384 }
5385
5386 let project = self.project()?;
5387 let position = self.selections.newest_anchor().head();
5388 let (buffer, buffer_position) = self
5389 .buffer
5390 .read(cx)
5391 .text_anchor_for_position(position, cx)?;
5392
5393 let settings = language_settings::language_settings(
5394 buffer
5395 .read(cx)
5396 .language_at(buffer_position)
5397 .map(|l| l.name()),
5398 buffer.read(cx).file(),
5399 cx,
5400 );
5401 if !settings.use_on_type_format {
5402 return None;
5403 }
5404
5405 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5406 // hence we do LSP request & edit on host side only — add formats to host's history.
5407 let push_to_lsp_host_history = true;
5408 // If this is not the host, append its history with new edits.
5409 let push_to_client_history = project.read(cx).is_via_collab();
5410
5411 let on_type_formatting = project.update(cx, |project, cx| {
5412 project.on_type_format(
5413 buffer.clone(),
5414 buffer_position,
5415 input,
5416 push_to_lsp_host_history,
5417 cx,
5418 )
5419 });
5420 Some(cx.spawn_in(window, async move |editor, cx| {
5421 if let Some(transaction) = on_type_formatting.await? {
5422 if push_to_client_history {
5423 buffer
5424 .update(cx, |buffer, _| {
5425 buffer.push_transaction(transaction, Instant::now());
5426 buffer.finalize_last_transaction();
5427 })
5428 .ok();
5429 }
5430 editor.update(cx, |editor, cx| {
5431 editor.refresh_document_highlights(cx);
5432 })?;
5433 }
5434 Ok(())
5435 }))
5436 }
5437
5438 pub fn show_word_completions(
5439 &mut self,
5440 _: &ShowWordCompletions,
5441 window: &mut Window,
5442 cx: &mut Context<Self>,
5443 ) {
5444 self.open_or_update_completions_menu(
5445 Some(CompletionsMenuSource::Words {
5446 ignore_threshold: true,
5447 }),
5448 None,
5449 false,
5450 window,
5451 cx,
5452 );
5453 }
5454
5455 pub fn show_completions(
5456 &mut self,
5457 _: &ShowCompletions,
5458 window: &mut Window,
5459 cx: &mut Context<Self>,
5460 ) {
5461 self.open_or_update_completions_menu(None, None, false, window, cx);
5462 }
5463
5464 fn open_or_update_completions_menu(
5465 &mut self,
5466 requested_source: Option<CompletionsMenuSource>,
5467 trigger: Option<String>,
5468 trigger_in_words: bool,
5469 window: &mut Window,
5470 cx: &mut Context<Self>,
5471 ) {
5472 if self.pending_rename.is_some() {
5473 return;
5474 }
5475
5476 let completions_source = self
5477 .context_menu
5478 .borrow()
5479 .as_ref()
5480 .and_then(|menu| match menu {
5481 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5482 CodeContextMenu::CodeActions(_) => None,
5483 });
5484
5485 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5486
5487 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5488 // inserted and selected. To handle that case, the start of the selection is used so that
5489 // the menu starts with all choices.
5490 let position = self
5491 .selections
5492 .newest_anchor()
5493 .start
5494 .bias_right(&multibuffer_snapshot);
5495 if position.diff_base_anchor.is_some() {
5496 return;
5497 }
5498 let buffer_position = multibuffer_snapshot.anchor_before(position);
5499 let Some(buffer) = buffer_position
5500 .text_anchor
5501 .buffer_id
5502 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5503 else {
5504 return;
5505 };
5506 let buffer_snapshot = buffer.read(cx).snapshot();
5507
5508 let query: Option<Arc<String>> =
5509 Self::completion_query(&multibuffer_snapshot, buffer_position)
5510 .map(|query| query.into());
5511
5512 drop(multibuffer_snapshot);
5513
5514 // Hide the current completions menu when query is empty. Without this, cached
5515 // completions from before the trigger char may be reused (#32774).
5516 if query.is_none() {
5517 let menu_is_open = matches!(
5518 self.context_menu.borrow().as_ref(),
5519 Some(CodeContextMenu::Completions(_))
5520 );
5521 if menu_is_open {
5522 self.hide_context_menu(window, cx);
5523 }
5524 }
5525
5526 let mut ignore_word_threshold = false;
5527 let provider = match requested_source {
5528 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5529 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5530 ignore_word_threshold = ignore_threshold;
5531 None
5532 }
5533 Some(CompletionsMenuSource::SnippetChoices)
5534 | Some(CompletionsMenuSource::SnippetsOnly) => {
5535 log::error!("bug: SnippetChoices requested_source is not handled");
5536 None
5537 }
5538 };
5539
5540 let sort_completions = provider
5541 .as_ref()
5542 .is_some_and(|provider| provider.sort_completions());
5543
5544 let filter_completions = provider
5545 .as_ref()
5546 .is_none_or(|provider| provider.filter_completions());
5547
5548 let was_snippets_only = matches!(
5549 completions_source,
5550 Some(CompletionsMenuSource::SnippetsOnly)
5551 );
5552
5553 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5554 if filter_completions {
5555 menu.filter(
5556 query.clone().unwrap_or_default(),
5557 buffer_position.text_anchor,
5558 &buffer,
5559 provider.clone(),
5560 window,
5561 cx,
5562 );
5563 }
5564 // When `is_incomplete` is false, no need to re-query completions when the current query
5565 // is a suffix of the initial query.
5566 let was_complete = !menu.is_incomplete;
5567 if was_complete && !was_snippets_only {
5568 // If the new query is a suffix of the old query (typing more characters) and
5569 // the previous result was complete, the existing completions can be filtered.
5570 //
5571 // Note that snippet completions are always complete.
5572 let query_matches = match (&menu.initial_query, &query) {
5573 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5574 (None, _) => true,
5575 _ => false,
5576 };
5577 if query_matches {
5578 let position_matches = if menu.initial_position == position {
5579 true
5580 } else {
5581 let snapshot = self.buffer.read(cx).read(cx);
5582 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5583 };
5584 if position_matches {
5585 return;
5586 }
5587 }
5588 }
5589 };
5590
5591 let Anchor {
5592 excerpt_id: buffer_excerpt_id,
5593 text_anchor: buffer_position,
5594 ..
5595 } = buffer_position;
5596
5597 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5598 buffer_snapshot.surrounding_word(buffer_position, None)
5599 {
5600 let word_to_exclude = buffer_snapshot
5601 .text_for_range(word_range.clone())
5602 .collect::<String>();
5603 (
5604 buffer_snapshot.anchor_before(word_range.start)
5605 ..buffer_snapshot.anchor_after(buffer_position),
5606 Some(word_to_exclude),
5607 )
5608 } else {
5609 (buffer_position..buffer_position, None)
5610 };
5611
5612 let language = buffer_snapshot
5613 .language_at(buffer_position)
5614 .map(|language| language.name());
5615
5616 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5617 .completions
5618 .clone();
5619
5620 let show_completion_documentation = buffer_snapshot
5621 .settings_at(buffer_position, cx)
5622 .show_completion_documentation;
5623
5624 // The document can be large, so stay in reasonable bounds when searching for words,
5625 // otherwise completion pop-up might be slow to appear.
5626 const WORD_LOOKUP_ROWS: u32 = 5_000;
5627 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5628 let min_word_search = buffer_snapshot.clip_point(
5629 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5630 Bias::Left,
5631 );
5632 let max_word_search = buffer_snapshot.clip_point(
5633 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5634 Bias::Right,
5635 );
5636 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5637 ..buffer_snapshot.point_to_offset(max_word_search);
5638
5639 let skip_digits = query
5640 .as_ref()
5641 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5642
5643 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5644 trigger.as_ref().is_none_or(|trigger| {
5645 provider.is_completion_trigger(
5646 &buffer,
5647 position.text_anchor,
5648 trigger,
5649 trigger_in_words,
5650 completions_source.is_some(),
5651 cx,
5652 )
5653 })
5654 });
5655
5656 let provider_responses = if let Some(provider) = &provider
5657 && load_provider_completions
5658 {
5659 let trigger_character =
5660 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5661 let completion_context = CompletionContext {
5662 trigger_kind: match &trigger_character {
5663 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5664 None => CompletionTriggerKind::INVOKED,
5665 },
5666 trigger_character,
5667 };
5668
5669 provider.completions(
5670 buffer_excerpt_id,
5671 &buffer,
5672 buffer_position,
5673 completion_context,
5674 window,
5675 cx,
5676 )
5677 } else {
5678 Task::ready(Ok(Vec::new()))
5679 };
5680
5681 let load_word_completions = if !self.word_completions_enabled {
5682 false
5683 } else if requested_source
5684 == Some(CompletionsMenuSource::Words {
5685 ignore_threshold: true,
5686 })
5687 {
5688 true
5689 } else {
5690 load_provider_completions
5691 && completion_settings.words != WordsCompletionMode::Disabled
5692 && (ignore_word_threshold || {
5693 let words_min_length = completion_settings.words_min_length;
5694 // check whether word has at least `words_min_length` characters
5695 let query_chars = query.iter().flat_map(|q| q.chars());
5696 query_chars.take(words_min_length).count() == words_min_length
5697 })
5698 };
5699
5700 let mut words = if load_word_completions {
5701 cx.background_spawn({
5702 let buffer_snapshot = buffer_snapshot.clone();
5703 async move {
5704 buffer_snapshot.words_in_range(WordsQuery {
5705 fuzzy_contents: None,
5706 range: word_search_range,
5707 skip_digits,
5708 })
5709 }
5710 })
5711 } else {
5712 Task::ready(BTreeMap::default())
5713 };
5714
5715 let snippets = if let Some(provider) = &provider
5716 && provider.show_snippets()
5717 && let Some(project) = self.project()
5718 {
5719 let char_classifier = buffer_snapshot
5720 .char_classifier_at(buffer_position)
5721 .scope_context(Some(CharScopeContext::Completion));
5722 project.update(cx, |project, cx| {
5723 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5724 })
5725 } else {
5726 Task::ready(Ok(CompletionResponse {
5727 completions: Vec::new(),
5728 display_options: Default::default(),
5729 is_incomplete: false,
5730 }))
5731 };
5732
5733 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5734
5735 let id = post_inc(&mut self.next_completion_id);
5736 let task = cx.spawn_in(window, async move |editor, cx| {
5737 let Ok(()) = editor.update(cx, |this, _| {
5738 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5739 }) else {
5740 return;
5741 };
5742
5743 // TODO: Ideally completions from different sources would be selectively re-queried, so
5744 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5745 let mut completions = Vec::new();
5746 let mut is_incomplete = false;
5747 let mut display_options: Option<CompletionDisplayOptions> = None;
5748 if let Some(provider_responses) = provider_responses.await.log_err()
5749 && !provider_responses.is_empty()
5750 {
5751 for response in provider_responses {
5752 completions.extend(response.completions);
5753 is_incomplete = is_incomplete || response.is_incomplete;
5754 match display_options.as_mut() {
5755 None => {
5756 display_options = Some(response.display_options);
5757 }
5758 Some(options) => options.merge(&response.display_options),
5759 }
5760 }
5761 if completion_settings.words == WordsCompletionMode::Fallback {
5762 words = Task::ready(BTreeMap::default());
5763 }
5764 }
5765 let display_options = display_options.unwrap_or_default();
5766
5767 let mut words = words.await;
5768 if let Some(word_to_exclude) = &word_to_exclude {
5769 words.remove(word_to_exclude);
5770 }
5771 for lsp_completion in &completions {
5772 words.remove(&lsp_completion.new_text);
5773 }
5774 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5775 replace_range: word_replace_range.clone(),
5776 new_text: word.clone(),
5777 label: CodeLabel::plain(word, None),
5778 match_start: None,
5779 snippet_deduplication_key: None,
5780 icon_path: None,
5781 documentation: None,
5782 source: CompletionSource::BufferWord {
5783 word_range,
5784 resolved: false,
5785 },
5786 insert_text_mode: Some(InsertTextMode::AS_IS),
5787 confirm: None,
5788 }));
5789
5790 completions.extend(
5791 snippets
5792 .await
5793 .into_iter()
5794 .flat_map(|response| response.completions),
5795 );
5796
5797 let menu = if completions.is_empty() {
5798 None
5799 } else {
5800 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5801 let languages = editor
5802 .workspace
5803 .as_ref()
5804 .and_then(|(workspace, _)| workspace.upgrade())
5805 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5806 let menu = CompletionsMenu::new(
5807 id,
5808 requested_source.unwrap_or(if load_provider_completions {
5809 CompletionsMenuSource::Normal
5810 } else {
5811 CompletionsMenuSource::SnippetsOnly
5812 }),
5813 sort_completions,
5814 show_completion_documentation,
5815 position,
5816 query.clone(),
5817 is_incomplete,
5818 buffer.clone(),
5819 completions.into(),
5820 display_options,
5821 snippet_sort_order,
5822 languages,
5823 language,
5824 cx,
5825 );
5826
5827 let query = if filter_completions { query } else { None };
5828 let matches_task = menu.do_async_filtering(
5829 query.unwrap_or_default(),
5830 buffer_position,
5831 &buffer,
5832 cx,
5833 );
5834 (menu, matches_task)
5835 }) else {
5836 return;
5837 };
5838
5839 let matches = matches_task.await;
5840
5841 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5842 // Newer menu already set, so exit.
5843 if let Some(CodeContextMenu::Completions(prev_menu)) =
5844 editor.context_menu.borrow().as_ref()
5845 && prev_menu.id > id
5846 {
5847 return;
5848 };
5849
5850 // Only valid to take prev_menu because either the new menu is immediately set
5851 // below, or the menu is hidden.
5852 if let Some(CodeContextMenu::Completions(prev_menu)) =
5853 editor.context_menu.borrow_mut().take()
5854 {
5855 let position_matches =
5856 if prev_menu.initial_position == menu.initial_position {
5857 true
5858 } else {
5859 let snapshot = editor.buffer.read(cx).read(cx);
5860 prev_menu.initial_position.to_offset(&snapshot)
5861 == menu.initial_position.to_offset(&snapshot)
5862 };
5863 if position_matches {
5864 // Preserve markdown cache before `set_filter_results` because it will
5865 // try to populate the documentation cache.
5866 menu.preserve_markdown_cache(prev_menu);
5867 }
5868 };
5869
5870 menu.set_filter_results(matches, provider, window, cx);
5871 }) else {
5872 return;
5873 };
5874
5875 menu.visible().then_some(menu)
5876 };
5877
5878 editor
5879 .update_in(cx, |editor, window, cx| {
5880 if editor.focus_handle.is_focused(window)
5881 && let Some(menu) = menu
5882 {
5883 *editor.context_menu.borrow_mut() =
5884 Some(CodeContextMenu::Completions(menu));
5885
5886 crate::hover_popover::hide_hover(editor, cx);
5887 if editor.show_edit_predictions_in_menu() {
5888 editor.update_visible_edit_prediction(window, cx);
5889 } else {
5890 editor.discard_edit_prediction(false, cx);
5891 }
5892
5893 cx.notify();
5894 return;
5895 }
5896
5897 if editor.completion_tasks.len() <= 1 {
5898 // If there are no more completion tasks and the last menu was empty, we should hide it.
5899 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5900 // If it was already hidden and we don't show edit predictions in the menu,
5901 // we should also show the edit prediction when available.
5902 if was_hidden && editor.show_edit_predictions_in_menu() {
5903 editor.update_visible_edit_prediction(window, cx);
5904 }
5905 }
5906 })
5907 .ok();
5908 });
5909
5910 self.completion_tasks.push((id, task));
5911 }
5912
5913 #[cfg(feature = "test-support")]
5914 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5915 let menu = self.context_menu.borrow();
5916 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5917 let completions = menu.completions.borrow();
5918 Some(completions.to_vec())
5919 } else {
5920 None
5921 }
5922 }
5923
5924 pub fn with_completions_menu_matching_id<R>(
5925 &self,
5926 id: CompletionId,
5927 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5928 ) -> R {
5929 let mut context_menu = self.context_menu.borrow_mut();
5930 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5931 return f(None);
5932 };
5933 if completions_menu.id != id {
5934 return f(None);
5935 }
5936 f(Some(completions_menu))
5937 }
5938
5939 pub fn confirm_completion(
5940 &mut self,
5941 action: &ConfirmCompletion,
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(action.item_ix, CompletionIntent::Complete, window, cx)
5947 }
5948
5949 pub fn confirm_completion_insert(
5950 &mut self,
5951 _: &ConfirmCompletionInsert,
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::CompleteWithInsert, window, cx)
5957 }
5958
5959 pub fn confirm_completion_replace(
5960 &mut self,
5961 _: &ConfirmCompletionReplace,
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(None, CompletionIntent::CompleteWithReplace, window, cx)
5967 }
5968
5969 pub fn compose_completion(
5970 &mut self,
5971 action: &ComposeCompletion,
5972 window: &mut Window,
5973 cx: &mut Context<Self>,
5974 ) -> Option<Task<Result<()>>> {
5975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5976 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5977 }
5978
5979 fn do_completion(
5980 &mut self,
5981 item_ix: Option<usize>,
5982 intent: CompletionIntent,
5983 window: &mut Window,
5984 cx: &mut Context<Editor>,
5985 ) -> Option<Task<Result<()>>> {
5986 use language::ToOffset as _;
5987
5988 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5989 else {
5990 return None;
5991 };
5992
5993 let candidate_id = {
5994 let entries = completions_menu.entries.borrow();
5995 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5996 if self.show_edit_predictions_in_menu() {
5997 self.discard_edit_prediction(true, cx);
5998 }
5999 mat.candidate_id
6000 };
6001
6002 let completion = completions_menu
6003 .completions
6004 .borrow()
6005 .get(candidate_id)?
6006 .clone();
6007 cx.stop_propagation();
6008
6009 let buffer_handle = completions_menu.buffer.clone();
6010
6011 let CompletionEdit {
6012 new_text,
6013 snippet,
6014 replace_range,
6015 } = process_completion_for_edit(
6016 &completion,
6017 intent,
6018 &buffer_handle,
6019 &completions_menu.initial_position.text_anchor,
6020 cx,
6021 );
6022
6023 let buffer = buffer_handle.read(cx);
6024 let snapshot = self.buffer.read(cx).snapshot(cx);
6025 let newest_anchor = self.selections.newest_anchor();
6026 let replace_range_multibuffer = {
6027 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6028 excerpt.map_range_from_buffer(replace_range.clone())
6029 };
6030 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6031 return None;
6032 }
6033
6034 let old_text = buffer
6035 .text_for_range(replace_range.clone())
6036 .collect::<String>();
6037 let lookbehind = newest_anchor
6038 .start
6039 .text_anchor
6040 .to_offset(buffer)
6041 .saturating_sub(replace_range.start.0);
6042 let lookahead = replace_range
6043 .end
6044 .0
6045 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6046 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6047 let suffix = &old_text[lookbehind.min(old_text.len())..];
6048
6049 let selections = self
6050 .selections
6051 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6052 let mut ranges = Vec::new();
6053 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6054
6055 for selection in &selections {
6056 let range = if selection.id == newest_anchor.id {
6057 replace_range_multibuffer.clone()
6058 } else {
6059 let mut range = selection.range();
6060
6061 // if prefix is present, don't duplicate it
6062 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6063 range.start = range.start.saturating_sub_usize(lookbehind);
6064
6065 // if suffix is also present, mimic the newest cursor and replace it
6066 if selection.id != newest_anchor.id
6067 && snapshot.contains_str_at(range.end, suffix)
6068 {
6069 range.end += lookahead;
6070 }
6071 }
6072 range
6073 };
6074
6075 ranges.push(range.clone());
6076
6077 if !self.linked_edit_ranges.is_empty() {
6078 let start_anchor = snapshot.anchor_before(range.start);
6079 let end_anchor = snapshot.anchor_after(range.end);
6080 if let Some(ranges) = self
6081 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6082 {
6083 for (buffer, edits) in ranges {
6084 linked_edits
6085 .entry(buffer.clone())
6086 .or_default()
6087 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6088 }
6089 }
6090 }
6091 }
6092
6093 let common_prefix_len = old_text
6094 .chars()
6095 .zip(new_text.chars())
6096 .take_while(|(a, b)| a == b)
6097 .map(|(a, _)| a.len_utf8())
6098 .sum::<usize>();
6099
6100 cx.emit(EditorEvent::InputHandled {
6101 utf16_range_to_replace: None,
6102 text: new_text[common_prefix_len..].into(),
6103 });
6104
6105 self.transact(window, cx, |editor, window, cx| {
6106 if let Some(mut snippet) = snippet {
6107 snippet.text = new_text.to_string();
6108 editor
6109 .insert_snippet(&ranges, snippet, window, cx)
6110 .log_err();
6111 } else {
6112 editor.buffer.update(cx, |multi_buffer, cx| {
6113 let auto_indent = match completion.insert_text_mode {
6114 Some(InsertTextMode::AS_IS) => None,
6115 _ => editor.autoindent_mode.clone(),
6116 };
6117 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6118 multi_buffer.edit(edits, auto_indent, cx);
6119 });
6120 }
6121 for (buffer, edits) in linked_edits {
6122 buffer.update(cx, |buffer, cx| {
6123 let snapshot = buffer.snapshot();
6124 let edits = edits
6125 .into_iter()
6126 .map(|(range, text)| {
6127 use text::ToPoint as TP;
6128 let end_point = TP::to_point(&range.end, &snapshot);
6129 let start_point = TP::to_point(&range.start, &snapshot);
6130 (start_point..end_point, text)
6131 })
6132 .sorted_by_key(|(range, _)| range.start);
6133 buffer.edit(edits, None, cx);
6134 })
6135 }
6136
6137 editor.refresh_edit_prediction(true, false, window, cx);
6138 });
6139 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6140
6141 let show_new_completions_on_confirm = completion
6142 .confirm
6143 .as_ref()
6144 .is_some_and(|confirm| confirm(intent, window, cx));
6145 if show_new_completions_on_confirm {
6146 self.open_or_update_completions_menu(None, None, false, window, cx);
6147 }
6148
6149 let provider = self.completion_provider.as_ref()?;
6150 drop(completion);
6151 let apply_edits = provider.apply_additional_edits_for_completion(
6152 buffer_handle,
6153 completions_menu.completions.clone(),
6154 candidate_id,
6155 true,
6156 cx,
6157 );
6158
6159 let editor_settings = EditorSettings::get_global(cx);
6160 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6161 // After the code completion is finished, users often want to know what signatures are needed.
6162 // so we should automatically call signature_help
6163 self.show_signature_help(&ShowSignatureHelp, window, cx);
6164 }
6165
6166 Some(cx.foreground_executor().spawn(async move {
6167 apply_edits.await?;
6168 Ok(())
6169 }))
6170 }
6171
6172 pub fn toggle_code_actions(
6173 &mut self,
6174 action: &ToggleCodeActions,
6175 window: &mut Window,
6176 cx: &mut Context<Self>,
6177 ) {
6178 let quick_launch = action.quick_launch;
6179 let mut context_menu = self.context_menu.borrow_mut();
6180 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6181 if code_actions.deployed_from == action.deployed_from {
6182 // Toggle if we're selecting the same one
6183 *context_menu = None;
6184 cx.notify();
6185 return;
6186 } else {
6187 // Otherwise, clear it and start a new one
6188 *context_menu = None;
6189 cx.notify();
6190 }
6191 }
6192 drop(context_menu);
6193 let snapshot = self.snapshot(window, cx);
6194 let deployed_from = action.deployed_from.clone();
6195 let action = action.clone();
6196 self.completion_tasks.clear();
6197 self.discard_edit_prediction(false, cx);
6198
6199 let multibuffer_point = match &action.deployed_from {
6200 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6201 DisplayPoint::new(*row, 0).to_point(&snapshot)
6202 }
6203 _ => self
6204 .selections
6205 .newest::<Point>(&snapshot.display_snapshot)
6206 .head(),
6207 };
6208 let Some((buffer, buffer_row)) = snapshot
6209 .buffer_snapshot()
6210 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6211 .and_then(|(buffer_snapshot, range)| {
6212 self.buffer()
6213 .read(cx)
6214 .buffer(buffer_snapshot.remote_id())
6215 .map(|buffer| (buffer, range.start.row))
6216 })
6217 else {
6218 return;
6219 };
6220 let buffer_id = buffer.read(cx).remote_id();
6221 let tasks = self
6222 .tasks
6223 .get(&(buffer_id, buffer_row))
6224 .map(|t| Arc::new(t.to_owned()));
6225
6226 if !self.focus_handle.is_focused(window) {
6227 return;
6228 }
6229 let project = self.project.clone();
6230
6231 let code_actions_task = match deployed_from {
6232 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6233 _ => self.code_actions(buffer_row, window, cx),
6234 };
6235
6236 let runnable_task = match deployed_from {
6237 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6238 _ => {
6239 let mut task_context_task = Task::ready(None);
6240 if let Some(tasks) = &tasks
6241 && let Some(project) = project
6242 {
6243 task_context_task =
6244 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6245 }
6246
6247 cx.spawn_in(window, {
6248 let buffer = buffer.clone();
6249 async move |editor, cx| {
6250 let task_context = task_context_task.await;
6251
6252 let resolved_tasks =
6253 tasks
6254 .zip(task_context.clone())
6255 .map(|(tasks, task_context)| ResolvedTasks {
6256 templates: tasks.resolve(&task_context).collect(),
6257 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6258 multibuffer_point.row,
6259 tasks.column,
6260 )),
6261 });
6262 let debug_scenarios = editor
6263 .update(cx, |editor, cx| {
6264 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6265 })?
6266 .await;
6267 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6268 }
6269 })
6270 }
6271 };
6272
6273 cx.spawn_in(window, async move |editor, cx| {
6274 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6275 let code_actions = code_actions_task.await;
6276 let spawn_straight_away = quick_launch
6277 && resolved_tasks
6278 .as_ref()
6279 .is_some_and(|tasks| tasks.templates.len() == 1)
6280 && code_actions
6281 .as_ref()
6282 .is_none_or(|actions| actions.is_empty())
6283 && debug_scenarios.is_empty();
6284
6285 editor.update_in(cx, |editor, window, cx| {
6286 crate::hover_popover::hide_hover(editor, cx);
6287 let actions = CodeActionContents::new(
6288 resolved_tasks,
6289 code_actions,
6290 debug_scenarios,
6291 task_context.unwrap_or_default(),
6292 );
6293
6294 // Don't show the menu if there are no actions available
6295 if actions.is_empty() {
6296 cx.notify();
6297 return Task::ready(Ok(()));
6298 }
6299
6300 *editor.context_menu.borrow_mut() =
6301 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6302 buffer,
6303 actions,
6304 selected_item: Default::default(),
6305 scroll_handle: UniformListScrollHandle::default(),
6306 deployed_from,
6307 }));
6308 cx.notify();
6309 if spawn_straight_away
6310 && let Some(task) = editor.confirm_code_action(
6311 &ConfirmCodeAction { item_ix: Some(0) },
6312 window,
6313 cx,
6314 )
6315 {
6316 return task;
6317 }
6318
6319 Task::ready(Ok(()))
6320 })
6321 })
6322 .detach_and_log_err(cx);
6323 }
6324
6325 fn debug_scenarios(
6326 &mut self,
6327 resolved_tasks: &Option<ResolvedTasks>,
6328 buffer: &Entity<Buffer>,
6329 cx: &mut App,
6330 ) -> Task<Vec<task::DebugScenario>> {
6331 maybe!({
6332 let project = self.project()?;
6333 let dap_store = project.read(cx).dap_store();
6334 let mut scenarios = vec![];
6335 let resolved_tasks = resolved_tasks.as_ref()?;
6336 let buffer = buffer.read(cx);
6337 let language = buffer.language()?;
6338 let file = buffer.file();
6339 let debug_adapter = language_settings(language.name().into(), file, cx)
6340 .debuggers
6341 .first()
6342 .map(SharedString::from)
6343 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6344
6345 dap_store.update(cx, |dap_store, cx| {
6346 for (_, task) in &resolved_tasks.templates {
6347 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6348 task.original_task().clone(),
6349 debug_adapter.clone().into(),
6350 task.display_label().to_owned().into(),
6351 cx,
6352 );
6353 scenarios.push(maybe_scenario);
6354 }
6355 });
6356 Some(cx.background_spawn(async move {
6357 futures::future::join_all(scenarios)
6358 .await
6359 .into_iter()
6360 .flatten()
6361 .collect::<Vec<_>>()
6362 }))
6363 })
6364 .unwrap_or_else(|| Task::ready(vec![]))
6365 }
6366
6367 fn code_actions(
6368 &mut self,
6369 buffer_row: u32,
6370 window: &mut Window,
6371 cx: &mut Context<Self>,
6372 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6373 let mut task = self.code_actions_task.take();
6374 cx.spawn_in(window, async move |editor, cx| {
6375 while let Some(prev_task) = task {
6376 prev_task.await.log_err();
6377 task = editor
6378 .update(cx, |this, _| this.code_actions_task.take())
6379 .ok()?;
6380 }
6381
6382 editor
6383 .update(cx, |editor, cx| {
6384 editor
6385 .available_code_actions
6386 .clone()
6387 .and_then(|(location, code_actions)| {
6388 let snapshot = location.buffer.read(cx).snapshot();
6389 let point_range = location.range.to_point(&snapshot);
6390 let point_range = point_range.start.row..=point_range.end.row;
6391 if point_range.contains(&buffer_row) {
6392 Some(code_actions)
6393 } else {
6394 None
6395 }
6396 })
6397 })
6398 .ok()
6399 .flatten()
6400 })
6401 }
6402
6403 pub fn confirm_code_action(
6404 &mut self,
6405 action: &ConfirmCodeAction,
6406 window: &mut Window,
6407 cx: &mut Context<Self>,
6408 ) -> Option<Task<Result<()>>> {
6409 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6410
6411 let actions_menu =
6412 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6413 menu
6414 } else {
6415 return None;
6416 };
6417
6418 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6419 let action = actions_menu.actions.get(action_ix)?;
6420 let title = action.label();
6421 let buffer = actions_menu.buffer;
6422 let workspace = self.workspace()?;
6423
6424 match action {
6425 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6426 workspace.update(cx, |workspace, cx| {
6427 workspace.schedule_resolved_task(
6428 task_source_kind,
6429 resolved_task,
6430 false,
6431 window,
6432 cx,
6433 );
6434
6435 Some(Task::ready(Ok(())))
6436 })
6437 }
6438 CodeActionsItem::CodeAction {
6439 excerpt_id,
6440 action,
6441 provider,
6442 } => {
6443 let apply_code_action =
6444 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6445 let workspace = workspace.downgrade();
6446 Some(cx.spawn_in(window, async move |editor, cx| {
6447 let project_transaction = apply_code_action.await?;
6448 Self::open_project_transaction(
6449 &editor,
6450 workspace,
6451 project_transaction,
6452 title,
6453 cx,
6454 )
6455 .await
6456 }))
6457 }
6458 CodeActionsItem::DebugScenario(scenario) => {
6459 let context = actions_menu.actions.context;
6460
6461 workspace.update(cx, |workspace, cx| {
6462 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6463 workspace.start_debug_session(
6464 scenario,
6465 context,
6466 Some(buffer),
6467 None,
6468 window,
6469 cx,
6470 );
6471 });
6472 Some(Task::ready(Ok(())))
6473 }
6474 }
6475 }
6476
6477 pub async fn open_project_transaction(
6478 editor: &WeakEntity<Editor>,
6479 workspace: WeakEntity<Workspace>,
6480 transaction: ProjectTransaction,
6481 title: String,
6482 cx: &mut AsyncWindowContext,
6483 ) -> Result<()> {
6484 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6485 cx.update(|_, cx| {
6486 entries.sort_unstable_by_key(|(buffer, _)| {
6487 buffer.read(cx).file().map(|f| f.path().clone())
6488 });
6489 })?;
6490 if entries.is_empty() {
6491 return Ok(());
6492 }
6493
6494 // If the project transaction's edits are all contained within this editor, then
6495 // avoid opening a new editor to display them.
6496
6497 if let [(buffer, transaction)] = &*entries {
6498 let excerpt = editor.update(cx, |editor, cx| {
6499 editor
6500 .buffer()
6501 .read(cx)
6502 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6503 })?;
6504 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6505 && excerpted_buffer == *buffer
6506 {
6507 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6508 let excerpt_range = excerpt_range.to_offset(buffer);
6509 buffer
6510 .edited_ranges_for_transaction::<usize>(transaction)
6511 .all(|range| {
6512 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6513 })
6514 })?;
6515
6516 if all_edits_within_excerpt {
6517 return Ok(());
6518 }
6519 }
6520 }
6521
6522 let mut ranges_to_highlight = Vec::new();
6523 let excerpt_buffer = cx.new(|cx| {
6524 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6525 for (buffer_handle, transaction) in &entries {
6526 let edited_ranges = buffer_handle
6527 .read(cx)
6528 .edited_ranges_for_transaction::<Point>(transaction)
6529 .collect::<Vec<_>>();
6530 let (ranges, _) = multibuffer.set_excerpts_for_path(
6531 PathKey::for_buffer(buffer_handle, cx),
6532 buffer_handle.clone(),
6533 edited_ranges,
6534 multibuffer_context_lines(cx),
6535 cx,
6536 );
6537
6538 ranges_to_highlight.extend(ranges);
6539 }
6540 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6541 multibuffer
6542 })?;
6543
6544 workspace.update_in(cx, |workspace, window, cx| {
6545 let project = workspace.project().clone();
6546 let editor =
6547 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6548 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6549 editor.update(cx, |editor, cx| {
6550 editor.highlight_background::<Self>(
6551 &ranges_to_highlight,
6552 |theme| theme.colors().editor_highlighted_line_background,
6553 cx,
6554 );
6555 });
6556 })?;
6557
6558 Ok(())
6559 }
6560
6561 pub fn clear_code_action_providers(&mut self) {
6562 self.code_action_providers.clear();
6563 self.available_code_actions.take();
6564 }
6565
6566 pub fn add_code_action_provider(
6567 &mut self,
6568 provider: Rc<dyn CodeActionProvider>,
6569 window: &mut Window,
6570 cx: &mut Context<Self>,
6571 ) {
6572 if self
6573 .code_action_providers
6574 .iter()
6575 .any(|existing_provider| existing_provider.id() == provider.id())
6576 {
6577 return;
6578 }
6579
6580 self.code_action_providers.push(provider);
6581 self.refresh_code_actions(window, cx);
6582 }
6583
6584 pub fn remove_code_action_provider(
6585 &mut self,
6586 id: Arc<str>,
6587 window: &mut Window,
6588 cx: &mut Context<Self>,
6589 ) {
6590 self.code_action_providers
6591 .retain(|provider| provider.id() != id);
6592 self.refresh_code_actions(window, cx);
6593 }
6594
6595 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6596 !self.code_action_providers.is_empty()
6597 && EditorSettings::get_global(cx).toolbar.code_actions
6598 }
6599
6600 pub fn has_available_code_actions(&self) -> bool {
6601 self.available_code_actions
6602 .as_ref()
6603 .is_some_and(|(_, actions)| !actions.is_empty())
6604 }
6605
6606 fn render_inline_code_actions(
6607 &self,
6608 icon_size: ui::IconSize,
6609 display_row: DisplayRow,
6610 is_active: bool,
6611 cx: &mut Context<Self>,
6612 ) -> AnyElement {
6613 let show_tooltip = !self.context_menu_visible();
6614 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6615 .icon_size(icon_size)
6616 .shape(ui::IconButtonShape::Square)
6617 .icon_color(ui::Color::Hidden)
6618 .toggle_state(is_active)
6619 .when(show_tooltip, |this| {
6620 this.tooltip({
6621 let focus_handle = self.focus_handle.clone();
6622 move |_window, cx| {
6623 Tooltip::for_action_in(
6624 "Toggle Code Actions",
6625 &ToggleCodeActions {
6626 deployed_from: None,
6627 quick_launch: false,
6628 },
6629 &focus_handle,
6630 cx,
6631 )
6632 }
6633 })
6634 })
6635 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6636 window.focus(&editor.focus_handle(cx));
6637 editor.toggle_code_actions(
6638 &crate::actions::ToggleCodeActions {
6639 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6640 display_row,
6641 )),
6642 quick_launch: false,
6643 },
6644 window,
6645 cx,
6646 );
6647 }))
6648 .into_any_element()
6649 }
6650
6651 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6652 &self.context_menu
6653 }
6654
6655 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6656 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6657 cx.background_executor()
6658 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6659 .await;
6660
6661 let (start_buffer, start, _, end, newest_selection) = this
6662 .update(cx, |this, cx| {
6663 let newest_selection = this.selections.newest_anchor().clone();
6664 if newest_selection.head().diff_base_anchor.is_some() {
6665 return None;
6666 }
6667 let display_snapshot = this.display_snapshot(cx);
6668 let newest_selection_adjusted =
6669 this.selections.newest_adjusted(&display_snapshot);
6670 let buffer = this.buffer.read(cx);
6671
6672 let (start_buffer, start) =
6673 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6674 let (end_buffer, end) =
6675 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6676
6677 Some((start_buffer, start, end_buffer, end, newest_selection))
6678 })?
6679 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6680 .context(
6681 "Expected selection to lie in a single buffer when refreshing code actions",
6682 )?;
6683 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6684 let providers = this.code_action_providers.clone();
6685 let tasks = this
6686 .code_action_providers
6687 .iter()
6688 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6689 .collect::<Vec<_>>();
6690 (providers, tasks)
6691 })?;
6692
6693 let mut actions = Vec::new();
6694 for (provider, provider_actions) in
6695 providers.into_iter().zip(future::join_all(tasks).await)
6696 {
6697 if let Some(provider_actions) = provider_actions.log_err() {
6698 actions.extend(provider_actions.into_iter().map(|action| {
6699 AvailableCodeAction {
6700 excerpt_id: newest_selection.start.excerpt_id,
6701 action,
6702 provider: provider.clone(),
6703 }
6704 }));
6705 }
6706 }
6707
6708 this.update(cx, |this, cx| {
6709 this.available_code_actions = if actions.is_empty() {
6710 None
6711 } else {
6712 Some((
6713 Location {
6714 buffer: start_buffer,
6715 range: start..end,
6716 },
6717 actions.into(),
6718 ))
6719 };
6720 cx.notify();
6721 })
6722 }));
6723 }
6724
6725 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6726 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6727 self.show_git_blame_inline = false;
6728
6729 self.show_git_blame_inline_delay_task =
6730 Some(cx.spawn_in(window, async move |this, cx| {
6731 cx.background_executor().timer(delay).await;
6732
6733 this.update(cx, |this, cx| {
6734 this.show_git_blame_inline = true;
6735 cx.notify();
6736 })
6737 .log_err();
6738 }));
6739 }
6740 }
6741
6742 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6743 let snapshot = self.snapshot(window, cx);
6744 let cursor = self
6745 .selections
6746 .newest::<Point>(&snapshot.display_snapshot)
6747 .head();
6748 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6749 else {
6750 return;
6751 };
6752
6753 let Some(blame) = self.blame.as_ref() else {
6754 return;
6755 };
6756
6757 let row_info = RowInfo {
6758 buffer_id: Some(buffer.remote_id()),
6759 buffer_row: Some(point.row),
6760 ..Default::default()
6761 };
6762 let Some((buffer, blame_entry)) = blame
6763 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6764 .flatten()
6765 else {
6766 return;
6767 };
6768
6769 let anchor = self.selections.newest_anchor().head();
6770 let position = self.to_pixel_point(anchor, &snapshot, window);
6771 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6772 self.show_blame_popover(
6773 buffer,
6774 &blame_entry,
6775 position + last_bounds.origin,
6776 true,
6777 cx,
6778 );
6779 };
6780 }
6781
6782 fn show_blame_popover(
6783 &mut self,
6784 buffer: BufferId,
6785 blame_entry: &BlameEntry,
6786 position: gpui::Point<Pixels>,
6787 ignore_timeout: bool,
6788 cx: &mut Context<Self>,
6789 ) {
6790 if let Some(state) = &mut self.inline_blame_popover {
6791 state.hide_task.take();
6792 } else {
6793 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6794 let blame_entry = blame_entry.clone();
6795 let show_task = cx.spawn(async move |editor, cx| {
6796 if !ignore_timeout {
6797 cx.background_executor()
6798 .timer(std::time::Duration::from_millis(blame_popover_delay))
6799 .await;
6800 }
6801 editor
6802 .update(cx, |editor, cx| {
6803 editor.inline_blame_popover_show_task.take();
6804 let Some(blame) = editor.blame.as_ref() else {
6805 return;
6806 };
6807 let blame = blame.read(cx);
6808 let details = blame.details_for_entry(buffer, &blame_entry);
6809 let markdown = cx.new(|cx| {
6810 Markdown::new(
6811 details
6812 .as_ref()
6813 .map(|message| message.message.clone())
6814 .unwrap_or_default(),
6815 None,
6816 None,
6817 cx,
6818 )
6819 });
6820 editor.inline_blame_popover = Some(InlineBlamePopover {
6821 position,
6822 hide_task: None,
6823 popover_bounds: None,
6824 popover_state: InlineBlamePopoverState {
6825 scroll_handle: ScrollHandle::new(),
6826 commit_message: details,
6827 markdown,
6828 },
6829 keyboard_grace: ignore_timeout,
6830 });
6831 cx.notify();
6832 })
6833 .ok();
6834 });
6835 self.inline_blame_popover_show_task = Some(show_task);
6836 }
6837 }
6838
6839 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6840 self.inline_blame_popover_show_task.take();
6841 if let Some(state) = &mut self.inline_blame_popover {
6842 let hide_task = cx.spawn(async move |editor, cx| {
6843 if !ignore_timeout {
6844 cx.background_executor()
6845 .timer(std::time::Duration::from_millis(100))
6846 .await;
6847 }
6848 editor
6849 .update(cx, |editor, cx| {
6850 editor.inline_blame_popover.take();
6851 cx.notify();
6852 })
6853 .ok();
6854 });
6855 state.hide_task = Some(hide_task);
6856 true
6857 } else {
6858 false
6859 }
6860 }
6861
6862 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6863 if self.pending_rename.is_some() {
6864 return None;
6865 }
6866
6867 let provider = self.semantics_provider.clone()?;
6868 let buffer = self.buffer.read(cx);
6869 let newest_selection = self.selections.newest_anchor().clone();
6870 let cursor_position = newest_selection.head();
6871 let (cursor_buffer, cursor_buffer_position) =
6872 buffer.text_anchor_for_position(cursor_position, cx)?;
6873 let (tail_buffer, tail_buffer_position) =
6874 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6875 if cursor_buffer != tail_buffer {
6876 return None;
6877 }
6878
6879 let snapshot = cursor_buffer.read(cx).snapshot();
6880 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6881 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6882 if start_word_range != end_word_range {
6883 self.document_highlights_task.take();
6884 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6885 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6886 return None;
6887 }
6888
6889 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6890 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6891 cx.background_executor()
6892 .timer(Duration::from_millis(debounce))
6893 .await;
6894
6895 let highlights = if let Some(highlights) = cx
6896 .update(|cx| {
6897 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6898 })
6899 .ok()
6900 .flatten()
6901 {
6902 highlights.await.log_err()
6903 } else {
6904 None
6905 };
6906
6907 if let Some(highlights) = highlights {
6908 this.update(cx, |this, cx| {
6909 if this.pending_rename.is_some() {
6910 return;
6911 }
6912
6913 let buffer = this.buffer.read(cx);
6914 if buffer
6915 .text_anchor_for_position(cursor_position, cx)
6916 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6917 {
6918 return;
6919 }
6920
6921 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6922 let mut write_ranges = Vec::new();
6923 let mut read_ranges = Vec::new();
6924 for highlight in highlights {
6925 let buffer_id = cursor_buffer.read(cx).remote_id();
6926 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6927 {
6928 let start = highlight
6929 .range
6930 .start
6931 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6932 let end = highlight
6933 .range
6934 .end
6935 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6936 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6937 continue;
6938 }
6939
6940 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
6941 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6942 write_ranges.push(range);
6943 } else {
6944 read_ranges.push(range);
6945 }
6946 }
6947 }
6948
6949 this.highlight_background::<DocumentHighlightRead>(
6950 &read_ranges,
6951 |theme| theme.colors().editor_document_highlight_read_background,
6952 cx,
6953 );
6954 this.highlight_background::<DocumentHighlightWrite>(
6955 &write_ranges,
6956 |theme| theme.colors().editor_document_highlight_write_background,
6957 cx,
6958 );
6959 cx.notify();
6960 })
6961 .log_err();
6962 }
6963 }));
6964 None
6965 }
6966
6967 fn prepare_highlight_query_from_selection(
6968 &mut self,
6969 window: &Window,
6970 cx: &mut Context<Editor>,
6971 ) -> Option<(String, Range<Anchor>)> {
6972 if matches!(self.mode, EditorMode::SingleLine) {
6973 return None;
6974 }
6975 if !EditorSettings::get_global(cx).selection_highlight {
6976 return None;
6977 }
6978 if self.selections.count() != 1 || self.selections.line_mode() {
6979 return None;
6980 }
6981 let snapshot = self.snapshot(window, cx);
6982 let selection = self.selections.newest::<Point>(&snapshot);
6983 // If the selection spans multiple rows OR it is empty
6984 if selection.start.row != selection.end.row
6985 || selection.start.column == selection.end.column
6986 {
6987 return None;
6988 }
6989 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6990 let query = snapshot
6991 .buffer_snapshot()
6992 .text_for_range(selection_anchor_range.clone())
6993 .collect::<String>();
6994 if query.trim().is_empty() {
6995 return None;
6996 }
6997 Some((query, selection_anchor_range))
6998 }
6999
7000 fn update_selection_occurrence_highlights(
7001 &mut self,
7002 query_text: String,
7003 query_range: Range<Anchor>,
7004 multi_buffer_range_to_query: Range<Point>,
7005 use_debounce: bool,
7006 window: &mut Window,
7007 cx: &mut Context<Editor>,
7008 ) -> Task<()> {
7009 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7010 cx.spawn_in(window, async move |editor, cx| {
7011 if use_debounce {
7012 cx.background_executor()
7013 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7014 .await;
7015 }
7016 let match_task = cx.background_spawn(async move {
7017 let buffer_ranges = multi_buffer_snapshot
7018 .range_to_buffer_ranges(multi_buffer_range_to_query)
7019 .into_iter()
7020 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7021 let mut match_ranges = Vec::new();
7022 let Ok(regex) = project::search::SearchQuery::text(
7023 query_text.clone(),
7024 false,
7025 false,
7026 false,
7027 Default::default(),
7028 Default::default(),
7029 false,
7030 None,
7031 ) else {
7032 return Vec::default();
7033 };
7034 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7035 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7036 match_ranges.extend(
7037 regex
7038 .search(
7039 buffer_snapshot,
7040 Some(search_range.start.0..search_range.end.0),
7041 )
7042 .await
7043 .into_iter()
7044 .filter_map(|match_range| {
7045 let match_start = buffer_snapshot
7046 .anchor_after(search_range.start + match_range.start);
7047 let match_end = buffer_snapshot
7048 .anchor_before(search_range.start + match_range.end);
7049 let match_anchor_range =
7050 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7051 (match_anchor_range != query_range).then_some(match_anchor_range)
7052 }),
7053 );
7054 }
7055 match_ranges
7056 });
7057 let match_ranges = match_task.await;
7058 editor
7059 .update_in(cx, |editor, _, cx| {
7060 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7061 if !match_ranges.is_empty() {
7062 editor.highlight_background::<SelectedTextHighlight>(
7063 &match_ranges,
7064 |theme| theme.colors().editor_document_highlight_bracket_background,
7065 cx,
7066 )
7067 }
7068 })
7069 .log_err();
7070 })
7071 }
7072
7073 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7074 struct NewlineFold;
7075 let type_id = std::any::TypeId::of::<NewlineFold>();
7076 if !self.mode.is_single_line() {
7077 return;
7078 }
7079 let snapshot = self.snapshot(window, cx);
7080 if snapshot.buffer_snapshot().max_point().row == 0 {
7081 return;
7082 }
7083 let task = cx.background_spawn(async move {
7084 let new_newlines = snapshot
7085 .buffer_chars_at(MultiBufferOffset(0))
7086 .filter_map(|(c, i)| {
7087 if c == '\n' {
7088 Some(
7089 snapshot.buffer_snapshot().anchor_after(i)
7090 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7091 )
7092 } else {
7093 None
7094 }
7095 })
7096 .collect::<Vec<_>>();
7097 let existing_newlines = snapshot
7098 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7099 .filter_map(|fold| {
7100 if fold.placeholder.type_tag == Some(type_id) {
7101 Some(fold.range.start..fold.range.end)
7102 } else {
7103 None
7104 }
7105 })
7106 .collect::<Vec<_>>();
7107
7108 (new_newlines, existing_newlines)
7109 });
7110 self.folding_newlines = cx.spawn(async move |this, cx| {
7111 let (new_newlines, existing_newlines) = task.await;
7112 if new_newlines == existing_newlines {
7113 return;
7114 }
7115 let placeholder = FoldPlaceholder {
7116 render: Arc::new(move |_, _, cx| {
7117 div()
7118 .bg(cx.theme().status().hint_background)
7119 .border_b_1()
7120 .size_full()
7121 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7122 .border_color(cx.theme().status().hint)
7123 .child("\\n")
7124 .into_any()
7125 }),
7126 constrain_width: false,
7127 merge_adjacent: false,
7128 type_tag: Some(type_id),
7129 };
7130 let creases = new_newlines
7131 .into_iter()
7132 .map(|range| Crease::simple(range, placeholder.clone()))
7133 .collect();
7134 this.update(cx, |this, cx| {
7135 this.display_map.update(cx, |display_map, cx| {
7136 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7137 display_map.fold(creases, cx);
7138 });
7139 })
7140 .ok();
7141 });
7142 }
7143
7144 fn refresh_selected_text_highlights(
7145 &mut self,
7146 on_buffer_edit: bool,
7147 window: &mut Window,
7148 cx: &mut Context<Editor>,
7149 ) {
7150 let Some((query_text, query_range)) =
7151 self.prepare_highlight_query_from_selection(window, cx)
7152 else {
7153 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7154 self.quick_selection_highlight_task.take();
7155 self.debounced_selection_highlight_task.take();
7156 return;
7157 };
7158 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7159 if on_buffer_edit
7160 || self
7161 .quick_selection_highlight_task
7162 .as_ref()
7163 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7164 {
7165 let multi_buffer_visible_start = self
7166 .scroll_manager
7167 .anchor()
7168 .anchor
7169 .to_point(&multi_buffer_snapshot);
7170 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7171 multi_buffer_visible_start
7172 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7173 Bias::Left,
7174 );
7175 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7176 self.quick_selection_highlight_task = Some((
7177 query_range.clone(),
7178 self.update_selection_occurrence_highlights(
7179 query_text.clone(),
7180 query_range.clone(),
7181 multi_buffer_visible_range,
7182 false,
7183 window,
7184 cx,
7185 ),
7186 ));
7187 }
7188 if on_buffer_edit
7189 || self
7190 .debounced_selection_highlight_task
7191 .as_ref()
7192 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7193 {
7194 let multi_buffer_start = multi_buffer_snapshot
7195 .anchor_before(MultiBufferOffset(0))
7196 .to_point(&multi_buffer_snapshot);
7197 let multi_buffer_end = multi_buffer_snapshot
7198 .anchor_after(multi_buffer_snapshot.len())
7199 .to_point(&multi_buffer_snapshot);
7200 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7201 self.debounced_selection_highlight_task = Some((
7202 query_range.clone(),
7203 self.update_selection_occurrence_highlights(
7204 query_text,
7205 query_range,
7206 multi_buffer_full_range,
7207 true,
7208 window,
7209 cx,
7210 ),
7211 ));
7212 }
7213 }
7214
7215 pub fn refresh_edit_prediction(
7216 &mut self,
7217 debounce: bool,
7218 user_requested: bool,
7219 window: &mut Window,
7220 cx: &mut Context<Self>,
7221 ) -> Option<()> {
7222 if DisableAiSettings::get_global(cx).disable_ai {
7223 return None;
7224 }
7225
7226 let provider = self.edit_prediction_provider()?;
7227 let cursor = self.selections.newest_anchor().head();
7228 let (buffer, cursor_buffer_position) =
7229 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7230
7231 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7232 self.discard_edit_prediction(false, cx);
7233 return None;
7234 }
7235
7236 self.update_visible_edit_prediction(window, cx);
7237
7238 if !user_requested
7239 && (!self.should_show_edit_predictions()
7240 || !self.is_focused(window)
7241 || buffer.read(cx).is_empty())
7242 {
7243 self.discard_edit_prediction(false, cx);
7244 return None;
7245 }
7246
7247 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7248 Some(())
7249 }
7250
7251 fn show_edit_predictions_in_menu(&self) -> bool {
7252 match self.edit_prediction_settings {
7253 EditPredictionSettings::Disabled => false,
7254 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7255 }
7256 }
7257
7258 pub fn edit_predictions_enabled(&self) -> bool {
7259 match self.edit_prediction_settings {
7260 EditPredictionSettings::Disabled => false,
7261 EditPredictionSettings::Enabled { .. } => true,
7262 }
7263 }
7264
7265 fn edit_prediction_requires_modifier(&self) -> bool {
7266 match self.edit_prediction_settings {
7267 EditPredictionSettings::Disabled => false,
7268 EditPredictionSettings::Enabled {
7269 preview_requires_modifier,
7270 ..
7271 } => preview_requires_modifier,
7272 }
7273 }
7274
7275 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7276 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7277 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7278 self.discard_edit_prediction(false, cx);
7279 } else {
7280 let selection = self.selections.newest_anchor();
7281 let cursor = selection.head();
7282
7283 if let Some((buffer, cursor_buffer_position)) =
7284 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7285 {
7286 self.edit_prediction_settings =
7287 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7288 }
7289 }
7290 }
7291
7292 fn edit_prediction_settings_at_position(
7293 &self,
7294 buffer: &Entity<Buffer>,
7295 buffer_position: language::Anchor,
7296 cx: &App,
7297 ) -> EditPredictionSettings {
7298 if !self.mode.is_full()
7299 || !self.show_edit_predictions_override.unwrap_or(true)
7300 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7301 {
7302 return EditPredictionSettings::Disabled;
7303 }
7304
7305 let buffer = buffer.read(cx);
7306
7307 let file = buffer.file();
7308
7309 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7310 return EditPredictionSettings::Disabled;
7311 };
7312
7313 let by_provider = matches!(
7314 self.menu_edit_predictions_policy,
7315 MenuEditPredictionsPolicy::ByProvider
7316 );
7317
7318 let show_in_menu = by_provider
7319 && self
7320 .edit_prediction_provider
7321 .as_ref()
7322 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7323
7324 let preview_requires_modifier =
7325 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7326
7327 EditPredictionSettings::Enabled {
7328 show_in_menu,
7329 preview_requires_modifier,
7330 }
7331 }
7332
7333 fn should_show_edit_predictions(&self) -> bool {
7334 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7335 }
7336
7337 pub fn edit_prediction_preview_is_active(&self) -> bool {
7338 matches!(
7339 self.edit_prediction_preview,
7340 EditPredictionPreview::Active { .. }
7341 )
7342 }
7343
7344 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7345 let cursor = self.selections.newest_anchor().head();
7346 if let Some((buffer, cursor_position)) =
7347 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7348 {
7349 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7350 } else {
7351 false
7352 }
7353 }
7354
7355 pub fn supports_minimap(&self, cx: &App) -> bool {
7356 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7357 }
7358
7359 fn edit_predictions_enabled_in_buffer(
7360 &self,
7361 buffer: &Entity<Buffer>,
7362 buffer_position: language::Anchor,
7363 cx: &App,
7364 ) -> bool {
7365 maybe!({
7366 if self.read_only(cx) {
7367 return Some(false);
7368 }
7369 let provider = self.edit_prediction_provider()?;
7370 if !provider.is_enabled(buffer, buffer_position, cx) {
7371 return Some(false);
7372 }
7373 let buffer = buffer.read(cx);
7374 let Some(file) = buffer.file() else {
7375 return Some(true);
7376 };
7377 let settings = all_language_settings(Some(file), cx);
7378 Some(settings.edit_predictions_enabled_for_file(file, cx))
7379 })
7380 .unwrap_or(false)
7381 }
7382
7383 fn cycle_edit_prediction(
7384 &mut self,
7385 direction: Direction,
7386 window: &mut Window,
7387 cx: &mut Context<Self>,
7388 ) -> Option<()> {
7389 let provider = self.edit_prediction_provider()?;
7390 let cursor = self.selections.newest_anchor().head();
7391 let (buffer, cursor_buffer_position) =
7392 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7393 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7394 return None;
7395 }
7396
7397 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7398 self.update_visible_edit_prediction(window, cx);
7399
7400 Some(())
7401 }
7402
7403 pub fn show_edit_prediction(
7404 &mut self,
7405 _: &ShowEditPrediction,
7406 window: &mut Window,
7407 cx: &mut Context<Self>,
7408 ) {
7409 if !self.has_active_edit_prediction() {
7410 self.refresh_edit_prediction(false, true, window, cx);
7411 return;
7412 }
7413
7414 self.update_visible_edit_prediction(window, cx);
7415 }
7416
7417 pub fn display_cursor_names(
7418 &mut self,
7419 _: &DisplayCursorNames,
7420 window: &mut Window,
7421 cx: &mut Context<Self>,
7422 ) {
7423 self.show_cursor_names(window, cx);
7424 }
7425
7426 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7427 self.show_cursor_names = true;
7428 cx.notify();
7429 cx.spawn_in(window, async move |this, cx| {
7430 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7431 this.update(cx, |this, cx| {
7432 this.show_cursor_names = false;
7433 cx.notify()
7434 })
7435 .ok()
7436 })
7437 .detach();
7438 }
7439
7440 pub fn next_edit_prediction(
7441 &mut self,
7442 _: &NextEditPrediction,
7443 window: &mut Window,
7444 cx: &mut Context<Self>,
7445 ) {
7446 if self.has_active_edit_prediction() {
7447 self.cycle_edit_prediction(Direction::Next, window, cx);
7448 } else {
7449 let is_copilot_disabled = self
7450 .refresh_edit_prediction(false, true, window, cx)
7451 .is_none();
7452 if is_copilot_disabled {
7453 cx.propagate();
7454 }
7455 }
7456 }
7457
7458 pub fn previous_edit_prediction(
7459 &mut self,
7460 _: &PreviousEditPrediction,
7461 window: &mut Window,
7462 cx: &mut Context<Self>,
7463 ) {
7464 if self.has_active_edit_prediction() {
7465 self.cycle_edit_prediction(Direction::Prev, window, cx);
7466 } else {
7467 let is_copilot_disabled = self
7468 .refresh_edit_prediction(false, true, window, cx)
7469 .is_none();
7470 if is_copilot_disabled {
7471 cx.propagate();
7472 }
7473 }
7474 }
7475
7476 pub fn accept_edit_prediction(
7477 &mut self,
7478 _: &AcceptEditPrediction,
7479 window: &mut Window,
7480 cx: &mut Context<Self>,
7481 ) {
7482 if self.show_edit_predictions_in_menu() {
7483 self.hide_context_menu(window, cx);
7484 }
7485
7486 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7487 return;
7488 };
7489
7490 match &active_edit_prediction.completion {
7491 EditPrediction::MoveWithin { target, .. } => {
7492 let target = *target;
7493
7494 if let Some(position_map) = &self.last_position_map {
7495 if position_map
7496 .visible_row_range
7497 .contains(&target.to_display_point(&position_map.snapshot).row())
7498 || !self.edit_prediction_requires_modifier()
7499 {
7500 self.unfold_ranges(&[target..target], true, false, cx);
7501 // Note that this is also done in vim's handler of the Tab action.
7502 self.change_selections(
7503 SelectionEffects::scroll(Autoscroll::newest()),
7504 window,
7505 cx,
7506 |selections| {
7507 selections.select_anchor_ranges([target..target]);
7508 },
7509 );
7510 self.clear_row_highlights::<EditPredictionPreview>();
7511
7512 self.edit_prediction_preview
7513 .set_previous_scroll_position(None);
7514 } else {
7515 self.edit_prediction_preview
7516 .set_previous_scroll_position(Some(
7517 position_map.snapshot.scroll_anchor,
7518 ));
7519
7520 self.highlight_rows::<EditPredictionPreview>(
7521 target..target,
7522 cx.theme().colors().editor_highlighted_line_background,
7523 RowHighlightOptions {
7524 autoscroll: true,
7525 ..Default::default()
7526 },
7527 cx,
7528 );
7529 self.request_autoscroll(Autoscroll::fit(), cx);
7530 }
7531 }
7532 }
7533 EditPrediction::MoveOutside { snapshot, target } => {
7534 if let Some(workspace) = self.workspace() {
7535 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7536 .detach_and_log_err(cx);
7537 }
7538 }
7539 EditPrediction::Edit { edits, .. } => {
7540 self.report_edit_prediction_event(
7541 active_edit_prediction.completion_id.clone(),
7542 true,
7543 cx,
7544 );
7545
7546 if let Some(provider) = self.edit_prediction_provider() {
7547 provider.accept(cx);
7548 }
7549
7550 // Store the transaction ID and selections before applying the edit
7551 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7552
7553 let snapshot = self.buffer.read(cx).snapshot(cx);
7554 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7555
7556 self.buffer.update(cx, |buffer, cx| {
7557 buffer.edit(edits.iter().cloned(), None, cx)
7558 });
7559
7560 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7561 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7562 });
7563
7564 let selections = self.selections.disjoint_anchors_arc();
7565 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7566 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7567 if has_new_transaction {
7568 self.selection_history
7569 .insert_transaction(transaction_id_now, selections);
7570 }
7571 }
7572
7573 self.update_visible_edit_prediction(window, cx);
7574 if self.active_edit_prediction.is_none() {
7575 self.refresh_edit_prediction(true, true, window, cx);
7576 }
7577
7578 cx.notify();
7579 }
7580 }
7581
7582 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7583 }
7584
7585 pub fn accept_partial_edit_prediction(
7586 &mut self,
7587 _: &AcceptPartialEditPrediction,
7588 window: &mut Window,
7589 cx: &mut Context<Self>,
7590 ) {
7591 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7592 return;
7593 };
7594 if self.selections.count() != 1 {
7595 return;
7596 }
7597
7598 match &active_edit_prediction.completion {
7599 EditPrediction::MoveWithin { target, .. } => {
7600 let target = *target;
7601 self.change_selections(
7602 SelectionEffects::scroll(Autoscroll::newest()),
7603 window,
7604 cx,
7605 |selections| {
7606 selections.select_anchor_ranges([target..target]);
7607 },
7608 );
7609 }
7610 EditPrediction::MoveOutside { snapshot, target } => {
7611 if let Some(workspace) = self.workspace() {
7612 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7613 .detach_and_log_err(cx);
7614 }
7615 }
7616 EditPrediction::Edit { edits, .. } => {
7617 self.report_edit_prediction_event(
7618 active_edit_prediction.completion_id.clone(),
7619 true,
7620 cx,
7621 );
7622
7623 // Find an insertion that starts at the cursor position.
7624 let snapshot = self.buffer.read(cx).snapshot(cx);
7625 let cursor_offset = self
7626 .selections
7627 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7628 .head();
7629 let insertion = edits.iter().find_map(|(range, text)| {
7630 let range = range.to_offset(&snapshot);
7631 if range.is_empty() && range.start == cursor_offset {
7632 Some(text)
7633 } else {
7634 None
7635 }
7636 });
7637
7638 if let Some(text) = insertion {
7639 let mut partial_completion = text
7640 .chars()
7641 .by_ref()
7642 .take_while(|c| c.is_alphabetic())
7643 .collect::<String>();
7644 if partial_completion.is_empty() {
7645 partial_completion = text
7646 .chars()
7647 .by_ref()
7648 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7649 .collect::<String>();
7650 }
7651
7652 cx.emit(EditorEvent::InputHandled {
7653 utf16_range_to_replace: None,
7654 text: partial_completion.clone().into(),
7655 });
7656
7657 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7658
7659 self.refresh_edit_prediction(true, true, window, cx);
7660 cx.notify();
7661 } else {
7662 self.accept_edit_prediction(&Default::default(), window, cx);
7663 }
7664 }
7665 }
7666 }
7667
7668 fn discard_edit_prediction(
7669 &mut self,
7670 should_report_edit_prediction_event: bool,
7671 cx: &mut Context<Self>,
7672 ) -> bool {
7673 if should_report_edit_prediction_event {
7674 let completion_id = self
7675 .active_edit_prediction
7676 .as_ref()
7677 .and_then(|active_completion| active_completion.completion_id.clone());
7678
7679 self.report_edit_prediction_event(completion_id, false, cx);
7680 }
7681
7682 if let Some(provider) = self.edit_prediction_provider() {
7683 provider.discard(cx);
7684 }
7685
7686 self.take_active_edit_prediction(cx)
7687 }
7688
7689 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7690 let Some(provider) = self.edit_prediction_provider() else {
7691 return;
7692 };
7693
7694 let Some((_, buffer, _)) = self
7695 .buffer
7696 .read(cx)
7697 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7698 else {
7699 return;
7700 };
7701
7702 let extension = buffer
7703 .read(cx)
7704 .file()
7705 .and_then(|file| Some(file.path().extension()?.to_string()));
7706
7707 let event_type = match accepted {
7708 true => "Edit Prediction Accepted",
7709 false => "Edit Prediction Discarded",
7710 };
7711 telemetry::event!(
7712 event_type,
7713 provider = provider.name(),
7714 prediction_id = id,
7715 suggestion_accepted = accepted,
7716 file_extension = extension,
7717 );
7718 }
7719
7720 fn open_editor_at_anchor(
7721 snapshot: &language::BufferSnapshot,
7722 target: language::Anchor,
7723 workspace: &Entity<Workspace>,
7724 window: &mut Window,
7725 cx: &mut App,
7726 ) -> Task<Result<()>> {
7727 workspace.update(cx, |workspace, cx| {
7728 let path = snapshot.file().map(|file| file.full_path(cx));
7729 let Some(path) =
7730 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7731 else {
7732 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7733 };
7734 let target = text::ToPoint::to_point(&target, snapshot);
7735 let item = workspace.open_path(path, None, true, window, cx);
7736 window.spawn(cx, async move |cx| {
7737 let Some(editor) = item.await?.downcast::<Editor>() else {
7738 return Ok(());
7739 };
7740 editor
7741 .update_in(cx, |editor, window, cx| {
7742 editor.go_to_singleton_buffer_point(target, window, cx);
7743 })
7744 .ok();
7745 anyhow::Ok(())
7746 })
7747 })
7748 }
7749
7750 pub fn has_active_edit_prediction(&self) -> bool {
7751 self.active_edit_prediction.is_some()
7752 }
7753
7754 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7755 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7756 return false;
7757 };
7758
7759 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7760 self.clear_highlights::<EditPredictionHighlight>(cx);
7761 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7762 true
7763 }
7764
7765 /// Returns true when we're displaying the edit prediction popover below the cursor
7766 /// like we are not previewing and the LSP autocomplete menu is visible
7767 /// or we are in `when_holding_modifier` mode.
7768 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7769 if self.edit_prediction_preview_is_active()
7770 || !self.show_edit_predictions_in_menu()
7771 || !self.edit_predictions_enabled()
7772 {
7773 return false;
7774 }
7775
7776 if self.has_visible_completions_menu() {
7777 return true;
7778 }
7779
7780 has_completion && self.edit_prediction_requires_modifier()
7781 }
7782
7783 fn handle_modifiers_changed(
7784 &mut self,
7785 modifiers: Modifiers,
7786 position_map: &PositionMap,
7787 window: &mut Window,
7788 cx: &mut Context<Self>,
7789 ) {
7790 // Ensure that the edit prediction preview is updated, even when not
7791 // enabled, if there's an active edit prediction preview.
7792 if self.show_edit_predictions_in_menu()
7793 || matches!(
7794 self.edit_prediction_preview,
7795 EditPredictionPreview::Active { .. }
7796 )
7797 {
7798 self.update_edit_prediction_preview(&modifiers, window, cx);
7799 }
7800
7801 self.update_selection_mode(&modifiers, position_map, window, cx);
7802
7803 let mouse_position = window.mouse_position();
7804 if !position_map.text_hitbox.is_hovered(window) {
7805 return;
7806 }
7807
7808 self.update_hovered_link(
7809 position_map.point_for_position(mouse_position),
7810 &position_map.snapshot,
7811 modifiers,
7812 window,
7813 cx,
7814 )
7815 }
7816
7817 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7818 match EditorSettings::get_global(cx).multi_cursor_modifier {
7819 MultiCursorModifier::Alt => modifiers.secondary(),
7820 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7821 }
7822 }
7823
7824 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7825 match EditorSettings::get_global(cx).multi_cursor_modifier {
7826 MultiCursorModifier::Alt => modifiers.alt,
7827 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7828 }
7829 }
7830
7831 fn columnar_selection_mode(
7832 modifiers: &Modifiers,
7833 cx: &mut Context<Self>,
7834 ) -> Option<ColumnarMode> {
7835 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7836 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7837 Some(ColumnarMode::FromMouse)
7838 } else if Self::is_alt_pressed(modifiers, cx) {
7839 Some(ColumnarMode::FromSelection)
7840 } else {
7841 None
7842 }
7843 } else {
7844 None
7845 }
7846 }
7847
7848 fn update_selection_mode(
7849 &mut self,
7850 modifiers: &Modifiers,
7851 position_map: &PositionMap,
7852 window: &mut Window,
7853 cx: &mut Context<Self>,
7854 ) {
7855 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7856 return;
7857 };
7858 if self.selections.pending_anchor().is_none() {
7859 return;
7860 }
7861
7862 let mouse_position = window.mouse_position();
7863 let point_for_position = position_map.point_for_position(mouse_position);
7864 let position = point_for_position.previous_valid;
7865
7866 self.select(
7867 SelectPhase::BeginColumnar {
7868 position,
7869 reset: false,
7870 mode,
7871 goal_column: point_for_position.exact_unclipped.column(),
7872 },
7873 window,
7874 cx,
7875 );
7876 }
7877
7878 fn update_edit_prediction_preview(
7879 &mut self,
7880 modifiers: &Modifiers,
7881 window: &mut Window,
7882 cx: &mut Context<Self>,
7883 ) {
7884 let mut modifiers_held = false;
7885 if let Some(accept_keystroke) = self
7886 .accept_edit_prediction_keybind(false, window, cx)
7887 .keystroke()
7888 {
7889 modifiers_held = modifiers_held
7890 || (accept_keystroke.modifiers() == modifiers
7891 && accept_keystroke.modifiers().modified());
7892 };
7893 if let Some(accept_partial_keystroke) = self
7894 .accept_edit_prediction_keybind(true, window, cx)
7895 .keystroke()
7896 {
7897 modifiers_held = modifiers_held
7898 || (accept_partial_keystroke.modifiers() == modifiers
7899 && accept_partial_keystroke.modifiers().modified());
7900 }
7901
7902 if modifiers_held {
7903 if matches!(
7904 self.edit_prediction_preview,
7905 EditPredictionPreview::Inactive { .. }
7906 ) {
7907 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7908 provider.provider.did_show(cx)
7909 }
7910
7911 self.edit_prediction_preview = EditPredictionPreview::Active {
7912 previous_scroll_position: None,
7913 since: Instant::now(),
7914 };
7915
7916 self.update_visible_edit_prediction(window, cx);
7917 cx.notify();
7918 }
7919 } else if let EditPredictionPreview::Active {
7920 previous_scroll_position,
7921 since,
7922 } = self.edit_prediction_preview
7923 {
7924 if let (Some(previous_scroll_position), Some(position_map)) =
7925 (previous_scroll_position, self.last_position_map.as_ref())
7926 {
7927 self.set_scroll_position(
7928 previous_scroll_position
7929 .scroll_position(&position_map.snapshot.display_snapshot),
7930 window,
7931 cx,
7932 );
7933 }
7934
7935 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7936 released_too_fast: since.elapsed() < Duration::from_millis(200),
7937 };
7938 self.clear_row_highlights::<EditPredictionPreview>();
7939 self.update_visible_edit_prediction(window, cx);
7940 cx.notify();
7941 }
7942 }
7943
7944 fn update_visible_edit_prediction(
7945 &mut self,
7946 _window: &mut Window,
7947 cx: &mut Context<Self>,
7948 ) -> Option<()> {
7949 if DisableAiSettings::get_global(cx).disable_ai {
7950 return None;
7951 }
7952
7953 if self.ime_transaction.is_some() {
7954 self.discard_edit_prediction(false, cx);
7955 return None;
7956 }
7957
7958 let selection = self.selections.newest_anchor();
7959 let cursor = selection.head();
7960 let multibuffer = self.buffer.read(cx).snapshot(cx);
7961 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7962 let excerpt_id = cursor.excerpt_id;
7963
7964 let show_in_menu = self.show_edit_predictions_in_menu();
7965 let completions_menu_has_precedence = !show_in_menu
7966 && (self.context_menu.borrow().is_some()
7967 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7968
7969 if completions_menu_has_precedence
7970 || !offset_selection.is_empty()
7971 || self
7972 .active_edit_prediction
7973 .as_ref()
7974 .is_some_and(|completion| {
7975 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7976 return false;
7977 };
7978 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7979 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7980 !invalidation_range.contains(&offset_selection.head())
7981 })
7982 {
7983 self.discard_edit_prediction(false, cx);
7984 return None;
7985 }
7986
7987 self.take_active_edit_prediction(cx);
7988 let Some(provider) = self.edit_prediction_provider() else {
7989 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7990 return None;
7991 };
7992
7993 let (buffer, cursor_buffer_position) =
7994 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7995
7996 self.edit_prediction_settings =
7997 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7998
7999 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8000
8001 if self.edit_prediction_indent_conflict {
8002 let cursor_point = cursor.to_point(&multibuffer);
8003
8004 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8005
8006 if let Some((_, indent)) = indents.iter().next()
8007 && indent.len == cursor_point.column
8008 {
8009 self.edit_prediction_indent_conflict = false;
8010 }
8011 }
8012
8013 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8014
8015 let (completion_id, edits, edit_preview) = match edit_prediction {
8016 edit_prediction::EditPrediction::Local {
8017 id,
8018 edits,
8019 edit_preview,
8020 } => (id, edits, edit_preview),
8021 edit_prediction::EditPrediction::Jump {
8022 id,
8023 snapshot,
8024 target,
8025 } => {
8026 self.stale_edit_prediction_in_menu = None;
8027 self.active_edit_prediction = Some(EditPredictionState {
8028 inlay_ids: vec![],
8029 completion: EditPrediction::MoveOutside { snapshot, target },
8030 completion_id: id,
8031 invalidation_range: None,
8032 });
8033 cx.notify();
8034 return Some(());
8035 }
8036 };
8037
8038 let edits = edits
8039 .into_iter()
8040 .flat_map(|(range, new_text)| {
8041 Some((
8042 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8043 new_text,
8044 ))
8045 })
8046 .collect::<Vec<_>>();
8047 if edits.is_empty() {
8048 return None;
8049 }
8050
8051 let first_edit_start = edits.first().unwrap().0.start;
8052 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8053 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8054
8055 let last_edit_end = edits.last().unwrap().0.end;
8056 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8057 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8058
8059 let cursor_row = cursor.to_point(&multibuffer).row;
8060
8061 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8062
8063 let mut inlay_ids = Vec::new();
8064 let invalidation_row_range;
8065 let move_invalidation_row_range = if cursor_row < edit_start_row {
8066 Some(cursor_row..edit_end_row)
8067 } else if cursor_row > edit_end_row {
8068 Some(edit_start_row..cursor_row)
8069 } else {
8070 None
8071 };
8072 let supports_jump = self
8073 .edit_prediction_provider
8074 .as_ref()
8075 .map(|provider| provider.provider.supports_jump_to_edit())
8076 .unwrap_or(true);
8077
8078 let is_move = supports_jump
8079 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8080 let completion = if is_move {
8081 invalidation_row_range =
8082 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8083 let target = first_edit_start;
8084 EditPrediction::MoveWithin { target, snapshot }
8085 } else {
8086 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8087 && !self.edit_predictions_hidden_for_vim_mode;
8088
8089 if show_completions_in_buffer {
8090 if let Some(provider) = &self.edit_prediction_provider {
8091 provider.provider.did_show(cx);
8092 }
8093 if edits
8094 .iter()
8095 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8096 {
8097 let mut inlays = Vec::new();
8098 for (range, new_text) in &edits {
8099 let inlay = Inlay::edit_prediction(
8100 post_inc(&mut self.next_inlay_id),
8101 range.start,
8102 new_text.as_ref(),
8103 );
8104 inlay_ids.push(inlay.id);
8105 inlays.push(inlay);
8106 }
8107
8108 self.splice_inlays(&[], inlays, cx);
8109 } else {
8110 let background_color = cx.theme().status().deleted_background;
8111 self.highlight_text::<EditPredictionHighlight>(
8112 edits.iter().map(|(range, _)| range.clone()).collect(),
8113 HighlightStyle {
8114 background_color: Some(background_color),
8115 ..Default::default()
8116 },
8117 cx,
8118 );
8119 }
8120 }
8121
8122 invalidation_row_range = edit_start_row..edit_end_row;
8123
8124 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8125 if provider.show_tab_accept_marker() {
8126 EditDisplayMode::TabAccept
8127 } else {
8128 EditDisplayMode::Inline
8129 }
8130 } else {
8131 EditDisplayMode::DiffPopover
8132 };
8133
8134 EditPrediction::Edit {
8135 edits,
8136 edit_preview,
8137 display_mode,
8138 snapshot,
8139 }
8140 };
8141
8142 let invalidation_range = multibuffer
8143 .anchor_before(Point::new(invalidation_row_range.start, 0))
8144 ..multibuffer.anchor_after(Point::new(
8145 invalidation_row_range.end,
8146 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8147 ));
8148
8149 self.stale_edit_prediction_in_menu = None;
8150 self.active_edit_prediction = Some(EditPredictionState {
8151 inlay_ids,
8152 completion,
8153 completion_id,
8154 invalidation_range: Some(invalidation_range),
8155 });
8156
8157 cx.notify();
8158
8159 Some(())
8160 }
8161
8162 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8163 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8164 }
8165
8166 fn clear_tasks(&mut self) {
8167 self.tasks.clear()
8168 }
8169
8170 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8171 if self.tasks.insert(key, value).is_some() {
8172 // This case should hopefully be rare, but just in case...
8173 log::error!(
8174 "multiple different run targets found on a single line, only the last target will be rendered"
8175 )
8176 }
8177 }
8178
8179 /// Get all display points of breakpoints that will be rendered within editor
8180 ///
8181 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8182 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8183 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8184 fn active_breakpoints(
8185 &self,
8186 range: Range<DisplayRow>,
8187 window: &mut Window,
8188 cx: &mut Context<Self>,
8189 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8190 let mut breakpoint_display_points = HashMap::default();
8191
8192 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8193 return breakpoint_display_points;
8194 };
8195
8196 let snapshot = self.snapshot(window, cx);
8197
8198 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8199 let Some(project) = self.project() else {
8200 return breakpoint_display_points;
8201 };
8202
8203 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8204 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8205
8206 for (buffer_snapshot, range, excerpt_id) in
8207 multi_buffer_snapshot.range_to_buffer_ranges(range)
8208 {
8209 let Some(buffer) = project
8210 .read(cx)
8211 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8212 else {
8213 continue;
8214 };
8215 let breakpoints = breakpoint_store.read(cx).breakpoints(
8216 &buffer,
8217 Some(
8218 buffer_snapshot.anchor_before(range.start)
8219 ..buffer_snapshot.anchor_after(range.end),
8220 ),
8221 buffer_snapshot,
8222 cx,
8223 );
8224 for (breakpoint, state) in breakpoints {
8225 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8226 let position = multi_buffer_anchor
8227 .to_point(&multi_buffer_snapshot)
8228 .to_display_point(&snapshot);
8229
8230 breakpoint_display_points.insert(
8231 position.row(),
8232 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8233 );
8234 }
8235 }
8236
8237 breakpoint_display_points
8238 }
8239
8240 fn breakpoint_context_menu(
8241 &self,
8242 anchor: Anchor,
8243 window: &mut Window,
8244 cx: &mut Context<Self>,
8245 ) -> Entity<ui::ContextMenu> {
8246 let weak_editor = cx.weak_entity();
8247 let focus_handle = self.focus_handle(cx);
8248
8249 let row = self
8250 .buffer
8251 .read(cx)
8252 .snapshot(cx)
8253 .summary_for_anchor::<Point>(&anchor)
8254 .row;
8255
8256 let breakpoint = self
8257 .breakpoint_at_row(row, window, cx)
8258 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8259
8260 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8261 "Edit Log Breakpoint"
8262 } else {
8263 "Set Log Breakpoint"
8264 };
8265
8266 let condition_breakpoint_msg = if breakpoint
8267 .as_ref()
8268 .is_some_and(|bp| bp.1.condition.is_some())
8269 {
8270 "Edit Condition Breakpoint"
8271 } else {
8272 "Set Condition Breakpoint"
8273 };
8274
8275 let hit_condition_breakpoint_msg = if breakpoint
8276 .as_ref()
8277 .is_some_and(|bp| bp.1.hit_condition.is_some())
8278 {
8279 "Edit Hit Condition Breakpoint"
8280 } else {
8281 "Set Hit Condition Breakpoint"
8282 };
8283
8284 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8285 "Unset Breakpoint"
8286 } else {
8287 "Set Breakpoint"
8288 };
8289
8290 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8291
8292 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8293 BreakpointState::Enabled => Some("Disable"),
8294 BreakpointState::Disabled => Some("Enable"),
8295 });
8296
8297 let (anchor, breakpoint) =
8298 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8299
8300 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8301 menu.on_blur_subscription(Subscription::new(|| {}))
8302 .context(focus_handle)
8303 .when(run_to_cursor, |this| {
8304 let weak_editor = weak_editor.clone();
8305 this.entry("Run to cursor", None, move |window, cx| {
8306 weak_editor
8307 .update(cx, |editor, cx| {
8308 editor.change_selections(
8309 SelectionEffects::no_scroll(),
8310 window,
8311 cx,
8312 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8313 );
8314 })
8315 .ok();
8316
8317 window.dispatch_action(Box::new(RunToCursor), cx);
8318 })
8319 .separator()
8320 })
8321 .when_some(toggle_state_msg, |this, msg| {
8322 this.entry(msg, None, {
8323 let weak_editor = weak_editor.clone();
8324 let breakpoint = breakpoint.clone();
8325 move |_window, cx| {
8326 weak_editor
8327 .update(cx, |this, cx| {
8328 this.edit_breakpoint_at_anchor(
8329 anchor,
8330 breakpoint.as_ref().clone(),
8331 BreakpointEditAction::InvertState,
8332 cx,
8333 );
8334 })
8335 .log_err();
8336 }
8337 })
8338 })
8339 .entry(set_breakpoint_msg, None, {
8340 let weak_editor = weak_editor.clone();
8341 let breakpoint = breakpoint.clone();
8342 move |_window, cx| {
8343 weak_editor
8344 .update(cx, |this, cx| {
8345 this.edit_breakpoint_at_anchor(
8346 anchor,
8347 breakpoint.as_ref().clone(),
8348 BreakpointEditAction::Toggle,
8349 cx,
8350 );
8351 })
8352 .log_err();
8353 }
8354 })
8355 .entry(log_breakpoint_msg, None, {
8356 let breakpoint = breakpoint.clone();
8357 let weak_editor = weak_editor.clone();
8358 move |window, cx| {
8359 weak_editor
8360 .update(cx, |this, cx| {
8361 this.add_edit_breakpoint_block(
8362 anchor,
8363 breakpoint.as_ref(),
8364 BreakpointPromptEditAction::Log,
8365 window,
8366 cx,
8367 );
8368 })
8369 .log_err();
8370 }
8371 })
8372 .entry(condition_breakpoint_msg, None, {
8373 let breakpoint = breakpoint.clone();
8374 let weak_editor = weak_editor.clone();
8375 move |window, cx| {
8376 weak_editor
8377 .update(cx, |this, cx| {
8378 this.add_edit_breakpoint_block(
8379 anchor,
8380 breakpoint.as_ref(),
8381 BreakpointPromptEditAction::Condition,
8382 window,
8383 cx,
8384 );
8385 })
8386 .log_err();
8387 }
8388 })
8389 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8390 weak_editor
8391 .update(cx, |this, cx| {
8392 this.add_edit_breakpoint_block(
8393 anchor,
8394 breakpoint.as_ref(),
8395 BreakpointPromptEditAction::HitCondition,
8396 window,
8397 cx,
8398 );
8399 })
8400 .log_err();
8401 })
8402 })
8403 }
8404
8405 fn render_breakpoint(
8406 &self,
8407 position: Anchor,
8408 row: DisplayRow,
8409 breakpoint: &Breakpoint,
8410 state: Option<BreakpointSessionState>,
8411 cx: &mut Context<Self>,
8412 ) -> IconButton {
8413 let is_rejected = state.is_some_and(|s| !s.verified);
8414 // Is it a breakpoint that shows up when hovering over gutter?
8415 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8416 (false, false),
8417 |PhantomBreakpointIndicator {
8418 is_active,
8419 display_row,
8420 collides_with_existing_breakpoint,
8421 }| {
8422 (
8423 is_active && display_row == row,
8424 collides_with_existing_breakpoint,
8425 )
8426 },
8427 );
8428
8429 let (color, icon) = {
8430 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8431 (false, false) => ui::IconName::DebugBreakpoint,
8432 (true, false) => ui::IconName::DebugLogBreakpoint,
8433 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8434 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8435 };
8436
8437 let color = cx.theme().colors();
8438
8439 let color = if is_phantom {
8440 if collides_with_existing {
8441 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8442 } else {
8443 Color::Hint
8444 }
8445 } else if is_rejected {
8446 Color::Disabled
8447 } else {
8448 Color::Debugger
8449 };
8450
8451 (color, icon)
8452 };
8453
8454 let breakpoint = Arc::from(breakpoint.clone());
8455
8456 let alt_as_text = gpui::Keystroke {
8457 modifiers: Modifiers::secondary_key(),
8458 ..Default::default()
8459 };
8460 let primary_action_text = if breakpoint.is_disabled() {
8461 "Enable breakpoint"
8462 } else if is_phantom && !collides_with_existing {
8463 "Set breakpoint"
8464 } else {
8465 "Unset breakpoint"
8466 };
8467 let focus_handle = self.focus_handle.clone();
8468
8469 let meta = if is_rejected {
8470 SharedString::from("No executable code is associated with this line.")
8471 } else if collides_with_existing && !breakpoint.is_disabled() {
8472 SharedString::from(format!(
8473 "{alt_as_text}-click to disable,\nright-click for more options."
8474 ))
8475 } else {
8476 SharedString::from("Right-click for more options.")
8477 };
8478 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8479 .icon_size(IconSize::XSmall)
8480 .size(ui::ButtonSize::None)
8481 .when(is_rejected, |this| {
8482 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8483 })
8484 .icon_color(color)
8485 .style(ButtonStyle::Transparent)
8486 .on_click(cx.listener({
8487 move |editor, event: &ClickEvent, window, cx| {
8488 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8489 BreakpointEditAction::InvertState
8490 } else {
8491 BreakpointEditAction::Toggle
8492 };
8493
8494 window.focus(&editor.focus_handle(cx));
8495 editor.edit_breakpoint_at_anchor(
8496 position,
8497 breakpoint.as_ref().clone(),
8498 edit_action,
8499 cx,
8500 );
8501 }
8502 }))
8503 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8504 editor.set_breakpoint_context_menu(
8505 row,
8506 Some(position),
8507 event.position(),
8508 window,
8509 cx,
8510 );
8511 }))
8512 .tooltip(move |_window, cx| {
8513 Tooltip::with_meta_in(
8514 primary_action_text,
8515 Some(&ToggleBreakpoint),
8516 meta.clone(),
8517 &focus_handle,
8518 cx,
8519 )
8520 })
8521 }
8522
8523 fn build_tasks_context(
8524 project: &Entity<Project>,
8525 buffer: &Entity<Buffer>,
8526 buffer_row: u32,
8527 tasks: &Arc<RunnableTasks>,
8528 cx: &mut Context<Self>,
8529 ) -> Task<Option<task::TaskContext>> {
8530 let position = Point::new(buffer_row, tasks.column);
8531 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8532 let location = Location {
8533 buffer: buffer.clone(),
8534 range: range_start..range_start,
8535 };
8536 // Fill in the environmental variables from the tree-sitter captures
8537 let mut captured_task_variables = TaskVariables::default();
8538 for (capture_name, value) in tasks.extra_variables.clone() {
8539 captured_task_variables.insert(
8540 task::VariableName::Custom(capture_name.into()),
8541 value.clone(),
8542 );
8543 }
8544 project.update(cx, |project, cx| {
8545 project.task_store().update(cx, |task_store, cx| {
8546 task_store.task_context_for_location(captured_task_variables, location, cx)
8547 })
8548 })
8549 }
8550
8551 pub fn spawn_nearest_task(
8552 &mut self,
8553 action: &SpawnNearestTask,
8554 window: &mut Window,
8555 cx: &mut Context<Self>,
8556 ) {
8557 let Some((workspace, _)) = self.workspace.clone() else {
8558 return;
8559 };
8560 let Some(project) = self.project.clone() else {
8561 return;
8562 };
8563
8564 // Try to find a closest, enclosing node using tree-sitter that has a task
8565 let Some((buffer, buffer_row, tasks)) = self
8566 .find_enclosing_node_task(cx)
8567 // Or find the task that's closest in row-distance.
8568 .or_else(|| self.find_closest_task(cx))
8569 else {
8570 return;
8571 };
8572
8573 let reveal_strategy = action.reveal;
8574 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8575 cx.spawn_in(window, async move |_, cx| {
8576 let context = task_context.await?;
8577 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8578
8579 let resolved = &mut resolved_task.resolved;
8580 resolved.reveal = reveal_strategy;
8581
8582 workspace
8583 .update_in(cx, |workspace, window, cx| {
8584 workspace.schedule_resolved_task(
8585 task_source_kind,
8586 resolved_task,
8587 false,
8588 window,
8589 cx,
8590 );
8591 })
8592 .ok()
8593 })
8594 .detach();
8595 }
8596
8597 fn find_closest_task(
8598 &mut self,
8599 cx: &mut Context<Self>,
8600 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8601 let cursor_row = self
8602 .selections
8603 .newest_adjusted(&self.display_snapshot(cx))
8604 .head()
8605 .row;
8606
8607 let ((buffer_id, row), tasks) = self
8608 .tasks
8609 .iter()
8610 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8611
8612 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8613 let tasks = Arc::new(tasks.to_owned());
8614 Some((buffer, *row, tasks))
8615 }
8616
8617 fn find_enclosing_node_task(
8618 &mut self,
8619 cx: &mut Context<Self>,
8620 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8621 let snapshot = self.buffer.read(cx).snapshot(cx);
8622 let offset = self
8623 .selections
8624 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8625 .head();
8626 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8627 let offset = excerpt.map_offset_to_buffer(offset);
8628 let buffer_id = excerpt.buffer().remote_id();
8629
8630 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8631 let mut cursor = layer.node().walk();
8632
8633 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8634 if cursor.node().end_byte() == offset.0 {
8635 cursor.goto_next_sibling();
8636 }
8637 }
8638
8639 // Ascend to the smallest ancestor that contains the range and has a task.
8640 loop {
8641 let node = cursor.node();
8642 let node_range = node.byte_range();
8643 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8644
8645 // Check if this node contains our offset
8646 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8647 // If it contains offset, check for task
8648 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8649 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8650 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8651 }
8652 }
8653
8654 if !cursor.goto_parent() {
8655 break;
8656 }
8657 }
8658 None
8659 }
8660
8661 fn render_run_indicator(
8662 &self,
8663 _style: &EditorStyle,
8664 is_active: bool,
8665 row: DisplayRow,
8666 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8667 cx: &mut Context<Self>,
8668 ) -> IconButton {
8669 let color = Color::Muted;
8670 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8671
8672 IconButton::new(
8673 ("run_indicator", row.0 as usize),
8674 ui::IconName::PlayOutlined,
8675 )
8676 .shape(ui::IconButtonShape::Square)
8677 .icon_size(IconSize::XSmall)
8678 .icon_color(color)
8679 .toggle_state(is_active)
8680 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8681 let quick_launch = match e {
8682 ClickEvent::Keyboard(_) => true,
8683 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8684 };
8685
8686 window.focus(&editor.focus_handle(cx));
8687 editor.toggle_code_actions(
8688 &ToggleCodeActions {
8689 deployed_from: Some(CodeActionSource::RunMenu(row)),
8690 quick_launch,
8691 },
8692 window,
8693 cx,
8694 );
8695 }))
8696 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8697 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8698 }))
8699 }
8700
8701 pub fn context_menu_visible(&self) -> bool {
8702 !self.edit_prediction_preview_is_active()
8703 && self
8704 .context_menu
8705 .borrow()
8706 .as_ref()
8707 .is_some_and(|menu| menu.visible())
8708 }
8709
8710 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8711 self.context_menu
8712 .borrow()
8713 .as_ref()
8714 .map(|menu| menu.origin())
8715 }
8716
8717 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8718 self.context_menu_options = Some(options);
8719 }
8720
8721 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8722 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8723
8724 fn render_edit_prediction_popover(
8725 &mut self,
8726 text_bounds: &Bounds<Pixels>,
8727 content_origin: gpui::Point<Pixels>,
8728 right_margin: Pixels,
8729 editor_snapshot: &EditorSnapshot,
8730 visible_row_range: Range<DisplayRow>,
8731 scroll_top: ScrollOffset,
8732 scroll_bottom: ScrollOffset,
8733 line_layouts: &[LineWithInvisibles],
8734 line_height: Pixels,
8735 scroll_position: gpui::Point<ScrollOffset>,
8736 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8737 newest_selection_head: Option<DisplayPoint>,
8738 editor_width: Pixels,
8739 style: &EditorStyle,
8740 window: &mut Window,
8741 cx: &mut App,
8742 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8743 if self.mode().is_minimap() {
8744 return None;
8745 }
8746 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8747
8748 if self.edit_prediction_visible_in_cursor_popover(true) {
8749 return None;
8750 }
8751
8752 match &active_edit_prediction.completion {
8753 EditPrediction::MoveWithin { target, .. } => {
8754 let target_display_point = target.to_display_point(editor_snapshot);
8755
8756 if self.edit_prediction_requires_modifier() {
8757 if !self.edit_prediction_preview_is_active() {
8758 return None;
8759 }
8760
8761 self.render_edit_prediction_modifier_jump_popover(
8762 text_bounds,
8763 content_origin,
8764 visible_row_range,
8765 line_layouts,
8766 line_height,
8767 scroll_pixel_position,
8768 newest_selection_head,
8769 target_display_point,
8770 window,
8771 cx,
8772 )
8773 } else {
8774 self.render_edit_prediction_eager_jump_popover(
8775 text_bounds,
8776 content_origin,
8777 editor_snapshot,
8778 visible_row_range,
8779 scroll_top,
8780 scroll_bottom,
8781 line_height,
8782 scroll_pixel_position,
8783 target_display_point,
8784 editor_width,
8785 window,
8786 cx,
8787 )
8788 }
8789 }
8790 EditPrediction::Edit {
8791 display_mode: EditDisplayMode::Inline,
8792 ..
8793 } => None,
8794 EditPrediction::Edit {
8795 display_mode: EditDisplayMode::TabAccept,
8796 edits,
8797 ..
8798 } => {
8799 let range = &edits.first()?.0;
8800 let target_display_point = range.end.to_display_point(editor_snapshot);
8801
8802 self.render_edit_prediction_end_of_line_popover(
8803 "Accept",
8804 editor_snapshot,
8805 visible_row_range,
8806 target_display_point,
8807 line_height,
8808 scroll_pixel_position,
8809 content_origin,
8810 editor_width,
8811 window,
8812 cx,
8813 )
8814 }
8815 EditPrediction::Edit {
8816 edits,
8817 edit_preview,
8818 display_mode: EditDisplayMode::DiffPopover,
8819 snapshot,
8820 } => self.render_edit_prediction_diff_popover(
8821 text_bounds,
8822 content_origin,
8823 right_margin,
8824 editor_snapshot,
8825 visible_row_range,
8826 line_layouts,
8827 line_height,
8828 scroll_position,
8829 scroll_pixel_position,
8830 newest_selection_head,
8831 editor_width,
8832 style,
8833 edits,
8834 edit_preview,
8835 snapshot,
8836 window,
8837 cx,
8838 ),
8839 EditPrediction::MoveOutside { snapshot, .. } => {
8840 let mut element = self
8841 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8842 .into_any();
8843
8844 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8845 let origin_x = text_bounds.size.width - size.width - px(30.);
8846 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8847 element.prepaint_at(origin, window, cx);
8848
8849 Some((element, origin))
8850 }
8851 }
8852 }
8853
8854 fn render_edit_prediction_modifier_jump_popover(
8855 &mut self,
8856 text_bounds: &Bounds<Pixels>,
8857 content_origin: gpui::Point<Pixels>,
8858 visible_row_range: Range<DisplayRow>,
8859 line_layouts: &[LineWithInvisibles],
8860 line_height: Pixels,
8861 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8862 newest_selection_head: Option<DisplayPoint>,
8863 target_display_point: DisplayPoint,
8864 window: &mut Window,
8865 cx: &mut App,
8866 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8867 let scrolled_content_origin =
8868 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8869
8870 const SCROLL_PADDING_Y: Pixels = px(12.);
8871
8872 if target_display_point.row() < visible_row_range.start {
8873 return self.render_edit_prediction_scroll_popover(
8874 |_| SCROLL_PADDING_Y,
8875 IconName::ArrowUp,
8876 visible_row_range,
8877 line_layouts,
8878 newest_selection_head,
8879 scrolled_content_origin,
8880 window,
8881 cx,
8882 );
8883 } else if target_display_point.row() >= visible_row_range.end {
8884 return self.render_edit_prediction_scroll_popover(
8885 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8886 IconName::ArrowDown,
8887 visible_row_range,
8888 line_layouts,
8889 newest_selection_head,
8890 scrolled_content_origin,
8891 window,
8892 cx,
8893 );
8894 }
8895
8896 const POLE_WIDTH: Pixels = px(2.);
8897
8898 let line_layout =
8899 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8900 let target_column = target_display_point.column() as usize;
8901
8902 let target_x = line_layout.x_for_index(target_column);
8903 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8904 - scroll_pixel_position.y;
8905
8906 let flag_on_right = target_x < text_bounds.size.width / 2.;
8907
8908 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8909 border_color.l += 0.001;
8910
8911 let mut element = v_flex()
8912 .items_end()
8913 .when(flag_on_right, |el| el.items_start())
8914 .child(if flag_on_right {
8915 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8916 .rounded_bl(px(0.))
8917 .rounded_tl(px(0.))
8918 .border_l_2()
8919 .border_color(border_color)
8920 } else {
8921 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8922 .rounded_br(px(0.))
8923 .rounded_tr(px(0.))
8924 .border_r_2()
8925 .border_color(border_color)
8926 })
8927 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8928 .into_any();
8929
8930 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8931
8932 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8933 - point(
8934 if flag_on_right {
8935 POLE_WIDTH
8936 } else {
8937 size.width - POLE_WIDTH
8938 },
8939 size.height - line_height,
8940 );
8941
8942 origin.x = origin.x.max(content_origin.x);
8943
8944 element.prepaint_at(origin, window, cx);
8945
8946 Some((element, origin))
8947 }
8948
8949 fn render_edit_prediction_scroll_popover(
8950 &mut self,
8951 to_y: impl Fn(Size<Pixels>) -> Pixels,
8952 scroll_icon: IconName,
8953 visible_row_range: Range<DisplayRow>,
8954 line_layouts: &[LineWithInvisibles],
8955 newest_selection_head: Option<DisplayPoint>,
8956 scrolled_content_origin: gpui::Point<Pixels>,
8957 window: &mut Window,
8958 cx: &mut App,
8959 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8960 let mut element = self
8961 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8962 .into_any();
8963
8964 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8965
8966 let cursor = newest_selection_head?;
8967 let cursor_row_layout =
8968 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8969 let cursor_column = cursor.column() as usize;
8970
8971 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8972
8973 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8974
8975 element.prepaint_at(origin, window, cx);
8976 Some((element, origin))
8977 }
8978
8979 fn render_edit_prediction_eager_jump_popover(
8980 &mut self,
8981 text_bounds: &Bounds<Pixels>,
8982 content_origin: gpui::Point<Pixels>,
8983 editor_snapshot: &EditorSnapshot,
8984 visible_row_range: Range<DisplayRow>,
8985 scroll_top: ScrollOffset,
8986 scroll_bottom: ScrollOffset,
8987 line_height: Pixels,
8988 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8989 target_display_point: DisplayPoint,
8990 editor_width: Pixels,
8991 window: &mut Window,
8992 cx: &mut App,
8993 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8994 if target_display_point.row().as_f64() < scroll_top {
8995 let mut element = self
8996 .render_edit_prediction_line_popover(
8997 "Jump to Edit",
8998 Some(IconName::ArrowUp),
8999 window,
9000 cx,
9001 )
9002 .into_any();
9003
9004 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9005 let offset = point(
9006 (text_bounds.size.width - size.width) / 2.,
9007 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9008 );
9009
9010 let origin = text_bounds.origin + offset;
9011 element.prepaint_at(origin, window, cx);
9012 Some((element, origin))
9013 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9014 let mut element = self
9015 .render_edit_prediction_line_popover(
9016 "Jump to Edit",
9017 Some(IconName::ArrowDown),
9018 window,
9019 cx,
9020 )
9021 .into_any();
9022
9023 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9024 let offset = point(
9025 (text_bounds.size.width - size.width) / 2.,
9026 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9027 );
9028
9029 let origin = text_bounds.origin + offset;
9030 element.prepaint_at(origin, window, cx);
9031 Some((element, origin))
9032 } else {
9033 self.render_edit_prediction_end_of_line_popover(
9034 "Jump to Edit",
9035 editor_snapshot,
9036 visible_row_range,
9037 target_display_point,
9038 line_height,
9039 scroll_pixel_position,
9040 content_origin,
9041 editor_width,
9042 window,
9043 cx,
9044 )
9045 }
9046 }
9047
9048 fn render_edit_prediction_end_of_line_popover(
9049 self: &mut Editor,
9050 label: &'static str,
9051 editor_snapshot: &EditorSnapshot,
9052 visible_row_range: Range<DisplayRow>,
9053 target_display_point: DisplayPoint,
9054 line_height: Pixels,
9055 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9056 content_origin: gpui::Point<Pixels>,
9057 editor_width: Pixels,
9058 window: &mut Window,
9059 cx: &mut App,
9060 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9061 let target_line_end = DisplayPoint::new(
9062 target_display_point.row(),
9063 editor_snapshot.line_len(target_display_point.row()),
9064 );
9065
9066 let mut element = self
9067 .render_edit_prediction_line_popover(label, None, window, cx)
9068 .into_any();
9069
9070 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9071
9072 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9073
9074 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9075 let mut origin = start_point
9076 + line_origin
9077 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9078 origin.x = origin.x.max(content_origin.x);
9079
9080 let max_x = content_origin.x + editor_width - size.width;
9081
9082 if origin.x > max_x {
9083 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9084
9085 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9086 origin.y += offset;
9087 IconName::ArrowUp
9088 } else {
9089 origin.y -= offset;
9090 IconName::ArrowDown
9091 };
9092
9093 element = self
9094 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9095 .into_any();
9096
9097 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9098
9099 origin.x = content_origin.x + editor_width - size.width - px(2.);
9100 }
9101
9102 element.prepaint_at(origin, window, cx);
9103 Some((element, origin))
9104 }
9105
9106 fn render_edit_prediction_diff_popover(
9107 self: &Editor,
9108 text_bounds: &Bounds<Pixels>,
9109 content_origin: gpui::Point<Pixels>,
9110 right_margin: Pixels,
9111 editor_snapshot: &EditorSnapshot,
9112 visible_row_range: Range<DisplayRow>,
9113 line_layouts: &[LineWithInvisibles],
9114 line_height: Pixels,
9115 scroll_position: gpui::Point<ScrollOffset>,
9116 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9117 newest_selection_head: Option<DisplayPoint>,
9118 editor_width: Pixels,
9119 style: &EditorStyle,
9120 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9121 edit_preview: &Option<language::EditPreview>,
9122 snapshot: &language::BufferSnapshot,
9123 window: &mut Window,
9124 cx: &mut App,
9125 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9126 let edit_start = edits
9127 .first()
9128 .unwrap()
9129 .0
9130 .start
9131 .to_display_point(editor_snapshot);
9132 let edit_end = edits
9133 .last()
9134 .unwrap()
9135 .0
9136 .end
9137 .to_display_point(editor_snapshot);
9138
9139 let is_visible = visible_row_range.contains(&edit_start.row())
9140 || visible_row_range.contains(&edit_end.row());
9141 if !is_visible {
9142 return None;
9143 }
9144
9145 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9146 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9147 } else {
9148 // Fallback for providers without edit_preview
9149 crate::edit_prediction_fallback_text(edits, cx)
9150 };
9151
9152 let styled_text = highlighted_edits.to_styled_text(&style.text);
9153 let line_count = highlighted_edits.text.lines().count();
9154
9155 const BORDER_WIDTH: Pixels = px(1.);
9156
9157 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9158 let has_keybind = keybind.is_some();
9159
9160 let mut element = h_flex()
9161 .items_start()
9162 .child(
9163 h_flex()
9164 .bg(cx.theme().colors().editor_background)
9165 .border(BORDER_WIDTH)
9166 .shadow_xs()
9167 .border_color(cx.theme().colors().border)
9168 .rounded_l_lg()
9169 .when(line_count > 1, |el| el.rounded_br_lg())
9170 .pr_1()
9171 .child(styled_text),
9172 )
9173 .child(
9174 h_flex()
9175 .h(line_height + BORDER_WIDTH * 2.)
9176 .px_1p5()
9177 .gap_1()
9178 // Workaround: For some reason, there's a gap if we don't do this
9179 .ml(-BORDER_WIDTH)
9180 .shadow(vec![gpui::BoxShadow {
9181 color: gpui::black().opacity(0.05),
9182 offset: point(px(1.), px(1.)),
9183 blur_radius: px(2.),
9184 spread_radius: px(0.),
9185 }])
9186 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9187 .border(BORDER_WIDTH)
9188 .border_color(cx.theme().colors().border)
9189 .rounded_r_lg()
9190 .id("edit_prediction_diff_popover_keybind")
9191 .when(!has_keybind, |el| {
9192 let status_colors = cx.theme().status();
9193
9194 el.bg(status_colors.error_background)
9195 .border_color(status_colors.error.opacity(0.6))
9196 .child(Icon::new(IconName::Info).color(Color::Error))
9197 .cursor_default()
9198 .hoverable_tooltip(move |_window, cx| {
9199 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9200 })
9201 })
9202 .children(keybind),
9203 )
9204 .into_any();
9205
9206 let longest_row =
9207 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9208 let longest_line_width = if visible_row_range.contains(&longest_row) {
9209 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9210 } else {
9211 layout_line(
9212 longest_row,
9213 editor_snapshot,
9214 style,
9215 editor_width,
9216 |_| false,
9217 window,
9218 cx,
9219 )
9220 .width
9221 };
9222
9223 let viewport_bounds =
9224 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9225 right: -right_margin,
9226 ..Default::default()
9227 });
9228
9229 let x_after_longest = Pixels::from(
9230 ScrollPixelOffset::from(
9231 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9232 ) - scroll_pixel_position.x,
9233 );
9234
9235 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9236
9237 // Fully visible if it can be displayed within the window (allow overlapping other
9238 // panes). However, this is only allowed if the popover starts within text_bounds.
9239 let can_position_to_the_right = x_after_longest < text_bounds.right()
9240 && x_after_longest + element_bounds.width < viewport_bounds.right();
9241
9242 let mut origin = if can_position_to_the_right {
9243 point(
9244 x_after_longest,
9245 text_bounds.origin.y
9246 + Pixels::from(
9247 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9248 - scroll_pixel_position.y,
9249 ),
9250 )
9251 } else {
9252 let cursor_row = newest_selection_head.map(|head| head.row());
9253 let above_edit = edit_start
9254 .row()
9255 .0
9256 .checked_sub(line_count as u32)
9257 .map(DisplayRow);
9258 let below_edit = Some(edit_end.row() + 1);
9259 let above_cursor =
9260 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9261 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9262
9263 // Place the edit popover adjacent to the edit if there is a location
9264 // available that is onscreen and does not obscure the cursor. Otherwise,
9265 // place it adjacent to the cursor.
9266 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9267 .into_iter()
9268 .flatten()
9269 .find(|&start_row| {
9270 let end_row = start_row + line_count as u32;
9271 visible_row_range.contains(&start_row)
9272 && visible_row_range.contains(&end_row)
9273 && cursor_row
9274 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9275 })?;
9276
9277 content_origin
9278 + point(
9279 Pixels::from(-scroll_pixel_position.x),
9280 Pixels::from(
9281 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9282 ),
9283 )
9284 };
9285
9286 origin.x -= BORDER_WIDTH;
9287
9288 window.defer_draw(element, origin, 1);
9289
9290 // Do not return an element, since it will already be drawn due to defer_draw.
9291 None
9292 }
9293
9294 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9295 px(30.)
9296 }
9297
9298 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9299 if self.read_only(cx) {
9300 cx.theme().players().read_only()
9301 } else {
9302 self.style.as_ref().unwrap().local_player
9303 }
9304 }
9305
9306 fn render_edit_prediction_accept_keybind(
9307 &self,
9308 window: &mut Window,
9309 cx: &mut App,
9310 ) -> Option<AnyElement> {
9311 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9312 let accept_keystroke = accept_binding.keystroke()?;
9313
9314 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9315
9316 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9317 Color::Accent
9318 } else {
9319 Color::Muted
9320 };
9321
9322 h_flex()
9323 .px_0p5()
9324 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9325 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9326 .text_size(TextSize::XSmall.rems(cx))
9327 .child(h_flex().children(ui::render_modifiers(
9328 accept_keystroke.modifiers(),
9329 PlatformStyle::platform(),
9330 Some(modifiers_color),
9331 Some(IconSize::XSmall.rems().into()),
9332 true,
9333 )))
9334 .when(is_platform_style_mac, |parent| {
9335 parent.child(accept_keystroke.key().to_string())
9336 })
9337 .when(!is_platform_style_mac, |parent| {
9338 parent.child(
9339 Key::new(
9340 util::capitalize(accept_keystroke.key()),
9341 Some(Color::Default),
9342 )
9343 .size(Some(IconSize::XSmall.rems().into())),
9344 )
9345 })
9346 .into_any()
9347 .into()
9348 }
9349
9350 fn render_edit_prediction_line_popover(
9351 &self,
9352 label: impl Into<SharedString>,
9353 icon: Option<IconName>,
9354 window: &mut Window,
9355 cx: &mut App,
9356 ) -> Stateful<Div> {
9357 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9358
9359 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9360 let has_keybind = keybind.is_some();
9361
9362 h_flex()
9363 .id("ep-line-popover")
9364 .py_0p5()
9365 .pl_1()
9366 .pr(padding_right)
9367 .gap_1()
9368 .rounded_md()
9369 .border_1()
9370 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9371 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9372 .shadow_xs()
9373 .when(!has_keybind, |el| {
9374 let status_colors = cx.theme().status();
9375
9376 el.bg(status_colors.error_background)
9377 .border_color(status_colors.error.opacity(0.6))
9378 .pl_2()
9379 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9380 .cursor_default()
9381 .hoverable_tooltip(move |_window, cx| {
9382 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9383 })
9384 })
9385 .children(keybind)
9386 .child(
9387 Label::new(label)
9388 .size(LabelSize::Small)
9389 .when(!has_keybind, |el| {
9390 el.color(cx.theme().status().error.into()).strikethrough()
9391 }),
9392 )
9393 .when(!has_keybind, |el| {
9394 el.child(
9395 h_flex().ml_1().child(
9396 Icon::new(IconName::Info)
9397 .size(IconSize::Small)
9398 .color(cx.theme().status().error.into()),
9399 ),
9400 )
9401 })
9402 .when_some(icon, |element, icon| {
9403 element.child(
9404 div()
9405 .mt(px(1.5))
9406 .child(Icon::new(icon).size(IconSize::Small)),
9407 )
9408 })
9409 }
9410
9411 fn render_edit_prediction_jump_outside_popover(
9412 &self,
9413 snapshot: &BufferSnapshot,
9414 window: &mut Window,
9415 cx: &mut App,
9416 ) -> Stateful<Div> {
9417 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9418 let has_keybind = keybind.is_some();
9419
9420 let file_name = snapshot
9421 .file()
9422 .map(|file| SharedString::new(file.file_name(cx)))
9423 .unwrap_or(SharedString::new_static("untitled"));
9424
9425 h_flex()
9426 .id("ep-jump-outside-popover")
9427 .py_1()
9428 .px_2()
9429 .gap_1()
9430 .rounded_md()
9431 .border_1()
9432 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9433 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9434 .shadow_xs()
9435 .when(!has_keybind, |el| {
9436 let status_colors = cx.theme().status();
9437
9438 el.bg(status_colors.error_background)
9439 .border_color(status_colors.error.opacity(0.6))
9440 .pl_2()
9441 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9442 .cursor_default()
9443 .hoverable_tooltip(move |_window, cx| {
9444 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9445 })
9446 })
9447 .children(keybind)
9448 .child(
9449 Label::new(file_name)
9450 .size(LabelSize::Small)
9451 .buffer_font(cx)
9452 .when(!has_keybind, |el| {
9453 el.color(cx.theme().status().error.into()).strikethrough()
9454 }),
9455 )
9456 .when(!has_keybind, |el| {
9457 el.child(
9458 h_flex().ml_1().child(
9459 Icon::new(IconName::Info)
9460 .size(IconSize::Small)
9461 .color(cx.theme().status().error.into()),
9462 ),
9463 )
9464 })
9465 .child(
9466 div()
9467 .mt(px(1.5))
9468 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9469 )
9470 }
9471
9472 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9473 let accent_color = cx.theme().colors().text_accent;
9474 let editor_bg_color = cx.theme().colors().editor_background;
9475 editor_bg_color.blend(accent_color.opacity(0.1))
9476 }
9477
9478 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9479 let accent_color = cx.theme().colors().text_accent;
9480 let editor_bg_color = cx.theme().colors().editor_background;
9481 editor_bg_color.blend(accent_color.opacity(0.6))
9482 }
9483 fn get_prediction_provider_icon_name(
9484 provider: &Option<RegisteredEditPredictionProvider>,
9485 ) -> IconName {
9486 match provider {
9487 Some(provider) => match provider.provider.name() {
9488 "copilot" => IconName::Copilot,
9489 "supermaven" => IconName::Supermaven,
9490 _ => IconName::ZedPredict,
9491 },
9492 None => IconName::ZedPredict,
9493 }
9494 }
9495
9496 fn render_edit_prediction_cursor_popover(
9497 &self,
9498 min_width: Pixels,
9499 max_width: Pixels,
9500 cursor_point: Point,
9501 style: &EditorStyle,
9502 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9503 _window: &Window,
9504 cx: &mut Context<Editor>,
9505 ) -> Option<AnyElement> {
9506 let provider = self.edit_prediction_provider.as_ref()?;
9507 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9508
9509 let is_refreshing = provider.provider.is_refreshing(cx);
9510
9511 fn pending_completion_container(icon: IconName) -> Div {
9512 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9513 }
9514
9515 let completion = match &self.active_edit_prediction {
9516 Some(prediction) => {
9517 if !self.has_visible_completions_menu() {
9518 const RADIUS: Pixels = px(6.);
9519 const BORDER_WIDTH: Pixels = px(1.);
9520
9521 return Some(
9522 h_flex()
9523 .elevation_2(cx)
9524 .border(BORDER_WIDTH)
9525 .border_color(cx.theme().colors().border)
9526 .when(accept_keystroke.is_none(), |el| {
9527 el.border_color(cx.theme().status().error)
9528 })
9529 .rounded(RADIUS)
9530 .rounded_tl(px(0.))
9531 .overflow_hidden()
9532 .child(div().px_1p5().child(match &prediction.completion {
9533 EditPrediction::MoveWithin { target, snapshot } => {
9534 use text::ToPoint as _;
9535 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9536 {
9537 Icon::new(IconName::ZedPredictDown)
9538 } else {
9539 Icon::new(IconName::ZedPredictUp)
9540 }
9541 }
9542 EditPrediction::MoveOutside { .. } => {
9543 // TODO [zeta2] custom icon for external jump?
9544 Icon::new(provider_icon)
9545 }
9546 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9547 }))
9548 .child(
9549 h_flex()
9550 .gap_1()
9551 .py_1()
9552 .px_2()
9553 .rounded_r(RADIUS - BORDER_WIDTH)
9554 .border_l_1()
9555 .border_color(cx.theme().colors().border)
9556 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9557 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9558 el.child(
9559 Label::new("Hold")
9560 .size(LabelSize::Small)
9561 .when(accept_keystroke.is_none(), |el| {
9562 el.strikethrough()
9563 })
9564 .line_height_style(LineHeightStyle::UiLabel),
9565 )
9566 })
9567 .id("edit_prediction_cursor_popover_keybind")
9568 .when(accept_keystroke.is_none(), |el| {
9569 let status_colors = cx.theme().status();
9570
9571 el.bg(status_colors.error_background)
9572 .border_color(status_colors.error.opacity(0.6))
9573 .child(Icon::new(IconName::Info).color(Color::Error))
9574 .cursor_default()
9575 .hoverable_tooltip(move |_window, cx| {
9576 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9577 .into()
9578 })
9579 })
9580 .when_some(
9581 accept_keystroke.as_ref(),
9582 |el, accept_keystroke| {
9583 el.child(h_flex().children(ui::render_modifiers(
9584 accept_keystroke.modifiers(),
9585 PlatformStyle::platform(),
9586 Some(Color::Default),
9587 Some(IconSize::XSmall.rems().into()),
9588 false,
9589 )))
9590 },
9591 ),
9592 )
9593 .into_any(),
9594 );
9595 }
9596
9597 self.render_edit_prediction_cursor_popover_preview(
9598 prediction,
9599 cursor_point,
9600 style,
9601 cx,
9602 )?
9603 }
9604
9605 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9606 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9607 stale_completion,
9608 cursor_point,
9609 style,
9610 cx,
9611 )?,
9612
9613 None => pending_completion_container(provider_icon)
9614 .child(Label::new("...").size(LabelSize::Small)),
9615 },
9616
9617 None => pending_completion_container(provider_icon)
9618 .child(Label::new("...").size(LabelSize::Small)),
9619 };
9620
9621 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9622 completion
9623 .with_animation(
9624 "loading-completion",
9625 Animation::new(Duration::from_secs(2))
9626 .repeat()
9627 .with_easing(pulsating_between(0.4, 0.8)),
9628 |label, delta| label.opacity(delta),
9629 )
9630 .into_any_element()
9631 } else {
9632 completion.into_any_element()
9633 };
9634
9635 let has_completion = self.active_edit_prediction.is_some();
9636
9637 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9638 Some(
9639 h_flex()
9640 .min_w(min_width)
9641 .max_w(max_width)
9642 .flex_1()
9643 .elevation_2(cx)
9644 .border_color(cx.theme().colors().border)
9645 .child(
9646 div()
9647 .flex_1()
9648 .py_1()
9649 .px_2()
9650 .overflow_hidden()
9651 .child(completion),
9652 )
9653 .when_some(accept_keystroke, |el, accept_keystroke| {
9654 if !accept_keystroke.modifiers().modified() {
9655 return el;
9656 }
9657
9658 el.child(
9659 h_flex()
9660 .h_full()
9661 .border_l_1()
9662 .rounded_r_lg()
9663 .border_color(cx.theme().colors().border)
9664 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9665 .gap_1()
9666 .py_1()
9667 .px_2()
9668 .child(
9669 h_flex()
9670 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9671 .when(is_platform_style_mac, |parent| parent.gap_1())
9672 .child(h_flex().children(ui::render_modifiers(
9673 accept_keystroke.modifiers(),
9674 PlatformStyle::platform(),
9675 Some(if !has_completion {
9676 Color::Muted
9677 } else {
9678 Color::Default
9679 }),
9680 None,
9681 false,
9682 ))),
9683 )
9684 .child(Label::new("Preview").into_any_element())
9685 .opacity(if has_completion { 1.0 } else { 0.4 }),
9686 )
9687 })
9688 .into_any(),
9689 )
9690 }
9691
9692 fn render_edit_prediction_cursor_popover_preview(
9693 &self,
9694 completion: &EditPredictionState,
9695 cursor_point: Point,
9696 style: &EditorStyle,
9697 cx: &mut Context<Editor>,
9698 ) -> Option<Div> {
9699 use text::ToPoint as _;
9700
9701 fn render_relative_row_jump(
9702 prefix: impl Into<String>,
9703 current_row: u32,
9704 target_row: u32,
9705 ) -> Div {
9706 let (row_diff, arrow) = if target_row < current_row {
9707 (current_row - target_row, IconName::ArrowUp)
9708 } else {
9709 (target_row - current_row, IconName::ArrowDown)
9710 };
9711
9712 h_flex()
9713 .child(
9714 Label::new(format!("{}{}", prefix.into(), row_diff))
9715 .color(Color::Muted)
9716 .size(LabelSize::Small),
9717 )
9718 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9719 }
9720
9721 let supports_jump = self
9722 .edit_prediction_provider
9723 .as_ref()
9724 .map(|provider| provider.provider.supports_jump_to_edit())
9725 .unwrap_or(true);
9726
9727 match &completion.completion {
9728 EditPrediction::MoveWithin {
9729 target, snapshot, ..
9730 } => {
9731 if !supports_jump {
9732 return None;
9733 }
9734
9735 Some(
9736 h_flex()
9737 .px_2()
9738 .gap_2()
9739 .flex_1()
9740 .child(
9741 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9742 Icon::new(IconName::ZedPredictDown)
9743 } else {
9744 Icon::new(IconName::ZedPredictUp)
9745 },
9746 )
9747 .child(Label::new("Jump to Edit")),
9748 )
9749 }
9750 EditPrediction::MoveOutside { snapshot, .. } => {
9751 let file_name = snapshot
9752 .file()
9753 .map(|file| file.file_name(cx))
9754 .unwrap_or("untitled");
9755 Some(
9756 h_flex()
9757 .px_2()
9758 .gap_2()
9759 .flex_1()
9760 .child(Icon::new(IconName::ZedPredict))
9761 .child(Label::new(format!("Jump to {file_name}"))),
9762 )
9763 }
9764 EditPrediction::Edit {
9765 edits,
9766 edit_preview,
9767 snapshot,
9768 display_mode: _,
9769 } => {
9770 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9771
9772 let (highlighted_edits, has_more_lines) =
9773 if let Some(edit_preview) = edit_preview.as_ref() {
9774 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9775 .first_line_preview()
9776 } else {
9777 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9778 };
9779
9780 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9781 .with_default_highlights(&style.text, highlighted_edits.highlights);
9782
9783 let preview = h_flex()
9784 .gap_1()
9785 .min_w_16()
9786 .child(styled_text)
9787 .when(has_more_lines, |parent| parent.child("…"));
9788
9789 let left = if supports_jump && first_edit_row != cursor_point.row {
9790 render_relative_row_jump("", cursor_point.row, first_edit_row)
9791 .into_any_element()
9792 } else {
9793 let icon_name =
9794 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9795 Icon::new(icon_name).into_any_element()
9796 };
9797
9798 Some(
9799 h_flex()
9800 .h_full()
9801 .flex_1()
9802 .gap_2()
9803 .pr_1()
9804 .overflow_x_hidden()
9805 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9806 .child(left)
9807 .child(preview),
9808 )
9809 }
9810 }
9811 }
9812
9813 pub fn render_context_menu(
9814 &self,
9815 style: &EditorStyle,
9816 max_height_in_lines: u32,
9817 window: &mut Window,
9818 cx: &mut Context<Editor>,
9819 ) -> Option<AnyElement> {
9820 let menu = self.context_menu.borrow();
9821 let menu = menu.as_ref()?;
9822 if !menu.visible() {
9823 return None;
9824 };
9825 Some(menu.render(style, max_height_in_lines, window, cx))
9826 }
9827
9828 fn render_context_menu_aside(
9829 &mut self,
9830 max_size: Size<Pixels>,
9831 window: &mut Window,
9832 cx: &mut Context<Editor>,
9833 ) -> Option<AnyElement> {
9834 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9835 if menu.visible() {
9836 menu.render_aside(max_size, window, cx)
9837 } else {
9838 None
9839 }
9840 })
9841 }
9842
9843 fn hide_context_menu(
9844 &mut self,
9845 window: &mut Window,
9846 cx: &mut Context<Self>,
9847 ) -> Option<CodeContextMenu> {
9848 cx.notify();
9849 self.completion_tasks.clear();
9850 let context_menu = self.context_menu.borrow_mut().take();
9851 self.stale_edit_prediction_in_menu.take();
9852 self.update_visible_edit_prediction(window, cx);
9853 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9854 && let Some(completion_provider) = &self.completion_provider
9855 {
9856 completion_provider.selection_changed(None, window, cx);
9857 }
9858 context_menu
9859 }
9860
9861 fn show_snippet_choices(
9862 &mut self,
9863 choices: &Vec<String>,
9864 selection: Range<Anchor>,
9865 cx: &mut Context<Self>,
9866 ) {
9867 let Some((_, buffer, _)) = self
9868 .buffer()
9869 .read(cx)
9870 .excerpt_containing(selection.start, cx)
9871 else {
9872 return;
9873 };
9874 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9875 else {
9876 return;
9877 };
9878 if buffer != end_buffer {
9879 log::error!("expected anchor range to have matching buffer IDs");
9880 return;
9881 }
9882
9883 let id = post_inc(&mut self.next_completion_id);
9884 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9885 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9886 CompletionsMenu::new_snippet_choices(
9887 id,
9888 true,
9889 choices,
9890 selection,
9891 buffer,
9892 snippet_sort_order,
9893 ),
9894 ));
9895 }
9896
9897 pub fn insert_snippet(
9898 &mut self,
9899 insertion_ranges: &[Range<MultiBufferOffset>],
9900 snippet: Snippet,
9901 window: &mut Window,
9902 cx: &mut Context<Self>,
9903 ) -> Result<()> {
9904 struct Tabstop<T> {
9905 is_end_tabstop: bool,
9906 ranges: Vec<Range<T>>,
9907 choices: Option<Vec<String>>,
9908 }
9909
9910 let tabstops = self.buffer.update(cx, |buffer, cx| {
9911 let snippet_text: Arc<str> = snippet.text.clone().into();
9912 let edits = insertion_ranges
9913 .iter()
9914 .cloned()
9915 .map(|range| (range, snippet_text.clone()));
9916 let autoindent_mode = AutoindentMode::Block {
9917 original_indent_columns: Vec::new(),
9918 };
9919 buffer.edit(edits, Some(autoindent_mode), cx);
9920
9921 let snapshot = &*buffer.read(cx);
9922 let snippet = &snippet;
9923 snippet
9924 .tabstops
9925 .iter()
9926 .map(|tabstop| {
9927 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9928 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9929 });
9930 let mut tabstop_ranges = tabstop
9931 .ranges
9932 .iter()
9933 .flat_map(|tabstop_range| {
9934 let mut delta = 0_isize;
9935 insertion_ranges.iter().map(move |insertion_range| {
9936 let insertion_start = insertion_range.start + delta;
9937 delta += snippet.text.len() as isize
9938 - (insertion_range.end - insertion_range.start) as isize;
9939
9940 let start =
9941 (insertion_start + tabstop_range.start).min(snapshot.len());
9942 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9943 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9944 })
9945 })
9946 .collect::<Vec<_>>();
9947 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9948
9949 Tabstop {
9950 is_end_tabstop,
9951 ranges: tabstop_ranges,
9952 choices: tabstop.choices.clone(),
9953 }
9954 })
9955 .collect::<Vec<_>>()
9956 });
9957 if let Some(tabstop) = tabstops.first() {
9958 self.change_selections(Default::default(), window, cx, |s| {
9959 // Reverse order so that the first range is the newest created selection.
9960 // Completions will use it and autoscroll will prioritize it.
9961 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9962 });
9963
9964 if let Some(choices) = &tabstop.choices
9965 && let Some(selection) = tabstop.ranges.first()
9966 {
9967 self.show_snippet_choices(choices, selection.clone(), cx)
9968 }
9969
9970 // If we're already at the last tabstop and it's at the end of the snippet,
9971 // we're done, we don't need to keep the state around.
9972 if !tabstop.is_end_tabstop {
9973 let choices = tabstops
9974 .iter()
9975 .map(|tabstop| tabstop.choices.clone())
9976 .collect();
9977
9978 let ranges = tabstops
9979 .into_iter()
9980 .map(|tabstop| tabstop.ranges)
9981 .collect::<Vec<_>>();
9982
9983 self.snippet_stack.push(SnippetState {
9984 active_index: 0,
9985 ranges,
9986 choices,
9987 });
9988 }
9989
9990 // Check whether the just-entered snippet ends with an auto-closable bracket.
9991 if self.autoclose_regions.is_empty() {
9992 let snapshot = self.buffer.read(cx).snapshot(cx);
9993 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9994 let selection_head = selection.head();
9995 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9996 continue;
9997 };
9998
9999 let mut bracket_pair = None;
10000 let max_lookup_length = scope
10001 .brackets()
10002 .map(|(pair, _)| {
10003 pair.start
10004 .as_str()
10005 .chars()
10006 .count()
10007 .max(pair.end.as_str().chars().count())
10008 })
10009 .max();
10010 if let Some(max_lookup_length) = max_lookup_length {
10011 let next_text = snapshot
10012 .chars_at(selection_head)
10013 .take(max_lookup_length)
10014 .collect::<String>();
10015 let prev_text = snapshot
10016 .reversed_chars_at(selection_head)
10017 .take(max_lookup_length)
10018 .collect::<String>();
10019
10020 for (pair, enabled) in scope.brackets() {
10021 if enabled
10022 && pair.close
10023 && prev_text.starts_with(pair.start.as_str())
10024 && next_text.starts_with(pair.end.as_str())
10025 {
10026 bracket_pair = Some(pair.clone());
10027 break;
10028 }
10029 }
10030 }
10031
10032 if let Some(pair) = bracket_pair {
10033 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10034 let autoclose_enabled =
10035 self.use_autoclose && snapshot_settings.use_autoclose;
10036 if autoclose_enabled {
10037 let start = snapshot.anchor_after(selection_head);
10038 let end = snapshot.anchor_after(selection_head);
10039 self.autoclose_regions.push(AutocloseRegion {
10040 selection_id: selection.id,
10041 range: start..end,
10042 pair,
10043 });
10044 }
10045 }
10046 }
10047 }
10048 }
10049 Ok(())
10050 }
10051
10052 pub fn move_to_next_snippet_tabstop(
10053 &mut self,
10054 window: &mut Window,
10055 cx: &mut Context<Self>,
10056 ) -> bool {
10057 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10058 }
10059
10060 pub fn move_to_prev_snippet_tabstop(
10061 &mut self,
10062 window: &mut Window,
10063 cx: &mut Context<Self>,
10064 ) -> bool {
10065 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10066 }
10067
10068 pub fn move_to_snippet_tabstop(
10069 &mut self,
10070 bias: Bias,
10071 window: &mut Window,
10072 cx: &mut Context<Self>,
10073 ) -> bool {
10074 if let Some(mut snippet) = self.snippet_stack.pop() {
10075 match bias {
10076 Bias::Left => {
10077 if snippet.active_index > 0 {
10078 snippet.active_index -= 1;
10079 } else {
10080 self.snippet_stack.push(snippet);
10081 return false;
10082 }
10083 }
10084 Bias::Right => {
10085 if snippet.active_index + 1 < snippet.ranges.len() {
10086 snippet.active_index += 1;
10087 } else {
10088 self.snippet_stack.push(snippet);
10089 return false;
10090 }
10091 }
10092 }
10093 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10094 self.change_selections(Default::default(), window, cx, |s| {
10095 // Reverse order so that the first range is the newest created selection.
10096 // Completions will use it and autoscroll will prioritize it.
10097 s.select_ranges(current_ranges.iter().rev().cloned())
10098 });
10099
10100 if let Some(choices) = &snippet.choices[snippet.active_index]
10101 && let Some(selection) = current_ranges.first()
10102 {
10103 self.show_snippet_choices(choices, selection.clone(), cx);
10104 }
10105
10106 // If snippet state is not at the last tabstop, push it back on the stack
10107 if snippet.active_index + 1 < snippet.ranges.len() {
10108 self.snippet_stack.push(snippet);
10109 }
10110 return true;
10111 }
10112 }
10113
10114 false
10115 }
10116
10117 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10118 self.transact(window, cx, |this, window, cx| {
10119 this.select_all(&SelectAll, window, cx);
10120 this.insert("", window, cx);
10121 });
10122 }
10123
10124 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10125 if self.read_only(cx) {
10126 return;
10127 }
10128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10129 self.transact(window, cx, |this, window, cx| {
10130 this.select_autoclose_pair(window, cx);
10131
10132 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10133
10134 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10135 if !this.linked_edit_ranges.is_empty() {
10136 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10137 let snapshot = this.buffer.read(cx).snapshot(cx);
10138
10139 for selection in selections.iter() {
10140 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10141 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10142 if selection_start.buffer_id != selection_end.buffer_id {
10143 continue;
10144 }
10145 if let Some(ranges) =
10146 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10147 {
10148 for (buffer, entries) in ranges {
10149 linked_ranges.entry(buffer).or_default().extend(entries);
10150 }
10151 }
10152 }
10153 }
10154
10155 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10156 for selection in &mut selections {
10157 if selection.is_empty() {
10158 let old_head = selection.head();
10159 let mut new_head =
10160 movement::left(&display_map, old_head.to_display_point(&display_map))
10161 .to_point(&display_map);
10162 if let Some((buffer, line_buffer_range)) = display_map
10163 .buffer_snapshot()
10164 .buffer_line_for_row(MultiBufferRow(old_head.row))
10165 {
10166 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10167 let indent_len = match indent_size.kind {
10168 IndentKind::Space => {
10169 buffer.settings_at(line_buffer_range.start, cx).tab_size
10170 }
10171 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10172 };
10173 if old_head.column <= indent_size.len && old_head.column > 0 {
10174 let indent_len = indent_len.get();
10175 new_head = cmp::min(
10176 new_head,
10177 MultiBufferPoint::new(
10178 old_head.row,
10179 ((old_head.column - 1) / indent_len) * indent_len,
10180 ),
10181 );
10182 }
10183 }
10184
10185 selection.set_head(new_head, SelectionGoal::None);
10186 }
10187 }
10188
10189 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10190 this.insert("", window, cx);
10191 let empty_str: Arc<str> = Arc::from("");
10192 for (buffer, edits) in linked_ranges {
10193 let snapshot = buffer.read(cx).snapshot();
10194 use text::ToPoint as TP;
10195
10196 let edits = edits
10197 .into_iter()
10198 .map(|range| {
10199 let end_point = TP::to_point(&range.end, &snapshot);
10200 let mut start_point = TP::to_point(&range.start, &snapshot);
10201
10202 if end_point == start_point {
10203 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10204 .saturating_sub(1);
10205 start_point =
10206 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10207 };
10208
10209 (start_point..end_point, empty_str.clone())
10210 })
10211 .sorted_by_key(|(range, _)| range.start)
10212 .collect::<Vec<_>>();
10213 buffer.update(cx, |this, cx| {
10214 this.edit(edits, None, cx);
10215 })
10216 }
10217 this.refresh_edit_prediction(true, false, window, cx);
10218 refresh_linked_ranges(this, window, cx);
10219 });
10220 }
10221
10222 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10223 if self.read_only(cx) {
10224 return;
10225 }
10226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10227 self.transact(window, cx, |this, window, cx| {
10228 this.change_selections(Default::default(), window, cx, |s| {
10229 s.move_with(|map, selection| {
10230 if selection.is_empty() {
10231 let cursor = movement::right(map, selection.head());
10232 selection.end = cursor;
10233 selection.reversed = true;
10234 selection.goal = SelectionGoal::None;
10235 }
10236 })
10237 });
10238 this.insert("", window, cx);
10239 this.refresh_edit_prediction(true, false, window, cx);
10240 });
10241 }
10242
10243 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10244 if self.mode.is_single_line() {
10245 cx.propagate();
10246 return;
10247 }
10248
10249 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10250 if self.move_to_prev_snippet_tabstop(window, cx) {
10251 return;
10252 }
10253 self.outdent(&Outdent, window, cx);
10254 }
10255
10256 pub fn next_snippet_tabstop(
10257 &mut self,
10258 _: &NextSnippetTabstop,
10259 window: &mut Window,
10260 cx: &mut Context<Self>,
10261 ) {
10262 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10263 cx.propagate();
10264 return;
10265 }
10266
10267 if self.move_to_next_snippet_tabstop(window, cx) {
10268 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10269 return;
10270 }
10271 cx.propagate();
10272 }
10273
10274 pub fn previous_snippet_tabstop(
10275 &mut self,
10276 _: &PreviousSnippetTabstop,
10277 window: &mut Window,
10278 cx: &mut Context<Self>,
10279 ) {
10280 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10281 cx.propagate();
10282 return;
10283 }
10284
10285 if self.move_to_prev_snippet_tabstop(window, cx) {
10286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10287 return;
10288 }
10289 cx.propagate();
10290 }
10291
10292 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10293 if self.mode.is_single_line() {
10294 cx.propagate();
10295 return;
10296 }
10297
10298 if self.move_to_next_snippet_tabstop(window, cx) {
10299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10300 return;
10301 }
10302 if self.read_only(cx) {
10303 return;
10304 }
10305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10306 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10307 let buffer = self.buffer.read(cx);
10308 let snapshot = buffer.snapshot(cx);
10309 let rows_iter = selections.iter().map(|s| s.head().row);
10310 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10311
10312 let has_some_cursor_in_whitespace = selections
10313 .iter()
10314 .filter(|selection| selection.is_empty())
10315 .any(|selection| {
10316 let cursor = selection.head();
10317 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10318 cursor.column < current_indent.len
10319 });
10320
10321 let mut edits = Vec::new();
10322 let mut prev_edited_row = 0;
10323 let mut row_delta = 0;
10324 for selection in &mut selections {
10325 if selection.start.row != prev_edited_row {
10326 row_delta = 0;
10327 }
10328 prev_edited_row = selection.end.row;
10329
10330 // If the selection is non-empty, then increase the indentation of the selected lines.
10331 if !selection.is_empty() {
10332 row_delta =
10333 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10334 continue;
10335 }
10336
10337 let cursor = selection.head();
10338 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10339 if let Some(suggested_indent) =
10340 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10341 {
10342 // Don't do anything if already at suggested indent
10343 // and there is any other cursor which is not
10344 if has_some_cursor_in_whitespace
10345 && cursor.column == current_indent.len
10346 && current_indent.len == suggested_indent.len
10347 {
10348 continue;
10349 }
10350
10351 // Adjust line and move cursor to suggested indent
10352 // if cursor is not at suggested indent
10353 if cursor.column < suggested_indent.len
10354 && cursor.column <= current_indent.len
10355 && current_indent.len <= suggested_indent.len
10356 {
10357 selection.start = Point::new(cursor.row, suggested_indent.len);
10358 selection.end = selection.start;
10359 if row_delta == 0 {
10360 edits.extend(Buffer::edit_for_indent_size_adjustment(
10361 cursor.row,
10362 current_indent,
10363 suggested_indent,
10364 ));
10365 row_delta = suggested_indent.len - current_indent.len;
10366 }
10367 continue;
10368 }
10369
10370 // If current indent is more than suggested indent
10371 // only move cursor to current indent and skip indent
10372 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10373 selection.start = Point::new(cursor.row, current_indent.len);
10374 selection.end = selection.start;
10375 continue;
10376 }
10377 }
10378
10379 // Otherwise, insert a hard or soft tab.
10380 let settings = buffer.language_settings_at(cursor, cx);
10381 let tab_size = if settings.hard_tabs {
10382 IndentSize::tab()
10383 } else {
10384 let tab_size = settings.tab_size.get();
10385 let indent_remainder = snapshot
10386 .text_for_range(Point::new(cursor.row, 0)..cursor)
10387 .flat_map(str::chars)
10388 .fold(row_delta % tab_size, |counter: u32, c| {
10389 if c == '\t' {
10390 0
10391 } else {
10392 (counter + 1) % tab_size
10393 }
10394 });
10395
10396 let chars_to_next_tab_stop = tab_size - indent_remainder;
10397 IndentSize::spaces(chars_to_next_tab_stop)
10398 };
10399 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10400 selection.end = selection.start;
10401 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10402 row_delta += tab_size.len;
10403 }
10404
10405 self.transact(window, cx, |this, window, cx| {
10406 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10407 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10408 this.refresh_edit_prediction(true, false, window, cx);
10409 });
10410 }
10411
10412 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10413 if self.read_only(cx) {
10414 return;
10415 }
10416 if self.mode.is_single_line() {
10417 cx.propagate();
10418 return;
10419 }
10420
10421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10422 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10423 let mut prev_edited_row = 0;
10424 let mut row_delta = 0;
10425 let mut edits = Vec::new();
10426 let buffer = self.buffer.read(cx);
10427 let snapshot = buffer.snapshot(cx);
10428 for selection in &mut selections {
10429 if selection.start.row != prev_edited_row {
10430 row_delta = 0;
10431 }
10432 prev_edited_row = selection.end.row;
10433
10434 row_delta =
10435 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10436 }
10437
10438 self.transact(window, cx, |this, window, cx| {
10439 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10440 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10441 });
10442 }
10443
10444 fn indent_selection(
10445 buffer: &MultiBuffer,
10446 snapshot: &MultiBufferSnapshot,
10447 selection: &mut Selection<Point>,
10448 edits: &mut Vec<(Range<Point>, String)>,
10449 delta_for_start_row: u32,
10450 cx: &App,
10451 ) -> u32 {
10452 let settings = buffer.language_settings_at(selection.start, cx);
10453 let tab_size = settings.tab_size.get();
10454 let indent_kind = if settings.hard_tabs {
10455 IndentKind::Tab
10456 } else {
10457 IndentKind::Space
10458 };
10459 let mut start_row = selection.start.row;
10460 let mut end_row = selection.end.row + 1;
10461
10462 // If a selection ends at the beginning of a line, don't indent
10463 // that last line.
10464 if selection.end.column == 0 && selection.end.row > selection.start.row {
10465 end_row -= 1;
10466 }
10467
10468 // Avoid re-indenting a row that has already been indented by a
10469 // previous selection, but still update this selection's column
10470 // to reflect that indentation.
10471 if delta_for_start_row > 0 {
10472 start_row += 1;
10473 selection.start.column += delta_for_start_row;
10474 if selection.end.row == selection.start.row {
10475 selection.end.column += delta_for_start_row;
10476 }
10477 }
10478
10479 let mut delta_for_end_row = 0;
10480 let has_multiple_rows = start_row + 1 != end_row;
10481 for row in start_row..end_row {
10482 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10483 let indent_delta = match (current_indent.kind, indent_kind) {
10484 (IndentKind::Space, IndentKind::Space) => {
10485 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10486 IndentSize::spaces(columns_to_next_tab_stop)
10487 }
10488 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10489 (_, IndentKind::Tab) => IndentSize::tab(),
10490 };
10491
10492 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10493 0
10494 } else {
10495 selection.start.column
10496 };
10497 let row_start = Point::new(row, start);
10498 edits.push((
10499 row_start..row_start,
10500 indent_delta.chars().collect::<String>(),
10501 ));
10502
10503 // Update this selection's endpoints to reflect the indentation.
10504 if row == selection.start.row {
10505 selection.start.column += indent_delta.len;
10506 }
10507 if row == selection.end.row {
10508 selection.end.column += indent_delta.len;
10509 delta_for_end_row = indent_delta.len;
10510 }
10511 }
10512
10513 if selection.start.row == selection.end.row {
10514 delta_for_start_row + delta_for_end_row
10515 } else {
10516 delta_for_end_row
10517 }
10518 }
10519
10520 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10521 if self.read_only(cx) {
10522 return;
10523 }
10524 if self.mode.is_single_line() {
10525 cx.propagate();
10526 return;
10527 }
10528
10529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10531 let selections = self.selections.all::<Point>(&display_map);
10532 let mut deletion_ranges = Vec::new();
10533 let mut last_outdent = None;
10534 {
10535 let buffer = self.buffer.read(cx);
10536 let snapshot = buffer.snapshot(cx);
10537 for selection in &selections {
10538 let settings = buffer.language_settings_at(selection.start, cx);
10539 let tab_size = settings.tab_size.get();
10540 let mut rows = selection.spanned_rows(false, &display_map);
10541
10542 // Avoid re-outdenting a row that has already been outdented by a
10543 // previous selection.
10544 if let Some(last_row) = last_outdent
10545 && last_row == rows.start
10546 {
10547 rows.start = rows.start.next_row();
10548 }
10549 let has_multiple_rows = rows.len() > 1;
10550 for row in rows.iter_rows() {
10551 let indent_size = snapshot.indent_size_for_line(row);
10552 if indent_size.len > 0 {
10553 let deletion_len = match indent_size.kind {
10554 IndentKind::Space => {
10555 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10556 if columns_to_prev_tab_stop == 0 {
10557 tab_size
10558 } else {
10559 columns_to_prev_tab_stop
10560 }
10561 }
10562 IndentKind::Tab => 1,
10563 };
10564 let start = if has_multiple_rows
10565 || deletion_len > selection.start.column
10566 || indent_size.len < selection.start.column
10567 {
10568 0
10569 } else {
10570 selection.start.column - deletion_len
10571 };
10572 deletion_ranges.push(
10573 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10574 );
10575 last_outdent = Some(row);
10576 }
10577 }
10578 }
10579 }
10580
10581 self.transact(window, cx, |this, window, cx| {
10582 this.buffer.update(cx, |buffer, cx| {
10583 let empty_str: Arc<str> = Arc::default();
10584 buffer.edit(
10585 deletion_ranges
10586 .into_iter()
10587 .map(|range| (range, empty_str.clone())),
10588 None,
10589 cx,
10590 );
10591 });
10592 let selections = this
10593 .selections
10594 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10595 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10596 });
10597 }
10598
10599 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10600 if self.read_only(cx) {
10601 return;
10602 }
10603 if self.mode.is_single_line() {
10604 cx.propagate();
10605 return;
10606 }
10607
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let selections = self
10610 .selections
10611 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10612 .into_iter()
10613 .map(|s| s.range());
10614
10615 self.transact(window, cx, |this, window, cx| {
10616 this.buffer.update(cx, |buffer, cx| {
10617 buffer.autoindent_ranges(selections, cx);
10618 });
10619 let selections = this
10620 .selections
10621 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10622 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10623 });
10624 }
10625
10626 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10628 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10629 let selections = self.selections.all::<Point>(&display_map);
10630
10631 let mut new_cursors = Vec::new();
10632 let mut edit_ranges = Vec::new();
10633 let mut selections = selections.iter().peekable();
10634 while let Some(selection) = selections.next() {
10635 let mut rows = selection.spanned_rows(false, &display_map);
10636
10637 // Accumulate contiguous regions of rows that we want to delete.
10638 while let Some(next_selection) = selections.peek() {
10639 let next_rows = next_selection.spanned_rows(false, &display_map);
10640 if next_rows.start <= rows.end {
10641 rows.end = next_rows.end;
10642 selections.next().unwrap();
10643 } else {
10644 break;
10645 }
10646 }
10647
10648 let buffer = display_map.buffer_snapshot();
10649 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10650 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10651 // If there's a line after the range, delete the \n from the end of the row range
10652 (
10653 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10654 rows.end,
10655 )
10656 } else {
10657 // If there isn't a line after the range, delete the \n from the line before the
10658 // start of the row range
10659 edit_start = edit_start.saturating_sub_usize(1);
10660 (buffer.len(), rows.start.previous_row())
10661 };
10662
10663 let text_layout_details = self.text_layout_details(window);
10664 let x = display_map.x_for_display_point(
10665 selection.head().to_display_point(&display_map),
10666 &text_layout_details,
10667 );
10668 let row = Point::new(target_row.0, 0)
10669 .to_display_point(&display_map)
10670 .row();
10671 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10672
10673 new_cursors.push((
10674 selection.id,
10675 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10676 SelectionGoal::None,
10677 ));
10678 edit_ranges.push(edit_start..edit_end);
10679 }
10680
10681 self.transact(window, cx, |this, window, cx| {
10682 let buffer = this.buffer.update(cx, |buffer, cx| {
10683 let empty_str: Arc<str> = Arc::default();
10684 buffer.edit(
10685 edit_ranges
10686 .into_iter()
10687 .map(|range| (range, empty_str.clone())),
10688 None,
10689 cx,
10690 );
10691 buffer.snapshot(cx)
10692 });
10693 let new_selections = new_cursors
10694 .into_iter()
10695 .map(|(id, cursor, goal)| {
10696 let cursor = cursor.to_point(&buffer);
10697 Selection {
10698 id,
10699 start: cursor,
10700 end: cursor,
10701 reversed: false,
10702 goal,
10703 }
10704 })
10705 .collect();
10706
10707 this.change_selections(Default::default(), window, cx, |s| {
10708 s.select(new_selections);
10709 });
10710 });
10711 }
10712
10713 pub fn join_lines_impl(
10714 &mut self,
10715 insert_whitespace: bool,
10716 window: &mut Window,
10717 cx: &mut Context<Self>,
10718 ) {
10719 if self.read_only(cx) {
10720 return;
10721 }
10722 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10723 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10724 let start = MultiBufferRow(selection.start.row);
10725 // Treat single line selections as if they include the next line. Otherwise this action
10726 // would do nothing for single line selections individual cursors.
10727 let end = if selection.start.row == selection.end.row {
10728 MultiBufferRow(selection.start.row + 1)
10729 } else {
10730 MultiBufferRow(selection.end.row)
10731 };
10732
10733 if let Some(last_row_range) = row_ranges.last_mut()
10734 && start <= last_row_range.end
10735 {
10736 last_row_range.end = end;
10737 continue;
10738 }
10739 row_ranges.push(start..end);
10740 }
10741
10742 let snapshot = self.buffer.read(cx).snapshot(cx);
10743 let mut cursor_positions = Vec::new();
10744 for row_range in &row_ranges {
10745 let anchor = snapshot.anchor_before(Point::new(
10746 row_range.end.previous_row().0,
10747 snapshot.line_len(row_range.end.previous_row()),
10748 ));
10749 cursor_positions.push(anchor..anchor);
10750 }
10751
10752 self.transact(window, cx, |this, window, cx| {
10753 for row_range in row_ranges.into_iter().rev() {
10754 for row in row_range.iter_rows().rev() {
10755 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10756 let next_line_row = row.next_row();
10757 let indent = snapshot.indent_size_for_line(next_line_row);
10758 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10759
10760 let replace =
10761 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10762 " "
10763 } else {
10764 ""
10765 };
10766
10767 this.buffer.update(cx, |buffer, cx| {
10768 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10769 });
10770 }
10771 }
10772
10773 this.change_selections(Default::default(), window, cx, |s| {
10774 s.select_anchor_ranges(cursor_positions)
10775 });
10776 });
10777 }
10778
10779 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10781 self.join_lines_impl(true, window, cx);
10782 }
10783
10784 pub fn sort_lines_case_sensitive(
10785 &mut self,
10786 _: &SortLinesCaseSensitive,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 ) {
10790 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10791 }
10792
10793 pub fn sort_lines_by_length(
10794 &mut self,
10795 _: &SortLinesByLength,
10796 window: &mut Window,
10797 cx: &mut Context<Self>,
10798 ) {
10799 self.manipulate_immutable_lines(window, cx, |lines| {
10800 lines.sort_by_key(|&line| line.chars().count())
10801 })
10802 }
10803
10804 pub fn sort_lines_case_insensitive(
10805 &mut self,
10806 _: &SortLinesCaseInsensitive,
10807 window: &mut Window,
10808 cx: &mut Context<Self>,
10809 ) {
10810 self.manipulate_immutable_lines(window, cx, |lines| {
10811 lines.sort_by_key(|line| line.to_lowercase())
10812 })
10813 }
10814
10815 pub fn unique_lines_case_insensitive(
10816 &mut self,
10817 _: &UniqueLinesCaseInsensitive,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 ) {
10821 self.manipulate_immutable_lines(window, cx, |lines| {
10822 let mut seen = HashSet::default();
10823 lines.retain(|line| seen.insert(line.to_lowercase()));
10824 })
10825 }
10826
10827 pub fn unique_lines_case_sensitive(
10828 &mut self,
10829 _: &UniqueLinesCaseSensitive,
10830 window: &mut Window,
10831 cx: &mut Context<Self>,
10832 ) {
10833 self.manipulate_immutable_lines(window, cx, |lines| {
10834 let mut seen = HashSet::default();
10835 lines.retain(|line| seen.insert(*line));
10836 })
10837 }
10838
10839 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10840 let snapshot = self.buffer.read(cx).snapshot(cx);
10841 for selection in self.selections.disjoint_anchors_arc().iter() {
10842 if snapshot
10843 .language_at(selection.start)
10844 .and_then(|lang| lang.config().wrap_characters.as_ref())
10845 .is_some()
10846 {
10847 return true;
10848 }
10849 }
10850 false
10851 }
10852
10853 fn wrap_selections_in_tag(
10854 &mut self,
10855 _: &WrapSelectionsInTag,
10856 window: &mut Window,
10857 cx: &mut Context<Self>,
10858 ) {
10859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10860
10861 let snapshot = self.buffer.read(cx).snapshot(cx);
10862
10863 let mut edits = Vec::new();
10864 let mut boundaries = Vec::new();
10865
10866 for selection in self
10867 .selections
10868 .all_adjusted(&self.display_snapshot(cx))
10869 .iter()
10870 {
10871 let Some(wrap_config) = snapshot
10872 .language_at(selection.start)
10873 .and_then(|lang| lang.config().wrap_characters.clone())
10874 else {
10875 continue;
10876 };
10877
10878 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10879 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10880
10881 let start_before = snapshot.anchor_before(selection.start);
10882 let end_after = snapshot.anchor_after(selection.end);
10883
10884 edits.push((start_before..start_before, open_tag));
10885 edits.push((end_after..end_after, close_tag));
10886
10887 boundaries.push((
10888 start_before,
10889 end_after,
10890 wrap_config.start_prefix.len(),
10891 wrap_config.end_suffix.len(),
10892 ));
10893 }
10894
10895 if edits.is_empty() {
10896 return;
10897 }
10898
10899 self.transact(window, cx, |this, window, cx| {
10900 let buffer = this.buffer.update(cx, |buffer, cx| {
10901 buffer.edit(edits, None, cx);
10902 buffer.snapshot(cx)
10903 });
10904
10905 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10906 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10907 boundaries.into_iter()
10908 {
10909 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10910 let close_offset = end_after
10911 .to_offset(&buffer)
10912 .saturating_sub_usize(end_suffix_len);
10913 new_selections.push(open_offset..open_offset);
10914 new_selections.push(close_offset..close_offset);
10915 }
10916
10917 this.change_selections(Default::default(), window, cx, |s| {
10918 s.select_ranges(new_selections);
10919 });
10920
10921 this.request_autoscroll(Autoscroll::fit(), cx);
10922 });
10923 }
10924
10925 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10926 let Some(project) = self.project.clone() else {
10927 return;
10928 };
10929 self.reload(project, window, cx)
10930 .detach_and_notify_err(window, cx);
10931 }
10932
10933 pub fn restore_file(
10934 &mut self,
10935 _: &::git::RestoreFile,
10936 window: &mut Window,
10937 cx: &mut Context<Self>,
10938 ) {
10939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10940 let mut buffer_ids = HashSet::default();
10941 let snapshot = self.buffer().read(cx).snapshot(cx);
10942 for selection in self
10943 .selections
10944 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10945 {
10946 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10947 }
10948
10949 let buffer = self.buffer().read(cx);
10950 let ranges = buffer_ids
10951 .into_iter()
10952 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10953 .collect::<Vec<_>>();
10954
10955 self.restore_hunks_in_ranges(ranges, window, cx);
10956 }
10957
10958 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10960 let selections = self
10961 .selections
10962 .all(&self.display_snapshot(cx))
10963 .into_iter()
10964 .map(|s| s.range())
10965 .collect();
10966 self.restore_hunks_in_ranges(selections, window, cx);
10967 }
10968
10969 pub fn restore_hunks_in_ranges(
10970 &mut self,
10971 ranges: Vec<Range<Point>>,
10972 window: &mut Window,
10973 cx: &mut Context<Editor>,
10974 ) {
10975 let mut revert_changes = HashMap::default();
10976 let chunk_by = self
10977 .snapshot(window, cx)
10978 .hunks_for_ranges(ranges)
10979 .into_iter()
10980 .chunk_by(|hunk| hunk.buffer_id);
10981 for (buffer_id, hunks) in &chunk_by {
10982 let hunks = hunks.collect::<Vec<_>>();
10983 for hunk in &hunks {
10984 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10985 }
10986 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10987 }
10988 drop(chunk_by);
10989 if !revert_changes.is_empty() {
10990 self.transact(window, cx, |editor, window, cx| {
10991 editor.restore(revert_changes, window, cx);
10992 });
10993 }
10994 }
10995
10996 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10997 if let Some(status) = self
10998 .addons
10999 .iter()
11000 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11001 {
11002 return Some(status);
11003 }
11004 self.project
11005 .as_ref()?
11006 .read(cx)
11007 .status_for_buffer_id(buffer_id, cx)
11008 }
11009
11010 pub fn open_active_item_in_terminal(
11011 &mut self,
11012 _: &OpenInTerminal,
11013 window: &mut Window,
11014 cx: &mut Context<Self>,
11015 ) {
11016 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11017 let project_path = buffer.read(cx).project_path(cx)?;
11018 let project = self.project()?.read(cx);
11019 let entry = project.entry_for_path(&project_path, cx)?;
11020 let parent = match &entry.canonical_path {
11021 Some(canonical_path) => canonical_path.to_path_buf(),
11022 None => project.absolute_path(&project_path, cx)?,
11023 }
11024 .parent()?
11025 .to_path_buf();
11026 Some(parent)
11027 }) {
11028 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11029 }
11030 }
11031
11032 fn set_breakpoint_context_menu(
11033 &mut self,
11034 display_row: DisplayRow,
11035 position: Option<Anchor>,
11036 clicked_point: gpui::Point<Pixels>,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 ) {
11040 let source = self
11041 .buffer
11042 .read(cx)
11043 .snapshot(cx)
11044 .anchor_before(Point::new(display_row.0, 0u32));
11045
11046 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11047
11048 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11049 self,
11050 source,
11051 clicked_point,
11052 context_menu,
11053 window,
11054 cx,
11055 );
11056 }
11057
11058 fn add_edit_breakpoint_block(
11059 &mut self,
11060 anchor: Anchor,
11061 breakpoint: &Breakpoint,
11062 edit_action: BreakpointPromptEditAction,
11063 window: &mut Window,
11064 cx: &mut Context<Self>,
11065 ) {
11066 let weak_editor = cx.weak_entity();
11067 let bp_prompt = cx.new(|cx| {
11068 BreakpointPromptEditor::new(
11069 weak_editor,
11070 anchor,
11071 breakpoint.clone(),
11072 edit_action,
11073 window,
11074 cx,
11075 )
11076 });
11077
11078 let height = bp_prompt.update(cx, |this, cx| {
11079 this.prompt
11080 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11081 });
11082 let cloned_prompt = bp_prompt.clone();
11083 let blocks = vec![BlockProperties {
11084 style: BlockStyle::Sticky,
11085 placement: BlockPlacement::Above(anchor),
11086 height: Some(height),
11087 render: Arc::new(move |cx| {
11088 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11089 cloned_prompt.clone().into_any_element()
11090 }),
11091 priority: 0,
11092 }];
11093
11094 let focus_handle = bp_prompt.focus_handle(cx);
11095 window.focus(&focus_handle);
11096
11097 let block_ids = self.insert_blocks(blocks, None, cx);
11098 bp_prompt.update(cx, |prompt, _| {
11099 prompt.add_block_ids(block_ids);
11100 });
11101 }
11102
11103 pub(crate) fn breakpoint_at_row(
11104 &self,
11105 row: u32,
11106 window: &mut Window,
11107 cx: &mut Context<Self>,
11108 ) -> Option<(Anchor, Breakpoint)> {
11109 let snapshot = self.snapshot(window, cx);
11110 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11111
11112 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11113 }
11114
11115 pub(crate) fn breakpoint_at_anchor(
11116 &self,
11117 breakpoint_position: Anchor,
11118 snapshot: &EditorSnapshot,
11119 cx: &mut Context<Self>,
11120 ) -> Option<(Anchor, Breakpoint)> {
11121 let buffer = self
11122 .buffer
11123 .read(cx)
11124 .buffer_for_anchor(breakpoint_position, cx)?;
11125
11126 let enclosing_excerpt = breakpoint_position.excerpt_id;
11127 let buffer_snapshot = buffer.read(cx).snapshot();
11128
11129 let row = buffer_snapshot
11130 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11131 .row;
11132
11133 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11134 let anchor_end = snapshot
11135 .buffer_snapshot()
11136 .anchor_after(Point::new(row, line_len));
11137
11138 self.breakpoint_store
11139 .as_ref()?
11140 .read_with(cx, |breakpoint_store, cx| {
11141 breakpoint_store
11142 .breakpoints(
11143 &buffer,
11144 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11145 &buffer_snapshot,
11146 cx,
11147 )
11148 .next()
11149 .and_then(|(bp, _)| {
11150 let breakpoint_row = buffer_snapshot
11151 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11152 .row;
11153
11154 if breakpoint_row == row {
11155 snapshot
11156 .buffer_snapshot()
11157 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11158 .map(|position| (position, bp.bp.clone()))
11159 } else {
11160 None
11161 }
11162 })
11163 })
11164 }
11165
11166 pub fn edit_log_breakpoint(
11167 &mut self,
11168 _: &EditLogBreakpoint,
11169 window: &mut Window,
11170 cx: &mut Context<Self>,
11171 ) {
11172 if self.breakpoint_store.is_none() {
11173 return;
11174 }
11175
11176 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11177 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11178 message: None,
11179 state: BreakpointState::Enabled,
11180 condition: None,
11181 hit_condition: None,
11182 });
11183
11184 self.add_edit_breakpoint_block(
11185 anchor,
11186 &breakpoint,
11187 BreakpointPromptEditAction::Log,
11188 window,
11189 cx,
11190 );
11191 }
11192 }
11193
11194 fn breakpoints_at_cursors(
11195 &self,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11199 let snapshot = self.snapshot(window, cx);
11200 let cursors = self
11201 .selections
11202 .disjoint_anchors_arc()
11203 .iter()
11204 .map(|selection| {
11205 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11206
11207 let breakpoint_position = self
11208 .breakpoint_at_row(cursor_position.row, window, cx)
11209 .map(|bp| bp.0)
11210 .unwrap_or_else(|| {
11211 snapshot
11212 .display_snapshot
11213 .buffer_snapshot()
11214 .anchor_after(Point::new(cursor_position.row, 0))
11215 });
11216
11217 let breakpoint = self
11218 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11219 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11220
11221 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11222 })
11223 // 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.
11224 .collect::<HashMap<Anchor, _>>();
11225
11226 cursors.into_iter().collect()
11227 }
11228
11229 pub fn enable_breakpoint(
11230 &mut self,
11231 _: &crate::actions::EnableBreakpoint,
11232 window: &mut Window,
11233 cx: &mut Context<Self>,
11234 ) {
11235 if self.breakpoint_store.is_none() {
11236 return;
11237 }
11238
11239 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11240 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11241 continue;
11242 };
11243 self.edit_breakpoint_at_anchor(
11244 anchor,
11245 breakpoint,
11246 BreakpointEditAction::InvertState,
11247 cx,
11248 );
11249 }
11250 }
11251
11252 pub fn disable_breakpoint(
11253 &mut self,
11254 _: &crate::actions::DisableBreakpoint,
11255 window: &mut Window,
11256 cx: &mut Context<Self>,
11257 ) {
11258 if self.breakpoint_store.is_none() {
11259 return;
11260 }
11261
11262 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11263 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11264 continue;
11265 };
11266 self.edit_breakpoint_at_anchor(
11267 anchor,
11268 breakpoint,
11269 BreakpointEditAction::InvertState,
11270 cx,
11271 );
11272 }
11273 }
11274
11275 pub fn toggle_breakpoint(
11276 &mut self,
11277 _: &crate::actions::ToggleBreakpoint,
11278 window: &mut Window,
11279 cx: &mut Context<Self>,
11280 ) {
11281 if self.breakpoint_store.is_none() {
11282 return;
11283 }
11284
11285 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11286 if let Some(breakpoint) = breakpoint {
11287 self.edit_breakpoint_at_anchor(
11288 anchor,
11289 breakpoint,
11290 BreakpointEditAction::Toggle,
11291 cx,
11292 );
11293 } else {
11294 self.edit_breakpoint_at_anchor(
11295 anchor,
11296 Breakpoint::new_standard(),
11297 BreakpointEditAction::Toggle,
11298 cx,
11299 );
11300 }
11301 }
11302 }
11303
11304 pub fn edit_breakpoint_at_anchor(
11305 &mut self,
11306 breakpoint_position: Anchor,
11307 breakpoint: Breakpoint,
11308 edit_action: BreakpointEditAction,
11309 cx: &mut Context<Self>,
11310 ) {
11311 let Some(breakpoint_store) = &self.breakpoint_store else {
11312 return;
11313 };
11314
11315 let Some(buffer) = self
11316 .buffer
11317 .read(cx)
11318 .buffer_for_anchor(breakpoint_position, cx)
11319 else {
11320 return;
11321 };
11322
11323 breakpoint_store.update(cx, |breakpoint_store, cx| {
11324 breakpoint_store.toggle_breakpoint(
11325 buffer,
11326 BreakpointWithPosition {
11327 position: breakpoint_position.text_anchor,
11328 bp: breakpoint,
11329 },
11330 edit_action,
11331 cx,
11332 );
11333 });
11334
11335 cx.notify();
11336 }
11337
11338 #[cfg(any(test, feature = "test-support"))]
11339 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11340 self.breakpoint_store.clone()
11341 }
11342
11343 pub fn prepare_restore_change(
11344 &self,
11345 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11346 hunk: &MultiBufferDiffHunk,
11347 cx: &mut App,
11348 ) -> Option<()> {
11349 if hunk.is_created_file() {
11350 return None;
11351 }
11352 let buffer = self.buffer.read(cx);
11353 let diff = buffer.diff_for(hunk.buffer_id)?;
11354 let buffer = buffer.buffer(hunk.buffer_id)?;
11355 let buffer = buffer.read(cx);
11356 let original_text = diff
11357 .read(cx)
11358 .base_text()
11359 .as_rope()
11360 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11361 let buffer_snapshot = buffer.snapshot();
11362 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11363 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11364 probe
11365 .0
11366 .start
11367 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11368 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11369 }) {
11370 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11371 Some(())
11372 } else {
11373 None
11374 }
11375 }
11376
11377 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11378 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11379 }
11380
11381 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11382 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11383 }
11384
11385 fn manipulate_lines<M>(
11386 &mut self,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 mut manipulate: M,
11390 ) where
11391 M: FnMut(&str) -> LineManipulationResult,
11392 {
11393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11394
11395 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11396 let buffer = self.buffer.read(cx).snapshot(cx);
11397
11398 let mut edits = Vec::new();
11399
11400 let selections = self.selections.all::<Point>(&display_map);
11401 let mut selections = selections.iter().peekable();
11402 let mut contiguous_row_selections = Vec::new();
11403 let mut new_selections = Vec::new();
11404 let mut added_lines = 0;
11405 let mut removed_lines = 0;
11406
11407 while let Some(selection) = selections.next() {
11408 let (start_row, end_row) = consume_contiguous_rows(
11409 &mut contiguous_row_selections,
11410 selection,
11411 &display_map,
11412 &mut selections,
11413 );
11414
11415 let start_point = Point::new(start_row.0, 0);
11416 let end_point = Point::new(
11417 end_row.previous_row().0,
11418 buffer.line_len(end_row.previous_row()),
11419 );
11420 let text = buffer
11421 .text_for_range(start_point..end_point)
11422 .collect::<String>();
11423
11424 let LineManipulationResult {
11425 new_text,
11426 line_count_before,
11427 line_count_after,
11428 } = manipulate(&text);
11429
11430 edits.push((start_point..end_point, new_text));
11431
11432 // Selections must change based on added and removed line count
11433 let start_row =
11434 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11435 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11436 new_selections.push(Selection {
11437 id: selection.id,
11438 start: start_row,
11439 end: end_row,
11440 goal: SelectionGoal::None,
11441 reversed: selection.reversed,
11442 });
11443
11444 if line_count_after > line_count_before {
11445 added_lines += line_count_after - line_count_before;
11446 } else if line_count_before > line_count_after {
11447 removed_lines += line_count_before - line_count_after;
11448 }
11449 }
11450
11451 self.transact(window, cx, |this, window, cx| {
11452 let buffer = this.buffer.update(cx, |buffer, cx| {
11453 buffer.edit(edits, None, cx);
11454 buffer.snapshot(cx)
11455 });
11456
11457 // Recalculate offsets on newly edited buffer
11458 let new_selections = new_selections
11459 .iter()
11460 .map(|s| {
11461 let start_point = Point::new(s.start.0, 0);
11462 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11463 Selection {
11464 id: s.id,
11465 start: buffer.point_to_offset(start_point),
11466 end: buffer.point_to_offset(end_point),
11467 goal: s.goal,
11468 reversed: s.reversed,
11469 }
11470 })
11471 .collect();
11472
11473 this.change_selections(Default::default(), window, cx, |s| {
11474 s.select(new_selections);
11475 });
11476
11477 this.request_autoscroll(Autoscroll::fit(), cx);
11478 });
11479 }
11480
11481 fn manipulate_immutable_lines<Fn>(
11482 &mut self,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 mut callback: Fn,
11486 ) where
11487 Fn: FnMut(&mut Vec<&str>),
11488 {
11489 self.manipulate_lines(window, cx, |text| {
11490 let mut lines: Vec<&str> = text.split('\n').collect();
11491 let line_count_before = lines.len();
11492
11493 callback(&mut lines);
11494
11495 LineManipulationResult {
11496 new_text: lines.join("\n"),
11497 line_count_before,
11498 line_count_after: lines.len(),
11499 }
11500 });
11501 }
11502
11503 fn manipulate_mutable_lines<Fn>(
11504 &mut self,
11505 window: &mut Window,
11506 cx: &mut Context<Self>,
11507 mut callback: Fn,
11508 ) where
11509 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11510 {
11511 self.manipulate_lines(window, cx, |text| {
11512 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11513 let line_count_before = lines.len();
11514
11515 callback(&mut lines);
11516
11517 LineManipulationResult {
11518 new_text: lines.join("\n"),
11519 line_count_before,
11520 line_count_after: lines.len(),
11521 }
11522 });
11523 }
11524
11525 pub fn convert_indentation_to_spaces(
11526 &mut self,
11527 _: &ConvertIndentationToSpaces,
11528 window: &mut Window,
11529 cx: &mut Context<Self>,
11530 ) {
11531 let settings = self.buffer.read(cx).language_settings(cx);
11532 let tab_size = settings.tab_size.get() as usize;
11533
11534 self.manipulate_mutable_lines(window, cx, |lines| {
11535 // Allocates a reasonably sized scratch buffer once for the whole loop
11536 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11537 // Avoids recomputing spaces that could be inserted many times
11538 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11539 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11540 .collect();
11541
11542 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11543 let mut chars = line.as_ref().chars();
11544 let mut col = 0;
11545 let mut changed = false;
11546
11547 for ch in chars.by_ref() {
11548 match ch {
11549 ' ' => {
11550 reindented_line.push(' ');
11551 col += 1;
11552 }
11553 '\t' => {
11554 // \t are converted to spaces depending on the current column
11555 let spaces_len = tab_size - (col % tab_size);
11556 reindented_line.extend(&space_cache[spaces_len - 1]);
11557 col += spaces_len;
11558 changed = true;
11559 }
11560 _ => {
11561 // If we dont append before break, the character is consumed
11562 reindented_line.push(ch);
11563 break;
11564 }
11565 }
11566 }
11567
11568 if !changed {
11569 reindented_line.clear();
11570 continue;
11571 }
11572 // Append the rest of the line and replace old reference with new one
11573 reindented_line.extend(chars);
11574 *line = Cow::Owned(reindented_line.clone());
11575 reindented_line.clear();
11576 }
11577 });
11578 }
11579
11580 pub fn convert_indentation_to_tabs(
11581 &mut self,
11582 _: &ConvertIndentationToTabs,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) {
11586 let settings = self.buffer.read(cx).language_settings(cx);
11587 let tab_size = settings.tab_size.get() as usize;
11588
11589 self.manipulate_mutable_lines(window, cx, |lines| {
11590 // Allocates a reasonably sized buffer once for the whole loop
11591 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11592 // Avoids recomputing spaces that could be inserted many times
11593 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11594 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11595 .collect();
11596
11597 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11598 let mut chars = line.chars();
11599 let mut spaces_count = 0;
11600 let mut first_non_indent_char = None;
11601 let mut changed = false;
11602
11603 for ch in chars.by_ref() {
11604 match ch {
11605 ' ' => {
11606 // Keep track of spaces. Append \t when we reach tab_size
11607 spaces_count += 1;
11608 changed = true;
11609 if spaces_count == tab_size {
11610 reindented_line.push('\t');
11611 spaces_count = 0;
11612 }
11613 }
11614 '\t' => {
11615 reindented_line.push('\t');
11616 spaces_count = 0;
11617 }
11618 _ => {
11619 // Dont append it yet, we might have remaining spaces
11620 first_non_indent_char = Some(ch);
11621 break;
11622 }
11623 }
11624 }
11625
11626 if !changed {
11627 reindented_line.clear();
11628 continue;
11629 }
11630 // Remaining spaces that didn't make a full tab stop
11631 if spaces_count > 0 {
11632 reindented_line.extend(&space_cache[spaces_count - 1]);
11633 }
11634 // If we consume an extra character that was not indentation, add it back
11635 if let Some(extra_char) = first_non_indent_char {
11636 reindented_line.push(extra_char);
11637 }
11638 // Append the rest of the line and replace old reference with new one
11639 reindented_line.extend(chars);
11640 *line = Cow::Owned(reindented_line.clone());
11641 reindented_line.clear();
11642 }
11643 });
11644 }
11645
11646 pub fn convert_to_upper_case(
11647 &mut self,
11648 _: &ConvertToUpperCase,
11649 window: &mut Window,
11650 cx: &mut Context<Self>,
11651 ) {
11652 self.manipulate_text(window, cx, |text| text.to_uppercase())
11653 }
11654
11655 pub fn convert_to_lower_case(
11656 &mut self,
11657 _: &ConvertToLowerCase,
11658 window: &mut Window,
11659 cx: &mut Context<Self>,
11660 ) {
11661 self.manipulate_text(window, cx, |text| text.to_lowercase())
11662 }
11663
11664 pub fn convert_to_title_case(
11665 &mut self,
11666 _: &ConvertToTitleCase,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.manipulate_text(window, cx, |text| {
11671 text.split('\n')
11672 .map(|line| line.to_case(Case::Title))
11673 .join("\n")
11674 })
11675 }
11676
11677 pub fn convert_to_snake_case(
11678 &mut self,
11679 _: &ConvertToSnakeCase,
11680 window: &mut Window,
11681 cx: &mut Context<Self>,
11682 ) {
11683 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11684 }
11685
11686 pub fn convert_to_kebab_case(
11687 &mut self,
11688 _: &ConvertToKebabCase,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11693 }
11694
11695 pub fn convert_to_upper_camel_case(
11696 &mut self,
11697 _: &ConvertToUpperCamelCase,
11698 window: &mut Window,
11699 cx: &mut Context<Self>,
11700 ) {
11701 self.manipulate_text(window, cx, |text| {
11702 text.split('\n')
11703 .map(|line| line.to_case(Case::UpperCamel))
11704 .join("\n")
11705 })
11706 }
11707
11708 pub fn convert_to_lower_camel_case(
11709 &mut self,
11710 _: &ConvertToLowerCamelCase,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11715 }
11716
11717 pub fn convert_to_opposite_case(
11718 &mut self,
11719 _: &ConvertToOppositeCase,
11720 window: &mut Window,
11721 cx: &mut Context<Self>,
11722 ) {
11723 self.manipulate_text(window, cx, |text| {
11724 text.chars()
11725 .fold(String::with_capacity(text.len()), |mut t, c| {
11726 if c.is_uppercase() {
11727 t.extend(c.to_lowercase());
11728 } else {
11729 t.extend(c.to_uppercase());
11730 }
11731 t
11732 })
11733 })
11734 }
11735
11736 pub fn convert_to_sentence_case(
11737 &mut self,
11738 _: &ConvertToSentenceCase,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11743 }
11744
11745 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11746 self.manipulate_text(window, cx, |text| {
11747 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11748 if has_upper_case_characters {
11749 text.to_lowercase()
11750 } else {
11751 text.to_uppercase()
11752 }
11753 })
11754 }
11755
11756 pub fn convert_to_rot13(
11757 &mut self,
11758 _: &ConvertToRot13,
11759 window: &mut Window,
11760 cx: &mut Context<Self>,
11761 ) {
11762 self.manipulate_text(window, cx, |text| {
11763 text.chars()
11764 .map(|c| match c {
11765 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11766 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11767 _ => c,
11768 })
11769 .collect()
11770 })
11771 }
11772
11773 pub fn convert_to_rot47(
11774 &mut self,
11775 _: &ConvertToRot47,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 self.manipulate_text(window, cx, |text| {
11780 text.chars()
11781 .map(|c| {
11782 let code_point = c as u32;
11783 if code_point >= 33 && code_point <= 126 {
11784 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11785 }
11786 c
11787 })
11788 .collect()
11789 })
11790 }
11791
11792 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11793 where
11794 Fn: FnMut(&str) -> String,
11795 {
11796 let buffer = self.buffer.read(cx).snapshot(cx);
11797
11798 let mut new_selections = Vec::new();
11799 let mut edits = Vec::new();
11800 let mut selection_adjustment = 0isize;
11801
11802 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11803 let selection_is_empty = selection.is_empty();
11804
11805 let (start, end) = if selection_is_empty {
11806 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11807 (word_range.start, word_range.end)
11808 } else {
11809 (
11810 buffer.point_to_offset(selection.start),
11811 buffer.point_to_offset(selection.end),
11812 )
11813 };
11814
11815 let text = buffer.text_for_range(start..end).collect::<String>();
11816 let old_length = text.len() as isize;
11817 let text = callback(&text);
11818
11819 new_selections.push(Selection {
11820 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11821 end: MultiBufferOffset(
11822 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11823 ),
11824 goal: SelectionGoal::None,
11825 id: selection.id,
11826 reversed: selection.reversed,
11827 });
11828
11829 selection_adjustment += old_length - text.len() as isize;
11830
11831 edits.push((start..end, text));
11832 }
11833
11834 self.transact(window, cx, |this, window, cx| {
11835 this.buffer.update(cx, |buffer, cx| {
11836 buffer.edit(edits, None, cx);
11837 });
11838
11839 this.change_selections(Default::default(), window, cx, |s| {
11840 s.select(new_selections);
11841 });
11842
11843 this.request_autoscroll(Autoscroll::fit(), cx);
11844 });
11845 }
11846
11847 pub fn move_selection_on_drop(
11848 &mut self,
11849 selection: &Selection<Anchor>,
11850 target: DisplayPoint,
11851 is_cut: bool,
11852 window: &mut Window,
11853 cx: &mut Context<Self>,
11854 ) {
11855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11856 let buffer = display_map.buffer_snapshot();
11857 let mut edits = Vec::new();
11858 let insert_point = display_map
11859 .clip_point(target, Bias::Left)
11860 .to_point(&display_map);
11861 let text = buffer
11862 .text_for_range(selection.start..selection.end)
11863 .collect::<String>();
11864 if is_cut {
11865 edits.push(((selection.start..selection.end), String::new()));
11866 }
11867 let insert_anchor = buffer.anchor_before(insert_point);
11868 edits.push(((insert_anchor..insert_anchor), text));
11869 let last_edit_start = insert_anchor.bias_left(buffer);
11870 let last_edit_end = insert_anchor.bias_right(buffer);
11871 self.transact(window, cx, |this, window, cx| {
11872 this.buffer.update(cx, |buffer, cx| {
11873 buffer.edit(edits, None, cx);
11874 });
11875 this.change_selections(Default::default(), window, cx, |s| {
11876 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11877 });
11878 });
11879 }
11880
11881 pub fn clear_selection_drag_state(&mut self) {
11882 self.selection_drag_state = SelectionDragState::None;
11883 }
11884
11885 pub fn duplicate(
11886 &mut self,
11887 upwards: bool,
11888 whole_lines: bool,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11893
11894 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11895 let buffer = display_map.buffer_snapshot();
11896 let selections = self.selections.all::<Point>(&display_map);
11897
11898 let mut edits = Vec::new();
11899 let mut selections_iter = selections.iter().peekable();
11900 while let Some(selection) = selections_iter.next() {
11901 let mut rows = selection.spanned_rows(false, &display_map);
11902 // duplicate line-wise
11903 if whole_lines || selection.start == selection.end {
11904 // Avoid duplicating the same lines twice.
11905 while let Some(next_selection) = selections_iter.peek() {
11906 let next_rows = next_selection.spanned_rows(false, &display_map);
11907 if next_rows.start < rows.end {
11908 rows.end = next_rows.end;
11909 selections_iter.next().unwrap();
11910 } else {
11911 break;
11912 }
11913 }
11914
11915 // Copy the text from the selected row region and splice it either at the start
11916 // or end of the region.
11917 let start = Point::new(rows.start.0, 0);
11918 let end = Point::new(
11919 rows.end.previous_row().0,
11920 buffer.line_len(rows.end.previous_row()),
11921 );
11922
11923 let mut text = buffer.text_for_range(start..end).collect::<String>();
11924
11925 let insert_location = if upwards {
11926 // When duplicating upward, we need to insert before the current line.
11927 // If we're on the last line and it doesn't end with a newline,
11928 // we need to add a newline before the duplicated content.
11929 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11930 && buffer.max_point().column > 0
11931 && !text.ends_with('\n');
11932
11933 if needs_leading_newline {
11934 text.insert(0, '\n');
11935 end
11936 } else {
11937 text.push('\n');
11938 Point::new(rows.start.0, 0)
11939 }
11940 } else {
11941 text.push('\n');
11942 start
11943 };
11944 edits.push((insert_location..insert_location, text));
11945 } else {
11946 // duplicate character-wise
11947 let start = selection.start;
11948 let end = selection.end;
11949 let text = buffer.text_for_range(start..end).collect::<String>();
11950 edits.push((selection.end..selection.end, text));
11951 }
11952 }
11953
11954 self.transact(window, cx, |this, window, cx| {
11955 this.buffer.update(cx, |buffer, cx| {
11956 buffer.edit(edits, None, cx);
11957 });
11958
11959 // When duplicating upward with whole lines, move the cursor to the duplicated line
11960 if upwards && whole_lines {
11961 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11962
11963 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11964 let mut new_ranges = Vec::new();
11965 let selections = s.all::<Point>(&display_map);
11966 let mut selections_iter = selections.iter().peekable();
11967
11968 while let Some(first_selection) = selections_iter.next() {
11969 // Group contiguous selections together to find the total row span
11970 let mut group_selections = vec![first_selection];
11971 let mut rows = first_selection.spanned_rows(false, &display_map);
11972
11973 while let Some(next_selection) = selections_iter.peek() {
11974 let next_rows = next_selection.spanned_rows(false, &display_map);
11975 if next_rows.start < rows.end {
11976 rows.end = next_rows.end;
11977 group_selections.push(selections_iter.next().unwrap());
11978 } else {
11979 break;
11980 }
11981 }
11982
11983 let row_count = rows.end.0 - rows.start.0;
11984
11985 // Move all selections in this group up by the total number of duplicated rows
11986 for selection in group_selections {
11987 let new_start = Point::new(
11988 selection.start.row.saturating_sub(row_count),
11989 selection.start.column,
11990 );
11991
11992 let new_end = Point::new(
11993 selection.end.row.saturating_sub(row_count),
11994 selection.end.column,
11995 );
11996
11997 new_ranges.push(new_start..new_end);
11998 }
11999 }
12000
12001 s.select_ranges(new_ranges);
12002 });
12003 }
12004
12005 this.request_autoscroll(Autoscroll::fit(), cx);
12006 });
12007 }
12008
12009 pub fn duplicate_line_up(
12010 &mut self,
12011 _: &DuplicateLineUp,
12012 window: &mut Window,
12013 cx: &mut Context<Self>,
12014 ) {
12015 self.duplicate(true, true, window, cx);
12016 }
12017
12018 pub fn duplicate_line_down(
12019 &mut self,
12020 _: &DuplicateLineDown,
12021 window: &mut Window,
12022 cx: &mut Context<Self>,
12023 ) {
12024 self.duplicate(false, true, window, cx);
12025 }
12026
12027 pub fn duplicate_selection(
12028 &mut self,
12029 _: &DuplicateSelection,
12030 window: &mut Window,
12031 cx: &mut Context<Self>,
12032 ) {
12033 self.duplicate(false, false, window, cx);
12034 }
12035
12036 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12038 if self.mode.is_single_line() {
12039 cx.propagate();
12040 return;
12041 }
12042
12043 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12044 let buffer = self.buffer.read(cx).snapshot(cx);
12045
12046 let mut edits = Vec::new();
12047 let mut unfold_ranges = Vec::new();
12048 let mut refold_creases = Vec::new();
12049
12050 let selections = self.selections.all::<Point>(&display_map);
12051 let mut selections = selections.iter().peekable();
12052 let mut contiguous_row_selections = Vec::new();
12053 let mut new_selections = Vec::new();
12054
12055 while let Some(selection) = selections.next() {
12056 // Find all the selections that span a contiguous row range
12057 let (start_row, end_row) = consume_contiguous_rows(
12058 &mut contiguous_row_selections,
12059 selection,
12060 &display_map,
12061 &mut selections,
12062 );
12063
12064 // Move the text spanned by the row range to be before the line preceding the row range
12065 if start_row.0 > 0 {
12066 let range_to_move = Point::new(
12067 start_row.previous_row().0,
12068 buffer.line_len(start_row.previous_row()),
12069 )
12070 ..Point::new(
12071 end_row.previous_row().0,
12072 buffer.line_len(end_row.previous_row()),
12073 );
12074 let insertion_point = display_map
12075 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12076 .0;
12077
12078 // Don't move lines across excerpts
12079 if buffer
12080 .excerpt_containing(insertion_point..range_to_move.end)
12081 .is_some()
12082 {
12083 let text = buffer
12084 .text_for_range(range_to_move.clone())
12085 .flat_map(|s| s.chars())
12086 .skip(1)
12087 .chain(['\n'])
12088 .collect::<String>();
12089
12090 edits.push((
12091 buffer.anchor_after(range_to_move.start)
12092 ..buffer.anchor_before(range_to_move.end),
12093 String::new(),
12094 ));
12095 let insertion_anchor = buffer.anchor_after(insertion_point);
12096 edits.push((insertion_anchor..insertion_anchor, text));
12097
12098 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12099
12100 // Move selections up
12101 new_selections.extend(contiguous_row_selections.drain(..).map(
12102 |mut selection| {
12103 selection.start.row -= row_delta;
12104 selection.end.row -= row_delta;
12105 selection
12106 },
12107 ));
12108
12109 // Move folds up
12110 unfold_ranges.push(range_to_move.clone());
12111 for fold in display_map.folds_in_range(
12112 buffer.anchor_before(range_to_move.start)
12113 ..buffer.anchor_after(range_to_move.end),
12114 ) {
12115 let mut start = fold.range.start.to_point(&buffer);
12116 let mut end = fold.range.end.to_point(&buffer);
12117 start.row -= row_delta;
12118 end.row -= row_delta;
12119 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12120 }
12121 }
12122 }
12123
12124 // If we didn't move line(s), preserve the existing selections
12125 new_selections.append(&mut contiguous_row_selections);
12126 }
12127
12128 self.transact(window, cx, |this, window, cx| {
12129 this.unfold_ranges(&unfold_ranges, true, true, cx);
12130 this.buffer.update(cx, |buffer, cx| {
12131 for (range, text) in edits {
12132 buffer.edit([(range, text)], None, cx);
12133 }
12134 });
12135 this.fold_creases(refold_creases, true, window, cx);
12136 this.change_selections(Default::default(), window, cx, |s| {
12137 s.select(new_selections);
12138 })
12139 });
12140 }
12141
12142 pub fn move_line_down(
12143 &mut self,
12144 _: &MoveLineDown,
12145 window: &mut Window,
12146 cx: &mut Context<Self>,
12147 ) {
12148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12149 if self.mode.is_single_line() {
12150 cx.propagate();
12151 return;
12152 }
12153
12154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12155 let buffer = self.buffer.read(cx).snapshot(cx);
12156
12157 let mut edits = Vec::new();
12158 let mut unfold_ranges = Vec::new();
12159 let mut refold_creases = Vec::new();
12160
12161 let selections = self.selections.all::<Point>(&display_map);
12162 let mut selections = selections.iter().peekable();
12163 let mut contiguous_row_selections = Vec::new();
12164 let mut new_selections = Vec::new();
12165
12166 while let Some(selection) = selections.next() {
12167 // Find all the selections that span a contiguous row range
12168 let (start_row, end_row) = consume_contiguous_rows(
12169 &mut contiguous_row_selections,
12170 selection,
12171 &display_map,
12172 &mut selections,
12173 );
12174
12175 // Move the text spanned by the row range to be after the last line of the row range
12176 if end_row.0 <= buffer.max_point().row {
12177 let range_to_move =
12178 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12179 let insertion_point = display_map
12180 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12181 .0;
12182
12183 // Don't move lines across excerpt boundaries
12184 if buffer
12185 .excerpt_containing(range_to_move.start..insertion_point)
12186 .is_some()
12187 {
12188 let mut text = String::from("\n");
12189 text.extend(buffer.text_for_range(range_to_move.clone()));
12190 text.pop(); // Drop trailing newline
12191 edits.push((
12192 buffer.anchor_after(range_to_move.start)
12193 ..buffer.anchor_before(range_to_move.end),
12194 String::new(),
12195 ));
12196 let insertion_anchor = buffer.anchor_after(insertion_point);
12197 edits.push((insertion_anchor..insertion_anchor, text));
12198
12199 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12200
12201 // Move selections down
12202 new_selections.extend(contiguous_row_selections.drain(..).map(
12203 |mut selection| {
12204 selection.start.row += row_delta;
12205 selection.end.row += row_delta;
12206 selection
12207 },
12208 ));
12209
12210 // Move folds down
12211 unfold_ranges.push(range_to_move.clone());
12212 for fold in display_map.folds_in_range(
12213 buffer.anchor_before(range_to_move.start)
12214 ..buffer.anchor_after(range_to_move.end),
12215 ) {
12216 let mut start = fold.range.start.to_point(&buffer);
12217 let mut end = fold.range.end.to_point(&buffer);
12218 start.row += row_delta;
12219 end.row += row_delta;
12220 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12221 }
12222 }
12223 }
12224
12225 // If we didn't move line(s), preserve the existing selections
12226 new_selections.append(&mut contiguous_row_selections);
12227 }
12228
12229 self.transact(window, cx, |this, window, cx| {
12230 this.unfold_ranges(&unfold_ranges, true, true, cx);
12231 this.buffer.update(cx, |buffer, cx| {
12232 for (range, text) in edits {
12233 buffer.edit([(range, text)], None, cx);
12234 }
12235 });
12236 this.fold_creases(refold_creases, true, window, cx);
12237 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12238 });
12239 }
12240
12241 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12243 let text_layout_details = &self.text_layout_details(window);
12244 self.transact(window, cx, |this, window, cx| {
12245 let edits = this.change_selections(Default::default(), window, cx, |s| {
12246 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12247 s.move_with(|display_map, selection| {
12248 if !selection.is_empty() {
12249 return;
12250 }
12251
12252 let mut head = selection.head();
12253 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12254 if head.column() == display_map.line_len(head.row()) {
12255 transpose_offset = display_map
12256 .buffer_snapshot()
12257 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12258 }
12259
12260 if transpose_offset == MultiBufferOffset(0) {
12261 return;
12262 }
12263
12264 *head.column_mut() += 1;
12265 head = display_map.clip_point(head, Bias::Right);
12266 let goal = SelectionGoal::HorizontalPosition(
12267 display_map
12268 .x_for_display_point(head, text_layout_details)
12269 .into(),
12270 );
12271 selection.collapse_to(head, goal);
12272
12273 let transpose_start = display_map
12274 .buffer_snapshot()
12275 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12276 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12277 let transpose_end = display_map
12278 .buffer_snapshot()
12279 .clip_offset(transpose_offset + 1usize, Bias::Right);
12280 if let Some(ch) = display_map
12281 .buffer_snapshot()
12282 .chars_at(transpose_start)
12283 .next()
12284 {
12285 edits.push((transpose_start..transpose_offset, String::new()));
12286 edits.push((transpose_end..transpose_end, ch.to_string()));
12287 }
12288 }
12289 });
12290 edits
12291 });
12292 this.buffer
12293 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12294 let selections = this
12295 .selections
12296 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12297 this.change_selections(Default::default(), window, cx, |s| {
12298 s.select(selections);
12299 });
12300 });
12301 }
12302
12303 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12305 if self.mode.is_single_line() {
12306 cx.propagate();
12307 return;
12308 }
12309
12310 self.rewrap_impl(RewrapOptions::default(), cx)
12311 }
12312
12313 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12314 let buffer = self.buffer.read(cx).snapshot(cx);
12315 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12316
12317 #[derive(Clone, Debug, PartialEq)]
12318 enum CommentFormat {
12319 /// single line comment, with prefix for line
12320 Line(String),
12321 /// single line within a block comment, with prefix for line
12322 BlockLine(String),
12323 /// a single line of a block comment that includes the initial delimiter
12324 BlockCommentWithStart(BlockCommentConfig),
12325 /// a single line of a block comment that includes the ending delimiter
12326 BlockCommentWithEnd(BlockCommentConfig),
12327 }
12328
12329 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12330 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12331 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12332 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12333 .peekable();
12334
12335 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12336 row
12337 } else {
12338 return Vec::new();
12339 };
12340
12341 let language_settings = buffer.language_settings_at(selection.head(), cx);
12342 let language_scope = buffer.language_scope_at(selection.head());
12343
12344 let indent_and_prefix_for_row =
12345 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12346 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12347 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12348 &language_scope
12349 {
12350 let indent_end = Point::new(row, indent.len);
12351 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12352 let line_text_after_indent = buffer
12353 .text_for_range(indent_end..line_end)
12354 .collect::<String>();
12355
12356 let is_within_comment_override = buffer
12357 .language_scope_at(indent_end)
12358 .is_some_and(|scope| scope.override_name() == Some("comment"));
12359 let comment_delimiters = if is_within_comment_override {
12360 // we are within a comment syntax node, but we don't
12361 // yet know what kind of comment: block, doc or line
12362 match (
12363 language_scope.documentation_comment(),
12364 language_scope.block_comment(),
12365 ) {
12366 (Some(config), _) | (_, Some(config))
12367 if buffer.contains_str_at(indent_end, &config.start) =>
12368 {
12369 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12370 }
12371 (Some(config), _) | (_, Some(config))
12372 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12373 {
12374 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12375 }
12376 (Some(config), _) | (_, Some(config))
12377 if buffer.contains_str_at(indent_end, &config.prefix) =>
12378 {
12379 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12380 }
12381 (_, _) => language_scope
12382 .line_comment_prefixes()
12383 .iter()
12384 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12385 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12386 }
12387 } else {
12388 // we not in an overridden comment node, but we may
12389 // be within a non-overridden line comment node
12390 language_scope
12391 .line_comment_prefixes()
12392 .iter()
12393 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12394 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12395 };
12396
12397 let rewrap_prefix = language_scope
12398 .rewrap_prefixes()
12399 .iter()
12400 .find_map(|prefix_regex| {
12401 prefix_regex.find(&line_text_after_indent).map(|mat| {
12402 if mat.start() == 0 {
12403 Some(mat.as_str().to_string())
12404 } else {
12405 None
12406 }
12407 })
12408 })
12409 .flatten();
12410 (comment_delimiters, rewrap_prefix)
12411 } else {
12412 (None, None)
12413 };
12414 (indent, comment_prefix, rewrap_prefix)
12415 };
12416
12417 let mut ranges = Vec::new();
12418 let from_empty_selection = selection.is_empty();
12419
12420 let mut current_range_start = first_row;
12421 let mut prev_row = first_row;
12422 let (
12423 mut current_range_indent,
12424 mut current_range_comment_delimiters,
12425 mut current_range_rewrap_prefix,
12426 ) = indent_and_prefix_for_row(first_row);
12427
12428 for row in non_blank_rows_iter.skip(1) {
12429 let has_paragraph_break = row > prev_row + 1;
12430
12431 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12432 indent_and_prefix_for_row(row);
12433
12434 let has_indent_change = row_indent != current_range_indent;
12435 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12436
12437 let has_boundary_change = has_comment_change
12438 || row_rewrap_prefix.is_some()
12439 || (has_indent_change && current_range_comment_delimiters.is_some());
12440
12441 if has_paragraph_break || has_boundary_change {
12442 ranges.push((
12443 language_settings.clone(),
12444 Point::new(current_range_start, 0)
12445 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12446 current_range_indent,
12447 current_range_comment_delimiters.clone(),
12448 current_range_rewrap_prefix.clone(),
12449 from_empty_selection,
12450 ));
12451 current_range_start = row;
12452 current_range_indent = row_indent;
12453 current_range_comment_delimiters = row_comment_delimiters;
12454 current_range_rewrap_prefix = row_rewrap_prefix;
12455 }
12456 prev_row = row;
12457 }
12458
12459 ranges.push((
12460 language_settings.clone(),
12461 Point::new(current_range_start, 0)
12462 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12463 current_range_indent,
12464 current_range_comment_delimiters,
12465 current_range_rewrap_prefix,
12466 from_empty_selection,
12467 ));
12468
12469 ranges
12470 });
12471
12472 let mut edits = Vec::new();
12473 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12474
12475 for (
12476 language_settings,
12477 wrap_range,
12478 mut indent_size,
12479 comment_prefix,
12480 rewrap_prefix,
12481 from_empty_selection,
12482 ) in wrap_ranges
12483 {
12484 let mut start_row = wrap_range.start.row;
12485 let mut end_row = wrap_range.end.row;
12486
12487 // Skip selections that overlap with a range that has already been rewrapped.
12488 let selection_range = start_row..end_row;
12489 if rewrapped_row_ranges
12490 .iter()
12491 .any(|range| range.overlaps(&selection_range))
12492 {
12493 continue;
12494 }
12495
12496 let tab_size = language_settings.tab_size;
12497
12498 let (line_prefix, inside_comment) = match &comment_prefix {
12499 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12500 (Some(prefix.as_str()), true)
12501 }
12502 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12503 (Some(prefix.as_ref()), true)
12504 }
12505 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12506 start: _,
12507 end: _,
12508 prefix,
12509 tab_size,
12510 })) => {
12511 indent_size.len += tab_size;
12512 (Some(prefix.as_ref()), true)
12513 }
12514 None => (None, false),
12515 };
12516 let indent_prefix = indent_size.chars().collect::<String>();
12517 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12518
12519 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12520 RewrapBehavior::InComments => inside_comment,
12521 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12522 RewrapBehavior::Anywhere => true,
12523 };
12524
12525 let should_rewrap = options.override_language_settings
12526 || allow_rewrap_based_on_language
12527 || self.hard_wrap.is_some();
12528 if !should_rewrap {
12529 continue;
12530 }
12531
12532 if from_empty_selection {
12533 'expand_upwards: while start_row > 0 {
12534 let prev_row = start_row - 1;
12535 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12536 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12537 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12538 {
12539 start_row = prev_row;
12540 } else {
12541 break 'expand_upwards;
12542 }
12543 }
12544
12545 'expand_downwards: while end_row < buffer.max_point().row {
12546 let next_row = end_row + 1;
12547 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12548 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12549 && !buffer.is_line_blank(MultiBufferRow(next_row))
12550 {
12551 end_row = next_row;
12552 } else {
12553 break 'expand_downwards;
12554 }
12555 }
12556 }
12557
12558 let start = Point::new(start_row, 0);
12559 let start_offset = ToOffset::to_offset(&start, &buffer);
12560 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12561 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12562 let mut first_line_delimiter = None;
12563 let mut last_line_delimiter = None;
12564 let Some(lines_without_prefixes) = selection_text
12565 .lines()
12566 .enumerate()
12567 .map(|(ix, line)| {
12568 let line_trimmed = line.trim_start();
12569 if rewrap_prefix.is_some() && ix > 0 {
12570 Ok(line_trimmed)
12571 } else if let Some(
12572 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12573 start,
12574 prefix,
12575 end,
12576 tab_size,
12577 })
12578 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12579 start,
12580 prefix,
12581 end,
12582 tab_size,
12583 }),
12584 ) = &comment_prefix
12585 {
12586 let line_trimmed = line_trimmed
12587 .strip_prefix(start.as_ref())
12588 .map(|s| {
12589 let mut indent_size = indent_size;
12590 indent_size.len -= tab_size;
12591 let indent_prefix: String = indent_size.chars().collect();
12592 first_line_delimiter = Some((indent_prefix, start));
12593 s.trim_start()
12594 })
12595 .unwrap_or(line_trimmed);
12596 let line_trimmed = line_trimmed
12597 .strip_suffix(end.as_ref())
12598 .map(|s| {
12599 last_line_delimiter = Some(end);
12600 s.trim_end()
12601 })
12602 .unwrap_or(line_trimmed);
12603 let line_trimmed = line_trimmed
12604 .strip_prefix(prefix.as_ref())
12605 .unwrap_or(line_trimmed);
12606 Ok(line_trimmed)
12607 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12608 line_trimmed.strip_prefix(prefix).with_context(|| {
12609 format!("line did not start with prefix {prefix:?}: {line:?}")
12610 })
12611 } else {
12612 line_trimmed
12613 .strip_prefix(&line_prefix.trim_start())
12614 .with_context(|| {
12615 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12616 })
12617 }
12618 })
12619 .collect::<Result<Vec<_>, _>>()
12620 .log_err()
12621 else {
12622 continue;
12623 };
12624
12625 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12626 buffer
12627 .language_settings_at(Point::new(start_row, 0), cx)
12628 .preferred_line_length as usize
12629 });
12630
12631 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12632 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12633 } else {
12634 line_prefix.clone()
12635 };
12636
12637 let wrapped_text = {
12638 let mut wrapped_text = wrap_with_prefix(
12639 line_prefix,
12640 subsequent_lines_prefix,
12641 lines_without_prefixes.join("\n"),
12642 wrap_column,
12643 tab_size,
12644 options.preserve_existing_whitespace,
12645 );
12646
12647 if let Some((indent, delimiter)) = first_line_delimiter {
12648 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12649 }
12650 if let Some(last_line) = last_line_delimiter {
12651 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12652 }
12653
12654 wrapped_text
12655 };
12656
12657 // TODO: should always use char-based diff while still supporting cursor behavior that
12658 // matches vim.
12659 let mut diff_options = DiffOptions::default();
12660 if options.override_language_settings {
12661 diff_options.max_word_diff_len = 0;
12662 diff_options.max_word_diff_line_count = 0;
12663 } else {
12664 diff_options.max_word_diff_len = usize::MAX;
12665 diff_options.max_word_diff_line_count = usize::MAX;
12666 }
12667
12668 for (old_range, new_text) in
12669 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12670 {
12671 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12672 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12673 edits.push((edit_start..edit_end, new_text));
12674 }
12675
12676 rewrapped_row_ranges.push(start_row..=end_row);
12677 }
12678
12679 self.buffer
12680 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12681 }
12682
12683 pub fn cut_common(
12684 &mut self,
12685 cut_no_selection_line: bool,
12686 window: &mut Window,
12687 cx: &mut Context<Self>,
12688 ) -> ClipboardItem {
12689 let mut text = String::new();
12690 let buffer = self.buffer.read(cx).snapshot(cx);
12691 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12692 let mut clipboard_selections = Vec::with_capacity(selections.len());
12693 {
12694 let max_point = buffer.max_point();
12695 let mut is_first = true;
12696 let mut prev_selection_was_entire_line = false;
12697 for selection in &mut selections {
12698 let is_entire_line =
12699 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12700 if is_entire_line {
12701 selection.start = Point::new(selection.start.row, 0);
12702 if !selection.is_empty() && selection.end.column == 0 {
12703 selection.end = cmp::min(max_point, selection.end);
12704 } else {
12705 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12706 }
12707 selection.goal = SelectionGoal::None;
12708 }
12709 if is_first {
12710 is_first = false;
12711 } else if !prev_selection_was_entire_line {
12712 text += "\n";
12713 }
12714 prev_selection_was_entire_line = is_entire_line;
12715 let mut len = 0;
12716 for chunk in buffer.text_for_range(selection.start..selection.end) {
12717 text.push_str(chunk);
12718 len += chunk.len();
12719 }
12720 clipboard_selections.push(ClipboardSelection {
12721 len,
12722 is_entire_line,
12723 first_line_indent: buffer
12724 .indent_size_for_line(MultiBufferRow(selection.start.row))
12725 .len,
12726 });
12727 }
12728 }
12729
12730 self.transact(window, cx, |this, window, cx| {
12731 this.change_selections(Default::default(), window, cx, |s| {
12732 s.select(selections);
12733 });
12734 this.insert("", window, cx);
12735 });
12736 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12737 }
12738
12739 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12741 let item = self.cut_common(true, window, cx);
12742 cx.write_to_clipboard(item);
12743 }
12744
12745 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12747 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12748 s.move_with(|snapshot, sel| {
12749 if sel.is_empty() {
12750 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12751 }
12752 if sel.is_empty() {
12753 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12754 }
12755 });
12756 });
12757 let item = self.cut_common(false, window, cx);
12758 cx.set_global(KillRing(item))
12759 }
12760
12761 pub fn kill_ring_yank(
12762 &mut self,
12763 _: &KillRingYank,
12764 window: &mut Window,
12765 cx: &mut Context<Self>,
12766 ) {
12767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12768 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12769 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12770 (kill_ring.text().to_string(), kill_ring.metadata_json())
12771 } else {
12772 return;
12773 }
12774 } else {
12775 return;
12776 };
12777 self.do_paste(&text, metadata, false, window, cx);
12778 }
12779
12780 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12781 self.do_copy(true, cx);
12782 }
12783
12784 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12785 self.do_copy(false, cx);
12786 }
12787
12788 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12789 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12790 let buffer = self.buffer.read(cx).read(cx);
12791 let mut text = String::new();
12792
12793 let mut clipboard_selections = Vec::with_capacity(selections.len());
12794 {
12795 let max_point = buffer.max_point();
12796 let mut is_first = true;
12797 let mut prev_selection_was_entire_line = false;
12798 for selection in &selections {
12799 let mut start = selection.start;
12800 let mut end = selection.end;
12801 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12802 let mut add_trailing_newline = false;
12803 if is_entire_line {
12804 start = Point::new(start.row, 0);
12805 let next_line_start = Point::new(end.row + 1, 0);
12806 if next_line_start <= max_point {
12807 end = next_line_start;
12808 } else {
12809 // We're on the last line without a trailing newline.
12810 // Copy to the end of the line and add a newline afterwards.
12811 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12812 add_trailing_newline = true;
12813 }
12814 }
12815
12816 let mut trimmed_selections = Vec::new();
12817 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12818 let row = MultiBufferRow(start.row);
12819 let first_indent = buffer.indent_size_for_line(row);
12820 if first_indent.len == 0 || start.column > first_indent.len {
12821 trimmed_selections.push(start..end);
12822 } else {
12823 trimmed_selections.push(
12824 Point::new(row.0, first_indent.len)
12825 ..Point::new(row.0, buffer.line_len(row)),
12826 );
12827 for row in start.row + 1..=end.row {
12828 let mut line_len = buffer.line_len(MultiBufferRow(row));
12829 if row == end.row {
12830 line_len = end.column;
12831 }
12832 if line_len == 0 {
12833 trimmed_selections
12834 .push(Point::new(row, 0)..Point::new(row, line_len));
12835 continue;
12836 }
12837 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12838 if row_indent_size.len >= first_indent.len {
12839 trimmed_selections.push(
12840 Point::new(row, first_indent.len)..Point::new(row, line_len),
12841 );
12842 } else {
12843 trimmed_selections.clear();
12844 trimmed_selections.push(start..end);
12845 break;
12846 }
12847 }
12848 }
12849 } else {
12850 trimmed_selections.push(start..end);
12851 }
12852
12853 for trimmed_range in trimmed_selections {
12854 if is_first {
12855 is_first = false;
12856 } else if !prev_selection_was_entire_line {
12857 text += "\n";
12858 }
12859 prev_selection_was_entire_line = is_entire_line;
12860 let mut len = 0;
12861 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12862 text.push_str(chunk);
12863 len += chunk.len();
12864 }
12865 if add_trailing_newline {
12866 text.push('\n');
12867 len += 1;
12868 }
12869 clipboard_selections.push(ClipboardSelection {
12870 len,
12871 is_entire_line,
12872 first_line_indent: buffer
12873 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12874 .len,
12875 });
12876 }
12877 }
12878 }
12879
12880 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12881 text,
12882 clipboard_selections,
12883 ));
12884 }
12885
12886 pub fn do_paste(
12887 &mut self,
12888 text: &String,
12889 clipboard_selections: Option<Vec<ClipboardSelection>>,
12890 handle_entire_lines: bool,
12891 window: &mut Window,
12892 cx: &mut Context<Self>,
12893 ) {
12894 if self.read_only(cx) {
12895 return;
12896 }
12897
12898 let clipboard_text = Cow::Borrowed(text.as_str());
12899
12900 self.transact(window, cx, |this, window, cx| {
12901 let had_active_edit_prediction = this.has_active_edit_prediction();
12902 let display_map = this.display_snapshot(cx);
12903 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12904 let cursor_offset = this
12905 .selections
12906 .last::<MultiBufferOffset>(&display_map)
12907 .head();
12908
12909 if let Some(mut clipboard_selections) = clipboard_selections {
12910 let all_selections_were_entire_line =
12911 clipboard_selections.iter().all(|s| s.is_entire_line);
12912 let first_selection_indent_column =
12913 clipboard_selections.first().map(|s| s.first_line_indent);
12914 if clipboard_selections.len() != old_selections.len() {
12915 clipboard_selections.drain(..);
12916 }
12917 let mut auto_indent_on_paste = true;
12918
12919 this.buffer.update(cx, |buffer, cx| {
12920 let snapshot = buffer.read(cx);
12921 auto_indent_on_paste = snapshot
12922 .language_settings_at(cursor_offset, cx)
12923 .auto_indent_on_paste;
12924
12925 let mut start_offset = 0;
12926 let mut edits = Vec::new();
12927 let mut original_indent_columns = Vec::new();
12928 for (ix, selection) in old_selections.iter().enumerate() {
12929 let to_insert;
12930 let entire_line;
12931 let original_indent_column;
12932 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12933 let end_offset = start_offset + clipboard_selection.len;
12934 to_insert = &clipboard_text[start_offset..end_offset];
12935 entire_line = clipboard_selection.is_entire_line;
12936 start_offset = if entire_line {
12937 end_offset
12938 } else {
12939 end_offset + 1
12940 };
12941 original_indent_column = Some(clipboard_selection.first_line_indent);
12942 } else {
12943 to_insert = &*clipboard_text;
12944 entire_line = all_selections_were_entire_line;
12945 original_indent_column = first_selection_indent_column
12946 }
12947
12948 let (range, to_insert) =
12949 if selection.is_empty() && handle_entire_lines && entire_line {
12950 // If the corresponding selection was empty when this slice of the
12951 // clipboard text was written, then the entire line containing the
12952 // selection was copied. If this selection is also currently empty,
12953 // then paste the line before the current line of the buffer.
12954 let column = selection.start.to_point(&snapshot).column as usize;
12955 let line_start = selection.start - column;
12956 (line_start..line_start, Cow::Borrowed(to_insert))
12957 } else {
12958 let language = snapshot.language_at(selection.head());
12959 let range = selection.range();
12960 if let Some(language) = language
12961 && language.name() == "Markdown".into()
12962 {
12963 edit_for_markdown_paste(
12964 &snapshot,
12965 range,
12966 to_insert,
12967 url::Url::parse(to_insert).ok(),
12968 )
12969 } else {
12970 (range, Cow::Borrowed(to_insert))
12971 }
12972 };
12973
12974 edits.push((range, to_insert));
12975 original_indent_columns.push(original_indent_column);
12976 }
12977 drop(snapshot);
12978
12979 buffer.edit(
12980 edits,
12981 if auto_indent_on_paste {
12982 Some(AutoindentMode::Block {
12983 original_indent_columns,
12984 })
12985 } else {
12986 None
12987 },
12988 cx,
12989 );
12990 });
12991
12992 let selections = this
12993 .selections
12994 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12995 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12996 } else {
12997 let url = url::Url::parse(&clipboard_text).ok();
12998
12999 let auto_indent_mode = if !clipboard_text.is_empty() {
13000 Some(AutoindentMode::Block {
13001 original_indent_columns: Vec::new(),
13002 })
13003 } else {
13004 None
13005 };
13006
13007 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13008 let snapshot = buffer.snapshot(cx);
13009
13010 let anchors = old_selections
13011 .iter()
13012 .map(|s| {
13013 let anchor = snapshot.anchor_after(s.head());
13014 s.map(|_| anchor)
13015 })
13016 .collect::<Vec<_>>();
13017
13018 let mut edits = Vec::new();
13019
13020 for selection in old_selections.iter() {
13021 let language = snapshot.language_at(selection.head());
13022 let range = selection.range();
13023
13024 let (edit_range, edit_text) = if let Some(language) = language
13025 && language.name() == "Markdown".into()
13026 {
13027 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13028 } else {
13029 (range, clipboard_text.clone())
13030 };
13031
13032 edits.push((edit_range, edit_text));
13033 }
13034
13035 drop(snapshot);
13036 buffer.edit(edits, auto_indent_mode, cx);
13037
13038 anchors
13039 });
13040
13041 this.change_selections(Default::default(), window, cx, |s| {
13042 s.select_anchors(selection_anchors);
13043 });
13044 }
13045
13046 // 🤔 | .. | show_in_menu |
13047 // | .. | true true
13048 // | had_edit_prediction | false true
13049
13050 let trigger_in_words =
13051 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13052
13053 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13054 });
13055 }
13056
13057 pub fn diff_clipboard_with_selection(
13058 &mut self,
13059 _: &DiffClipboardWithSelection,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 let selections = self
13064 .selections
13065 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13066
13067 if selections.is_empty() {
13068 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13069 return;
13070 };
13071
13072 let clipboard_text = match cx.read_from_clipboard() {
13073 Some(item) => match item.entries().first() {
13074 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13075 _ => None,
13076 },
13077 None => None,
13078 };
13079
13080 let Some(clipboard_text) = clipboard_text else {
13081 log::warn!("Clipboard doesn't contain text.");
13082 return;
13083 };
13084
13085 window.dispatch_action(
13086 Box::new(DiffClipboardWithSelectionData {
13087 clipboard_text,
13088 editor: cx.entity(),
13089 }),
13090 cx,
13091 );
13092 }
13093
13094 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13095 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13096 if let Some(item) = cx.read_from_clipboard() {
13097 let entries = item.entries();
13098
13099 match entries.first() {
13100 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13101 // of all the pasted entries.
13102 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13103 .do_paste(
13104 clipboard_string.text(),
13105 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13106 true,
13107 window,
13108 cx,
13109 ),
13110 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13111 }
13112 }
13113 }
13114
13115 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13116 if self.read_only(cx) {
13117 return;
13118 }
13119
13120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13121
13122 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13123 if let Some((selections, _)) =
13124 self.selection_history.transaction(transaction_id).cloned()
13125 {
13126 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13127 s.select_anchors(selections.to_vec());
13128 });
13129 } else {
13130 log::error!(
13131 "No entry in selection_history found for undo. \
13132 This may correspond to a bug where undo does not update the selection. \
13133 If this is occurring, please add details to \
13134 https://github.com/zed-industries/zed/issues/22692"
13135 );
13136 }
13137 self.request_autoscroll(Autoscroll::fit(), cx);
13138 self.unmark_text(window, cx);
13139 self.refresh_edit_prediction(true, false, window, cx);
13140 cx.emit(EditorEvent::Edited { transaction_id });
13141 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13142 }
13143 }
13144
13145 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13146 if self.read_only(cx) {
13147 return;
13148 }
13149
13150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13151
13152 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13153 if let Some((_, Some(selections))) =
13154 self.selection_history.transaction(transaction_id).cloned()
13155 {
13156 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13157 s.select_anchors(selections.to_vec());
13158 });
13159 } else {
13160 log::error!(
13161 "No entry in selection_history found for redo. \
13162 This may correspond to a bug where undo does not update the selection. \
13163 If this is occurring, please add details to \
13164 https://github.com/zed-industries/zed/issues/22692"
13165 );
13166 }
13167 self.request_autoscroll(Autoscroll::fit(), cx);
13168 self.unmark_text(window, cx);
13169 self.refresh_edit_prediction(true, false, window, cx);
13170 cx.emit(EditorEvent::Edited { transaction_id });
13171 }
13172 }
13173
13174 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13175 self.buffer
13176 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13177 }
13178
13179 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13180 self.buffer
13181 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13182 }
13183
13184 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13186 self.change_selections(Default::default(), window, cx, |s| {
13187 s.move_with(|map, selection| {
13188 let cursor = if selection.is_empty() {
13189 movement::left(map, selection.start)
13190 } else {
13191 selection.start
13192 };
13193 selection.collapse_to(cursor, SelectionGoal::None);
13194 });
13195 })
13196 }
13197
13198 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13200 self.change_selections(Default::default(), window, cx, |s| {
13201 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13202 })
13203 }
13204
13205 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13207 self.change_selections(Default::default(), window, cx, |s| {
13208 s.move_with(|map, selection| {
13209 let cursor = if selection.is_empty() {
13210 movement::right(map, selection.end)
13211 } else {
13212 selection.end
13213 };
13214 selection.collapse_to(cursor, SelectionGoal::None)
13215 });
13216 })
13217 }
13218
13219 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13221 self.change_selections(Default::default(), window, cx, |s| {
13222 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13223 });
13224 }
13225
13226 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13227 if self.take_rename(true, window, cx).is_some() {
13228 return;
13229 }
13230
13231 if self.mode.is_single_line() {
13232 cx.propagate();
13233 return;
13234 }
13235
13236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13237
13238 let text_layout_details = &self.text_layout_details(window);
13239 let selection_count = self.selections.count();
13240 let first_selection = self.selections.first_anchor();
13241
13242 self.change_selections(Default::default(), window, cx, |s| {
13243 s.move_with(|map, selection| {
13244 if !selection.is_empty() {
13245 selection.goal = SelectionGoal::None;
13246 }
13247 let (cursor, goal) = movement::up(
13248 map,
13249 selection.start,
13250 selection.goal,
13251 false,
13252 text_layout_details,
13253 );
13254 selection.collapse_to(cursor, goal);
13255 });
13256 });
13257
13258 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13259 {
13260 cx.propagate();
13261 }
13262 }
13263
13264 pub fn move_up_by_lines(
13265 &mut self,
13266 action: &MoveUpByLines,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 if self.take_rename(true, window, cx).is_some() {
13271 return;
13272 }
13273
13274 if self.mode.is_single_line() {
13275 cx.propagate();
13276 return;
13277 }
13278
13279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13280
13281 let text_layout_details = &self.text_layout_details(window);
13282
13283 self.change_selections(Default::default(), window, cx, |s| {
13284 s.move_with(|map, selection| {
13285 if !selection.is_empty() {
13286 selection.goal = SelectionGoal::None;
13287 }
13288 let (cursor, goal) = movement::up_by_rows(
13289 map,
13290 selection.start,
13291 action.lines,
13292 selection.goal,
13293 false,
13294 text_layout_details,
13295 );
13296 selection.collapse_to(cursor, goal);
13297 });
13298 })
13299 }
13300
13301 pub fn move_down_by_lines(
13302 &mut self,
13303 action: &MoveDownByLines,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 if self.take_rename(true, window, cx).is_some() {
13308 return;
13309 }
13310
13311 if self.mode.is_single_line() {
13312 cx.propagate();
13313 return;
13314 }
13315
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317
13318 let text_layout_details = &self.text_layout_details(window);
13319
13320 self.change_selections(Default::default(), window, cx, |s| {
13321 s.move_with(|map, selection| {
13322 if !selection.is_empty() {
13323 selection.goal = SelectionGoal::None;
13324 }
13325 let (cursor, goal) = movement::down_by_rows(
13326 map,
13327 selection.start,
13328 action.lines,
13329 selection.goal,
13330 false,
13331 text_layout_details,
13332 );
13333 selection.collapse_to(cursor, goal);
13334 });
13335 })
13336 }
13337
13338 pub fn select_down_by_lines(
13339 &mut self,
13340 action: &SelectDownByLines,
13341 window: &mut Window,
13342 cx: &mut Context<Self>,
13343 ) {
13344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13345 let text_layout_details = &self.text_layout_details(window);
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.move_heads_with(|map, head, goal| {
13348 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13349 })
13350 })
13351 }
13352
13353 pub fn select_up_by_lines(
13354 &mut self,
13355 action: &SelectUpByLines,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) {
13359 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13360 let text_layout_details = &self.text_layout_details(window);
13361 self.change_selections(Default::default(), window, cx, |s| {
13362 s.move_heads_with(|map, head, goal| {
13363 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13364 })
13365 })
13366 }
13367
13368 pub fn select_page_up(
13369 &mut self,
13370 _: &SelectPageUp,
13371 window: &mut Window,
13372 cx: &mut Context<Self>,
13373 ) {
13374 let Some(row_count) = self.visible_row_count() else {
13375 return;
13376 };
13377
13378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13379
13380 let text_layout_details = &self.text_layout_details(window);
13381
13382 self.change_selections(Default::default(), window, cx, |s| {
13383 s.move_heads_with(|map, head, goal| {
13384 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13385 })
13386 })
13387 }
13388
13389 pub fn move_page_up(
13390 &mut self,
13391 action: &MovePageUp,
13392 window: &mut Window,
13393 cx: &mut Context<Self>,
13394 ) {
13395 if self.take_rename(true, window, cx).is_some() {
13396 return;
13397 }
13398
13399 if self
13400 .context_menu
13401 .borrow_mut()
13402 .as_mut()
13403 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13404 .unwrap_or(false)
13405 {
13406 return;
13407 }
13408
13409 if matches!(self.mode, EditorMode::SingleLine) {
13410 cx.propagate();
13411 return;
13412 }
13413
13414 let Some(row_count) = self.visible_row_count() else {
13415 return;
13416 };
13417
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419
13420 let effects = if action.center_cursor {
13421 SelectionEffects::scroll(Autoscroll::center())
13422 } else {
13423 SelectionEffects::default()
13424 };
13425
13426 let text_layout_details = &self.text_layout_details(window);
13427
13428 self.change_selections(effects, window, cx, |s| {
13429 s.move_with(|map, selection| {
13430 if !selection.is_empty() {
13431 selection.goal = SelectionGoal::None;
13432 }
13433 let (cursor, goal) = movement::up_by_rows(
13434 map,
13435 selection.end,
13436 row_count,
13437 selection.goal,
13438 false,
13439 text_layout_details,
13440 );
13441 selection.collapse_to(cursor, goal);
13442 });
13443 });
13444 }
13445
13446 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13448 let text_layout_details = &self.text_layout_details(window);
13449 self.change_selections(Default::default(), window, cx, |s| {
13450 s.move_heads_with(|map, head, goal| {
13451 movement::up(map, head, goal, false, text_layout_details)
13452 })
13453 })
13454 }
13455
13456 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13457 self.take_rename(true, window, cx);
13458
13459 if self.mode.is_single_line() {
13460 cx.propagate();
13461 return;
13462 }
13463
13464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13465
13466 let text_layout_details = &self.text_layout_details(window);
13467 let selection_count = self.selections.count();
13468 let first_selection = self.selections.first_anchor();
13469
13470 self.change_selections(Default::default(), window, cx, |s| {
13471 s.move_with(|map, selection| {
13472 if !selection.is_empty() {
13473 selection.goal = SelectionGoal::None;
13474 }
13475 let (cursor, goal) = movement::down(
13476 map,
13477 selection.end,
13478 selection.goal,
13479 false,
13480 text_layout_details,
13481 );
13482 selection.collapse_to(cursor, goal);
13483 });
13484 });
13485
13486 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13487 {
13488 cx.propagate();
13489 }
13490 }
13491
13492 pub fn select_page_down(
13493 &mut self,
13494 _: &SelectPageDown,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) {
13498 let Some(row_count) = self.visible_row_count() else {
13499 return;
13500 };
13501
13502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13503
13504 let text_layout_details = &self.text_layout_details(window);
13505
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_heads_with(|map, head, goal| {
13508 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13509 })
13510 })
13511 }
13512
13513 pub fn move_page_down(
13514 &mut self,
13515 action: &MovePageDown,
13516 window: &mut Window,
13517 cx: &mut Context<Self>,
13518 ) {
13519 if self.take_rename(true, window, cx).is_some() {
13520 return;
13521 }
13522
13523 if self
13524 .context_menu
13525 .borrow_mut()
13526 .as_mut()
13527 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13528 .unwrap_or(false)
13529 {
13530 return;
13531 }
13532
13533 if matches!(self.mode, EditorMode::SingleLine) {
13534 cx.propagate();
13535 return;
13536 }
13537
13538 let Some(row_count) = self.visible_row_count() else {
13539 return;
13540 };
13541
13542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13543
13544 let effects = if action.center_cursor {
13545 SelectionEffects::scroll(Autoscroll::center())
13546 } else {
13547 SelectionEffects::default()
13548 };
13549
13550 let text_layout_details = &self.text_layout_details(window);
13551 self.change_selections(effects, window, cx, |s| {
13552 s.move_with(|map, selection| {
13553 if !selection.is_empty() {
13554 selection.goal = SelectionGoal::None;
13555 }
13556 let (cursor, goal) = movement::down_by_rows(
13557 map,
13558 selection.end,
13559 row_count,
13560 selection.goal,
13561 false,
13562 text_layout_details,
13563 );
13564 selection.collapse_to(cursor, goal);
13565 });
13566 });
13567 }
13568
13569 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13571 let text_layout_details = &self.text_layout_details(window);
13572 self.change_selections(Default::default(), window, cx, |s| {
13573 s.move_heads_with(|map, head, goal| {
13574 movement::down(map, head, goal, false, text_layout_details)
13575 })
13576 });
13577 }
13578
13579 pub fn context_menu_first(
13580 &mut self,
13581 _: &ContextMenuFirst,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13586 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13587 }
13588 }
13589
13590 pub fn context_menu_prev(
13591 &mut self,
13592 _: &ContextMenuPrevious,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13597 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13598 }
13599 }
13600
13601 pub fn context_menu_next(
13602 &mut self,
13603 _: &ContextMenuNext,
13604 window: &mut Window,
13605 cx: &mut Context<Self>,
13606 ) {
13607 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13608 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13609 }
13610 }
13611
13612 pub fn context_menu_last(
13613 &mut self,
13614 _: &ContextMenuLast,
13615 window: &mut Window,
13616 cx: &mut Context<Self>,
13617 ) {
13618 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13619 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13620 }
13621 }
13622
13623 pub fn signature_help_prev(
13624 &mut self,
13625 _: &SignatureHelpPrevious,
13626 _: &mut Window,
13627 cx: &mut Context<Self>,
13628 ) {
13629 if let Some(popover) = self.signature_help_state.popover_mut() {
13630 if popover.current_signature == 0 {
13631 popover.current_signature = popover.signatures.len() - 1;
13632 } else {
13633 popover.current_signature -= 1;
13634 }
13635 cx.notify();
13636 }
13637 }
13638
13639 pub fn signature_help_next(
13640 &mut self,
13641 _: &SignatureHelpNext,
13642 _: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) {
13645 if let Some(popover) = self.signature_help_state.popover_mut() {
13646 if popover.current_signature + 1 == popover.signatures.len() {
13647 popover.current_signature = 0;
13648 } else {
13649 popover.current_signature += 1;
13650 }
13651 cx.notify();
13652 }
13653 }
13654
13655 pub fn move_to_previous_word_start(
13656 &mut self,
13657 _: &MoveToPreviousWordStart,
13658 window: &mut Window,
13659 cx: &mut Context<Self>,
13660 ) {
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 self.change_selections(Default::default(), window, cx, |s| {
13663 s.move_cursors_with(|map, head, _| {
13664 (
13665 movement::previous_word_start(map, head),
13666 SelectionGoal::None,
13667 )
13668 });
13669 })
13670 }
13671
13672 pub fn move_to_previous_subword_start(
13673 &mut self,
13674 _: &MoveToPreviousSubwordStart,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13679 self.change_selections(Default::default(), window, cx, |s| {
13680 s.move_cursors_with(|map, head, _| {
13681 (
13682 movement::previous_subword_start(map, head),
13683 SelectionGoal::None,
13684 )
13685 });
13686 })
13687 }
13688
13689 pub fn select_to_previous_word_start(
13690 &mut self,
13691 _: &SelectToPreviousWordStart,
13692 window: &mut Window,
13693 cx: &mut Context<Self>,
13694 ) {
13695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13696 self.change_selections(Default::default(), window, cx, |s| {
13697 s.move_heads_with(|map, head, _| {
13698 (
13699 movement::previous_word_start(map, head),
13700 SelectionGoal::None,
13701 )
13702 });
13703 })
13704 }
13705
13706 pub fn select_to_previous_subword_start(
13707 &mut self,
13708 _: &SelectToPreviousSubwordStart,
13709 window: &mut Window,
13710 cx: &mut Context<Self>,
13711 ) {
13712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13713 self.change_selections(Default::default(), window, cx, |s| {
13714 s.move_heads_with(|map, head, _| {
13715 (
13716 movement::previous_subword_start(map, head),
13717 SelectionGoal::None,
13718 )
13719 });
13720 })
13721 }
13722
13723 pub fn delete_to_previous_word_start(
13724 &mut self,
13725 action: &DeleteToPreviousWordStart,
13726 window: &mut Window,
13727 cx: &mut Context<Self>,
13728 ) {
13729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13730 self.transact(window, cx, |this, window, cx| {
13731 this.select_autoclose_pair(window, cx);
13732 this.change_selections(Default::default(), window, cx, |s| {
13733 s.move_with(|map, selection| {
13734 if selection.is_empty() {
13735 let mut cursor = if action.ignore_newlines {
13736 movement::previous_word_start(map, selection.head())
13737 } else {
13738 movement::previous_word_start_or_newline(map, selection.head())
13739 };
13740 cursor = movement::adjust_greedy_deletion(
13741 map,
13742 selection.head(),
13743 cursor,
13744 action.ignore_brackets,
13745 );
13746 selection.set_head(cursor, SelectionGoal::None);
13747 }
13748 });
13749 });
13750 this.insert("", window, cx);
13751 });
13752 }
13753
13754 pub fn delete_to_previous_subword_start(
13755 &mut self,
13756 _: &DeleteToPreviousSubwordStart,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13761 self.transact(window, cx, |this, window, cx| {
13762 this.select_autoclose_pair(window, cx);
13763 this.change_selections(Default::default(), window, cx, |s| {
13764 s.move_with(|map, selection| {
13765 if selection.is_empty() {
13766 let mut cursor = movement::previous_subword_start(map, selection.head());
13767 cursor =
13768 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13769 selection.set_head(cursor, SelectionGoal::None);
13770 }
13771 });
13772 });
13773 this.insert("", window, cx);
13774 });
13775 }
13776
13777 pub fn move_to_next_word_end(
13778 &mut self,
13779 _: &MoveToNextWordEnd,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) {
13783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13784 self.change_selections(Default::default(), window, cx, |s| {
13785 s.move_cursors_with(|map, head, _| {
13786 (movement::next_word_end(map, head), SelectionGoal::None)
13787 });
13788 })
13789 }
13790
13791 pub fn move_to_next_subword_end(
13792 &mut self,
13793 _: &MoveToNextSubwordEnd,
13794 window: &mut Window,
13795 cx: &mut Context<Self>,
13796 ) {
13797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13798 self.change_selections(Default::default(), window, cx, |s| {
13799 s.move_cursors_with(|map, head, _| {
13800 (movement::next_subword_end(map, head), SelectionGoal::None)
13801 });
13802 })
13803 }
13804
13805 pub fn select_to_next_word_end(
13806 &mut self,
13807 _: &SelectToNextWordEnd,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_heads_with(|map, head, _| {
13814 (movement::next_word_end(map, head), SelectionGoal::None)
13815 });
13816 })
13817 }
13818
13819 pub fn select_to_next_subword_end(
13820 &mut self,
13821 _: &SelectToNextSubwordEnd,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13826 self.change_selections(Default::default(), window, cx, |s| {
13827 s.move_heads_with(|map, head, _| {
13828 (movement::next_subword_end(map, head), SelectionGoal::None)
13829 });
13830 })
13831 }
13832
13833 pub fn delete_to_next_word_end(
13834 &mut self,
13835 action: &DeleteToNextWordEnd,
13836 window: &mut Window,
13837 cx: &mut Context<Self>,
13838 ) {
13839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13840 self.transact(window, cx, |this, window, cx| {
13841 this.change_selections(Default::default(), window, cx, |s| {
13842 s.move_with(|map, selection| {
13843 if selection.is_empty() {
13844 let mut cursor = if action.ignore_newlines {
13845 movement::next_word_end(map, selection.head())
13846 } else {
13847 movement::next_word_end_or_newline(map, selection.head())
13848 };
13849 cursor = movement::adjust_greedy_deletion(
13850 map,
13851 selection.head(),
13852 cursor,
13853 action.ignore_brackets,
13854 );
13855 selection.set_head(cursor, SelectionGoal::None);
13856 }
13857 });
13858 });
13859 this.insert("", window, cx);
13860 });
13861 }
13862
13863 pub fn delete_to_next_subword_end(
13864 &mut self,
13865 _: &DeleteToNextSubwordEnd,
13866 window: &mut Window,
13867 cx: &mut Context<Self>,
13868 ) {
13869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13870 self.transact(window, cx, |this, window, cx| {
13871 this.change_selections(Default::default(), window, cx, |s| {
13872 s.move_with(|map, selection| {
13873 if selection.is_empty() {
13874 let mut cursor = movement::next_subword_end(map, selection.head());
13875 cursor =
13876 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13877 selection.set_head(cursor, SelectionGoal::None);
13878 }
13879 });
13880 });
13881 this.insert("", window, cx);
13882 });
13883 }
13884
13885 pub fn move_to_beginning_of_line(
13886 &mut self,
13887 action: &MoveToBeginningOfLine,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13892 self.change_selections(Default::default(), window, cx, |s| {
13893 s.move_cursors_with(|map, head, _| {
13894 (
13895 movement::indented_line_beginning(
13896 map,
13897 head,
13898 action.stop_at_soft_wraps,
13899 action.stop_at_indent,
13900 ),
13901 SelectionGoal::None,
13902 )
13903 });
13904 })
13905 }
13906
13907 pub fn select_to_beginning_of_line(
13908 &mut self,
13909 action: &SelectToBeginningOfLine,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13914 self.change_selections(Default::default(), window, cx, |s| {
13915 s.move_heads_with(|map, head, _| {
13916 (
13917 movement::indented_line_beginning(
13918 map,
13919 head,
13920 action.stop_at_soft_wraps,
13921 action.stop_at_indent,
13922 ),
13923 SelectionGoal::None,
13924 )
13925 });
13926 });
13927 }
13928
13929 pub fn delete_to_beginning_of_line(
13930 &mut self,
13931 action: &DeleteToBeginningOfLine,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13936 self.transact(window, cx, |this, window, cx| {
13937 this.change_selections(Default::default(), window, cx, |s| {
13938 s.move_with(|_, selection| {
13939 selection.reversed = true;
13940 });
13941 });
13942
13943 this.select_to_beginning_of_line(
13944 &SelectToBeginningOfLine {
13945 stop_at_soft_wraps: false,
13946 stop_at_indent: action.stop_at_indent,
13947 },
13948 window,
13949 cx,
13950 );
13951 this.backspace(&Backspace, window, cx);
13952 });
13953 }
13954
13955 pub fn move_to_end_of_line(
13956 &mut self,
13957 action: &MoveToEndOfLine,
13958 window: &mut Window,
13959 cx: &mut Context<Self>,
13960 ) {
13961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13962 self.change_selections(Default::default(), window, cx, |s| {
13963 s.move_cursors_with(|map, head, _| {
13964 (
13965 movement::line_end(map, head, action.stop_at_soft_wraps),
13966 SelectionGoal::None,
13967 )
13968 });
13969 })
13970 }
13971
13972 pub fn select_to_end_of_line(
13973 &mut self,
13974 action: &SelectToEndOfLine,
13975 window: &mut Window,
13976 cx: &mut Context<Self>,
13977 ) {
13978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13979 self.change_selections(Default::default(), window, cx, |s| {
13980 s.move_heads_with(|map, head, _| {
13981 (
13982 movement::line_end(map, head, action.stop_at_soft_wraps),
13983 SelectionGoal::None,
13984 )
13985 });
13986 })
13987 }
13988
13989 pub fn delete_to_end_of_line(
13990 &mut self,
13991 _: &DeleteToEndOfLine,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13996 self.transact(window, cx, |this, window, cx| {
13997 this.select_to_end_of_line(
13998 &SelectToEndOfLine {
13999 stop_at_soft_wraps: false,
14000 },
14001 window,
14002 cx,
14003 );
14004 this.delete(&Delete, window, cx);
14005 });
14006 }
14007
14008 pub fn cut_to_end_of_line(
14009 &mut self,
14010 action: &CutToEndOfLine,
14011 window: &mut Window,
14012 cx: &mut Context<Self>,
14013 ) {
14014 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14015 self.transact(window, cx, |this, window, cx| {
14016 this.select_to_end_of_line(
14017 &SelectToEndOfLine {
14018 stop_at_soft_wraps: false,
14019 },
14020 window,
14021 cx,
14022 );
14023 if !action.stop_at_newlines {
14024 this.change_selections(Default::default(), window, cx, |s| {
14025 s.move_with(|_, sel| {
14026 if sel.is_empty() {
14027 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14028 }
14029 });
14030 });
14031 }
14032 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14033 let item = this.cut_common(false, window, cx);
14034 cx.write_to_clipboard(item);
14035 });
14036 }
14037
14038 pub fn move_to_start_of_paragraph(
14039 &mut self,
14040 _: &MoveToStartOfParagraph,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 if matches!(self.mode, EditorMode::SingleLine) {
14045 cx.propagate();
14046 return;
14047 }
14048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14049 self.change_selections(Default::default(), window, cx, |s| {
14050 s.move_with(|map, selection| {
14051 selection.collapse_to(
14052 movement::start_of_paragraph(map, selection.head(), 1),
14053 SelectionGoal::None,
14054 )
14055 });
14056 })
14057 }
14058
14059 pub fn move_to_end_of_paragraph(
14060 &mut self,
14061 _: &MoveToEndOfParagraph,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) {
14065 if matches!(self.mode, EditorMode::SingleLine) {
14066 cx.propagate();
14067 return;
14068 }
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.move_with(|map, selection| {
14072 selection.collapse_to(
14073 movement::end_of_paragraph(map, selection.head(), 1),
14074 SelectionGoal::None,
14075 )
14076 });
14077 })
14078 }
14079
14080 pub fn select_to_start_of_paragraph(
14081 &mut self,
14082 _: &SelectToStartOfParagraph,
14083 window: &mut Window,
14084 cx: &mut Context<Self>,
14085 ) {
14086 if matches!(self.mode, EditorMode::SingleLine) {
14087 cx.propagate();
14088 return;
14089 }
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 self.change_selections(Default::default(), window, cx, |s| {
14092 s.move_heads_with(|map, head, _| {
14093 (
14094 movement::start_of_paragraph(map, head, 1),
14095 SelectionGoal::None,
14096 )
14097 });
14098 })
14099 }
14100
14101 pub fn select_to_end_of_paragraph(
14102 &mut self,
14103 _: &SelectToEndOfParagraph,
14104 window: &mut Window,
14105 cx: &mut Context<Self>,
14106 ) {
14107 if matches!(self.mode, EditorMode::SingleLine) {
14108 cx.propagate();
14109 return;
14110 }
14111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14112 self.change_selections(Default::default(), window, cx, |s| {
14113 s.move_heads_with(|map, head, _| {
14114 (
14115 movement::end_of_paragraph(map, head, 1),
14116 SelectionGoal::None,
14117 )
14118 });
14119 })
14120 }
14121
14122 pub fn move_to_start_of_excerpt(
14123 &mut self,
14124 _: &MoveToStartOfExcerpt,
14125 window: &mut Window,
14126 cx: &mut Context<Self>,
14127 ) {
14128 if matches!(self.mode, EditorMode::SingleLine) {
14129 cx.propagate();
14130 return;
14131 }
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14133 self.change_selections(Default::default(), window, cx, |s| {
14134 s.move_with(|map, selection| {
14135 selection.collapse_to(
14136 movement::start_of_excerpt(
14137 map,
14138 selection.head(),
14139 workspace::searchable::Direction::Prev,
14140 ),
14141 SelectionGoal::None,
14142 )
14143 });
14144 })
14145 }
14146
14147 pub fn move_to_start_of_next_excerpt(
14148 &mut self,
14149 _: &MoveToStartOfNextExcerpt,
14150 window: &mut Window,
14151 cx: &mut Context<Self>,
14152 ) {
14153 if matches!(self.mode, EditorMode::SingleLine) {
14154 cx.propagate();
14155 return;
14156 }
14157
14158 self.change_selections(Default::default(), window, cx, |s| {
14159 s.move_with(|map, selection| {
14160 selection.collapse_to(
14161 movement::start_of_excerpt(
14162 map,
14163 selection.head(),
14164 workspace::searchable::Direction::Next,
14165 ),
14166 SelectionGoal::None,
14167 )
14168 });
14169 })
14170 }
14171
14172 pub fn move_to_end_of_excerpt(
14173 &mut self,
14174 _: &MoveToEndOfExcerpt,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) {
14178 if matches!(self.mode, EditorMode::SingleLine) {
14179 cx.propagate();
14180 return;
14181 }
14182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14183 self.change_selections(Default::default(), window, cx, |s| {
14184 s.move_with(|map, selection| {
14185 selection.collapse_to(
14186 movement::end_of_excerpt(
14187 map,
14188 selection.head(),
14189 workspace::searchable::Direction::Next,
14190 ),
14191 SelectionGoal::None,
14192 )
14193 });
14194 })
14195 }
14196
14197 pub fn move_to_end_of_previous_excerpt(
14198 &mut self,
14199 _: &MoveToEndOfPreviousExcerpt,
14200 window: &mut Window,
14201 cx: &mut Context<Self>,
14202 ) {
14203 if matches!(self.mode, EditorMode::SingleLine) {
14204 cx.propagate();
14205 return;
14206 }
14207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14208 self.change_selections(Default::default(), window, cx, |s| {
14209 s.move_with(|map, selection| {
14210 selection.collapse_to(
14211 movement::end_of_excerpt(
14212 map,
14213 selection.head(),
14214 workspace::searchable::Direction::Prev,
14215 ),
14216 SelectionGoal::None,
14217 )
14218 });
14219 })
14220 }
14221
14222 pub fn select_to_start_of_excerpt(
14223 &mut self,
14224 _: &SelectToStartOfExcerpt,
14225 window: &mut Window,
14226 cx: &mut Context<Self>,
14227 ) {
14228 if matches!(self.mode, EditorMode::SingleLine) {
14229 cx.propagate();
14230 return;
14231 }
14232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14233 self.change_selections(Default::default(), window, cx, |s| {
14234 s.move_heads_with(|map, head, _| {
14235 (
14236 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14237 SelectionGoal::None,
14238 )
14239 });
14240 })
14241 }
14242
14243 pub fn select_to_start_of_next_excerpt(
14244 &mut self,
14245 _: &SelectToStartOfNextExcerpt,
14246 window: &mut Window,
14247 cx: &mut Context<Self>,
14248 ) {
14249 if matches!(self.mode, EditorMode::SingleLine) {
14250 cx.propagate();
14251 return;
14252 }
14253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14254 self.change_selections(Default::default(), window, cx, |s| {
14255 s.move_heads_with(|map, head, _| {
14256 (
14257 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14258 SelectionGoal::None,
14259 )
14260 });
14261 })
14262 }
14263
14264 pub fn select_to_end_of_excerpt(
14265 &mut self,
14266 _: &SelectToEndOfExcerpt,
14267 window: &mut Window,
14268 cx: &mut Context<Self>,
14269 ) {
14270 if matches!(self.mode, EditorMode::SingleLine) {
14271 cx.propagate();
14272 return;
14273 }
14274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14275 self.change_selections(Default::default(), window, cx, |s| {
14276 s.move_heads_with(|map, head, _| {
14277 (
14278 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14279 SelectionGoal::None,
14280 )
14281 });
14282 })
14283 }
14284
14285 pub fn select_to_end_of_previous_excerpt(
14286 &mut self,
14287 _: &SelectToEndOfPreviousExcerpt,
14288 window: &mut Window,
14289 cx: &mut Context<Self>,
14290 ) {
14291 if matches!(self.mode, EditorMode::SingleLine) {
14292 cx.propagate();
14293 return;
14294 }
14295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14296 self.change_selections(Default::default(), window, cx, |s| {
14297 s.move_heads_with(|map, head, _| {
14298 (
14299 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14300 SelectionGoal::None,
14301 )
14302 });
14303 })
14304 }
14305
14306 pub fn move_to_beginning(
14307 &mut self,
14308 _: &MoveToBeginning,
14309 window: &mut Window,
14310 cx: &mut Context<Self>,
14311 ) {
14312 if matches!(self.mode, EditorMode::SingleLine) {
14313 cx.propagate();
14314 return;
14315 }
14316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14317 self.change_selections(Default::default(), window, cx, |s| {
14318 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14319 });
14320 }
14321
14322 pub fn select_to_beginning(
14323 &mut self,
14324 _: &SelectToBeginning,
14325 window: &mut Window,
14326 cx: &mut Context<Self>,
14327 ) {
14328 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14329 selection.set_head(Point::zero(), SelectionGoal::None);
14330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14331 self.change_selections(Default::default(), window, cx, |s| {
14332 s.select(vec![selection]);
14333 });
14334 }
14335
14336 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14337 if matches!(self.mode, EditorMode::SingleLine) {
14338 cx.propagate();
14339 return;
14340 }
14341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14342 let cursor = self.buffer.read(cx).read(cx).len();
14343 self.change_selections(Default::default(), window, cx, |s| {
14344 s.select_ranges(vec![cursor..cursor])
14345 });
14346 }
14347
14348 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14349 self.nav_history = nav_history;
14350 }
14351
14352 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14353 self.nav_history.as_ref()
14354 }
14355
14356 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14357 self.push_to_nav_history(
14358 self.selections.newest_anchor().head(),
14359 None,
14360 false,
14361 true,
14362 cx,
14363 );
14364 }
14365
14366 fn push_to_nav_history(
14367 &mut self,
14368 cursor_anchor: Anchor,
14369 new_position: Option<Point>,
14370 is_deactivate: bool,
14371 always: bool,
14372 cx: &mut Context<Self>,
14373 ) {
14374 if let Some(nav_history) = self.nav_history.as_mut() {
14375 let buffer = self.buffer.read(cx).read(cx);
14376 let cursor_position = cursor_anchor.to_point(&buffer);
14377 let scroll_state = self.scroll_manager.anchor();
14378 let scroll_top_row = scroll_state.top_row(&buffer);
14379 drop(buffer);
14380
14381 if let Some(new_position) = new_position {
14382 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14383 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14384 return;
14385 }
14386 }
14387
14388 nav_history.push(
14389 Some(NavigationData {
14390 cursor_anchor,
14391 cursor_position,
14392 scroll_anchor: scroll_state,
14393 scroll_top_row,
14394 }),
14395 cx,
14396 );
14397 cx.emit(EditorEvent::PushedToNavHistory {
14398 anchor: cursor_anchor,
14399 is_deactivate,
14400 })
14401 }
14402 }
14403
14404 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14406 let buffer = self.buffer.read(cx).snapshot(cx);
14407 let mut selection = self
14408 .selections
14409 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14410 selection.set_head(buffer.len(), SelectionGoal::None);
14411 self.change_selections(Default::default(), window, cx, |s| {
14412 s.select(vec![selection]);
14413 });
14414 }
14415
14416 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14418 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14419 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14420 });
14421 }
14422
14423 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14425 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14426 let mut selections = self.selections.all::<Point>(&display_map);
14427 let max_point = display_map.buffer_snapshot().max_point();
14428 for selection in &mut selections {
14429 let rows = selection.spanned_rows(true, &display_map);
14430 selection.start = Point::new(rows.start.0, 0);
14431 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14432 selection.reversed = false;
14433 }
14434 self.change_selections(Default::default(), window, cx, |s| {
14435 s.select(selections);
14436 });
14437 }
14438
14439 pub fn split_selection_into_lines(
14440 &mut self,
14441 action: &SplitSelectionIntoLines,
14442 window: &mut Window,
14443 cx: &mut Context<Self>,
14444 ) {
14445 let selections = self
14446 .selections
14447 .all::<Point>(&self.display_snapshot(cx))
14448 .into_iter()
14449 .map(|selection| selection.start..selection.end)
14450 .collect::<Vec<_>>();
14451 self.unfold_ranges(&selections, true, true, cx);
14452
14453 let mut new_selection_ranges = Vec::new();
14454 {
14455 let buffer = self.buffer.read(cx).read(cx);
14456 for selection in selections {
14457 for row in selection.start.row..selection.end.row {
14458 let line_start = Point::new(row, 0);
14459 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14460
14461 if action.keep_selections {
14462 // Keep the selection range for each line
14463 let selection_start = if row == selection.start.row {
14464 selection.start
14465 } else {
14466 line_start
14467 };
14468 new_selection_ranges.push(selection_start..line_end);
14469 } else {
14470 // Collapse to cursor at end of line
14471 new_selection_ranges.push(line_end..line_end);
14472 }
14473 }
14474
14475 let is_multiline_selection = selection.start.row != selection.end.row;
14476 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14477 // so this action feels more ergonomic when paired with other selection operations
14478 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14479 if !should_skip_last {
14480 if action.keep_selections {
14481 if is_multiline_selection {
14482 let line_start = Point::new(selection.end.row, 0);
14483 new_selection_ranges.push(line_start..selection.end);
14484 } else {
14485 new_selection_ranges.push(selection.start..selection.end);
14486 }
14487 } else {
14488 new_selection_ranges.push(selection.end..selection.end);
14489 }
14490 }
14491 }
14492 }
14493 self.change_selections(Default::default(), window, cx, |s| {
14494 s.select_ranges(new_selection_ranges);
14495 });
14496 }
14497
14498 pub fn add_selection_above(
14499 &mut self,
14500 action: &AddSelectionAbove,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 self.add_selection(true, action.skip_soft_wrap, window, cx);
14505 }
14506
14507 pub fn add_selection_below(
14508 &mut self,
14509 action: &AddSelectionBelow,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 self.add_selection(false, action.skip_soft_wrap, window, cx);
14514 }
14515
14516 fn add_selection(
14517 &mut self,
14518 above: bool,
14519 skip_soft_wrap: bool,
14520 window: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) {
14523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14524
14525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14526 let all_selections = self.selections.all::<Point>(&display_map);
14527 let text_layout_details = self.text_layout_details(window);
14528
14529 let (mut columnar_selections, new_selections_to_columnarize) = {
14530 if let Some(state) = self.add_selections_state.as_ref() {
14531 let columnar_selection_ids: HashSet<_> = state
14532 .groups
14533 .iter()
14534 .flat_map(|group| group.stack.iter())
14535 .copied()
14536 .collect();
14537
14538 all_selections
14539 .into_iter()
14540 .partition(|s| columnar_selection_ids.contains(&s.id))
14541 } else {
14542 (Vec::new(), all_selections)
14543 }
14544 };
14545
14546 let mut state = self
14547 .add_selections_state
14548 .take()
14549 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14550
14551 for selection in new_selections_to_columnarize {
14552 let range = selection.display_range(&display_map).sorted();
14553 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14554 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14555 let positions = start_x.min(end_x)..start_x.max(end_x);
14556 let mut stack = Vec::new();
14557 for row in range.start.row().0..=range.end.row().0 {
14558 if let Some(selection) = self.selections.build_columnar_selection(
14559 &display_map,
14560 DisplayRow(row),
14561 &positions,
14562 selection.reversed,
14563 &text_layout_details,
14564 ) {
14565 stack.push(selection.id);
14566 columnar_selections.push(selection);
14567 }
14568 }
14569 if !stack.is_empty() {
14570 if above {
14571 stack.reverse();
14572 }
14573 state.groups.push(AddSelectionsGroup { above, stack });
14574 }
14575 }
14576
14577 let mut final_selections = Vec::new();
14578 let end_row = if above {
14579 DisplayRow(0)
14580 } else {
14581 display_map.max_point().row()
14582 };
14583
14584 let mut last_added_item_per_group = HashMap::default();
14585 for group in state.groups.iter_mut() {
14586 if let Some(last_id) = group.stack.last() {
14587 last_added_item_per_group.insert(*last_id, group);
14588 }
14589 }
14590
14591 for selection in columnar_selections {
14592 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14593 if above == group.above {
14594 let range = selection.display_range(&display_map).sorted();
14595 debug_assert_eq!(range.start.row(), range.end.row());
14596 let mut row = range.start.row();
14597 let positions =
14598 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14599 Pixels::from(start)..Pixels::from(end)
14600 } else {
14601 let start_x =
14602 display_map.x_for_display_point(range.start, &text_layout_details);
14603 let end_x =
14604 display_map.x_for_display_point(range.end, &text_layout_details);
14605 start_x.min(end_x)..start_x.max(end_x)
14606 };
14607
14608 let mut maybe_new_selection = None;
14609 let direction = if above { -1 } else { 1 };
14610
14611 while row != end_row {
14612 if skip_soft_wrap {
14613 row = display_map
14614 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14615 .row();
14616 } else if above {
14617 row.0 -= 1;
14618 } else {
14619 row.0 += 1;
14620 }
14621
14622 if let Some(new_selection) = self.selections.build_columnar_selection(
14623 &display_map,
14624 row,
14625 &positions,
14626 selection.reversed,
14627 &text_layout_details,
14628 ) {
14629 maybe_new_selection = Some(new_selection);
14630 break;
14631 }
14632 }
14633
14634 if let Some(new_selection) = maybe_new_selection {
14635 group.stack.push(new_selection.id);
14636 if above {
14637 final_selections.push(new_selection);
14638 final_selections.push(selection);
14639 } else {
14640 final_selections.push(selection);
14641 final_selections.push(new_selection);
14642 }
14643 } else {
14644 final_selections.push(selection);
14645 }
14646 } else {
14647 group.stack.pop();
14648 }
14649 } else {
14650 final_selections.push(selection);
14651 }
14652 }
14653
14654 self.change_selections(Default::default(), window, cx, |s| {
14655 s.select(final_selections);
14656 });
14657
14658 let final_selection_ids: HashSet<_> = self
14659 .selections
14660 .all::<Point>(&display_map)
14661 .iter()
14662 .map(|s| s.id)
14663 .collect();
14664 state.groups.retain_mut(|group| {
14665 // selections might get merged above so we remove invalid items from stacks
14666 group.stack.retain(|id| final_selection_ids.contains(id));
14667
14668 // single selection in stack can be treated as initial state
14669 group.stack.len() > 1
14670 });
14671
14672 if !state.groups.is_empty() {
14673 self.add_selections_state = Some(state);
14674 }
14675 }
14676
14677 fn select_match_ranges(
14678 &mut self,
14679 range: Range<MultiBufferOffset>,
14680 reversed: bool,
14681 replace_newest: bool,
14682 auto_scroll: Option<Autoscroll>,
14683 window: &mut Window,
14684 cx: &mut Context<Editor>,
14685 ) {
14686 self.unfold_ranges(
14687 std::slice::from_ref(&range),
14688 false,
14689 auto_scroll.is_some(),
14690 cx,
14691 );
14692 let effects = if let Some(scroll) = auto_scroll {
14693 SelectionEffects::scroll(scroll)
14694 } else {
14695 SelectionEffects::no_scroll()
14696 };
14697 self.change_selections(effects, window, cx, |s| {
14698 if replace_newest {
14699 s.delete(s.newest_anchor().id);
14700 }
14701 if reversed {
14702 s.insert_range(range.end..range.start);
14703 } else {
14704 s.insert_range(range);
14705 }
14706 });
14707 }
14708
14709 pub fn select_next_match_internal(
14710 &mut self,
14711 display_map: &DisplaySnapshot,
14712 replace_newest: bool,
14713 autoscroll: Option<Autoscroll>,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) -> Result<()> {
14717 let buffer = display_map.buffer_snapshot();
14718 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14719 if let Some(mut select_next_state) = self.select_next_state.take() {
14720 let query = &select_next_state.query;
14721 if !select_next_state.done {
14722 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14723 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14724 let mut next_selected_range = None;
14725
14726 let bytes_after_last_selection =
14727 buffer.bytes_in_range(last_selection.end..buffer.len());
14728 let bytes_before_first_selection =
14729 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14730 let query_matches = query
14731 .stream_find_iter(bytes_after_last_selection)
14732 .map(|result| (last_selection.end, result))
14733 .chain(
14734 query
14735 .stream_find_iter(bytes_before_first_selection)
14736 .map(|result| (MultiBufferOffset(0), result)),
14737 );
14738
14739 for (start_offset, query_match) in query_matches {
14740 let query_match = query_match.unwrap(); // can only fail due to I/O
14741 let offset_range =
14742 start_offset + query_match.start()..start_offset + query_match.end();
14743
14744 if !select_next_state.wordwise
14745 || (!buffer.is_inside_word(offset_range.start, None)
14746 && !buffer.is_inside_word(offset_range.end, None))
14747 {
14748 let idx = selections
14749 .partition_point(|selection| selection.end <= offset_range.start);
14750 let overlaps = selections
14751 .get(idx)
14752 .map_or(false, |selection| selection.start < offset_range.end);
14753
14754 if !overlaps {
14755 next_selected_range = Some(offset_range);
14756 break;
14757 }
14758 }
14759 }
14760
14761 if let Some(next_selected_range) = next_selected_range {
14762 self.select_match_ranges(
14763 next_selected_range,
14764 last_selection.reversed,
14765 replace_newest,
14766 autoscroll,
14767 window,
14768 cx,
14769 );
14770 } else {
14771 select_next_state.done = true;
14772 }
14773 }
14774
14775 self.select_next_state = Some(select_next_state);
14776 } else {
14777 let mut only_carets = true;
14778 let mut same_text_selected = true;
14779 let mut selected_text = None;
14780
14781 let mut selections_iter = selections.iter().peekable();
14782 while let Some(selection) = selections_iter.next() {
14783 if selection.start != selection.end {
14784 only_carets = false;
14785 }
14786
14787 if same_text_selected {
14788 if selected_text.is_none() {
14789 selected_text =
14790 Some(buffer.text_for_range(selection.range()).collect::<String>());
14791 }
14792
14793 if let Some(next_selection) = selections_iter.peek() {
14794 if next_selection.len() == selection.len() {
14795 let next_selected_text = buffer
14796 .text_for_range(next_selection.range())
14797 .collect::<String>();
14798 if Some(next_selected_text) != selected_text {
14799 same_text_selected = false;
14800 selected_text = None;
14801 }
14802 } else {
14803 same_text_selected = false;
14804 selected_text = None;
14805 }
14806 }
14807 }
14808 }
14809
14810 if only_carets {
14811 for selection in &mut selections {
14812 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14813 selection.start = word_range.start;
14814 selection.end = word_range.end;
14815 selection.goal = SelectionGoal::None;
14816 selection.reversed = false;
14817 self.select_match_ranges(
14818 selection.start..selection.end,
14819 selection.reversed,
14820 replace_newest,
14821 autoscroll,
14822 window,
14823 cx,
14824 );
14825 }
14826
14827 if selections.len() == 1 {
14828 let selection = selections
14829 .last()
14830 .expect("ensured that there's only one selection");
14831 let query = buffer
14832 .text_for_range(selection.start..selection.end)
14833 .collect::<String>();
14834 let is_empty = query.is_empty();
14835 let select_state = SelectNextState {
14836 query: self.build_query(&[query], cx)?,
14837 wordwise: true,
14838 done: is_empty,
14839 };
14840 self.select_next_state = Some(select_state);
14841 } else {
14842 self.select_next_state = None;
14843 }
14844 } else if let Some(selected_text) = selected_text {
14845 self.select_next_state = Some(SelectNextState {
14846 query: self.build_query(&[selected_text], cx)?,
14847 wordwise: false,
14848 done: false,
14849 });
14850 self.select_next_match_internal(
14851 display_map,
14852 replace_newest,
14853 autoscroll,
14854 window,
14855 cx,
14856 )?;
14857 }
14858 }
14859 Ok(())
14860 }
14861
14862 pub fn select_all_matches(
14863 &mut self,
14864 _action: &SelectAllMatches,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) -> Result<()> {
14868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14869
14870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14871
14872 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14873 let Some(select_next_state) = self.select_next_state.as_mut() else {
14874 return Ok(());
14875 };
14876 if select_next_state.done {
14877 return Ok(());
14878 }
14879
14880 let mut new_selections = Vec::new();
14881
14882 let reversed = self
14883 .selections
14884 .oldest::<MultiBufferOffset>(&display_map)
14885 .reversed;
14886 let buffer = display_map.buffer_snapshot();
14887 let query_matches = select_next_state
14888 .query
14889 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14890
14891 for query_match in query_matches.into_iter() {
14892 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14893 let offset_range = if reversed {
14894 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14895 } else {
14896 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14897 };
14898
14899 if !select_next_state.wordwise
14900 || (!buffer.is_inside_word(offset_range.start, None)
14901 && !buffer.is_inside_word(offset_range.end, None))
14902 {
14903 new_selections.push(offset_range.start..offset_range.end);
14904 }
14905 }
14906
14907 select_next_state.done = true;
14908
14909 if new_selections.is_empty() {
14910 log::error!("bug: new_selections is empty in select_all_matches");
14911 return Ok(());
14912 }
14913
14914 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14915 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14916 selections.select_ranges(new_selections)
14917 });
14918
14919 Ok(())
14920 }
14921
14922 pub fn select_next(
14923 &mut self,
14924 action: &SelectNext,
14925 window: &mut Window,
14926 cx: &mut Context<Self>,
14927 ) -> Result<()> {
14928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14930 self.select_next_match_internal(
14931 &display_map,
14932 action.replace_newest,
14933 Some(Autoscroll::newest()),
14934 window,
14935 cx,
14936 )?;
14937 Ok(())
14938 }
14939
14940 pub fn select_previous(
14941 &mut self,
14942 action: &SelectPrevious,
14943 window: &mut Window,
14944 cx: &mut Context<Self>,
14945 ) -> Result<()> {
14946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14948 let buffer = display_map.buffer_snapshot();
14949 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14950 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14951 let query = &select_prev_state.query;
14952 if !select_prev_state.done {
14953 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14954 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14955 let mut next_selected_range = None;
14956 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14957 let bytes_before_last_selection =
14958 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14959 let bytes_after_first_selection =
14960 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14961 let query_matches = query
14962 .stream_find_iter(bytes_before_last_selection)
14963 .map(|result| (last_selection.start, result))
14964 .chain(
14965 query
14966 .stream_find_iter(bytes_after_first_selection)
14967 .map(|result| (buffer.len(), result)),
14968 );
14969 for (end_offset, query_match) in query_matches {
14970 let query_match = query_match.unwrap(); // can only fail due to I/O
14971 let offset_range =
14972 end_offset - query_match.end()..end_offset - query_match.start();
14973
14974 if !select_prev_state.wordwise
14975 || (!buffer.is_inside_word(offset_range.start, None)
14976 && !buffer.is_inside_word(offset_range.end, None))
14977 {
14978 next_selected_range = Some(offset_range);
14979 break;
14980 }
14981 }
14982
14983 if let Some(next_selected_range) = next_selected_range {
14984 self.select_match_ranges(
14985 next_selected_range,
14986 last_selection.reversed,
14987 action.replace_newest,
14988 Some(Autoscroll::newest()),
14989 window,
14990 cx,
14991 );
14992 } else {
14993 select_prev_state.done = true;
14994 }
14995 }
14996
14997 self.select_prev_state = Some(select_prev_state);
14998 } else {
14999 let mut only_carets = true;
15000 let mut same_text_selected = true;
15001 let mut selected_text = None;
15002
15003 let mut selections_iter = selections.iter().peekable();
15004 while let Some(selection) = selections_iter.next() {
15005 if selection.start != selection.end {
15006 only_carets = false;
15007 }
15008
15009 if same_text_selected {
15010 if selected_text.is_none() {
15011 selected_text =
15012 Some(buffer.text_for_range(selection.range()).collect::<String>());
15013 }
15014
15015 if let Some(next_selection) = selections_iter.peek() {
15016 if next_selection.len() == selection.len() {
15017 let next_selected_text = buffer
15018 .text_for_range(next_selection.range())
15019 .collect::<String>();
15020 if Some(next_selected_text) != selected_text {
15021 same_text_selected = false;
15022 selected_text = None;
15023 }
15024 } else {
15025 same_text_selected = false;
15026 selected_text = None;
15027 }
15028 }
15029 }
15030 }
15031
15032 if only_carets {
15033 for selection in &mut selections {
15034 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15035 selection.start = word_range.start;
15036 selection.end = word_range.end;
15037 selection.goal = SelectionGoal::None;
15038 selection.reversed = false;
15039 self.select_match_ranges(
15040 selection.start..selection.end,
15041 selection.reversed,
15042 action.replace_newest,
15043 Some(Autoscroll::newest()),
15044 window,
15045 cx,
15046 );
15047 }
15048 if selections.len() == 1 {
15049 let selection = selections
15050 .last()
15051 .expect("ensured that there's only one selection");
15052 let query = buffer
15053 .text_for_range(selection.start..selection.end)
15054 .collect::<String>();
15055 let is_empty = query.is_empty();
15056 let select_state = SelectNextState {
15057 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15058 wordwise: true,
15059 done: is_empty,
15060 };
15061 self.select_prev_state = Some(select_state);
15062 } else {
15063 self.select_prev_state = None;
15064 }
15065 } else if let Some(selected_text) = selected_text {
15066 self.select_prev_state = Some(SelectNextState {
15067 query: self
15068 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15069 wordwise: false,
15070 done: false,
15071 });
15072 self.select_previous(action, window, cx)?;
15073 }
15074 }
15075 Ok(())
15076 }
15077
15078 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15079 /// setting the case sensitivity based on the global
15080 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15081 /// editor's settings.
15082 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15083 where
15084 I: IntoIterator<Item = P>,
15085 P: AsRef<[u8]>,
15086 {
15087 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15088 || EditorSettings::get_global(cx).search.case_sensitive,
15089 |value| value,
15090 );
15091
15092 let mut builder = AhoCorasickBuilder::new();
15093 builder.ascii_case_insensitive(!case_sensitive);
15094 builder.build(patterns)
15095 }
15096
15097 pub fn find_next_match(
15098 &mut self,
15099 _: &FindNextMatch,
15100 window: &mut Window,
15101 cx: &mut Context<Self>,
15102 ) -> Result<()> {
15103 let selections = self.selections.disjoint_anchors_arc();
15104 match selections.first() {
15105 Some(first) if selections.len() >= 2 => {
15106 self.change_selections(Default::default(), window, cx, |s| {
15107 s.select_ranges([first.range()]);
15108 });
15109 }
15110 _ => self.select_next(
15111 &SelectNext {
15112 replace_newest: true,
15113 },
15114 window,
15115 cx,
15116 )?,
15117 }
15118 Ok(())
15119 }
15120
15121 pub fn find_previous_match(
15122 &mut self,
15123 _: &FindPreviousMatch,
15124 window: &mut Window,
15125 cx: &mut Context<Self>,
15126 ) -> Result<()> {
15127 let selections = self.selections.disjoint_anchors_arc();
15128 match selections.last() {
15129 Some(last) if selections.len() >= 2 => {
15130 self.change_selections(Default::default(), window, cx, |s| {
15131 s.select_ranges([last.range()]);
15132 });
15133 }
15134 _ => self.select_previous(
15135 &SelectPrevious {
15136 replace_newest: true,
15137 },
15138 window,
15139 cx,
15140 )?,
15141 }
15142 Ok(())
15143 }
15144
15145 pub fn toggle_comments(
15146 &mut self,
15147 action: &ToggleComments,
15148 window: &mut Window,
15149 cx: &mut Context<Self>,
15150 ) {
15151 if self.read_only(cx) {
15152 return;
15153 }
15154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15155 let text_layout_details = &self.text_layout_details(window);
15156 self.transact(window, cx, |this, window, cx| {
15157 let mut selections = this
15158 .selections
15159 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15160 let mut edits = Vec::new();
15161 let mut selection_edit_ranges = Vec::new();
15162 let mut last_toggled_row = None;
15163 let snapshot = this.buffer.read(cx).read(cx);
15164 let empty_str: Arc<str> = Arc::default();
15165 let mut suffixes_inserted = Vec::new();
15166 let ignore_indent = action.ignore_indent;
15167
15168 fn comment_prefix_range(
15169 snapshot: &MultiBufferSnapshot,
15170 row: MultiBufferRow,
15171 comment_prefix: &str,
15172 comment_prefix_whitespace: &str,
15173 ignore_indent: bool,
15174 ) -> Range<Point> {
15175 let indent_size = if ignore_indent {
15176 0
15177 } else {
15178 snapshot.indent_size_for_line(row).len
15179 };
15180
15181 let start = Point::new(row.0, indent_size);
15182
15183 let mut line_bytes = snapshot
15184 .bytes_in_range(start..snapshot.max_point())
15185 .flatten()
15186 .copied();
15187
15188 // If this line currently begins with the line comment prefix, then record
15189 // the range containing the prefix.
15190 if line_bytes
15191 .by_ref()
15192 .take(comment_prefix.len())
15193 .eq(comment_prefix.bytes())
15194 {
15195 // Include any whitespace that matches the comment prefix.
15196 let matching_whitespace_len = line_bytes
15197 .zip(comment_prefix_whitespace.bytes())
15198 .take_while(|(a, b)| a == b)
15199 .count() as u32;
15200 let end = Point::new(
15201 start.row,
15202 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15203 );
15204 start..end
15205 } else {
15206 start..start
15207 }
15208 }
15209
15210 fn comment_suffix_range(
15211 snapshot: &MultiBufferSnapshot,
15212 row: MultiBufferRow,
15213 comment_suffix: &str,
15214 comment_suffix_has_leading_space: bool,
15215 ) -> Range<Point> {
15216 let end = Point::new(row.0, snapshot.line_len(row));
15217 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15218
15219 let mut line_end_bytes = snapshot
15220 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15221 .flatten()
15222 .copied();
15223
15224 let leading_space_len = if suffix_start_column > 0
15225 && line_end_bytes.next() == Some(b' ')
15226 && comment_suffix_has_leading_space
15227 {
15228 1
15229 } else {
15230 0
15231 };
15232
15233 // If this line currently begins with the line comment prefix, then record
15234 // the range containing the prefix.
15235 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15236 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15237 start..end
15238 } else {
15239 end..end
15240 }
15241 }
15242
15243 // TODO: Handle selections that cross excerpts
15244 for selection in &mut selections {
15245 let start_column = snapshot
15246 .indent_size_for_line(MultiBufferRow(selection.start.row))
15247 .len;
15248 let language = if let Some(language) =
15249 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15250 {
15251 language
15252 } else {
15253 continue;
15254 };
15255
15256 selection_edit_ranges.clear();
15257
15258 // If multiple selections contain a given row, avoid processing that
15259 // row more than once.
15260 let mut start_row = MultiBufferRow(selection.start.row);
15261 if last_toggled_row == Some(start_row) {
15262 start_row = start_row.next_row();
15263 }
15264 let end_row =
15265 if selection.end.row > selection.start.row && selection.end.column == 0 {
15266 MultiBufferRow(selection.end.row - 1)
15267 } else {
15268 MultiBufferRow(selection.end.row)
15269 };
15270 last_toggled_row = Some(end_row);
15271
15272 if start_row > end_row {
15273 continue;
15274 }
15275
15276 // If the language has line comments, toggle those.
15277 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15278
15279 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15280 if ignore_indent {
15281 full_comment_prefixes = full_comment_prefixes
15282 .into_iter()
15283 .map(|s| Arc::from(s.trim_end()))
15284 .collect();
15285 }
15286
15287 if !full_comment_prefixes.is_empty() {
15288 let first_prefix = full_comment_prefixes
15289 .first()
15290 .expect("prefixes is non-empty");
15291 let prefix_trimmed_lengths = full_comment_prefixes
15292 .iter()
15293 .map(|p| p.trim_end_matches(' ').len())
15294 .collect::<SmallVec<[usize; 4]>>();
15295
15296 let mut all_selection_lines_are_comments = true;
15297
15298 for row in start_row.0..=end_row.0 {
15299 let row = MultiBufferRow(row);
15300 if start_row < end_row && snapshot.is_line_blank(row) {
15301 continue;
15302 }
15303
15304 let prefix_range = full_comment_prefixes
15305 .iter()
15306 .zip(prefix_trimmed_lengths.iter().copied())
15307 .map(|(prefix, trimmed_prefix_len)| {
15308 comment_prefix_range(
15309 snapshot.deref(),
15310 row,
15311 &prefix[..trimmed_prefix_len],
15312 &prefix[trimmed_prefix_len..],
15313 ignore_indent,
15314 )
15315 })
15316 .max_by_key(|range| range.end.column - range.start.column)
15317 .expect("prefixes is non-empty");
15318
15319 if prefix_range.is_empty() {
15320 all_selection_lines_are_comments = false;
15321 }
15322
15323 selection_edit_ranges.push(prefix_range);
15324 }
15325
15326 if all_selection_lines_are_comments {
15327 edits.extend(
15328 selection_edit_ranges
15329 .iter()
15330 .cloned()
15331 .map(|range| (range, empty_str.clone())),
15332 );
15333 } else {
15334 let min_column = selection_edit_ranges
15335 .iter()
15336 .map(|range| range.start.column)
15337 .min()
15338 .unwrap_or(0);
15339 edits.extend(selection_edit_ranges.iter().map(|range| {
15340 let position = Point::new(range.start.row, min_column);
15341 (position..position, first_prefix.clone())
15342 }));
15343 }
15344 } else if let Some(BlockCommentConfig {
15345 start: full_comment_prefix,
15346 end: comment_suffix,
15347 ..
15348 }) = language.block_comment()
15349 {
15350 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15351 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15352 let prefix_range = comment_prefix_range(
15353 snapshot.deref(),
15354 start_row,
15355 comment_prefix,
15356 comment_prefix_whitespace,
15357 ignore_indent,
15358 );
15359 let suffix_range = comment_suffix_range(
15360 snapshot.deref(),
15361 end_row,
15362 comment_suffix.trim_start_matches(' '),
15363 comment_suffix.starts_with(' '),
15364 );
15365
15366 if prefix_range.is_empty() || suffix_range.is_empty() {
15367 edits.push((
15368 prefix_range.start..prefix_range.start,
15369 full_comment_prefix.clone(),
15370 ));
15371 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15372 suffixes_inserted.push((end_row, comment_suffix.len()));
15373 } else {
15374 edits.push((prefix_range, empty_str.clone()));
15375 edits.push((suffix_range, empty_str.clone()));
15376 }
15377 } else {
15378 continue;
15379 }
15380 }
15381
15382 drop(snapshot);
15383 this.buffer.update(cx, |buffer, cx| {
15384 buffer.edit(edits, None, cx);
15385 });
15386
15387 // Adjust selections so that they end before any comment suffixes that
15388 // were inserted.
15389 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15390 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15391 let snapshot = this.buffer.read(cx).read(cx);
15392 for selection in &mut selections {
15393 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15394 match row.cmp(&MultiBufferRow(selection.end.row)) {
15395 Ordering::Less => {
15396 suffixes_inserted.next();
15397 continue;
15398 }
15399 Ordering::Greater => break,
15400 Ordering::Equal => {
15401 if selection.end.column == snapshot.line_len(row) {
15402 if selection.is_empty() {
15403 selection.start.column -= suffix_len as u32;
15404 }
15405 selection.end.column -= suffix_len as u32;
15406 }
15407 break;
15408 }
15409 }
15410 }
15411 }
15412
15413 drop(snapshot);
15414 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15415
15416 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15417 let selections_on_single_row = selections.windows(2).all(|selections| {
15418 selections[0].start.row == selections[1].start.row
15419 && selections[0].end.row == selections[1].end.row
15420 && selections[0].start.row == selections[0].end.row
15421 });
15422 let selections_selecting = selections
15423 .iter()
15424 .any(|selection| selection.start != selection.end);
15425 let advance_downwards = action.advance_downwards
15426 && selections_on_single_row
15427 && !selections_selecting
15428 && !matches!(this.mode, EditorMode::SingleLine);
15429
15430 if advance_downwards {
15431 let snapshot = this.buffer.read(cx).snapshot(cx);
15432
15433 this.change_selections(Default::default(), window, cx, |s| {
15434 s.move_cursors_with(|display_snapshot, display_point, _| {
15435 let mut point = display_point.to_point(display_snapshot);
15436 point.row += 1;
15437 point = snapshot.clip_point(point, Bias::Left);
15438 let display_point = point.to_display_point(display_snapshot);
15439 let goal = SelectionGoal::HorizontalPosition(
15440 display_snapshot
15441 .x_for_display_point(display_point, text_layout_details)
15442 .into(),
15443 );
15444 (display_point, goal)
15445 })
15446 });
15447 }
15448 });
15449 }
15450
15451 pub fn select_enclosing_symbol(
15452 &mut self,
15453 _: &SelectEnclosingSymbol,
15454 window: &mut Window,
15455 cx: &mut Context<Self>,
15456 ) {
15457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15458
15459 let buffer = self.buffer.read(cx).snapshot(cx);
15460 let old_selections = self
15461 .selections
15462 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15463 .into_boxed_slice();
15464
15465 fn update_selection(
15466 selection: &Selection<MultiBufferOffset>,
15467 buffer_snap: &MultiBufferSnapshot,
15468 ) -> Option<Selection<MultiBufferOffset>> {
15469 let cursor = selection.head();
15470 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15471 for symbol in symbols.iter().rev() {
15472 let start = symbol.range.start.to_offset(buffer_snap);
15473 let end = symbol.range.end.to_offset(buffer_snap);
15474 let new_range = start..end;
15475 if start < selection.start || end > selection.end {
15476 return Some(Selection {
15477 id: selection.id,
15478 start: new_range.start,
15479 end: new_range.end,
15480 goal: SelectionGoal::None,
15481 reversed: selection.reversed,
15482 });
15483 }
15484 }
15485 None
15486 }
15487
15488 let mut selected_larger_symbol = false;
15489 let new_selections = old_selections
15490 .iter()
15491 .map(|selection| match update_selection(selection, &buffer) {
15492 Some(new_selection) => {
15493 if new_selection.range() != selection.range() {
15494 selected_larger_symbol = true;
15495 }
15496 new_selection
15497 }
15498 None => selection.clone(),
15499 })
15500 .collect::<Vec<_>>();
15501
15502 if selected_larger_symbol {
15503 self.change_selections(Default::default(), window, cx, |s| {
15504 s.select(new_selections);
15505 });
15506 }
15507 }
15508
15509 pub fn select_larger_syntax_node(
15510 &mut self,
15511 _: &SelectLargerSyntaxNode,
15512 window: &mut Window,
15513 cx: &mut Context<Self>,
15514 ) {
15515 let Some(visible_row_count) = self.visible_row_count() else {
15516 return;
15517 };
15518 let old_selections: Box<[_]> = self
15519 .selections
15520 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15521 .into();
15522 if old_selections.is_empty() {
15523 return;
15524 }
15525
15526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15527
15528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15529 let buffer = self.buffer.read(cx).snapshot(cx);
15530
15531 let mut selected_larger_node = false;
15532 let mut new_selections = old_selections
15533 .iter()
15534 .map(|selection| {
15535 let old_range = selection.start..selection.end;
15536
15537 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15538 // manually select word at selection
15539 if ["string_content", "inline"].contains(&node.kind()) {
15540 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15541 // ignore if word is already selected
15542 if !word_range.is_empty() && old_range != word_range {
15543 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15544 // only select word if start and end point belongs to same word
15545 if word_range == last_word_range {
15546 selected_larger_node = true;
15547 return Selection {
15548 id: selection.id,
15549 start: word_range.start,
15550 end: word_range.end,
15551 goal: SelectionGoal::None,
15552 reversed: selection.reversed,
15553 };
15554 }
15555 }
15556 }
15557 }
15558
15559 let mut new_range = old_range.clone();
15560 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15561 new_range = range;
15562 if !node.is_named() {
15563 continue;
15564 }
15565 if !display_map.intersects_fold(new_range.start)
15566 && !display_map.intersects_fold(new_range.end)
15567 {
15568 break;
15569 }
15570 }
15571
15572 selected_larger_node |= new_range != old_range;
15573 Selection {
15574 id: selection.id,
15575 start: new_range.start,
15576 end: new_range.end,
15577 goal: SelectionGoal::None,
15578 reversed: selection.reversed,
15579 }
15580 })
15581 .collect::<Vec<_>>();
15582
15583 if !selected_larger_node {
15584 return; // don't put this call in the history
15585 }
15586
15587 // scroll based on transformation done to the last selection created by the user
15588 let (last_old, last_new) = old_selections
15589 .last()
15590 .zip(new_selections.last().cloned())
15591 .expect("old_selections isn't empty");
15592
15593 // revert selection
15594 let is_selection_reversed = {
15595 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15596 new_selections.last_mut().expect("checked above").reversed =
15597 should_newest_selection_be_reversed;
15598 should_newest_selection_be_reversed
15599 };
15600
15601 if selected_larger_node {
15602 self.select_syntax_node_history.disable_clearing = true;
15603 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15604 s.select(new_selections.clone());
15605 });
15606 self.select_syntax_node_history.disable_clearing = false;
15607 }
15608
15609 let start_row = last_new.start.to_display_point(&display_map).row().0;
15610 let end_row = last_new.end.to_display_point(&display_map).row().0;
15611 let selection_height = end_row - start_row + 1;
15612 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15613
15614 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15615 let scroll_behavior = if fits_on_the_screen {
15616 self.request_autoscroll(Autoscroll::fit(), cx);
15617 SelectSyntaxNodeScrollBehavior::FitSelection
15618 } else if is_selection_reversed {
15619 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15620 SelectSyntaxNodeScrollBehavior::CursorTop
15621 } else {
15622 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15623 SelectSyntaxNodeScrollBehavior::CursorBottom
15624 };
15625
15626 self.select_syntax_node_history.push((
15627 old_selections,
15628 scroll_behavior,
15629 is_selection_reversed,
15630 ));
15631 }
15632
15633 pub fn select_smaller_syntax_node(
15634 &mut self,
15635 _: &SelectSmallerSyntaxNode,
15636 window: &mut Window,
15637 cx: &mut Context<Self>,
15638 ) {
15639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15640
15641 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15642 self.select_syntax_node_history.pop()
15643 {
15644 if let Some(selection) = selections.last_mut() {
15645 selection.reversed = is_selection_reversed;
15646 }
15647
15648 self.select_syntax_node_history.disable_clearing = true;
15649 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15650 s.select(selections.to_vec());
15651 });
15652 self.select_syntax_node_history.disable_clearing = false;
15653
15654 match scroll_behavior {
15655 SelectSyntaxNodeScrollBehavior::CursorTop => {
15656 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15657 }
15658 SelectSyntaxNodeScrollBehavior::FitSelection => {
15659 self.request_autoscroll(Autoscroll::fit(), cx);
15660 }
15661 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15662 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15663 }
15664 }
15665 }
15666 }
15667
15668 pub fn unwrap_syntax_node(
15669 &mut self,
15670 _: &UnwrapSyntaxNode,
15671 window: &mut Window,
15672 cx: &mut Context<Self>,
15673 ) {
15674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15675
15676 let buffer = self.buffer.read(cx).snapshot(cx);
15677 let selections = self
15678 .selections
15679 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15680 .into_iter()
15681 // subtracting the offset requires sorting
15682 .sorted_by_key(|i| i.start);
15683
15684 let full_edits = selections
15685 .into_iter()
15686 .filter_map(|selection| {
15687 let child = if selection.is_empty()
15688 && let Some((_, ancestor_range)) =
15689 buffer.syntax_ancestor(selection.start..selection.end)
15690 {
15691 ancestor_range
15692 } else {
15693 selection.range()
15694 };
15695
15696 let mut parent = child.clone();
15697 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15698 parent = ancestor_range;
15699 if parent.start < child.start || parent.end > child.end {
15700 break;
15701 }
15702 }
15703
15704 if parent == child {
15705 return None;
15706 }
15707 let text = buffer.text_for_range(child).collect::<String>();
15708 Some((selection.id, parent, text))
15709 })
15710 .collect::<Vec<_>>();
15711 if full_edits.is_empty() {
15712 return;
15713 }
15714
15715 self.transact(window, cx, |this, window, cx| {
15716 this.buffer.update(cx, |buffer, cx| {
15717 buffer.edit(
15718 full_edits
15719 .iter()
15720 .map(|(_, p, t)| (p.clone(), t.clone()))
15721 .collect::<Vec<_>>(),
15722 None,
15723 cx,
15724 );
15725 });
15726 this.change_selections(Default::default(), window, cx, |s| {
15727 let mut offset = 0;
15728 let mut selections = vec![];
15729 for (id, parent, text) in full_edits {
15730 let start = parent.start - offset;
15731 offset += (parent.end - parent.start) - text.len();
15732 selections.push(Selection {
15733 id,
15734 start,
15735 end: start + text.len(),
15736 reversed: false,
15737 goal: Default::default(),
15738 });
15739 }
15740 s.select(selections);
15741 });
15742 });
15743 }
15744
15745 pub fn select_next_syntax_node(
15746 &mut self,
15747 _: &SelectNextSyntaxNode,
15748 window: &mut Window,
15749 cx: &mut Context<Self>,
15750 ) {
15751 let old_selections: Box<[_]> = self
15752 .selections
15753 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15754 .into();
15755 if old_selections.is_empty() {
15756 return;
15757 }
15758
15759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15760
15761 let buffer = self.buffer.read(cx).snapshot(cx);
15762 let mut selected_sibling = false;
15763
15764 let new_selections = old_selections
15765 .iter()
15766 .map(|selection| {
15767 let old_range = selection.start..selection.end;
15768
15769 let old_range =
15770 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15771 let excerpt = buffer.excerpt_containing(old_range.clone());
15772
15773 if let Some(mut excerpt) = excerpt
15774 && let Some(node) = excerpt
15775 .buffer()
15776 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15777 {
15778 let new_range = excerpt.map_range_from_buffer(
15779 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15780 );
15781 selected_sibling = true;
15782 Selection {
15783 id: selection.id,
15784 start: new_range.start,
15785 end: new_range.end,
15786 goal: SelectionGoal::None,
15787 reversed: selection.reversed,
15788 }
15789 } else {
15790 selection.clone()
15791 }
15792 })
15793 .collect::<Vec<_>>();
15794
15795 if selected_sibling {
15796 self.change_selections(
15797 SelectionEffects::scroll(Autoscroll::fit()),
15798 window,
15799 cx,
15800 |s| {
15801 s.select(new_selections);
15802 },
15803 );
15804 }
15805 }
15806
15807 pub fn select_prev_syntax_node(
15808 &mut self,
15809 _: &SelectPreviousSyntaxNode,
15810 window: &mut Window,
15811 cx: &mut Context<Self>,
15812 ) {
15813 let old_selections: Box<[_]> = self
15814 .selections
15815 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15816 .into();
15817 if old_selections.is_empty() {
15818 return;
15819 }
15820
15821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15822
15823 let buffer = self.buffer.read(cx).snapshot(cx);
15824 let mut selected_sibling = false;
15825
15826 let new_selections = old_selections
15827 .iter()
15828 .map(|selection| {
15829 let old_range = selection.start..selection.end;
15830 let old_range =
15831 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15832 let excerpt = buffer.excerpt_containing(old_range.clone());
15833
15834 if let Some(mut excerpt) = excerpt
15835 && let Some(node) = excerpt
15836 .buffer()
15837 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15838 {
15839 let new_range = excerpt.map_range_from_buffer(
15840 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15841 );
15842 selected_sibling = true;
15843 Selection {
15844 id: selection.id,
15845 start: new_range.start,
15846 end: new_range.end,
15847 goal: SelectionGoal::None,
15848 reversed: selection.reversed,
15849 }
15850 } else {
15851 selection.clone()
15852 }
15853 })
15854 .collect::<Vec<_>>();
15855
15856 if selected_sibling {
15857 self.change_selections(
15858 SelectionEffects::scroll(Autoscroll::fit()),
15859 window,
15860 cx,
15861 |s| {
15862 s.select(new_selections);
15863 },
15864 );
15865 }
15866 }
15867
15868 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15869 if !EditorSettings::get_global(cx).gutter.runnables {
15870 self.clear_tasks();
15871 return Task::ready(());
15872 }
15873 let project = self.project().map(Entity::downgrade);
15874 let task_sources = self.lsp_task_sources(cx);
15875 let multi_buffer = self.buffer.downgrade();
15876 cx.spawn_in(window, async move |editor, cx| {
15877 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15878 let Some(project) = project.and_then(|p| p.upgrade()) else {
15879 return;
15880 };
15881 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15882 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15883 }) else {
15884 return;
15885 };
15886
15887 let hide_runnables = project
15888 .update(cx, |project, _| project.is_via_collab())
15889 .unwrap_or(true);
15890 if hide_runnables {
15891 return;
15892 }
15893 let new_rows =
15894 cx.background_spawn({
15895 let snapshot = display_snapshot.clone();
15896 async move {
15897 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15898 }
15899 })
15900 .await;
15901 let Ok(lsp_tasks) =
15902 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15903 else {
15904 return;
15905 };
15906 let lsp_tasks = lsp_tasks.await;
15907
15908 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15909 lsp_tasks
15910 .into_iter()
15911 .flat_map(|(kind, tasks)| {
15912 tasks.into_iter().filter_map(move |(location, task)| {
15913 Some((kind.clone(), location?, task))
15914 })
15915 })
15916 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15917 let buffer = location.target.buffer;
15918 let buffer_snapshot = buffer.read(cx).snapshot();
15919 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15920 |(excerpt_id, snapshot, _)| {
15921 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15922 display_snapshot
15923 .buffer_snapshot()
15924 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15925 } else {
15926 None
15927 }
15928 },
15929 );
15930 if let Some(offset) = offset {
15931 let task_buffer_range =
15932 location.target.range.to_point(&buffer_snapshot);
15933 let context_buffer_range =
15934 task_buffer_range.to_offset(&buffer_snapshot);
15935 let context_range = BufferOffset(context_buffer_range.start)
15936 ..BufferOffset(context_buffer_range.end);
15937
15938 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15939 .or_insert_with(|| RunnableTasks {
15940 templates: Vec::new(),
15941 offset,
15942 column: task_buffer_range.start.column,
15943 extra_variables: HashMap::default(),
15944 context_range,
15945 })
15946 .templates
15947 .push((kind, task.original_task().clone()));
15948 }
15949
15950 acc
15951 })
15952 }) else {
15953 return;
15954 };
15955
15956 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15957 buffer.language_settings(cx).tasks.prefer_lsp
15958 }) else {
15959 return;
15960 };
15961
15962 let rows = Self::runnable_rows(
15963 project,
15964 display_snapshot,
15965 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15966 new_rows,
15967 cx.clone(),
15968 )
15969 .await;
15970 editor
15971 .update(cx, |editor, _| {
15972 editor.clear_tasks();
15973 for (key, mut value) in rows {
15974 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15975 value.templates.extend(lsp_tasks.templates);
15976 }
15977
15978 editor.insert_tasks(key, value);
15979 }
15980 for (key, value) in lsp_tasks_by_rows {
15981 editor.insert_tasks(key, value);
15982 }
15983 })
15984 .ok();
15985 })
15986 }
15987 fn fetch_runnable_ranges(
15988 snapshot: &DisplaySnapshot,
15989 range: Range<Anchor>,
15990 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15991 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15992 }
15993
15994 fn runnable_rows(
15995 project: Entity<Project>,
15996 snapshot: DisplaySnapshot,
15997 prefer_lsp: bool,
15998 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
15999 cx: AsyncWindowContext,
16000 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16001 cx.spawn(async move |cx| {
16002 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16003 for (run_range, mut runnable) in runnable_ranges {
16004 let Some(tasks) = cx
16005 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16006 .ok()
16007 else {
16008 continue;
16009 };
16010 let mut tasks = tasks.await;
16011
16012 if prefer_lsp {
16013 tasks.retain(|(task_kind, _)| {
16014 !matches!(task_kind, TaskSourceKind::Language { .. })
16015 });
16016 }
16017 if tasks.is_empty() {
16018 continue;
16019 }
16020
16021 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16022 let Some(row) = snapshot
16023 .buffer_snapshot()
16024 .buffer_line_for_row(MultiBufferRow(point.row))
16025 .map(|(_, range)| range.start.row)
16026 else {
16027 continue;
16028 };
16029
16030 let context_range =
16031 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16032 runnable_rows.push((
16033 (runnable.buffer_id, row),
16034 RunnableTasks {
16035 templates: tasks,
16036 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16037 context_range,
16038 column: point.column,
16039 extra_variables: runnable.extra_captures,
16040 },
16041 ));
16042 }
16043 runnable_rows
16044 })
16045 }
16046
16047 fn templates_with_tags(
16048 project: &Entity<Project>,
16049 runnable: &mut Runnable,
16050 cx: &mut App,
16051 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16052 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16053 let (worktree_id, file) = project
16054 .buffer_for_id(runnable.buffer, cx)
16055 .and_then(|buffer| buffer.read(cx).file())
16056 .map(|file| (file.worktree_id(cx), file.clone()))
16057 .unzip();
16058
16059 (
16060 project.task_store().read(cx).task_inventory().cloned(),
16061 worktree_id,
16062 file,
16063 )
16064 });
16065
16066 let tags = mem::take(&mut runnable.tags);
16067 let language = runnable.language.clone();
16068 cx.spawn(async move |cx| {
16069 let mut templates_with_tags = Vec::new();
16070 if let Some(inventory) = inventory {
16071 for RunnableTag(tag) in tags {
16072 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16073 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16074 }) else {
16075 return templates_with_tags;
16076 };
16077 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16078 move |(_, template)| {
16079 template.tags.iter().any(|source_tag| source_tag == &tag)
16080 },
16081 ));
16082 }
16083 }
16084 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16085
16086 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16087 // Strongest source wins; if we have worktree tag binding, prefer that to
16088 // global and language bindings;
16089 // if we have a global binding, prefer that to language binding.
16090 let first_mismatch = templates_with_tags
16091 .iter()
16092 .position(|(tag_source, _)| tag_source != leading_tag_source);
16093 if let Some(index) = first_mismatch {
16094 templates_with_tags.truncate(index);
16095 }
16096 }
16097
16098 templates_with_tags
16099 })
16100 }
16101
16102 pub fn move_to_enclosing_bracket(
16103 &mut self,
16104 _: &MoveToEnclosingBracket,
16105 window: &mut Window,
16106 cx: &mut Context<Self>,
16107 ) {
16108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16109 self.change_selections(Default::default(), window, cx, |s| {
16110 s.move_offsets_with(|snapshot, selection| {
16111 let Some(enclosing_bracket_ranges) =
16112 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16113 else {
16114 return;
16115 };
16116
16117 let mut best_length = usize::MAX;
16118 let mut best_inside = false;
16119 let mut best_in_bracket_range = false;
16120 let mut best_destination = None;
16121 for (open, close) in enclosing_bracket_ranges {
16122 let close = close.to_inclusive();
16123 let length = *close.end() - open.start;
16124 let inside = selection.start >= open.end && selection.end <= *close.start();
16125 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16126 || close.contains(&selection.head());
16127
16128 // If best is next to a bracket and current isn't, skip
16129 if !in_bracket_range && best_in_bracket_range {
16130 continue;
16131 }
16132
16133 // Prefer smaller lengths unless best is inside and current isn't
16134 if length > best_length && (best_inside || !inside) {
16135 continue;
16136 }
16137
16138 best_length = length;
16139 best_inside = inside;
16140 best_in_bracket_range = in_bracket_range;
16141 best_destination = Some(
16142 if close.contains(&selection.start) && close.contains(&selection.end) {
16143 if inside { open.end } else { open.start }
16144 } else if inside {
16145 *close.start()
16146 } else {
16147 *close.end()
16148 },
16149 );
16150 }
16151
16152 if let Some(destination) = best_destination {
16153 selection.collapse_to(destination, SelectionGoal::None);
16154 }
16155 })
16156 });
16157 }
16158
16159 pub fn undo_selection(
16160 &mut self,
16161 _: &UndoSelection,
16162 window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) {
16165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16166 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16167 self.selection_history.mode = SelectionHistoryMode::Undoing;
16168 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16169 this.end_selection(window, cx);
16170 this.change_selections(
16171 SelectionEffects::scroll(Autoscroll::newest()),
16172 window,
16173 cx,
16174 |s| s.select_anchors(entry.selections.to_vec()),
16175 );
16176 });
16177 self.selection_history.mode = SelectionHistoryMode::Normal;
16178
16179 self.select_next_state = entry.select_next_state;
16180 self.select_prev_state = entry.select_prev_state;
16181 self.add_selections_state = entry.add_selections_state;
16182 }
16183 }
16184
16185 pub fn redo_selection(
16186 &mut self,
16187 _: &RedoSelection,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16192 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16193 self.selection_history.mode = SelectionHistoryMode::Redoing;
16194 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16195 this.end_selection(window, cx);
16196 this.change_selections(
16197 SelectionEffects::scroll(Autoscroll::newest()),
16198 window,
16199 cx,
16200 |s| s.select_anchors(entry.selections.to_vec()),
16201 );
16202 });
16203 self.selection_history.mode = SelectionHistoryMode::Normal;
16204
16205 self.select_next_state = entry.select_next_state;
16206 self.select_prev_state = entry.select_prev_state;
16207 self.add_selections_state = entry.add_selections_state;
16208 }
16209 }
16210
16211 pub fn expand_excerpts(
16212 &mut self,
16213 action: &ExpandExcerpts,
16214 _: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) {
16217 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16218 }
16219
16220 pub fn expand_excerpts_down(
16221 &mut self,
16222 action: &ExpandExcerptsDown,
16223 _: &mut Window,
16224 cx: &mut Context<Self>,
16225 ) {
16226 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16227 }
16228
16229 pub fn expand_excerpts_up(
16230 &mut self,
16231 action: &ExpandExcerptsUp,
16232 _: &mut Window,
16233 cx: &mut Context<Self>,
16234 ) {
16235 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16236 }
16237
16238 pub fn expand_excerpts_for_direction(
16239 &mut self,
16240 lines: u32,
16241 direction: ExpandExcerptDirection,
16242
16243 cx: &mut Context<Self>,
16244 ) {
16245 let selections = self.selections.disjoint_anchors_arc();
16246
16247 let lines = if lines == 0 {
16248 EditorSettings::get_global(cx).expand_excerpt_lines
16249 } else {
16250 lines
16251 };
16252
16253 self.buffer.update(cx, |buffer, cx| {
16254 let snapshot = buffer.snapshot(cx);
16255 let mut excerpt_ids = selections
16256 .iter()
16257 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16258 .collect::<Vec<_>>();
16259 excerpt_ids.sort();
16260 excerpt_ids.dedup();
16261 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16262 })
16263 }
16264
16265 pub fn expand_excerpt(
16266 &mut self,
16267 excerpt: ExcerptId,
16268 direction: ExpandExcerptDirection,
16269 window: &mut Window,
16270 cx: &mut Context<Self>,
16271 ) {
16272 let current_scroll_position = self.scroll_position(cx);
16273 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16274 let mut scroll = None;
16275
16276 if direction == ExpandExcerptDirection::Down {
16277 let multi_buffer = self.buffer.read(cx);
16278 let snapshot = multi_buffer.snapshot(cx);
16279 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16280 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16281 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16282 {
16283 let buffer_snapshot = buffer.read(cx).snapshot();
16284 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16285 let last_row = buffer_snapshot.max_point().row;
16286 let lines_below = last_row.saturating_sub(excerpt_end_row);
16287 if lines_below >= lines_to_expand {
16288 scroll = Some(
16289 current_scroll_position
16290 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16291 );
16292 }
16293 }
16294 }
16295 if direction == ExpandExcerptDirection::Up
16296 && self
16297 .buffer
16298 .read(cx)
16299 .snapshot(cx)
16300 .excerpt_before(excerpt)
16301 .is_none()
16302 {
16303 scroll = Some(current_scroll_position);
16304 }
16305
16306 self.buffer.update(cx, |buffer, cx| {
16307 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16308 });
16309
16310 if let Some(new_scroll_position) = scroll {
16311 self.set_scroll_position(new_scroll_position, window, cx);
16312 }
16313 }
16314
16315 pub fn go_to_singleton_buffer_point(
16316 &mut self,
16317 point: Point,
16318 window: &mut Window,
16319 cx: &mut Context<Self>,
16320 ) {
16321 self.go_to_singleton_buffer_range(point..point, window, cx);
16322 }
16323
16324 pub fn go_to_singleton_buffer_range(
16325 &mut self,
16326 range: Range<Point>,
16327 window: &mut Window,
16328 cx: &mut Context<Self>,
16329 ) {
16330 let multibuffer = self.buffer().read(cx);
16331 let Some(buffer) = multibuffer.as_singleton() else {
16332 return;
16333 };
16334 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16335 return;
16336 };
16337 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16338 return;
16339 };
16340 self.change_selections(
16341 SelectionEffects::default().nav_history(true),
16342 window,
16343 cx,
16344 |s| s.select_anchor_ranges([start..end]),
16345 );
16346 }
16347
16348 pub fn go_to_diagnostic(
16349 &mut self,
16350 action: &GoToDiagnostic,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) {
16354 if !self.diagnostics_enabled() {
16355 return;
16356 }
16357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16358 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16359 }
16360
16361 pub fn go_to_prev_diagnostic(
16362 &mut self,
16363 action: &GoToPreviousDiagnostic,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) {
16367 if !self.diagnostics_enabled() {
16368 return;
16369 }
16370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16371 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16372 }
16373
16374 pub fn go_to_diagnostic_impl(
16375 &mut self,
16376 direction: Direction,
16377 severity: GoToDiagnosticSeverityFilter,
16378 window: &mut Window,
16379 cx: &mut Context<Self>,
16380 ) {
16381 let buffer = self.buffer.read(cx).snapshot(cx);
16382 let selection = self
16383 .selections
16384 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16385
16386 let mut active_group_id = None;
16387 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16388 && active_group.active_range.start.to_offset(&buffer) == selection.start
16389 {
16390 active_group_id = Some(active_group.group_id);
16391 }
16392
16393 fn filtered<'a>(
16394 severity: GoToDiagnosticSeverityFilter,
16395 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16396 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16397 diagnostics
16398 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16399 .filter(|entry| entry.range.start != entry.range.end)
16400 .filter(|entry| !entry.diagnostic.is_unnecessary)
16401 }
16402
16403 let before = filtered(
16404 severity,
16405 buffer
16406 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16407 .filter(|entry| entry.range.start <= selection.start),
16408 );
16409 let after = filtered(
16410 severity,
16411 buffer
16412 .diagnostics_in_range(selection.start..buffer.len())
16413 .filter(|entry| entry.range.start >= selection.start),
16414 );
16415
16416 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16417 if direction == Direction::Prev {
16418 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16419 {
16420 for diagnostic in prev_diagnostics.into_iter().rev() {
16421 if diagnostic.range.start != selection.start
16422 || active_group_id
16423 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16424 {
16425 found = Some(diagnostic);
16426 break 'outer;
16427 }
16428 }
16429 }
16430 } else {
16431 for diagnostic in after.chain(before) {
16432 if diagnostic.range.start != selection.start
16433 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16434 {
16435 found = Some(diagnostic);
16436 break;
16437 }
16438 }
16439 }
16440 let Some(next_diagnostic) = found else {
16441 return;
16442 };
16443
16444 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16445 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16446 return;
16447 };
16448 let snapshot = self.snapshot(window, cx);
16449 if snapshot.intersects_fold(next_diagnostic.range.start) {
16450 self.unfold_ranges(
16451 std::slice::from_ref(&next_diagnostic.range),
16452 true,
16453 false,
16454 cx,
16455 );
16456 }
16457 self.change_selections(Default::default(), window, cx, |s| {
16458 s.select_ranges(vec![
16459 next_diagnostic.range.start..next_diagnostic.range.start,
16460 ])
16461 });
16462 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16463 self.refresh_edit_prediction(false, true, window, cx);
16464 }
16465
16466 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16468 let snapshot = self.snapshot(window, cx);
16469 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16470 self.go_to_hunk_before_or_after_position(
16471 &snapshot,
16472 selection.head(),
16473 Direction::Next,
16474 window,
16475 cx,
16476 );
16477 }
16478
16479 pub fn go_to_hunk_before_or_after_position(
16480 &mut self,
16481 snapshot: &EditorSnapshot,
16482 position: Point,
16483 direction: Direction,
16484 window: &mut Window,
16485 cx: &mut Context<Editor>,
16486 ) {
16487 let row = if direction == Direction::Next {
16488 self.hunk_after_position(snapshot, position)
16489 .map(|hunk| hunk.row_range.start)
16490 } else {
16491 self.hunk_before_position(snapshot, position)
16492 };
16493
16494 if let Some(row) = row {
16495 let destination = Point::new(row.0, 0);
16496 let autoscroll = Autoscroll::center();
16497
16498 self.unfold_ranges(&[destination..destination], false, false, cx);
16499 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16500 s.select_ranges([destination..destination]);
16501 });
16502 }
16503 }
16504
16505 fn hunk_after_position(
16506 &mut self,
16507 snapshot: &EditorSnapshot,
16508 position: Point,
16509 ) -> Option<MultiBufferDiffHunk> {
16510 snapshot
16511 .buffer_snapshot()
16512 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16513 .find(|hunk| hunk.row_range.start.0 > position.row)
16514 .or_else(|| {
16515 snapshot
16516 .buffer_snapshot()
16517 .diff_hunks_in_range(Point::zero()..position)
16518 .find(|hunk| hunk.row_range.end.0 < position.row)
16519 })
16520 }
16521
16522 fn go_to_prev_hunk(
16523 &mut self,
16524 _: &GoToPreviousHunk,
16525 window: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) {
16528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16529 let snapshot = self.snapshot(window, cx);
16530 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16531 self.go_to_hunk_before_or_after_position(
16532 &snapshot,
16533 selection.head(),
16534 Direction::Prev,
16535 window,
16536 cx,
16537 );
16538 }
16539
16540 fn hunk_before_position(
16541 &mut self,
16542 snapshot: &EditorSnapshot,
16543 position: Point,
16544 ) -> Option<MultiBufferRow> {
16545 snapshot
16546 .buffer_snapshot()
16547 .diff_hunk_before(position)
16548 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16549 }
16550
16551 fn go_to_next_change(
16552 &mut self,
16553 _: &GoToNextChange,
16554 window: &mut Window,
16555 cx: &mut Context<Self>,
16556 ) {
16557 if let Some(selections) = self
16558 .change_list
16559 .next_change(1, Direction::Next)
16560 .map(|s| s.to_vec())
16561 {
16562 self.change_selections(Default::default(), window, cx, |s| {
16563 let map = s.display_snapshot();
16564 s.select_display_ranges(selections.iter().map(|a| {
16565 let point = a.to_display_point(&map);
16566 point..point
16567 }))
16568 })
16569 }
16570 }
16571
16572 fn go_to_previous_change(
16573 &mut self,
16574 _: &GoToPreviousChange,
16575 window: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) {
16578 if let Some(selections) = self
16579 .change_list
16580 .next_change(1, Direction::Prev)
16581 .map(|s| s.to_vec())
16582 {
16583 self.change_selections(Default::default(), window, cx, |s| {
16584 let map = s.display_snapshot();
16585 s.select_display_ranges(selections.iter().map(|a| {
16586 let point = a.to_display_point(&map);
16587 point..point
16588 }))
16589 })
16590 }
16591 }
16592
16593 pub fn go_to_next_document_highlight(
16594 &mut self,
16595 _: &GoToNextDocumentHighlight,
16596 window: &mut Window,
16597 cx: &mut Context<Self>,
16598 ) {
16599 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16600 }
16601
16602 pub fn go_to_prev_document_highlight(
16603 &mut self,
16604 _: &GoToPreviousDocumentHighlight,
16605 window: &mut Window,
16606 cx: &mut Context<Self>,
16607 ) {
16608 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16609 }
16610
16611 pub fn go_to_document_highlight_before_or_after_position(
16612 &mut self,
16613 direction: Direction,
16614 window: &mut Window,
16615 cx: &mut Context<Editor>,
16616 ) {
16617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16618 let snapshot = self.snapshot(window, cx);
16619 let buffer = &snapshot.buffer_snapshot();
16620 let position = self
16621 .selections
16622 .newest::<Point>(&snapshot.display_snapshot)
16623 .head();
16624 let anchor_position = buffer.anchor_after(position);
16625
16626 // Get all document highlights (both read and write)
16627 let mut all_highlights = Vec::new();
16628
16629 if let Some((_, read_highlights)) = self
16630 .background_highlights
16631 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16632 {
16633 all_highlights.extend(read_highlights.iter());
16634 }
16635
16636 if let Some((_, write_highlights)) = self
16637 .background_highlights
16638 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16639 {
16640 all_highlights.extend(write_highlights.iter());
16641 }
16642
16643 if all_highlights.is_empty() {
16644 return;
16645 }
16646
16647 // Sort highlights by position
16648 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16649
16650 let target_highlight = match direction {
16651 Direction::Next => {
16652 // Find the first highlight after the current position
16653 all_highlights
16654 .iter()
16655 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16656 }
16657 Direction::Prev => {
16658 // Find the last highlight before the current position
16659 all_highlights
16660 .iter()
16661 .rev()
16662 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16663 }
16664 };
16665
16666 if let Some(highlight) = target_highlight {
16667 let destination = highlight.start.to_point(buffer);
16668 let autoscroll = Autoscroll::center();
16669
16670 self.unfold_ranges(&[destination..destination], false, false, cx);
16671 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16672 s.select_ranges([destination..destination]);
16673 });
16674 }
16675 }
16676
16677 fn go_to_line<T: 'static>(
16678 &mut self,
16679 position: Anchor,
16680 highlight_color: Option<Hsla>,
16681 window: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 let snapshot = self.snapshot(window, cx).display_snapshot;
16685 let position = position.to_point(&snapshot.buffer_snapshot());
16686 let start = snapshot
16687 .buffer_snapshot()
16688 .clip_point(Point::new(position.row, 0), Bias::Left);
16689 let end = start + Point::new(1, 0);
16690 let start = snapshot.buffer_snapshot().anchor_before(start);
16691 let end = snapshot.buffer_snapshot().anchor_before(end);
16692
16693 self.highlight_rows::<T>(
16694 start..end,
16695 highlight_color
16696 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16697 Default::default(),
16698 cx,
16699 );
16700
16701 if self.buffer.read(cx).is_singleton() {
16702 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16703 }
16704 }
16705
16706 pub fn go_to_definition(
16707 &mut self,
16708 _: &GoToDefinition,
16709 window: &mut Window,
16710 cx: &mut Context<Self>,
16711 ) -> Task<Result<Navigated>> {
16712 let definition =
16713 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16714 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16715 cx.spawn_in(window, async move |editor, cx| {
16716 if definition.await? == Navigated::Yes {
16717 return Ok(Navigated::Yes);
16718 }
16719 match fallback_strategy {
16720 GoToDefinitionFallback::None => Ok(Navigated::No),
16721 GoToDefinitionFallback::FindAllReferences => {
16722 match editor.update_in(cx, |editor, window, cx| {
16723 editor.find_all_references(&FindAllReferences, window, cx)
16724 })? {
16725 Some(references) => references.await,
16726 None => Ok(Navigated::No),
16727 }
16728 }
16729 }
16730 })
16731 }
16732
16733 pub fn go_to_declaration(
16734 &mut self,
16735 _: &GoToDeclaration,
16736 window: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) -> Task<Result<Navigated>> {
16739 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16740 }
16741
16742 pub fn go_to_declaration_split(
16743 &mut self,
16744 _: &GoToDeclaration,
16745 window: &mut Window,
16746 cx: &mut Context<Self>,
16747 ) -> Task<Result<Navigated>> {
16748 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16749 }
16750
16751 pub fn go_to_implementation(
16752 &mut self,
16753 _: &GoToImplementation,
16754 window: &mut Window,
16755 cx: &mut Context<Self>,
16756 ) -> Task<Result<Navigated>> {
16757 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16758 }
16759
16760 pub fn go_to_implementation_split(
16761 &mut self,
16762 _: &GoToImplementationSplit,
16763 window: &mut Window,
16764 cx: &mut Context<Self>,
16765 ) -> Task<Result<Navigated>> {
16766 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16767 }
16768
16769 pub fn go_to_type_definition(
16770 &mut self,
16771 _: &GoToTypeDefinition,
16772 window: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) -> Task<Result<Navigated>> {
16775 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16776 }
16777
16778 pub fn go_to_definition_split(
16779 &mut self,
16780 _: &GoToDefinitionSplit,
16781 window: &mut Window,
16782 cx: &mut Context<Self>,
16783 ) -> Task<Result<Navigated>> {
16784 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16785 }
16786
16787 pub fn go_to_type_definition_split(
16788 &mut self,
16789 _: &GoToTypeDefinitionSplit,
16790 window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) -> Task<Result<Navigated>> {
16793 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16794 }
16795
16796 fn go_to_definition_of_kind(
16797 &mut self,
16798 kind: GotoDefinitionKind,
16799 split: bool,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) -> Task<Result<Navigated>> {
16803 let Some(provider) = self.semantics_provider.clone() else {
16804 return Task::ready(Ok(Navigated::No));
16805 };
16806 let head = self
16807 .selections
16808 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16809 .head();
16810 let buffer = self.buffer.read(cx);
16811 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16812 return Task::ready(Ok(Navigated::No));
16813 };
16814 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16815 return Task::ready(Ok(Navigated::No));
16816 };
16817
16818 cx.spawn_in(window, async move |editor, cx| {
16819 let Some(definitions) = definitions.await? else {
16820 return Ok(Navigated::No);
16821 };
16822 let navigated = editor
16823 .update_in(cx, |editor, window, cx| {
16824 editor.navigate_to_hover_links(
16825 Some(kind),
16826 definitions
16827 .into_iter()
16828 .filter(|location| {
16829 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16830 })
16831 .map(HoverLink::Text)
16832 .collect::<Vec<_>>(),
16833 split,
16834 window,
16835 cx,
16836 )
16837 })?
16838 .await?;
16839 anyhow::Ok(navigated)
16840 })
16841 }
16842
16843 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16844 let selection = self.selections.newest_anchor();
16845 let head = selection.head();
16846 let tail = selection.tail();
16847
16848 let Some((buffer, start_position)) =
16849 self.buffer.read(cx).text_anchor_for_position(head, cx)
16850 else {
16851 return;
16852 };
16853
16854 let end_position = if head != tail {
16855 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16856 return;
16857 };
16858 Some(pos)
16859 } else {
16860 None
16861 };
16862
16863 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16864 let url = if let Some(end_pos) = end_position {
16865 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16866 } else {
16867 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16868 };
16869
16870 if let Some(url) = url {
16871 cx.update(|window, cx| {
16872 if parse_zed_link(&url, cx).is_some() {
16873 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16874 } else {
16875 cx.open_url(&url);
16876 }
16877 })?;
16878 }
16879
16880 anyhow::Ok(())
16881 });
16882
16883 url_finder.detach();
16884 }
16885
16886 pub fn open_selected_filename(
16887 &mut self,
16888 _: &OpenSelectedFilename,
16889 window: &mut Window,
16890 cx: &mut Context<Self>,
16891 ) {
16892 let Some(workspace) = self.workspace() else {
16893 return;
16894 };
16895
16896 let position = self.selections.newest_anchor().head();
16897
16898 let Some((buffer, buffer_position)) =
16899 self.buffer.read(cx).text_anchor_for_position(position, cx)
16900 else {
16901 return;
16902 };
16903
16904 let project = self.project.clone();
16905
16906 cx.spawn_in(window, async move |_, cx| {
16907 let result = find_file(&buffer, project, buffer_position, cx).await;
16908
16909 if let Some((_, path)) = result {
16910 workspace
16911 .update_in(cx, |workspace, window, cx| {
16912 workspace.open_resolved_path(path, window, cx)
16913 })?
16914 .await?;
16915 }
16916 anyhow::Ok(())
16917 })
16918 .detach();
16919 }
16920
16921 pub(crate) fn navigate_to_hover_links(
16922 &mut self,
16923 kind: Option<GotoDefinitionKind>,
16924 definitions: Vec<HoverLink>,
16925 split: bool,
16926 window: &mut Window,
16927 cx: &mut Context<Editor>,
16928 ) -> Task<Result<Navigated>> {
16929 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16930 let mut first_url_or_file = None;
16931 let definitions: Vec<_> = definitions
16932 .into_iter()
16933 .filter_map(|def| match def {
16934 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16935 HoverLink::InlayHint(lsp_location, server_id) => {
16936 let computation =
16937 self.compute_target_location(lsp_location, server_id, window, cx);
16938 Some(cx.background_spawn(computation))
16939 }
16940 HoverLink::Url(url) => {
16941 first_url_or_file = Some(Either::Left(url));
16942 None
16943 }
16944 HoverLink::File(path) => {
16945 first_url_or_file = Some(Either::Right(path));
16946 None
16947 }
16948 })
16949 .collect();
16950
16951 let workspace = self.workspace();
16952
16953 cx.spawn_in(window, async move |editor, cx| {
16954 let locations: Vec<Location> = future::join_all(definitions)
16955 .await
16956 .into_iter()
16957 .filter_map(|location| location.transpose())
16958 .collect::<Result<_>>()
16959 .context("location tasks")?;
16960 let mut locations = cx.update(|_, cx| {
16961 locations
16962 .into_iter()
16963 .map(|location| {
16964 let buffer = location.buffer.read(cx);
16965 (location.buffer, location.range.to_point(buffer))
16966 })
16967 .into_group_map()
16968 })?;
16969 let mut num_locations = 0;
16970 for ranges in locations.values_mut() {
16971 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16972 ranges.dedup();
16973 num_locations += ranges.len();
16974 }
16975
16976 if num_locations > 1 {
16977 let Some(workspace) = workspace else {
16978 return Ok(Navigated::No);
16979 };
16980
16981 let tab_kind = match kind {
16982 Some(GotoDefinitionKind::Implementation) => "Implementations",
16983 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16984 Some(GotoDefinitionKind::Declaration) => "Declarations",
16985 Some(GotoDefinitionKind::Type) => "Types",
16986 };
16987 let title = editor
16988 .update_in(cx, |_, _, cx| {
16989 let target = locations
16990 .iter()
16991 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16992 .map(|(buffer, location)| {
16993 buffer
16994 .read(cx)
16995 .text_for_range(location.clone())
16996 .collect::<String>()
16997 })
16998 .filter(|text| !text.contains('\n'))
16999 .unique()
17000 .take(3)
17001 .join(", ");
17002 if target.is_empty() {
17003 tab_kind.to_owned()
17004 } else {
17005 format!("{tab_kind} for {target}")
17006 }
17007 })
17008 .context("buffer title")?;
17009
17010 let opened = workspace
17011 .update_in(cx, |workspace, window, cx| {
17012 Self::open_locations_in_multibuffer(
17013 workspace,
17014 locations,
17015 title,
17016 split,
17017 MultibufferSelectionMode::First,
17018 window,
17019 cx,
17020 )
17021 })
17022 .is_ok();
17023
17024 anyhow::Ok(Navigated::from_bool(opened))
17025 } else if num_locations == 0 {
17026 // If there is one url or file, open it directly
17027 match first_url_or_file {
17028 Some(Either::Left(url)) => {
17029 cx.update(|_, cx| cx.open_url(&url))?;
17030 Ok(Navigated::Yes)
17031 }
17032 Some(Either::Right(path)) => {
17033 let Some(workspace) = workspace else {
17034 return Ok(Navigated::No);
17035 };
17036
17037 workspace
17038 .update_in(cx, |workspace, window, cx| {
17039 workspace.open_resolved_path(path, window, cx)
17040 })?
17041 .await?;
17042 Ok(Navigated::Yes)
17043 }
17044 None => Ok(Navigated::No),
17045 }
17046 } else {
17047 let Some(workspace) = workspace else {
17048 return Ok(Navigated::No);
17049 };
17050
17051 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17052 let target_range = target_ranges.first().unwrap().clone();
17053
17054 editor.update_in(cx, |editor, window, cx| {
17055 let range = target_range.to_point(target_buffer.read(cx));
17056 let range = editor.range_for_match(&range);
17057 let range = collapse_multiline_range(range);
17058
17059 if !split
17060 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17061 {
17062 editor.go_to_singleton_buffer_range(range, window, cx);
17063 } else {
17064 let pane = workspace.read(cx).active_pane().clone();
17065 window.defer(cx, move |window, cx| {
17066 let target_editor: Entity<Self> =
17067 workspace.update(cx, |workspace, cx| {
17068 let pane = if split {
17069 workspace.adjacent_pane(window, cx)
17070 } else {
17071 workspace.active_pane().clone()
17072 };
17073
17074 workspace.open_project_item(
17075 pane,
17076 target_buffer.clone(),
17077 true,
17078 true,
17079 window,
17080 cx,
17081 )
17082 });
17083 target_editor.update(cx, |target_editor, cx| {
17084 // When selecting a definition in a different buffer, disable the nav history
17085 // to avoid creating a history entry at the previous cursor location.
17086 pane.update(cx, |pane, _| pane.disable_history());
17087 target_editor.go_to_singleton_buffer_range(range, window, cx);
17088 pane.update(cx, |pane, _| pane.enable_history());
17089 });
17090 });
17091 }
17092 Navigated::Yes
17093 })
17094 }
17095 })
17096 }
17097
17098 fn compute_target_location(
17099 &self,
17100 lsp_location: lsp::Location,
17101 server_id: LanguageServerId,
17102 window: &mut Window,
17103 cx: &mut Context<Self>,
17104 ) -> Task<anyhow::Result<Option<Location>>> {
17105 let Some(project) = self.project.clone() else {
17106 return Task::ready(Ok(None));
17107 };
17108
17109 cx.spawn_in(window, async move |editor, cx| {
17110 let location_task = editor.update(cx, |_, cx| {
17111 project.update(cx, |project, cx| {
17112 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17113 })
17114 })?;
17115 let location = Some({
17116 let target_buffer_handle = location_task.await.context("open local buffer")?;
17117 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17118 let target_start = target_buffer
17119 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17120 let target_end = target_buffer
17121 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17122 target_buffer.anchor_after(target_start)
17123 ..target_buffer.anchor_before(target_end)
17124 })?;
17125 Location {
17126 buffer: target_buffer_handle,
17127 range,
17128 }
17129 });
17130 Ok(location)
17131 })
17132 }
17133
17134 fn go_to_next_reference(
17135 &mut self,
17136 _: &GoToNextReference,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) {
17140 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17141 if let Some(task) = task {
17142 task.detach();
17143 };
17144 }
17145
17146 fn go_to_prev_reference(
17147 &mut self,
17148 _: &GoToPreviousReference,
17149 window: &mut Window,
17150 cx: &mut Context<Self>,
17151 ) {
17152 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17153 if let Some(task) = task {
17154 task.detach();
17155 };
17156 }
17157
17158 pub fn go_to_reference_before_or_after_position(
17159 &mut self,
17160 direction: Direction,
17161 count: usize,
17162 window: &mut Window,
17163 cx: &mut Context<Self>,
17164 ) -> Option<Task<Result<()>>> {
17165 let selection = self.selections.newest_anchor();
17166 let head = selection.head();
17167
17168 let multi_buffer = self.buffer.read(cx);
17169
17170 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17171 let workspace = self.workspace()?;
17172 let project = workspace.read(cx).project().clone();
17173 let references =
17174 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17175 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17176 let Some(locations) = references.await? else {
17177 return Ok(());
17178 };
17179
17180 if locations.is_empty() {
17181 // totally normal - the cursor may be on something which is not
17182 // a symbol (e.g. a keyword)
17183 log::info!("no references found under cursor");
17184 return Ok(());
17185 }
17186
17187 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17188
17189 let (locations, current_location_index) =
17190 multi_buffer.update(cx, |multi_buffer, cx| {
17191 let mut locations = locations
17192 .into_iter()
17193 .filter_map(|loc| {
17194 let start = multi_buffer.buffer_anchor_to_anchor(
17195 &loc.buffer,
17196 loc.range.start,
17197 cx,
17198 )?;
17199 let end = multi_buffer.buffer_anchor_to_anchor(
17200 &loc.buffer,
17201 loc.range.end,
17202 cx,
17203 )?;
17204 Some(start..end)
17205 })
17206 .collect::<Vec<_>>();
17207
17208 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17209 // There is an O(n) implementation, but given this list will be
17210 // small (usually <100 items), the extra O(log(n)) factor isn't
17211 // worth the (surprisingly large amount of) extra complexity.
17212 locations
17213 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17214
17215 let head_offset = head.to_offset(&multi_buffer_snapshot);
17216
17217 let current_location_index = locations.iter().position(|loc| {
17218 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17219 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17220 });
17221
17222 (locations, current_location_index)
17223 })?;
17224
17225 let Some(current_location_index) = current_location_index else {
17226 // This indicates something has gone wrong, because we already
17227 // handle the "no references" case above
17228 log::error!(
17229 "failed to find current reference under cursor. Total references: {}",
17230 locations.len()
17231 );
17232 return Ok(());
17233 };
17234
17235 let destination_location_index = match direction {
17236 Direction::Next => (current_location_index + count) % locations.len(),
17237 Direction::Prev => {
17238 (current_location_index + locations.len() - count % locations.len())
17239 % locations.len()
17240 }
17241 };
17242
17243 // TODO(cameron): is this needed?
17244 // the thinking is to avoid "jumping to the current location" (avoid
17245 // polluting "jumplist" in vim terms)
17246 if current_location_index == destination_location_index {
17247 return Ok(());
17248 }
17249
17250 let Range { start, end } = locations[destination_location_index];
17251
17252 editor.update_in(cx, |editor, window, cx| {
17253 let effects = SelectionEffects::default();
17254
17255 editor.unfold_ranges(&[start..end], false, false, cx);
17256 editor.change_selections(effects, window, cx, |s| {
17257 s.select_ranges([start..start]);
17258 });
17259 })?;
17260
17261 Ok(())
17262 }))
17263 }
17264
17265 pub fn find_all_references(
17266 &mut self,
17267 _: &FindAllReferences,
17268 window: &mut Window,
17269 cx: &mut Context<Self>,
17270 ) -> Option<Task<Result<Navigated>>> {
17271 let selection = self
17272 .selections
17273 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17274 let multi_buffer = self.buffer.read(cx);
17275 let head = selection.head();
17276
17277 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17278 let head_anchor = multi_buffer_snapshot.anchor_at(
17279 head,
17280 if head < selection.tail() {
17281 Bias::Right
17282 } else {
17283 Bias::Left
17284 },
17285 );
17286
17287 match self
17288 .find_all_references_task_sources
17289 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17290 {
17291 Ok(_) => {
17292 log::info!(
17293 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17294 );
17295 return None;
17296 }
17297 Err(i) => {
17298 self.find_all_references_task_sources.insert(i, head_anchor);
17299 }
17300 }
17301
17302 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17303 let workspace = self.workspace()?;
17304 let project = workspace.read(cx).project().clone();
17305 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17306 Some(cx.spawn_in(window, async move |editor, cx| {
17307 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17308 if let Ok(i) = editor
17309 .find_all_references_task_sources
17310 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17311 {
17312 editor.find_all_references_task_sources.remove(i);
17313 }
17314 });
17315
17316 let Some(locations) = references.await? else {
17317 return anyhow::Ok(Navigated::No);
17318 };
17319 let mut locations = cx.update(|_, cx| {
17320 locations
17321 .into_iter()
17322 .map(|location| {
17323 let buffer = location.buffer.read(cx);
17324 (location.buffer, location.range.to_point(buffer))
17325 })
17326 .into_group_map()
17327 })?;
17328 if locations.is_empty() {
17329 return anyhow::Ok(Navigated::No);
17330 }
17331 for ranges in locations.values_mut() {
17332 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17333 ranges.dedup();
17334 }
17335
17336 workspace.update_in(cx, |workspace, window, cx| {
17337 let target = locations
17338 .iter()
17339 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17340 .map(|(buffer, location)| {
17341 buffer
17342 .read(cx)
17343 .text_for_range(location.clone())
17344 .collect::<String>()
17345 })
17346 .filter(|text| !text.contains('\n'))
17347 .unique()
17348 .take(3)
17349 .join(", ");
17350 let title = if target.is_empty() {
17351 "References".to_owned()
17352 } else {
17353 format!("References to {target}")
17354 };
17355 Self::open_locations_in_multibuffer(
17356 workspace,
17357 locations,
17358 title,
17359 false,
17360 MultibufferSelectionMode::First,
17361 window,
17362 cx,
17363 );
17364 Navigated::Yes
17365 })
17366 }))
17367 }
17368
17369 /// Opens a multibuffer with the given project locations in it
17370 pub fn open_locations_in_multibuffer(
17371 workspace: &mut Workspace,
17372 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17373 title: String,
17374 split: bool,
17375 multibuffer_selection_mode: MultibufferSelectionMode,
17376 window: &mut Window,
17377 cx: &mut Context<Workspace>,
17378 ) {
17379 if locations.is_empty() {
17380 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17381 return;
17382 }
17383
17384 let capability = workspace.project().read(cx).capability();
17385 let mut ranges = <Vec<Range<Anchor>>>::new();
17386
17387 // a key to find existing multibuffer editors with the same set of locations
17388 // to prevent us from opening more and more multibuffer tabs for searches and the like
17389 let mut key = (title.clone(), vec![]);
17390 let excerpt_buffer = cx.new(|cx| {
17391 let key = &mut key.1;
17392 let mut multibuffer = MultiBuffer::new(capability);
17393 for (buffer, mut ranges_for_buffer) in locations {
17394 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17395 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17396 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17397 PathKey::for_buffer(&buffer, cx),
17398 buffer.clone(),
17399 ranges_for_buffer,
17400 multibuffer_context_lines(cx),
17401 cx,
17402 );
17403 ranges.extend(new_ranges)
17404 }
17405
17406 multibuffer.with_title(title)
17407 });
17408 let existing = workspace.active_pane().update(cx, |pane, cx| {
17409 pane.items()
17410 .filter_map(|item| item.downcast::<Editor>())
17411 .find(|editor| {
17412 editor
17413 .read(cx)
17414 .lookup_key
17415 .as_ref()
17416 .and_then(|it| {
17417 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17418 })
17419 .is_some_and(|it| *it == key)
17420 })
17421 });
17422 let editor = existing.unwrap_or_else(|| {
17423 cx.new(|cx| {
17424 let mut editor = Editor::for_multibuffer(
17425 excerpt_buffer,
17426 Some(workspace.project().clone()),
17427 window,
17428 cx,
17429 );
17430 editor.lookup_key = Some(Box::new(key));
17431 editor
17432 })
17433 });
17434 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17435 MultibufferSelectionMode::First => {
17436 if let Some(first_range) = ranges.first() {
17437 editor.change_selections(
17438 SelectionEffects::no_scroll(),
17439 window,
17440 cx,
17441 |selections| {
17442 selections.clear_disjoint();
17443 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17444 },
17445 );
17446 }
17447 editor.highlight_background::<Self>(
17448 &ranges,
17449 |theme| theme.colors().editor_highlighted_line_background,
17450 cx,
17451 );
17452 }
17453 MultibufferSelectionMode::All => {
17454 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17455 selections.clear_disjoint();
17456 selections.select_anchor_ranges(ranges);
17457 });
17458 }
17459 });
17460
17461 let item = Box::new(editor);
17462 let item_id = item.item_id();
17463
17464 if split {
17465 let pane = workspace.adjacent_pane(window, cx);
17466 workspace.add_item(pane, item, None, true, true, window, cx);
17467 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17468 let (preview_item_id, preview_item_idx) =
17469 workspace.active_pane().read_with(cx, |pane, _| {
17470 (pane.preview_item_id(), pane.preview_item_idx())
17471 });
17472
17473 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17474
17475 if let Some(preview_item_id) = preview_item_id {
17476 workspace.active_pane().update(cx, |pane, cx| {
17477 pane.remove_item(preview_item_id, false, false, window, cx);
17478 });
17479 }
17480 } else {
17481 workspace.add_item_to_active_pane(item, None, true, window, cx);
17482 }
17483 workspace.active_pane().update(cx, |pane, cx| {
17484 pane.set_preview_item_id(Some(item_id), cx);
17485 });
17486 }
17487
17488 pub fn rename(
17489 &mut self,
17490 _: &Rename,
17491 window: &mut Window,
17492 cx: &mut Context<Self>,
17493 ) -> Option<Task<Result<()>>> {
17494 use language::ToOffset as _;
17495
17496 let provider = self.semantics_provider.clone()?;
17497 let selection = self.selections.newest_anchor().clone();
17498 let (cursor_buffer, cursor_buffer_position) = self
17499 .buffer
17500 .read(cx)
17501 .text_anchor_for_position(selection.head(), cx)?;
17502 let (tail_buffer, cursor_buffer_position_end) = self
17503 .buffer
17504 .read(cx)
17505 .text_anchor_for_position(selection.tail(), cx)?;
17506 if tail_buffer != cursor_buffer {
17507 return None;
17508 }
17509
17510 let snapshot = cursor_buffer.read(cx).snapshot();
17511 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17512 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17513 let prepare_rename = provider
17514 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17515 .unwrap_or_else(|| Task::ready(Ok(None)));
17516 drop(snapshot);
17517
17518 Some(cx.spawn_in(window, async move |this, cx| {
17519 let rename_range = if let Some(range) = prepare_rename.await? {
17520 Some(range)
17521 } else {
17522 this.update(cx, |this, cx| {
17523 let buffer = this.buffer.read(cx).snapshot(cx);
17524 let mut buffer_highlights = this
17525 .document_highlights_for_position(selection.head(), &buffer)
17526 .filter(|highlight| {
17527 highlight.start.excerpt_id == selection.head().excerpt_id
17528 && highlight.end.excerpt_id == selection.head().excerpt_id
17529 });
17530 buffer_highlights
17531 .next()
17532 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17533 })?
17534 };
17535 if let Some(rename_range) = rename_range {
17536 this.update_in(cx, |this, window, cx| {
17537 let snapshot = cursor_buffer.read(cx).snapshot();
17538 let rename_buffer_range = rename_range.to_offset(&snapshot);
17539 let cursor_offset_in_rename_range =
17540 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17541 let cursor_offset_in_rename_range_end =
17542 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17543
17544 this.take_rename(false, window, cx);
17545 let buffer = this.buffer.read(cx).read(cx);
17546 let cursor_offset = selection.head().to_offset(&buffer);
17547 let rename_start =
17548 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17549 let rename_end = rename_start + rename_buffer_range.len();
17550 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17551 let mut old_highlight_id = None;
17552 let old_name: Arc<str> = buffer
17553 .chunks(rename_start..rename_end, true)
17554 .map(|chunk| {
17555 if old_highlight_id.is_none() {
17556 old_highlight_id = chunk.syntax_highlight_id;
17557 }
17558 chunk.text
17559 })
17560 .collect::<String>()
17561 .into();
17562
17563 drop(buffer);
17564
17565 // Position the selection in the rename editor so that it matches the current selection.
17566 this.show_local_selections = false;
17567 let rename_editor = cx.new(|cx| {
17568 let mut editor = Editor::single_line(window, cx);
17569 editor.buffer.update(cx, |buffer, cx| {
17570 buffer.edit(
17571 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17572 None,
17573 cx,
17574 )
17575 });
17576 let cursor_offset_in_rename_range =
17577 MultiBufferOffset(cursor_offset_in_rename_range);
17578 let cursor_offset_in_rename_range_end =
17579 MultiBufferOffset(cursor_offset_in_rename_range_end);
17580 let rename_selection_range = match cursor_offset_in_rename_range
17581 .cmp(&cursor_offset_in_rename_range_end)
17582 {
17583 Ordering::Equal => {
17584 editor.select_all(&SelectAll, window, cx);
17585 return editor;
17586 }
17587 Ordering::Less => {
17588 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17589 }
17590 Ordering::Greater => {
17591 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17592 }
17593 };
17594 if rename_selection_range.end.0 > old_name.len() {
17595 editor.select_all(&SelectAll, window, cx);
17596 } else {
17597 editor.change_selections(Default::default(), window, cx, |s| {
17598 s.select_ranges([rename_selection_range]);
17599 });
17600 }
17601 editor
17602 });
17603 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17604 if e == &EditorEvent::Focused {
17605 cx.emit(EditorEvent::FocusedIn)
17606 }
17607 })
17608 .detach();
17609
17610 let write_highlights =
17611 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17612 let read_highlights =
17613 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17614 let ranges = write_highlights
17615 .iter()
17616 .flat_map(|(_, ranges)| ranges.iter())
17617 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17618 .cloned()
17619 .collect();
17620
17621 this.highlight_text::<Rename>(
17622 ranges,
17623 HighlightStyle {
17624 fade_out: Some(0.6),
17625 ..Default::default()
17626 },
17627 cx,
17628 );
17629 let rename_focus_handle = rename_editor.focus_handle(cx);
17630 window.focus(&rename_focus_handle);
17631 let block_id = this.insert_blocks(
17632 [BlockProperties {
17633 style: BlockStyle::Flex,
17634 placement: BlockPlacement::Below(range.start),
17635 height: Some(1),
17636 render: Arc::new({
17637 let rename_editor = rename_editor.clone();
17638 move |cx: &mut BlockContext| {
17639 let mut text_style = cx.editor_style.text.clone();
17640 if let Some(highlight_style) = old_highlight_id
17641 .and_then(|h| h.style(&cx.editor_style.syntax))
17642 {
17643 text_style = text_style.highlight(highlight_style);
17644 }
17645 div()
17646 .block_mouse_except_scroll()
17647 .pl(cx.anchor_x)
17648 .child(EditorElement::new(
17649 &rename_editor,
17650 EditorStyle {
17651 background: cx.theme().system().transparent,
17652 local_player: cx.editor_style.local_player,
17653 text: text_style,
17654 scrollbar_width: cx.editor_style.scrollbar_width,
17655 syntax: cx.editor_style.syntax.clone(),
17656 status: cx.editor_style.status.clone(),
17657 inlay_hints_style: HighlightStyle {
17658 font_weight: Some(FontWeight::BOLD),
17659 ..make_inlay_hints_style(cx.app)
17660 },
17661 edit_prediction_styles: make_suggestion_styles(
17662 cx.app,
17663 ),
17664 ..EditorStyle::default()
17665 },
17666 ))
17667 .into_any_element()
17668 }
17669 }),
17670 priority: 0,
17671 }],
17672 Some(Autoscroll::fit()),
17673 cx,
17674 )[0];
17675 this.pending_rename = Some(RenameState {
17676 range,
17677 old_name,
17678 editor: rename_editor,
17679 block_id,
17680 });
17681 })?;
17682 }
17683
17684 Ok(())
17685 }))
17686 }
17687
17688 pub fn confirm_rename(
17689 &mut self,
17690 _: &ConfirmRename,
17691 window: &mut Window,
17692 cx: &mut Context<Self>,
17693 ) -> Option<Task<Result<()>>> {
17694 let rename = self.take_rename(false, window, cx)?;
17695 let workspace = self.workspace()?.downgrade();
17696 let (buffer, start) = self
17697 .buffer
17698 .read(cx)
17699 .text_anchor_for_position(rename.range.start, cx)?;
17700 let (end_buffer, _) = self
17701 .buffer
17702 .read(cx)
17703 .text_anchor_for_position(rename.range.end, cx)?;
17704 if buffer != end_buffer {
17705 return None;
17706 }
17707
17708 let old_name = rename.old_name;
17709 let new_name = rename.editor.read(cx).text(cx);
17710
17711 let rename = self.semantics_provider.as_ref()?.perform_rename(
17712 &buffer,
17713 start,
17714 new_name.clone(),
17715 cx,
17716 )?;
17717
17718 Some(cx.spawn_in(window, async move |editor, cx| {
17719 let project_transaction = rename.await?;
17720 Self::open_project_transaction(
17721 &editor,
17722 workspace,
17723 project_transaction,
17724 format!("Rename: {} → {}", old_name, new_name),
17725 cx,
17726 )
17727 .await?;
17728
17729 editor.update(cx, |editor, cx| {
17730 editor.refresh_document_highlights(cx);
17731 })?;
17732 Ok(())
17733 }))
17734 }
17735
17736 fn take_rename(
17737 &mut self,
17738 moving_cursor: bool,
17739 window: &mut Window,
17740 cx: &mut Context<Self>,
17741 ) -> Option<RenameState> {
17742 let rename = self.pending_rename.take()?;
17743 if rename.editor.focus_handle(cx).is_focused(window) {
17744 window.focus(&self.focus_handle);
17745 }
17746
17747 self.remove_blocks(
17748 [rename.block_id].into_iter().collect(),
17749 Some(Autoscroll::fit()),
17750 cx,
17751 );
17752 self.clear_highlights::<Rename>(cx);
17753 self.show_local_selections = true;
17754
17755 if moving_cursor {
17756 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17757 editor
17758 .selections
17759 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17760 .head()
17761 });
17762
17763 // Update the selection to match the position of the selection inside
17764 // the rename editor.
17765 let snapshot = self.buffer.read(cx).read(cx);
17766 let rename_range = rename.range.to_offset(&snapshot);
17767 let cursor_in_editor = snapshot
17768 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17769 .min(rename_range.end);
17770 drop(snapshot);
17771
17772 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17773 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17774 });
17775 } else {
17776 self.refresh_document_highlights(cx);
17777 }
17778
17779 Some(rename)
17780 }
17781
17782 pub fn pending_rename(&self) -> Option<&RenameState> {
17783 self.pending_rename.as_ref()
17784 }
17785
17786 fn format(
17787 &mut self,
17788 _: &Format,
17789 window: &mut Window,
17790 cx: &mut Context<Self>,
17791 ) -> Option<Task<Result<()>>> {
17792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17793
17794 let project = match &self.project {
17795 Some(project) => project.clone(),
17796 None => return None,
17797 };
17798
17799 Some(self.perform_format(
17800 project,
17801 FormatTrigger::Manual,
17802 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17803 window,
17804 cx,
17805 ))
17806 }
17807
17808 fn format_selections(
17809 &mut self,
17810 _: &FormatSelections,
17811 window: &mut Window,
17812 cx: &mut Context<Self>,
17813 ) -> Option<Task<Result<()>>> {
17814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17815
17816 let project = match &self.project {
17817 Some(project) => project.clone(),
17818 None => return None,
17819 };
17820
17821 let ranges = self
17822 .selections
17823 .all_adjusted(&self.display_snapshot(cx))
17824 .into_iter()
17825 .map(|selection| selection.range())
17826 .collect_vec();
17827
17828 Some(self.perform_format(
17829 project,
17830 FormatTrigger::Manual,
17831 FormatTarget::Ranges(ranges),
17832 window,
17833 cx,
17834 ))
17835 }
17836
17837 fn perform_format(
17838 &mut self,
17839 project: Entity<Project>,
17840 trigger: FormatTrigger,
17841 target: FormatTarget,
17842 window: &mut Window,
17843 cx: &mut Context<Self>,
17844 ) -> Task<Result<()>> {
17845 let buffer = self.buffer.clone();
17846 let (buffers, target) = match target {
17847 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17848 FormatTarget::Ranges(selection_ranges) => {
17849 let multi_buffer = buffer.read(cx);
17850 let snapshot = multi_buffer.read(cx);
17851 let mut buffers = HashSet::default();
17852 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17853 BTreeMap::new();
17854 for selection_range in selection_ranges {
17855 for (buffer, buffer_range, _) in
17856 snapshot.range_to_buffer_ranges(selection_range)
17857 {
17858 let buffer_id = buffer.remote_id();
17859 let start = buffer.anchor_before(buffer_range.start);
17860 let end = buffer.anchor_after(buffer_range.end);
17861 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17862 buffer_id_to_ranges
17863 .entry(buffer_id)
17864 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17865 .or_insert_with(|| vec![start..end]);
17866 }
17867 }
17868 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17869 }
17870 };
17871
17872 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17873 let selections_prev = transaction_id_prev
17874 .and_then(|transaction_id_prev| {
17875 // default to selections as they were after the last edit, if we have them,
17876 // instead of how they are now.
17877 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17878 // will take you back to where you made the last edit, instead of staying where you scrolled
17879 self.selection_history
17880 .transaction(transaction_id_prev)
17881 .map(|t| t.0.clone())
17882 })
17883 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17884
17885 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17886 let format = project.update(cx, |project, cx| {
17887 project.format(buffers, target, true, trigger, cx)
17888 });
17889
17890 cx.spawn_in(window, async move |editor, cx| {
17891 let transaction = futures::select_biased! {
17892 transaction = format.log_err().fuse() => transaction,
17893 () = timeout => {
17894 log::warn!("timed out waiting for formatting");
17895 None
17896 }
17897 };
17898
17899 buffer
17900 .update(cx, |buffer, cx| {
17901 if let Some(transaction) = transaction
17902 && !buffer.is_singleton()
17903 {
17904 buffer.push_transaction(&transaction.0, cx);
17905 }
17906 cx.notify();
17907 })
17908 .ok();
17909
17910 if let Some(transaction_id_now) =
17911 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17912 {
17913 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17914 if has_new_transaction {
17915 _ = editor.update(cx, |editor, _| {
17916 editor
17917 .selection_history
17918 .insert_transaction(transaction_id_now, selections_prev);
17919 });
17920 }
17921 }
17922
17923 Ok(())
17924 })
17925 }
17926
17927 fn organize_imports(
17928 &mut self,
17929 _: &OrganizeImports,
17930 window: &mut Window,
17931 cx: &mut Context<Self>,
17932 ) -> Option<Task<Result<()>>> {
17933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17934 let project = match &self.project {
17935 Some(project) => project.clone(),
17936 None => return None,
17937 };
17938 Some(self.perform_code_action_kind(
17939 project,
17940 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17941 window,
17942 cx,
17943 ))
17944 }
17945
17946 fn perform_code_action_kind(
17947 &mut self,
17948 project: Entity<Project>,
17949 kind: CodeActionKind,
17950 window: &mut Window,
17951 cx: &mut Context<Self>,
17952 ) -> Task<Result<()>> {
17953 let buffer = self.buffer.clone();
17954 let buffers = buffer.read(cx).all_buffers();
17955 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17956 let apply_action = project.update(cx, |project, cx| {
17957 project.apply_code_action_kind(buffers, kind, true, cx)
17958 });
17959 cx.spawn_in(window, async move |_, cx| {
17960 let transaction = futures::select_biased! {
17961 () = timeout => {
17962 log::warn!("timed out waiting for executing code action");
17963 None
17964 }
17965 transaction = apply_action.log_err().fuse() => transaction,
17966 };
17967 buffer
17968 .update(cx, |buffer, cx| {
17969 // check if we need this
17970 if let Some(transaction) = transaction
17971 && !buffer.is_singleton()
17972 {
17973 buffer.push_transaction(&transaction.0, cx);
17974 }
17975 cx.notify();
17976 })
17977 .ok();
17978 Ok(())
17979 })
17980 }
17981
17982 pub fn restart_language_server(
17983 &mut self,
17984 _: &RestartLanguageServer,
17985 _: &mut Window,
17986 cx: &mut Context<Self>,
17987 ) {
17988 if let Some(project) = self.project.clone() {
17989 self.buffer.update(cx, |multi_buffer, cx| {
17990 project.update(cx, |project, cx| {
17991 project.restart_language_servers_for_buffers(
17992 multi_buffer.all_buffers().into_iter().collect(),
17993 HashSet::default(),
17994 cx,
17995 );
17996 });
17997 })
17998 }
17999 }
18000
18001 pub fn stop_language_server(
18002 &mut self,
18003 _: &StopLanguageServer,
18004 _: &mut Window,
18005 cx: &mut Context<Self>,
18006 ) {
18007 if let Some(project) = self.project.clone() {
18008 self.buffer.update(cx, |multi_buffer, cx| {
18009 project.update(cx, |project, cx| {
18010 project.stop_language_servers_for_buffers(
18011 multi_buffer.all_buffers().into_iter().collect(),
18012 HashSet::default(),
18013 cx,
18014 );
18015 });
18016 });
18017 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18018 }
18019 }
18020
18021 fn cancel_language_server_work(
18022 workspace: &mut Workspace,
18023 _: &actions::CancelLanguageServerWork,
18024 _: &mut Window,
18025 cx: &mut Context<Workspace>,
18026 ) {
18027 let project = workspace.project();
18028 let buffers = workspace
18029 .active_item(cx)
18030 .and_then(|item| item.act_as::<Editor>(cx))
18031 .map_or(HashSet::default(), |editor| {
18032 editor.read(cx).buffer.read(cx).all_buffers()
18033 });
18034 project.update(cx, |project, cx| {
18035 project.cancel_language_server_work_for_buffers(buffers, cx);
18036 });
18037 }
18038
18039 fn show_character_palette(
18040 &mut self,
18041 _: &ShowCharacterPalette,
18042 window: &mut Window,
18043 _: &mut Context<Self>,
18044 ) {
18045 window.show_character_palette();
18046 }
18047
18048 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18049 if !self.diagnostics_enabled() {
18050 return;
18051 }
18052
18053 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18054 let buffer = self.buffer.read(cx).snapshot(cx);
18055 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18056 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18057 let is_valid = buffer
18058 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18059 .any(|entry| {
18060 entry.diagnostic.is_primary
18061 && !entry.range.is_empty()
18062 && entry.range.start == primary_range_start
18063 && entry.diagnostic.message == active_diagnostics.active_message
18064 });
18065
18066 if !is_valid {
18067 self.dismiss_diagnostics(cx);
18068 }
18069 }
18070 }
18071
18072 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18073 match &self.active_diagnostics {
18074 ActiveDiagnostic::Group(group) => Some(group),
18075 _ => None,
18076 }
18077 }
18078
18079 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18080 if !self.diagnostics_enabled() {
18081 return;
18082 }
18083 self.dismiss_diagnostics(cx);
18084 self.active_diagnostics = ActiveDiagnostic::All;
18085 }
18086
18087 fn activate_diagnostics(
18088 &mut self,
18089 buffer_id: BufferId,
18090 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18095 return;
18096 }
18097 self.dismiss_diagnostics(cx);
18098 let snapshot = self.snapshot(window, cx);
18099 let buffer = self.buffer.read(cx).snapshot(cx);
18100 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18101 return;
18102 };
18103
18104 let diagnostic_group = buffer
18105 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18106 .collect::<Vec<_>>();
18107
18108 let language_registry = self
18109 .project()
18110 .map(|project| project.read(cx).languages().clone());
18111
18112 let blocks = renderer.render_group(
18113 diagnostic_group,
18114 buffer_id,
18115 snapshot,
18116 cx.weak_entity(),
18117 language_registry,
18118 cx,
18119 );
18120
18121 let blocks = self.display_map.update(cx, |display_map, cx| {
18122 display_map.insert_blocks(blocks, cx).into_iter().collect()
18123 });
18124 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18125 active_range: buffer.anchor_before(diagnostic.range.start)
18126 ..buffer.anchor_after(diagnostic.range.end),
18127 active_message: diagnostic.diagnostic.message.clone(),
18128 group_id: diagnostic.diagnostic.group_id,
18129 blocks,
18130 });
18131 cx.notify();
18132 }
18133
18134 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18135 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18136 return;
18137 };
18138
18139 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18140 if let ActiveDiagnostic::Group(group) = prev {
18141 self.display_map.update(cx, |display_map, cx| {
18142 display_map.remove_blocks(group.blocks, cx);
18143 });
18144 cx.notify();
18145 }
18146 }
18147
18148 /// Disable inline diagnostics rendering for this editor.
18149 pub fn disable_inline_diagnostics(&mut self) {
18150 self.inline_diagnostics_enabled = false;
18151 self.inline_diagnostics_update = Task::ready(());
18152 self.inline_diagnostics.clear();
18153 }
18154
18155 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18156 self.diagnostics_enabled = false;
18157 self.dismiss_diagnostics(cx);
18158 self.inline_diagnostics_update = Task::ready(());
18159 self.inline_diagnostics.clear();
18160 }
18161
18162 pub fn disable_word_completions(&mut self) {
18163 self.word_completions_enabled = false;
18164 }
18165
18166 pub fn diagnostics_enabled(&self) -> bool {
18167 self.diagnostics_enabled && self.mode.is_full()
18168 }
18169
18170 pub fn inline_diagnostics_enabled(&self) -> bool {
18171 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18172 }
18173
18174 pub fn show_inline_diagnostics(&self) -> bool {
18175 self.show_inline_diagnostics
18176 }
18177
18178 pub fn toggle_inline_diagnostics(
18179 &mut self,
18180 _: &ToggleInlineDiagnostics,
18181 window: &mut Window,
18182 cx: &mut Context<Editor>,
18183 ) {
18184 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18185 self.refresh_inline_diagnostics(false, window, cx);
18186 }
18187
18188 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18189 self.diagnostics_max_severity = severity;
18190 self.display_map.update(cx, |display_map, _| {
18191 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18192 });
18193 }
18194
18195 pub fn toggle_diagnostics(
18196 &mut self,
18197 _: &ToggleDiagnostics,
18198 window: &mut Window,
18199 cx: &mut Context<Editor>,
18200 ) {
18201 if !self.diagnostics_enabled() {
18202 return;
18203 }
18204
18205 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18206 EditorSettings::get_global(cx)
18207 .diagnostics_max_severity
18208 .filter(|severity| severity != &DiagnosticSeverity::Off)
18209 .unwrap_or(DiagnosticSeverity::Hint)
18210 } else {
18211 DiagnosticSeverity::Off
18212 };
18213 self.set_max_diagnostics_severity(new_severity, cx);
18214 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18215 self.active_diagnostics = ActiveDiagnostic::None;
18216 self.inline_diagnostics_update = Task::ready(());
18217 self.inline_diagnostics.clear();
18218 } else {
18219 self.refresh_inline_diagnostics(false, window, cx);
18220 }
18221
18222 cx.notify();
18223 }
18224
18225 pub fn toggle_minimap(
18226 &mut self,
18227 _: &ToggleMinimap,
18228 window: &mut Window,
18229 cx: &mut Context<Editor>,
18230 ) {
18231 if self.supports_minimap(cx) {
18232 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18233 }
18234 }
18235
18236 fn refresh_inline_diagnostics(
18237 &mut self,
18238 debounce: bool,
18239 window: &mut Window,
18240 cx: &mut Context<Self>,
18241 ) {
18242 let max_severity = ProjectSettings::get_global(cx)
18243 .diagnostics
18244 .inline
18245 .max_severity
18246 .unwrap_or(self.diagnostics_max_severity);
18247
18248 if !self.inline_diagnostics_enabled()
18249 || !self.diagnostics_enabled()
18250 || !self.show_inline_diagnostics
18251 || max_severity == DiagnosticSeverity::Off
18252 {
18253 self.inline_diagnostics_update = Task::ready(());
18254 self.inline_diagnostics.clear();
18255 return;
18256 }
18257
18258 let debounce_ms = ProjectSettings::get_global(cx)
18259 .diagnostics
18260 .inline
18261 .update_debounce_ms;
18262 let debounce = if debounce && debounce_ms > 0 {
18263 Some(Duration::from_millis(debounce_ms))
18264 } else {
18265 None
18266 };
18267 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18268 if let Some(debounce) = debounce {
18269 cx.background_executor().timer(debounce).await;
18270 }
18271 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18272 editor
18273 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18274 .ok()
18275 }) else {
18276 return;
18277 };
18278
18279 let new_inline_diagnostics = cx
18280 .background_spawn(async move {
18281 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18282 for diagnostic_entry in
18283 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18284 {
18285 let message = diagnostic_entry
18286 .diagnostic
18287 .message
18288 .split_once('\n')
18289 .map(|(line, _)| line)
18290 .map(SharedString::new)
18291 .unwrap_or_else(|| {
18292 SharedString::new(&*diagnostic_entry.diagnostic.message)
18293 });
18294 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18295 let (Ok(i) | Err(i)) = inline_diagnostics
18296 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18297 inline_diagnostics.insert(
18298 i,
18299 (
18300 start_anchor,
18301 InlineDiagnostic {
18302 message,
18303 group_id: diagnostic_entry.diagnostic.group_id,
18304 start: diagnostic_entry.range.start.to_point(&snapshot),
18305 is_primary: diagnostic_entry.diagnostic.is_primary,
18306 severity: diagnostic_entry.diagnostic.severity,
18307 },
18308 ),
18309 );
18310 }
18311 inline_diagnostics
18312 })
18313 .await;
18314
18315 editor
18316 .update(cx, |editor, cx| {
18317 editor.inline_diagnostics = new_inline_diagnostics;
18318 cx.notify();
18319 })
18320 .ok();
18321 });
18322 }
18323
18324 fn pull_diagnostics(
18325 &mut self,
18326 buffer_id: Option<BufferId>,
18327 window: &Window,
18328 cx: &mut Context<Self>,
18329 ) -> Option<()> {
18330 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18331 return None;
18332 }
18333 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18334 .diagnostics
18335 .lsp_pull_diagnostics;
18336 if !pull_diagnostics_settings.enabled {
18337 return None;
18338 }
18339 let project = self.project()?.downgrade();
18340 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18341 let mut buffers = self.buffer.read(cx).all_buffers();
18342 buffers.retain(|buffer| {
18343 let buffer_id_to_retain = buffer.read(cx).remote_id();
18344 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18345 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18346 });
18347 if buffers.is_empty() {
18348 self.pull_diagnostics_task = Task::ready(());
18349 return None;
18350 }
18351
18352 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18353 cx.background_executor().timer(debounce).await;
18354
18355 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18356 buffers
18357 .into_iter()
18358 .filter_map(|buffer| {
18359 project
18360 .update(cx, |project, cx| {
18361 project.lsp_store().update(cx, |lsp_store, cx| {
18362 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18363 })
18364 })
18365 .ok()
18366 })
18367 .collect::<FuturesUnordered<_>>()
18368 }) else {
18369 return;
18370 };
18371
18372 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18373 match pull_task {
18374 Ok(()) => {
18375 if editor
18376 .update_in(cx, |editor, window, cx| {
18377 editor.update_diagnostics_state(window, cx);
18378 })
18379 .is_err()
18380 {
18381 return;
18382 }
18383 }
18384 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18385 }
18386 }
18387 });
18388
18389 Some(())
18390 }
18391
18392 pub fn set_selections_from_remote(
18393 &mut self,
18394 selections: Vec<Selection<Anchor>>,
18395 pending_selection: Option<Selection<Anchor>>,
18396 window: &mut Window,
18397 cx: &mut Context<Self>,
18398 ) {
18399 let old_cursor_position = self.selections.newest_anchor().head();
18400 self.selections
18401 .change_with(&self.display_snapshot(cx), |s| {
18402 s.select_anchors(selections);
18403 if let Some(pending_selection) = pending_selection {
18404 s.set_pending(pending_selection, SelectMode::Character);
18405 } else {
18406 s.clear_pending();
18407 }
18408 });
18409 self.selections_did_change(
18410 false,
18411 &old_cursor_position,
18412 SelectionEffects::default(),
18413 window,
18414 cx,
18415 );
18416 }
18417
18418 pub fn transact(
18419 &mut self,
18420 window: &mut Window,
18421 cx: &mut Context<Self>,
18422 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18423 ) -> Option<TransactionId> {
18424 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18425 this.start_transaction_at(Instant::now(), window, cx);
18426 update(this, window, cx);
18427 this.end_transaction_at(Instant::now(), cx)
18428 })
18429 }
18430
18431 pub fn start_transaction_at(
18432 &mut self,
18433 now: Instant,
18434 window: &mut Window,
18435 cx: &mut Context<Self>,
18436 ) -> Option<TransactionId> {
18437 self.end_selection(window, cx);
18438 if let Some(tx_id) = self
18439 .buffer
18440 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18441 {
18442 self.selection_history
18443 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18444 cx.emit(EditorEvent::TransactionBegun {
18445 transaction_id: tx_id,
18446 });
18447 Some(tx_id)
18448 } else {
18449 None
18450 }
18451 }
18452
18453 pub fn end_transaction_at(
18454 &mut self,
18455 now: Instant,
18456 cx: &mut Context<Self>,
18457 ) -> Option<TransactionId> {
18458 if let Some(transaction_id) = self
18459 .buffer
18460 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18461 {
18462 if let Some((_, end_selections)) =
18463 self.selection_history.transaction_mut(transaction_id)
18464 {
18465 *end_selections = Some(self.selections.disjoint_anchors_arc());
18466 } else {
18467 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18468 }
18469
18470 cx.emit(EditorEvent::Edited { transaction_id });
18471 Some(transaction_id)
18472 } else {
18473 None
18474 }
18475 }
18476
18477 pub fn modify_transaction_selection_history(
18478 &mut self,
18479 transaction_id: TransactionId,
18480 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18481 ) -> bool {
18482 self.selection_history
18483 .transaction_mut(transaction_id)
18484 .map(modify)
18485 .is_some()
18486 }
18487
18488 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18489 if self.selection_mark_mode {
18490 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18491 s.move_with(|_, sel| {
18492 sel.collapse_to(sel.head(), SelectionGoal::None);
18493 });
18494 })
18495 }
18496 self.selection_mark_mode = true;
18497 cx.notify();
18498 }
18499
18500 pub fn swap_selection_ends(
18501 &mut self,
18502 _: &actions::SwapSelectionEnds,
18503 window: &mut Window,
18504 cx: &mut Context<Self>,
18505 ) {
18506 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18507 s.move_with(|_, sel| {
18508 if sel.start != sel.end {
18509 sel.reversed = !sel.reversed
18510 }
18511 });
18512 });
18513 self.request_autoscroll(Autoscroll::newest(), cx);
18514 cx.notify();
18515 }
18516
18517 pub fn toggle_focus(
18518 workspace: &mut Workspace,
18519 _: &actions::ToggleFocus,
18520 window: &mut Window,
18521 cx: &mut Context<Workspace>,
18522 ) {
18523 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18524 return;
18525 };
18526 workspace.activate_item(&item, true, true, window, cx);
18527 }
18528
18529 pub fn toggle_fold(
18530 &mut self,
18531 _: &actions::ToggleFold,
18532 window: &mut Window,
18533 cx: &mut Context<Self>,
18534 ) {
18535 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18537 let selection = self.selections.newest::<Point>(&display_map);
18538
18539 let range = if selection.is_empty() {
18540 let point = selection.head().to_display_point(&display_map);
18541 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18542 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18543 .to_point(&display_map);
18544 start..end
18545 } else {
18546 selection.range()
18547 };
18548 if display_map.folds_in_range(range).next().is_some() {
18549 self.unfold_lines(&Default::default(), window, cx)
18550 } else {
18551 self.fold(&Default::default(), window, cx)
18552 }
18553 } else {
18554 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18555 let buffer_ids: HashSet<_> = self
18556 .selections
18557 .disjoint_anchor_ranges()
18558 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18559 .collect();
18560
18561 let should_unfold = buffer_ids
18562 .iter()
18563 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18564
18565 for buffer_id in buffer_ids {
18566 if should_unfold {
18567 self.unfold_buffer(buffer_id, cx);
18568 } else {
18569 self.fold_buffer(buffer_id, cx);
18570 }
18571 }
18572 }
18573 }
18574
18575 pub fn toggle_fold_recursive(
18576 &mut self,
18577 _: &actions::ToggleFoldRecursive,
18578 window: &mut Window,
18579 cx: &mut Context<Self>,
18580 ) {
18581 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18582
18583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18584 let range = if selection.is_empty() {
18585 let point = selection.head().to_display_point(&display_map);
18586 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18587 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18588 .to_point(&display_map);
18589 start..end
18590 } else {
18591 selection.range()
18592 };
18593 if display_map.folds_in_range(range).next().is_some() {
18594 self.unfold_recursive(&Default::default(), window, cx)
18595 } else {
18596 self.fold_recursive(&Default::default(), window, cx)
18597 }
18598 }
18599
18600 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18601 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18602 let mut to_fold = Vec::new();
18603 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18604 let selections = self.selections.all_adjusted(&display_map);
18605
18606 for selection in selections {
18607 let range = selection.range().sorted();
18608 let buffer_start_row = range.start.row;
18609
18610 if range.start.row != range.end.row {
18611 let mut found = false;
18612 let mut row = range.start.row;
18613 while row <= range.end.row {
18614 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18615 {
18616 found = true;
18617 row = crease.range().end.row + 1;
18618 to_fold.push(crease);
18619 } else {
18620 row += 1
18621 }
18622 }
18623 if found {
18624 continue;
18625 }
18626 }
18627
18628 for row in (0..=range.start.row).rev() {
18629 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18630 && crease.range().end.row >= buffer_start_row
18631 {
18632 to_fold.push(crease);
18633 if row <= range.start.row {
18634 break;
18635 }
18636 }
18637 }
18638 }
18639
18640 self.fold_creases(to_fold, true, window, cx);
18641 } else {
18642 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18643 let buffer_ids = self
18644 .selections
18645 .disjoint_anchor_ranges()
18646 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18647 .collect::<HashSet<_>>();
18648 for buffer_id in buffer_ids {
18649 self.fold_buffer(buffer_id, cx);
18650 }
18651 }
18652 }
18653
18654 pub fn toggle_fold_all(
18655 &mut self,
18656 _: &actions::ToggleFoldAll,
18657 window: &mut Window,
18658 cx: &mut Context<Self>,
18659 ) {
18660 if self.buffer.read(cx).is_singleton() {
18661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18662 let has_folds = display_map
18663 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18664 .next()
18665 .is_some();
18666
18667 if has_folds {
18668 self.unfold_all(&actions::UnfoldAll, window, cx);
18669 } else {
18670 self.fold_all(&actions::FoldAll, window, cx);
18671 }
18672 } else {
18673 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18674 let should_unfold = buffer_ids
18675 .iter()
18676 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18677
18678 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18679 editor
18680 .update_in(cx, |editor, _, cx| {
18681 for buffer_id in buffer_ids {
18682 if should_unfold {
18683 editor.unfold_buffer(buffer_id, cx);
18684 } else {
18685 editor.fold_buffer(buffer_id, cx);
18686 }
18687 }
18688 })
18689 .ok();
18690 });
18691 }
18692 }
18693
18694 fn fold_at_level(
18695 &mut self,
18696 fold_at: &FoldAtLevel,
18697 window: &mut Window,
18698 cx: &mut Context<Self>,
18699 ) {
18700 if !self.buffer.read(cx).is_singleton() {
18701 return;
18702 }
18703
18704 let fold_at_level = fold_at.0;
18705 let snapshot = self.buffer.read(cx).snapshot(cx);
18706 let mut to_fold = Vec::new();
18707 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18708
18709 let row_ranges_to_keep: Vec<Range<u32>> = self
18710 .selections
18711 .all::<Point>(&self.display_snapshot(cx))
18712 .into_iter()
18713 .map(|sel| sel.start.row..sel.end.row)
18714 .collect();
18715
18716 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18717 while start_row < end_row {
18718 match self
18719 .snapshot(window, cx)
18720 .crease_for_buffer_row(MultiBufferRow(start_row))
18721 {
18722 Some(crease) => {
18723 let nested_start_row = crease.range().start.row + 1;
18724 let nested_end_row = crease.range().end.row;
18725
18726 if current_level < fold_at_level {
18727 stack.push((nested_start_row, nested_end_row, current_level + 1));
18728 } else if current_level == fold_at_level {
18729 // Fold iff there is no selection completely contained within the fold region
18730 if !row_ranges_to_keep.iter().any(|selection| {
18731 selection.end >= nested_start_row
18732 && selection.start <= nested_end_row
18733 }) {
18734 to_fold.push(crease);
18735 }
18736 }
18737
18738 start_row = nested_end_row + 1;
18739 }
18740 None => start_row += 1,
18741 }
18742 }
18743 }
18744
18745 self.fold_creases(to_fold, true, window, cx);
18746 }
18747
18748 pub fn fold_at_level_1(
18749 &mut self,
18750 _: &actions::FoldAtLevel1,
18751 window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) {
18754 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18755 }
18756
18757 pub fn fold_at_level_2(
18758 &mut self,
18759 _: &actions::FoldAtLevel2,
18760 window: &mut Window,
18761 cx: &mut Context<Self>,
18762 ) {
18763 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18764 }
18765
18766 pub fn fold_at_level_3(
18767 &mut self,
18768 _: &actions::FoldAtLevel3,
18769 window: &mut Window,
18770 cx: &mut Context<Self>,
18771 ) {
18772 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18773 }
18774
18775 pub fn fold_at_level_4(
18776 &mut self,
18777 _: &actions::FoldAtLevel4,
18778 window: &mut Window,
18779 cx: &mut Context<Self>,
18780 ) {
18781 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18782 }
18783
18784 pub fn fold_at_level_5(
18785 &mut self,
18786 _: &actions::FoldAtLevel5,
18787 window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18791 }
18792
18793 pub fn fold_at_level_6(
18794 &mut self,
18795 _: &actions::FoldAtLevel6,
18796 window: &mut Window,
18797 cx: &mut Context<Self>,
18798 ) {
18799 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18800 }
18801
18802 pub fn fold_at_level_7(
18803 &mut self,
18804 _: &actions::FoldAtLevel7,
18805 window: &mut Window,
18806 cx: &mut Context<Self>,
18807 ) {
18808 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18809 }
18810
18811 pub fn fold_at_level_8(
18812 &mut self,
18813 _: &actions::FoldAtLevel8,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18818 }
18819
18820 pub fn fold_at_level_9(
18821 &mut self,
18822 _: &actions::FoldAtLevel9,
18823 window: &mut Window,
18824 cx: &mut Context<Self>,
18825 ) {
18826 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18827 }
18828
18829 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18830 if self.buffer.read(cx).is_singleton() {
18831 let mut fold_ranges = Vec::new();
18832 let snapshot = self.buffer.read(cx).snapshot(cx);
18833
18834 for row in 0..snapshot.max_row().0 {
18835 if let Some(foldable_range) = self
18836 .snapshot(window, cx)
18837 .crease_for_buffer_row(MultiBufferRow(row))
18838 {
18839 fold_ranges.push(foldable_range);
18840 }
18841 }
18842
18843 self.fold_creases(fold_ranges, true, window, cx);
18844 } else {
18845 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18846 editor
18847 .update_in(cx, |editor, _, cx| {
18848 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18849 editor.fold_buffer(buffer_id, cx);
18850 }
18851 })
18852 .ok();
18853 });
18854 }
18855 }
18856
18857 pub fn fold_function_bodies(
18858 &mut self,
18859 _: &actions::FoldFunctionBodies,
18860 window: &mut Window,
18861 cx: &mut Context<Self>,
18862 ) {
18863 let snapshot = self.buffer.read(cx).snapshot(cx);
18864
18865 let ranges = snapshot
18866 .text_object_ranges(
18867 MultiBufferOffset(0)..snapshot.len(),
18868 TreeSitterOptions::default(),
18869 )
18870 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18871 .collect::<Vec<_>>();
18872
18873 let creases = ranges
18874 .into_iter()
18875 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18876 .collect();
18877
18878 self.fold_creases(creases, true, window, cx);
18879 }
18880
18881 pub fn fold_recursive(
18882 &mut self,
18883 _: &actions::FoldRecursive,
18884 window: &mut Window,
18885 cx: &mut Context<Self>,
18886 ) {
18887 let mut to_fold = Vec::new();
18888 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18889 let selections = self.selections.all_adjusted(&display_map);
18890
18891 for selection in selections {
18892 let range = selection.range().sorted();
18893 let buffer_start_row = range.start.row;
18894
18895 if range.start.row != range.end.row {
18896 let mut found = false;
18897 for row in range.start.row..=range.end.row {
18898 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18899 found = true;
18900 to_fold.push(crease);
18901 }
18902 }
18903 if found {
18904 continue;
18905 }
18906 }
18907
18908 for row in (0..=range.start.row).rev() {
18909 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18910 if crease.range().end.row >= buffer_start_row {
18911 to_fold.push(crease);
18912 } else {
18913 break;
18914 }
18915 }
18916 }
18917 }
18918
18919 self.fold_creases(to_fold, true, window, cx);
18920 }
18921
18922 pub fn fold_at(
18923 &mut self,
18924 buffer_row: MultiBufferRow,
18925 window: &mut Window,
18926 cx: &mut Context<Self>,
18927 ) {
18928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18929
18930 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18931 let autoscroll = self
18932 .selections
18933 .all::<Point>(&display_map)
18934 .iter()
18935 .any(|selection| crease.range().overlaps(&selection.range()));
18936
18937 self.fold_creases(vec![crease], autoscroll, window, cx);
18938 }
18939 }
18940
18941 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18942 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18944 let buffer = display_map.buffer_snapshot();
18945 let selections = self.selections.all::<Point>(&display_map);
18946 let ranges = selections
18947 .iter()
18948 .map(|s| {
18949 let range = s.display_range(&display_map).sorted();
18950 let mut start = range.start.to_point(&display_map);
18951 let mut end = range.end.to_point(&display_map);
18952 start.column = 0;
18953 end.column = buffer.line_len(MultiBufferRow(end.row));
18954 start..end
18955 })
18956 .collect::<Vec<_>>();
18957
18958 self.unfold_ranges(&ranges, true, true, cx);
18959 } else {
18960 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18961 let buffer_ids = self
18962 .selections
18963 .disjoint_anchor_ranges()
18964 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18965 .collect::<HashSet<_>>();
18966 for buffer_id in buffer_ids {
18967 self.unfold_buffer(buffer_id, cx);
18968 }
18969 }
18970 }
18971
18972 pub fn unfold_recursive(
18973 &mut self,
18974 _: &UnfoldRecursive,
18975 _window: &mut Window,
18976 cx: &mut Context<Self>,
18977 ) {
18978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18979 let selections = self.selections.all::<Point>(&display_map);
18980 let ranges = selections
18981 .iter()
18982 .map(|s| {
18983 let mut range = s.display_range(&display_map).sorted();
18984 *range.start.column_mut() = 0;
18985 *range.end.column_mut() = display_map.line_len(range.end.row());
18986 let start = range.start.to_point(&display_map);
18987 let end = range.end.to_point(&display_map);
18988 start..end
18989 })
18990 .collect::<Vec<_>>();
18991
18992 self.unfold_ranges(&ranges, true, true, cx);
18993 }
18994
18995 pub fn unfold_at(
18996 &mut self,
18997 buffer_row: MultiBufferRow,
18998 _window: &mut Window,
18999 cx: &mut Context<Self>,
19000 ) {
19001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19002
19003 let intersection_range = Point::new(buffer_row.0, 0)
19004 ..Point::new(
19005 buffer_row.0,
19006 display_map.buffer_snapshot().line_len(buffer_row),
19007 );
19008
19009 let autoscroll = self
19010 .selections
19011 .all::<Point>(&display_map)
19012 .iter()
19013 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19014
19015 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19016 }
19017
19018 pub fn unfold_all(
19019 &mut self,
19020 _: &actions::UnfoldAll,
19021 _window: &mut Window,
19022 cx: &mut Context<Self>,
19023 ) {
19024 if self.buffer.read(cx).is_singleton() {
19025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19026 self.unfold_ranges(
19027 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19028 true,
19029 true,
19030 cx,
19031 );
19032 } else {
19033 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19034 editor
19035 .update(cx, |editor, cx| {
19036 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19037 editor.unfold_buffer(buffer_id, cx);
19038 }
19039 })
19040 .ok();
19041 });
19042 }
19043 }
19044
19045 pub fn fold_selected_ranges(
19046 &mut self,
19047 _: &FoldSelectedRanges,
19048 window: &mut Window,
19049 cx: &mut Context<Self>,
19050 ) {
19051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19052 let selections = self.selections.all_adjusted(&display_map);
19053 let ranges = selections
19054 .into_iter()
19055 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19056 .collect::<Vec<_>>();
19057 self.fold_creases(ranges, true, window, cx);
19058 }
19059
19060 pub fn fold_ranges<T: ToOffset + Clone>(
19061 &mut self,
19062 ranges: Vec<Range<T>>,
19063 auto_scroll: bool,
19064 window: &mut Window,
19065 cx: &mut Context<Self>,
19066 ) {
19067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19068 let ranges = ranges
19069 .into_iter()
19070 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19071 .collect::<Vec<_>>();
19072 self.fold_creases(ranges, auto_scroll, window, cx);
19073 }
19074
19075 pub fn fold_creases<T: ToOffset + Clone>(
19076 &mut self,
19077 creases: Vec<Crease<T>>,
19078 auto_scroll: bool,
19079 _window: &mut Window,
19080 cx: &mut Context<Self>,
19081 ) {
19082 if creases.is_empty() {
19083 return;
19084 }
19085
19086 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19087
19088 if auto_scroll {
19089 self.request_autoscroll(Autoscroll::fit(), cx);
19090 }
19091
19092 cx.notify();
19093
19094 self.scrollbar_marker_state.dirty = true;
19095 self.folds_did_change(cx);
19096 }
19097
19098 /// Removes any folds whose ranges intersect any of the given ranges.
19099 pub fn unfold_ranges<T: ToOffset + Clone>(
19100 &mut self,
19101 ranges: &[Range<T>],
19102 inclusive: bool,
19103 auto_scroll: bool,
19104 cx: &mut Context<Self>,
19105 ) {
19106 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19107 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19108 });
19109 self.folds_did_change(cx);
19110 }
19111
19112 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19113 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19114 return;
19115 }
19116
19117 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19118 self.display_map.update(cx, |display_map, cx| {
19119 display_map.fold_buffers([buffer_id], cx)
19120 });
19121
19122 let snapshot = self.display_snapshot(cx);
19123 self.selections.change_with(&snapshot, |selections| {
19124 selections.remove_selections_from_buffer(buffer_id);
19125 });
19126
19127 cx.emit(EditorEvent::BufferFoldToggled {
19128 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19129 folded: true,
19130 });
19131 cx.notify();
19132 }
19133
19134 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19135 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19136 return;
19137 }
19138 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19139 self.display_map.update(cx, |display_map, cx| {
19140 display_map.unfold_buffers([buffer_id], cx);
19141 });
19142 cx.emit(EditorEvent::BufferFoldToggled {
19143 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19144 folded: false,
19145 });
19146 cx.notify();
19147 }
19148
19149 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19150 self.display_map.read(cx).is_buffer_folded(buffer)
19151 }
19152
19153 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19154 self.display_map.read(cx).folded_buffers()
19155 }
19156
19157 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19158 self.display_map.update(cx, |display_map, cx| {
19159 display_map.disable_header_for_buffer(buffer_id, cx);
19160 });
19161 cx.notify();
19162 }
19163
19164 /// Removes any folds with the given ranges.
19165 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19166 &mut self,
19167 ranges: &[Range<T>],
19168 type_id: TypeId,
19169 auto_scroll: bool,
19170 cx: &mut Context<Self>,
19171 ) {
19172 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19173 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19174 });
19175 self.folds_did_change(cx);
19176 }
19177
19178 fn remove_folds_with<T: ToOffset + Clone>(
19179 &mut self,
19180 ranges: &[Range<T>],
19181 auto_scroll: bool,
19182 cx: &mut Context<Self>,
19183 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19184 ) {
19185 if ranges.is_empty() {
19186 return;
19187 }
19188
19189 let mut buffers_affected = HashSet::default();
19190 let multi_buffer = self.buffer().read(cx);
19191 for range in ranges {
19192 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19193 buffers_affected.insert(buffer.read(cx).remote_id());
19194 };
19195 }
19196
19197 self.display_map.update(cx, update);
19198
19199 if auto_scroll {
19200 self.request_autoscroll(Autoscroll::fit(), cx);
19201 }
19202
19203 cx.notify();
19204 self.scrollbar_marker_state.dirty = true;
19205 self.active_indent_guides_state.dirty = true;
19206 }
19207
19208 pub fn update_renderer_widths(
19209 &mut self,
19210 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19211 cx: &mut Context<Self>,
19212 ) -> bool {
19213 self.display_map
19214 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19215 }
19216
19217 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19218 self.display_map.read(cx).fold_placeholder.clone()
19219 }
19220
19221 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19222 self.use_base_text_line_numbers = show;
19223 }
19224
19225 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19226 self.buffer.update(cx, |buffer, cx| {
19227 buffer.set_all_diff_hunks_expanded(cx);
19228 });
19229 }
19230
19231 pub fn expand_all_diff_hunks(
19232 &mut self,
19233 _: &ExpandAllDiffHunks,
19234 _window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 self.buffer.update(cx, |buffer, cx| {
19238 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19239 });
19240 }
19241
19242 pub fn collapse_all_diff_hunks(
19243 &mut self,
19244 _: &CollapseAllDiffHunks,
19245 _window: &mut Window,
19246 cx: &mut Context<Self>,
19247 ) {
19248 self.buffer.update(cx, |buffer, cx| {
19249 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19250 });
19251 }
19252
19253 pub fn toggle_selected_diff_hunks(
19254 &mut self,
19255 _: &ToggleSelectedDiffHunks,
19256 _window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 let ranges: Vec<_> = self
19260 .selections
19261 .disjoint_anchors()
19262 .iter()
19263 .map(|s| s.range())
19264 .collect();
19265 self.toggle_diff_hunks_in_ranges(ranges, cx);
19266 }
19267
19268 pub fn diff_hunks_in_ranges<'a>(
19269 &'a self,
19270 ranges: &'a [Range<Anchor>],
19271 buffer: &'a MultiBufferSnapshot,
19272 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19273 ranges.iter().flat_map(move |range| {
19274 let end_excerpt_id = range.end.excerpt_id;
19275 let range = range.to_point(buffer);
19276 let mut peek_end = range.end;
19277 if range.end.row < buffer.max_row().0 {
19278 peek_end = Point::new(range.end.row + 1, 0);
19279 }
19280 buffer
19281 .diff_hunks_in_range(range.start..peek_end)
19282 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19283 })
19284 }
19285
19286 pub fn has_stageable_diff_hunks_in_ranges(
19287 &self,
19288 ranges: &[Range<Anchor>],
19289 snapshot: &MultiBufferSnapshot,
19290 ) -> bool {
19291 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19292 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19293 }
19294
19295 pub fn toggle_staged_selected_diff_hunks(
19296 &mut self,
19297 _: &::git::ToggleStaged,
19298 _: &mut Window,
19299 cx: &mut Context<Self>,
19300 ) {
19301 let snapshot = self.buffer.read(cx).snapshot(cx);
19302 let ranges: Vec<_> = self
19303 .selections
19304 .disjoint_anchors()
19305 .iter()
19306 .map(|s| s.range())
19307 .collect();
19308 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19309 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19310 }
19311
19312 pub fn set_render_diff_hunk_controls(
19313 &mut self,
19314 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19315 cx: &mut Context<Self>,
19316 ) {
19317 self.render_diff_hunk_controls = render_diff_hunk_controls;
19318 cx.notify();
19319 }
19320
19321 pub fn stage_and_next(
19322 &mut self,
19323 _: &::git::StageAndNext,
19324 window: &mut Window,
19325 cx: &mut Context<Self>,
19326 ) {
19327 self.do_stage_or_unstage_and_next(true, window, cx);
19328 }
19329
19330 pub fn unstage_and_next(
19331 &mut self,
19332 _: &::git::UnstageAndNext,
19333 window: &mut Window,
19334 cx: &mut Context<Self>,
19335 ) {
19336 self.do_stage_or_unstage_and_next(false, window, cx);
19337 }
19338
19339 pub fn stage_or_unstage_diff_hunks(
19340 &mut self,
19341 stage: bool,
19342 ranges: Vec<Range<Anchor>>,
19343 cx: &mut Context<Self>,
19344 ) {
19345 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19346 cx.spawn(async move |this, cx| {
19347 task.await?;
19348 this.update(cx, |this, cx| {
19349 let snapshot = this.buffer.read(cx).snapshot(cx);
19350 let chunk_by = this
19351 .diff_hunks_in_ranges(&ranges, &snapshot)
19352 .chunk_by(|hunk| hunk.buffer_id);
19353 for (buffer_id, hunks) in &chunk_by {
19354 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19355 }
19356 })
19357 })
19358 .detach_and_log_err(cx);
19359 }
19360
19361 fn save_buffers_for_ranges_if_needed(
19362 &mut self,
19363 ranges: &[Range<Anchor>],
19364 cx: &mut Context<Editor>,
19365 ) -> Task<Result<()>> {
19366 let multibuffer = self.buffer.read(cx);
19367 let snapshot = multibuffer.read(cx);
19368 let buffer_ids: HashSet<_> = ranges
19369 .iter()
19370 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19371 .collect();
19372 drop(snapshot);
19373
19374 let mut buffers = HashSet::default();
19375 for buffer_id in buffer_ids {
19376 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19377 let buffer = buffer_entity.read(cx);
19378 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19379 {
19380 buffers.insert(buffer_entity);
19381 }
19382 }
19383 }
19384
19385 if let Some(project) = &self.project {
19386 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19387 } else {
19388 Task::ready(Ok(()))
19389 }
19390 }
19391
19392 fn do_stage_or_unstage_and_next(
19393 &mut self,
19394 stage: bool,
19395 window: &mut Window,
19396 cx: &mut Context<Self>,
19397 ) {
19398 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19399
19400 if ranges.iter().any(|range| range.start != range.end) {
19401 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19402 return;
19403 }
19404
19405 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19406 let snapshot = self.snapshot(window, cx);
19407 let position = self
19408 .selections
19409 .newest::<Point>(&snapshot.display_snapshot)
19410 .head();
19411 let mut row = snapshot
19412 .buffer_snapshot()
19413 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19414 .find(|hunk| hunk.row_range.start.0 > position.row)
19415 .map(|hunk| hunk.row_range.start);
19416
19417 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19418 // Outside of the project diff editor, wrap around to the beginning.
19419 if !all_diff_hunks_expanded {
19420 row = row.or_else(|| {
19421 snapshot
19422 .buffer_snapshot()
19423 .diff_hunks_in_range(Point::zero()..position)
19424 .find(|hunk| hunk.row_range.end.0 < position.row)
19425 .map(|hunk| hunk.row_range.start)
19426 });
19427 }
19428
19429 if let Some(row) = row {
19430 let destination = Point::new(row.0, 0);
19431 let autoscroll = Autoscroll::center();
19432
19433 self.unfold_ranges(&[destination..destination], false, false, cx);
19434 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19435 s.select_ranges([destination..destination]);
19436 });
19437 }
19438 }
19439
19440 fn do_stage_or_unstage(
19441 &self,
19442 stage: bool,
19443 buffer_id: BufferId,
19444 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19445 cx: &mut App,
19446 ) -> Option<()> {
19447 let project = self.project()?;
19448 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19449 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19450 let buffer_snapshot = buffer.read(cx).snapshot();
19451 let file_exists = buffer_snapshot
19452 .file()
19453 .is_some_and(|file| file.disk_state().exists());
19454 diff.update(cx, |diff, cx| {
19455 diff.stage_or_unstage_hunks(
19456 stage,
19457 &hunks
19458 .map(|hunk| buffer_diff::DiffHunk {
19459 buffer_range: hunk.buffer_range,
19460 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19461 ..hunk.diff_base_byte_range.end.0,
19462 secondary_status: hunk.secondary_status,
19463 range: Point::zero()..Point::zero(), // unused
19464 })
19465 .collect::<Vec<_>>(),
19466 &buffer_snapshot,
19467 file_exists,
19468 cx,
19469 )
19470 });
19471 None
19472 }
19473
19474 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19475 let ranges: Vec<_> = self
19476 .selections
19477 .disjoint_anchors()
19478 .iter()
19479 .map(|s| s.range())
19480 .collect();
19481 self.buffer
19482 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19483 }
19484
19485 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19486 self.buffer.update(cx, |buffer, cx| {
19487 let ranges = vec![Anchor::min()..Anchor::max()];
19488 if !buffer.all_diff_hunks_expanded()
19489 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19490 {
19491 buffer.collapse_diff_hunks(ranges, cx);
19492 true
19493 } else {
19494 false
19495 }
19496 })
19497 }
19498
19499 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19500 if self.buffer.read(cx).all_diff_hunks_expanded() {
19501 return true;
19502 }
19503 let ranges = vec![Anchor::min()..Anchor::max()];
19504 self.buffer
19505 .read(cx)
19506 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19507 }
19508
19509 fn toggle_diff_hunks_in_ranges(
19510 &mut self,
19511 ranges: Vec<Range<Anchor>>,
19512 cx: &mut Context<Editor>,
19513 ) {
19514 self.buffer.update(cx, |buffer, cx| {
19515 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19516 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19517 })
19518 }
19519
19520 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19521 self.buffer.update(cx, |buffer, cx| {
19522 let snapshot = buffer.snapshot(cx);
19523 let excerpt_id = range.end.excerpt_id;
19524 let point_range = range.to_point(&snapshot);
19525 let expand = !buffer.single_hunk_is_expanded(range, cx);
19526 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19527 })
19528 }
19529
19530 pub(crate) fn apply_all_diff_hunks(
19531 &mut self,
19532 _: &ApplyAllDiffHunks,
19533 window: &mut Window,
19534 cx: &mut Context<Self>,
19535 ) {
19536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19537
19538 let buffers = self.buffer.read(cx).all_buffers();
19539 for branch_buffer in buffers {
19540 branch_buffer.update(cx, |branch_buffer, cx| {
19541 branch_buffer.merge_into_base(Vec::new(), cx);
19542 });
19543 }
19544
19545 if let Some(project) = self.project.clone() {
19546 self.save(
19547 SaveOptions {
19548 format: true,
19549 autosave: false,
19550 },
19551 project,
19552 window,
19553 cx,
19554 )
19555 .detach_and_log_err(cx);
19556 }
19557 }
19558
19559 pub(crate) fn apply_selected_diff_hunks(
19560 &mut self,
19561 _: &ApplyDiffHunk,
19562 window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) {
19565 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19566 let snapshot = self.snapshot(window, cx);
19567 let hunks = snapshot.hunks_for_ranges(
19568 self.selections
19569 .all(&snapshot.display_snapshot)
19570 .into_iter()
19571 .map(|selection| selection.range()),
19572 );
19573 let mut ranges_by_buffer = HashMap::default();
19574 self.transact(window, cx, |editor, _window, cx| {
19575 for hunk in hunks {
19576 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19577 ranges_by_buffer
19578 .entry(buffer.clone())
19579 .or_insert_with(Vec::new)
19580 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19581 }
19582 }
19583
19584 for (buffer, ranges) in ranges_by_buffer {
19585 buffer.update(cx, |buffer, cx| {
19586 buffer.merge_into_base(ranges, cx);
19587 });
19588 }
19589 });
19590
19591 if let Some(project) = self.project.clone() {
19592 self.save(
19593 SaveOptions {
19594 format: true,
19595 autosave: false,
19596 },
19597 project,
19598 window,
19599 cx,
19600 )
19601 .detach_and_log_err(cx);
19602 }
19603 }
19604
19605 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19606 if hovered != self.gutter_hovered {
19607 self.gutter_hovered = hovered;
19608 cx.notify();
19609 }
19610 }
19611
19612 pub fn insert_blocks(
19613 &mut self,
19614 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19615 autoscroll: Option<Autoscroll>,
19616 cx: &mut Context<Self>,
19617 ) -> Vec<CustomBlockId> {
19618 let blocks = self
19619 .display_map
19620 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19621 if let Some(autoscroll) = autoscroll {
19622 self.request_autoscroll(autoscroll, cx);
19623 }
19624 cx.notify();
19625 blocks
19626 }
19627
19628 pub fn resize_blocks(
19629 &mut self,
19630 heights: HashMap<CustomBlockId, u32>,
19631 autoscroll: Option<Autoscroll>,
19632 cx: &mut Context<Self>,
19633 ) {
19634 self.display_map
19635 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19636 if let Some(autoscroll) = autoscroll {
19637 self.request_autoscroll(autoscroll, cx);
19638 }
19639 cx.notify();
19640 }
19641
19642 pub fn replace_blocks(
19643 &mut self,
19644 renderers: HashMap<CustomBlockId, RenderBlock>,
19645 autoscroll: Option<Autoscroll>,
19646 cx: &mut Context<Self>,
19647 ) {
19648 self.display_map
19649 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19650 if let Some(autoscroll) = autoscroll {
19651 self.request_autoscroll(autoscroll, cx);
19652 }
19653 cx.notify();
19654 }
19655
19656 pub fn remove_blocks(
19657 &mut self,
19658 block_ids: HashSet<CustomBlockId>,
19659 autoscroll: Option<Autoscroll>,
19660 cx: &mut Context<Self>,
19661 ) {
19662 self.display_map.update(cx, |display_map, cx| {
19663 display_map.remove_blocks(block_ids, cx)
19664 });
19665 if let Some(autoscroll) = autoscroll {
19666 self.request_autoscroll(autoscroll, cx);
19667 }
19668 cx.notify();
19669 }
19670
19671 pub fn row_for_block(
19672 &self,
19673 block_id: CustomBlockId,
19674 cx: &mut Context<Self>,
19675 ) -> Option<DisplayRow> {
19676 self.display_map
19677 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19678 }
19679
19680 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19681 self.focused_block = Some(focused_block);
19682 }
19683
19684 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19685 self.focused_block.take()
19686 }
19687
19688 pub fn insert_creases(
19689 &mut self,
19690 creases: impl IntoIterator<Item = Crease<Anchor>>,
19691 cx: &mut Context<Self>,
19692 ) -> Vec<CreaseId> {
19693 self.display_map
19694 .update(cx, |map, cx| map.insert_creases(creases, cx))
19695 }
19696
19697 pub fn remove_creases(
19698 &mut self,
19699 ids: impl IntoIterator<Item = CreaseId>,
19700 cx: &mut Context<Self>,
19701 ) -> Vec<(CreaseId, Range<Anchor>)> {
19702 self.display_map
19703 .update(cx, |map, cx| map.remove_creases(ids, cx))
19704 }
19705
19706 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19707 self.display_map
19708 .update(cx, |map, cx| map.snapshot(cx))
19709 .longest_row()
19710 }
19711
19712 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19713 self.display_map
19714 .update(cx, |map, cx| map.snapshot(cx))
19715 .max_point()
19716 }
19717
19718 pub fn text(&self, cx: &App) -> String {
19719 self.buffer.read(cx).read(cx).text()
19720 }
19721
19722 pub fn is_empty(&self, cx: &App) -> bool {
19723 self.buffer.read(cx).read(cx).is_empty()
19724 }
19725
19726 pub fn text_option(&self, cx: &App) -> Option<String> {
19727 let text = self.text(cx);
19728 let text = text.trim();
19729
19730 if text.is_empty() {
19731 return None;
19732 }
19733
19734 Some(text.to_string())
19735 }
19736
19737 pub fn set_text(
19738 &mut self,
19739 text: impl Into<Arc<str>>,
19740 window: &mut Window,
19741 cx: &mut Context<Self>,
19742 ) {
19743 self.transact(window, cx, |this, _, cx| {
19744 this.buffer
19745 .read(cx)
19746 .as_singleton()
19747 .expect("you can only call set_text on editors for singleton buffers")
19748 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19749 });
19750 }
19751
19752 pub fn display_text(&self, cx: &mut App) -> String {
19753 self.display_map
19754 .update(cx, |map, cx| map.snapshot(cx))
19755 .text()
19756 }
19757
19758 fn create_minimap(
19759 &self,
19760 minimap_settings: MinimapSettings,
19761 window: &mut Window,
19762 cx: &mut Context<Self>,
19763 ) -> Option<Entity<Self>> {
19764 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19765 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19766 }
19767
19768 fn initialize_new_minimap(
19769 &self,
19770 minimap_settings: MinimapSettings,
19771 window: &mut Window,
19772 cx: &mut Context<Self>,
19773 ) -> Entity<Self> {
19774 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19775
19776 let mut minimap = Editor::new_internal(
19777 EditorMode::Minimap {
19778 parent: cx.weak_entity(),
19779 },
19780 self.buffer.clone(),
19781 None,
19782 Some(self.display_map.clone()),
19783 window,
19784 cx,
19785 );
19786 minimap.scroll_manager.clone_state(&self.scroll_manager);
19787 minimap.set_text_style_refinement(TextStyleRefinement {
19788 font_size: Some(MINIMAP_FONT_SIZE),
19789 font_weight: Some(MINIMAP_FONT_WEIGHT),
19790 ..Default::default()
19791 });
19792 minimap.update_minimap_configuration(minimap_settings, cx);
19793 cx.new(|_| minimap)
19794 }
19795
19796 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19797 let current_line_highlight = minimap_settings
19798 .current_line_highlight
19799 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19800 self.set_current_line_highlight(Some(current_line_highlight));
19801 }
19802
19803 pub fn minimap(&self) -> Option<&Entity<Self>> {
19804 self.minimap
19805 .as_ref()
19806 .filter(|_| self.minimap_visibility.visible())
19807 }
19808
19809 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19810 let mut wrap_guides = smallvec![];
19811
19812 if self.show_wrap_guides == Some(false) {
19813 return wrap_guides;
19814 }
19815
19816 let settings = self.buffer.read(cx).language_settings(cx);
19817 if settings.show_wrap_guides {
19818 match self.soft_wrap_mode(cx) {
19819 SoftWrap::Column(soft_wrap) => {
19820 wrap_guides.push((soft_wrap as usize, true));
19821 }
19822 SoftWrap::Bounded(soft_wrap) => {
19823 wrap_guides.push((soft_wrap as usize, true));
19824 }
19825 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19826 }
19827 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19828 }
19829
19830 wrap_guides
19831 }
19832
19833 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19834 let settings = self.buffer.read(cx).language_settings(cx);
19835 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19836 match mode {
19837 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19838 SoftWrap::None
19839 }
19840 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19841 language_settings::SoftWrap::PreferredLineLength => {
19842 SoftWrap::Column(settings.preferred_line_length)
19843 }
19844 language_settings::SoftWrap::Bounded => {
19845 SoftWrap::Bounded(settings.preferred_line_length)
19846 }
19847 }
19848 }
19849
19850 pub fn set_soft_wrap_mode(
19851 &mut self,
19852 mode: language_settings::SoftWrap,
19853
19854 cx: &mut Context<Self>,
19855 ) {
19856 self.soft_wrap_mode_override = Some(mode);
19857 cx.notify();
19858 }
19859
19860 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19861 self.hard_wrap = hard_wrap;
19862 cx.notify();
19863 }
19864
19865 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19866 self.text_style_refinement = Some(style);
19867 }
19868
19869 /// called by the Element so we know what style we were most recently rendered with.
19870 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19871 // We intentionally do not inform the display map about the minimap style
19872 // so that wrapping is not recalculated and stays consistent for the editor
19873 // and its linked minimap.
19874 if !self.mode.is_minimap() {
19875 let font = style.text.font();
19876 let font_size = style.text.font_size.to_pixels(window.rem_size());
19877 let display_map = self
19878 .placeholder_display_map
19879 .as_ref()
19880 .filter(|_| self.is_empty(cx))
19881 .unwrap_or(&self.display_map);
19882
19883 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19884 }
19885 self.style = Some(style);
19886 }
19887
19888 pub fn style(&self) -> Option<&EditorStyle> {
19889 self.style.as_ref()
19890 }
19891
19892 // Called by the element. This method is not designed to be called outside of the editor
19893 // element's layout code because it does not notify when rewrapping is computed synchronously.
19894 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19895 if self.is_empty(cx) {
19896 self.placeholder_display_map
19897 .as_ref()
19898 .map_or(false, |display_map| {
19899 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19900 })
19901 } else {
19902 self.display_map
19903 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19904 }
19905 }
19906
19907 pub fn set_soft_wrap(&mut self) {
19908 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19909 }
19910
19911 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19912 if self.soft_wrap_mode_override.is_some() {
19913 self.soft_wrap_mode_override.take();
19914 } else {
19915 let soft_wrap = match self.soft_wrap_mode(cx) {
19916 SoftWrap::GitDiff => return,
19917 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19918 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19919 language_settings::SoftWrap::None
19920 }
19921 };
19922 self.soft_wrap_mode_override = Some(soft_wrap);
19923 }
19924 cx.notify();
19925 }
19926
19927 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19928 let Some(workspace) = self.workspace() else {
19929 return;
19930 };
19931 let fs = workspace.read(cx).app_state().fs.clone();
19932 let current_show = TabBarSettings::get_global(cx).show;
19933 update_settings_file(fs, cx, move |setting, _| {
19934 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19935 });
19936 }
19937
19938 pub fn toggle_indent_guides(
19939 &mut self,
19940 _: &ToggleIndentGuides,
19941 _: &mut Window,
19942 cx: &mut Context<Self>,
19943 ) {
19944 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19945 self.buffer
19946 .read(cx)
19947 .language_settings(cx)
19948 .indent_guides
19949 .enabled
19950 });
19951 self.show_indent_guides = Some(!currently_enabled);
19952 cx.notify();
19953 }
19954
19955 fn should_show_indent_guides(&self) -> Option<bool> {
19956 self.show_indent_guides
19957 }
19958
19959 pub fn toggle_line_numbers(
19960 &mut self,
19961 _: &ToggleLineNumbers,
19962 _: &mut Window,
19963 cx: &mut Context<Self>,
19964 ) {
19965 let mut editor_settings = EditorSettings::get_global(cx).clone();
19966 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19967 EditorSettings::override_global(editor_settings, cx);
19968 }
19969
19970 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19971 if let Some(show_line_numbers) = self.show_line_numbers {
19972 return show_line_numbers;
19973 }
19974 EditorSettings::get_global(cx).gutter.line_numbers
19975 }
19976
19977 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19978 match (
19979 self.use_relative_line_numbers,
19980 EditorSettings::get_global(cx).relative_line_numbers,
19981 ) {
19982 (None, setting) => setting,
19983 (Some(false), _) => RelativeLineNumbers::Disabled,
19984 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19985 (Some(true), _) => RelativeLineNumbers::Enabled,
19986 }
19987 }
19988
19989 pub fn toggle_relative_line_numbers(
19990 &mut self,
19991 _: &ToggleRelativeLineNumbers,
19992 _: &mut Window,
19993 cx: &mut Context<Self>,
19994 ) {
19995 let is_relative = self.relative_line_numbers(cx);
19996 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19997 }
19998
19999 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20000 self.use_relative_line_numbers = is_relative;
20001 cx.notify();
20002 }
20003
20004 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20005 self.show_gutter = show_gutter;
20006 cx.notify();
20007 }
20008
20009 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20010 self.show_scrollbars = ScrollbarAxes {
20011 horizontal: show,
20012 vertical: show,
20013 };
20014 cx.notify();
20015 }
20016
20017 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20018 self.show_scrollbars.vertical = show;
20019 cx.notify();
20020 }
20021
20022 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20023 self.show_scrollbars.horizontal = show;
20024 cx.notify();
20025 }
20026
20027 pub fn set_minimap_visibility(
20028 &mut self,
20029 minimap_visibility: MinimapVisibility,
20030 window: &mut Window,
20031 cx: &mut Context<Self>,
20032 ) {
20033 if self.minimap_visibility != minimap_visibility {
20034 if minimap_visibility.visible() && self.minimap.is_none() {
20035 let minimap_settings = EditorSettings::get_global(cx).minimap;
20036 self.minimap =
20037 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20038 }
20039 self.minimap_visibility = minimap_visibility;
20040 cx.notify();
20041 }
20042 }
20043
20044 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20045 self.set_show_scrollbars(false, cx);
20046 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20047 }
20048
20049 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20050 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20051 }
20052
20053 /// Normally the text in full mode and auto height editors is padded on the
20054 /// left side by roughly half a character width for improved hit testing.
20055 ///
20056 /// Use this method to disable this for cases where this is not wanted (e.g.
20057 /// if you want to align the editor text with some other text above or below)
20058 /// or if you want to add this padding to single-line editors.
20059 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20060 self.offset_content = offset_content;
20061 cx.notify();
20062 }
20063
20064 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20065 self.show_line_numbers = Some(show_line_numbers);
20066 cx.notify();
20067 }
20068
20069 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20070 self.disable_expand_excerpt_buttons = true;
20071 cx.notify();
20072 }
20073
20074 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20075 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20076 cx.notify();
20077 }
20078
20079 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20080 self.show_code_actions = Some(show_code_actions);
20081 cx.notify();
20082 }
20083
20084 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20085 self.show_runnables = Some(show_runnables);
20086 cx.notify();
20087 }
20088
20089 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20090 self.show_breakpoints = Some(show_breakpoints);
20091 cx.notify();
20092 }
20093
20094 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20095 if self.display_map.read(cx).masked != masked {
20096 self.display_map.update(cx, |map, _| map.masked = masked);
20097 }
20098 cx.notify()
20099 }
20100
20101 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20102 self.show_wrap_guides = Some(show_wrap_guides);
20103 cx.notify();
20104 }
20105
20106 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20107 self.show_indent_guides = Some(show_indent_guides);
20108 cx.notify();
20109 }
20110
20111 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20112 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20113 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20114 && let Some(dir) = file.abs_path(cx).parent()
20115 {
20116 return Some(dir.to_owned());
20117 }
20118 }
20119
20120 None
20121 }
20122
20123 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20124 self.active_excerpt(cx)?
20125 .1
20126 .read(cx)
20127 .file()
20128 .and_then(|f| f.as_local())
20129 }
20130
20131 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20132 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20133 let buffer = buffer.read(cx);
20134 if let Some(project_path) = buffer.project_path(cx) {
20135 let project = self.project()?.read(cx);
20136 project.absolute_path(&project_path, cx)
20137 } else {
20138 buffer
20139 .file()
20140 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20141 }
20142 })
20143 }
20144
20145 pub fn reveal_in_finder(
20146 &mut self,
20147 _: &RevealInFileManager,
20148 _window: &mut Window,
20149 cx: &mut Context<Self>,
20150 ) {
20151 if let Some(target) = self.target_file(cx) {
20152 cx.reveal_path(&target.abs_path(cx));
20153 }
20154 }
20155
20156 pub fn copy_path(
20157 &mut self,
20158 _: &zed_actions::workspace::CopyPath,
20159 _window: &mut Window,
20160 cx: &mut Context<Self>,
20161 ) {
20162 if let Some(path) = self.target_file_abs_path(cx)
20163 && let Some(path) = path.to_str()
20164 {
20165 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20166 } else {
20167 cx.propagate();
20168 }
20169 }
20170
20171 pub fn copy_relative_path(
20172 &mut self,
20173 _: &zed_actions::workspace::CopyRelativePath,
20174 _window: &mut Window,
20175 cx: &mut Context<Self>,
20176 ) {
20177 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20178 let project = self.project()?.read(cx);
20179 let path = buffer.read(cx).file()?.path();
20180 let path = path.display(project.path_style(cx));
20181 Some(path)
20182 }) {
20183 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20184 } else {
20185 cx.propagate();
20186 }
20187 }
20188
20189 /// Returns the project path for the editor's buffer, if any buffer is
20190 /// opened in the editor.
20191 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20192 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20193 buffer.read(cx).project_path(cx)
20194 } else {
20195 None
20196 }
20197 }
20198
20199 // Returns true if the editor handled a go-to-line request
20200 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20201 maybe!({
20202 let breakpoint_store = self.breakpoint_store.as_ref()?;
20203
20204 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20205 else {
20206 self.clear_row_highlights::<ActiveDebugLine>();
20207 return None;
20208 };
20209
20210 let position = active_stack_frame.position;
20211 let buffer_id = position.buffer_id?;
20212 let snapshot = self
20213 .project
20214 .as_ref()?
20215 .read(cx)
20216 .buffer_for_id(buffer_id, cx)?
20217 .read(cx)
20218 .snapshot();
20219
20220 let mut handled = false;
20221 for (id, ExcerptRange { context, .. }) in
20222 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20223 {
20224 if context.start.cmp(&position, &snapshot).is_ge()
20225 || context.end.cmp(&position, &snapshot).is_lt()
20226 {
20227 continue;
20228 }
20229 let snapshot = self.buffer.read(cx).snapshot(cx);
20230 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20231
20232 handled = true;
20233 self.clear_row_highlights::<ActiveDebugLine>();
20234
20235 self.go_to_line::<ActiveDebugLine>(
20236 multibuffer_anchor,
20237 Some(cx.theme().colors().editor_debugger_active_line_background),
20238 window,
20239 cx,
20240 );
20241
20242 cx.notify();
20243 }
20244
20245 handled.then_some(())
20246 })
20247 .is_some()
20248 }
20249
20250 pub fn copy_file_name_without_extension(
20251 &mut self,
20252 _: &CopyFileNameWithoutExtension,
20253 _: &mut Window,
20254 cx: &mut Context<Self>,
20255 ) {
20256 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20257 let file = buffer.read(cx).file()?;
20258 file.path().file_stem()
20259 }) {
20260 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20261 }
20262 }
20263
20264 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20265 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20266 let file = buffer.read(cx).file()?;
20267 Some(file.file_name(cx))
20268 }) {
20269 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20270 }
20271 }
20272
20273 pub fn toggle_git_blame(
20274 &mut self,
20275 _: &::git::Blame,
20276 window: &mut Window,
20277 cx: &mut Context<Self>,
20278 ) {
20279 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20280
20281 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20282 self.start_git_blame(true, window, cx);
20283 }
20284
20285 cx.notify();
20286 }
20287
20288 pub fn toggle_git_blame_inline(
20289 &mut self,
20290 _: &ToggleGitBlameInline,
20291 window: &mut Window,
20292 cx: &mut Context<Self>,
20293 ) {
20294 self.toggle_git_blame_inline_internal(true, window, cx);
20295 cx.notify();
20296 }
20297
20298 pub fn open_git_blame_commit(
20299 &mut self,
20300 _: &OpenGitBlameCommit,
20301 window: &mut Window,
20302 cx: &mut Context<Self>,
20303 ) {
20304 self.open_git_blame_commit_internal(window, cx);
20305 }
20306
20307 fn open_git_blame_commit_internal(
20308 &mut self,
20309 window: &mut Window,
20310 cx: &mut Context<Self>,
20311 ) -> Option<()> {
20312 let blame = self.blame.as_ref()?;
20313 let snapshot = self.snapshot(window, cx);
20314 let cursor = self
20315 .selections
20316 .newest::<Point>(&snapshot.display_snapshot)
20317 .head();
20318 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20319 let (_, blame_entry) = blame
20320 .update(cx, |blame, cx| {
20321 blame
20322 .blame_for_rows(
20323 &[RowInfo {
20324 buffer_id: Some(buffer.remote_id()),
20325 buffer_row: Some(point.row),
20326 ..Default::default()
20327 }],
20328 cx,
20329 )
20330 .next()
20331 })
20332 .flatten()?;
20333 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20334 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20335 let workspace = self.workspace()?.downgrade();
20336 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20337 None
20338 }
20339
20340 pub fn git_blame_inline_enabled(&self) -> bool {
20341 self.git_blame_inline_enabled
20342 }
20343
20344 pub fn toggle_selection_menu(
20345 &mut self,
20346 _: &ToggleSelectionMenu,
20347 _: &mut Window,
20348 cx: &mut Context<Self>,
20349 ) {
20350 self.show_selection_menu = self
20351 .show_selection_menu
20352 .map(|show_selections_menu| !show_selections_menu)
20353 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20354
20355 cx.notify();
20356 }
20357
20358 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20359 self.show_selection_menu
20360 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20361 }
20362
20363 fn start_git_blame(
20364 &mut self,
20365 user_triggered: bool,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) {
20369 if let Some(project) = self.project() {
20370 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20371 && buffer.read(cx).file().is_none()
20372 {
20373 return;
20374 }
20375
20376 let focused = self.focus_handle(cx).contains_focused(window, cx);
20377
20378 let project = project.clone();
20379 let blame = cx
20380 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20381 self.blame_subscription =
20382 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20383 self.blame = Some(blame);
20384 }
20385 }
20386
20387 fn toggle_git_blame_inline_internal(
20388 &mut self,
20389 user_triggered: bool,
20390 window: &mut Window,
20391 cx: &mut Context<Self>,
20392 ) {
20393 if self.git_blame_inline_enabled {
20394 self.git_blame_inline_enabled = false;
20395 self.show_git_blame_inline = false;
20396 self.show_git_blame_inline_delay_task.take();
20397 } else {
20398 self.git_blame_inline_enabled = true;
20399 self.start_git_blame_inline(user_triggered, window, cx);
20400 }
20401
20402 cx.notify();
20403 }
20404
20405 fn start_git_blame_inline(
20406 &mut self,
20407 user_triggered: bool,
20408 window: &mut Window,
20409 cx: &mut Context<Self>,
20410 ) {
20411 self.start_git_blame(user_triggered, window, cx);
20412
20413 if ProjectSettings::get_global(cx)
20414 .git
20415 .inline_blame_delay()
20416 .is_some()
20417 {
20418 self.start_inline_blame_timer(window, cx);
20419 } else {
20420 self.show_git_blame_inline = true
20421 }
20422 }
20423
20424 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20425 self.blame.as_ref()
20426 }
20427
20428 pub fn show_git_blame_gutter(&self) -> bool {
20429 self.show_git_blame_gutter
20430 }
20431
20432 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20433 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20434 }
20435
20436 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20437 self.show_git_blame_inline
20438 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20439 && !self.newest_selection_head_on_empty_line(cx)
20440 && self.has_blame_entries(cx)
20441 }
20442
20443 fn has_blame_entries(&self, cx: &App) -> bool {
20444 self.blame()
20445 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20446 }
20447
20448 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20449 let cursor_anchor = self.selections.newest_anchor().head();
20450
20451 let snapshot = self.buffer.read(cx).snapshot(cx);
20452 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20453
20454 snapshot.line_len(buffer_row) == 0
20455 }
20456
20457 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20458 let buffer_and_selection = maybe!({
20459 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20460 let selection_range = selection.range();
20461
20462 let multi_buffer = self.buffer().read(cx);
20463 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20464 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20465
20466 let (buffer, range, _) = if selection.reversed {
20467 buffer_ranges.first()
20468 } else {
20469 buffer_ranges.last()
20470 }?;
20471
20472 let selection = text::ToPoint::to_point(&range.start, buffer).row
20473 ..text::ToPoint::to_point(&range.end, buffer).row;
20474 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20475 });
20476
20477 let Some((buffer, selection)) = buffer_and_selection else {
20478 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20479 };
20480
20481 let Some(project) = self.project() else {
20482 return Task::ready(Err(anyhow!("editor does not have project")));
20483 };
20484
20485 project.update(cx, |project, cx| {
20486 project.get_permalink_to_line(&buffer, selection, cx)
20487 })
20488 }
20489
20490 pub fn copy_permalink_to_line(
20491 &mut self,
20492 _: &CopyPermalinkToLine,
20493 window: &mut Window,
20494 cx: &mut Context<Self>,
20495 ) {
20496 let permalink_task = self.get_permalink_to_line(cx);
20497 let workspace = self.workspace();
20498
20499 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20500 Ok(permalink) => {
20501 cx.update(|_, cx| {
20502 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20503 })
20504 .ok();
20505 }
20506 Err(err) => {
20507 let message = format!("Failed to copy permalink: {err}");
20508
20509 anyhow::Result::<()>::Err(err).log_err();
20510
20511 if let Some(workspace) = workspace {
20512 workspace
20513 .update_in(cx, |workspace, _, cx| {
20514 struct CopyPermalinkToLine;
20515
20516 workspace.show_toast(
20517 Toast::new(
20518 NotificationId::unique::<CopyPermalinkToLine>(),
20519 message,
20520 ),
20521 cx,
20522 )
20523 })
20524 .ok();
20525 }
20526 }
20527 })
20528 .detach();
20529 }
20530
20531 pub fn copy_file_location(
20532 &mut self,
20533 _: &CopyFileLocation,
20534 _: &mut Window,
20535 cx: &mut Context<Self>,
20536 ) {
20537 let selection = self
20538 .selections
20539 .newest::<Point>(&self.display_snapshot(cx))
20540 .start
20541 .row
20542 + 1;
20543 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20544 let project = self.project()?.read(cx);
20545 let file = buffer.read(cx).file()?;
20546 let path = file.path().display(project.path_style(cx));
20547
20548 Some(format!("{path}:{selection}"))
20549 }) {
20550 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20551 }
20552 }
20553
20554 pub fn open_permalink_to_line(
20555 &mut self,
20556 _: &OpenPermalinkToLine,
20557 window: &mut Window,
20558 cx: &mut Context<Self>,
20559 ) {
20560 let permalink_task = self.get_permalink_to_line(cx);
20561 let workspace = self.workspace();
20562
20563 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20564 Ok(permalink) => {
20565 cx.update(|_, cx| {
20566 cx.open_url(permalink.as_ref());
20567 })
20568 .ok();
20569 }
20570 Err(err) => {
20571 let message = format!("Failed to open permalink: {err}");
20572
20573 anyhow::Result::<()>::Err(err).log_err();
20574
20575 if let Some(workspace) = workspace {
20576 workspace
20577 .update(cx, |workspace, cx| {
20578 struct OpenPermalinkToLine;
20579
20580 workspace.show_toast(
20581 Toast::new(
20582 NotificationId::unique::<OpenPermalinkToLine>(),
20583 message,
20584 ),
20585 cx,
20586 )
20587 })
20588 .ok();
20589 }
20590 }
20591 })
20592 .detach();
20593 }
20594
20595 pub fn insert_uuid_v4(
20596 &mut self,
20597 _: &InsertUuidV4,
20598 window: &mut Window,
20599 cx: &mut Context<Self>,
20600 ) {
20601 self.insert_uuid(UuidVersion::V4, window, cx);
20602 }
20603
20604 pub fn insert_uuid_v7(
20605 &mut self,
20606 _: &InsertUuidV7,
20607 window: &mut Window,
20608 cx: &mut Context<Self>,
20609 ) {
20610 self.insert_uuid(UuidVersion::V7, window, cx);
20611 }
20612
20613 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20615 self.transact(window, cx, |this, window, cx| {
20616 let edits = this
20617 .selections
20618 .all::<Point>(&this.display_snapshot(cx))
20619 .into_iter()
20620 .map(|selection| {
20621 let uuid = match version {
20622 UuidVersion::V4 => uuid::Uuid::new_v4(),
20623 UuidVersion::V7 => uuid::Uuid::now_v7(),
20624 };
20625
20626 (selection.range(), uuid.to_string())
20627 });
20628 this.edit(edits, cx);
20629 this.refresh_edit_prediction(true, false, window, cx);
20630 });
20631 }
20632
20633 pub fn open_selections_in_multibuffer(
20634 &mut self,
20635 _: &OpenSelectionsInMultibuffer,
20636 window: &mut Window,
20637 cx: &mut Context<Self>,
20638 ) {
20639 let multibuffer = self.buffer.read(cx);
20640
20641 let Some(buffer) = multibuffer.as_singleton() else {
20642 return;
20643 };
20644
20645 let Some(workspace) = self.workspace() else {
20646 return;
20647 };
20648
20649 let title = multibuffer.title(cx).to_string();
20650
20651 let locations = self
20652 .selections
20653 .all_anchors(&self.display_snapshot(cx))
20654 .iter()
20655 .map(|selection| {
20656 (
20657 buffer.clone(),
20658 (selection.start.text_anchor..selection.end.text_anchor)
20659 .to_point(buffer.read(cx)),
20660 )
20661 })
20662 .into_group_map();
20663
20664 cx.spawn_in(window, async move |_, cx| {
20665 workspace.update_in(cx, |workspace, window, cx| {
20666 Self::open_locations_in_multibuffer(
20667 workspace,
20668 locations,
20669 format!("Selections for '{title}'"),
20670 false,
20671 MultibufferSelectionMode::All,
20672 window,
20673 cx,
20674 );
20675 })
20676 })
20677 .detach();
20678 }
20679
20680 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20681 /// last highlight added will be used.
20682 ///
20683 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20684 pub fn highlight_rows<T: 'static>(
20685 &mut self,
20686 range: Range<Anchor>,
20687 color: Hsla,
20688 options: RowHighlightOptions,
20689 cx: &mut Context<Self>,
20690 ) {
20691 let snapshot = self.buffer().read(cx).snapshot(cx);
20692 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20693 let ix = row_highlights.binary_search_by(|highlight| {
20694 Ordering::Equal
20695 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20696 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20697 });
20698
20699 if let Err(mut ix) = ix {
20700 let index = post_inc(&mut self.highlight_order);
20701
20702 // If this range intersects with the preceding highlight, then merge it with
20703 // the preceding highlight. Otherwise insert a new highlight.
20704 let mut merged = false;
20705 if ix > 0 {
20706 let prev_highlight = &mut row_highlights[ix - 1];
20707 if prev_highlight
20708 .range
20709 .end
20710 .cmp(&range.start, &snapshot)
20711 .is_ge()
20712 {
20713 ix -= 1;
20714 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20715 prev_highlight.range.end = range.end;
20716 }
20717 merged = true;
20718 prev_highlight.index = index;
20719 prev_highlight.color = color;
20720 prev_highlight.options = options;
20721 }
20722 }
20723
20724 if !merged {
20725 row_highlights.insert(
20726 ix,
20727 RowHighlight {
20728 range,
20729 index,
20730 color,
20731 options,
20732 type_id: TypeId::of::<T>(),
20733 },
20734 );
20735 }
20736
20737 // If any of the following highlights intersect with this one, merge them.
20738 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20739 let highlight = &row_highlights[ix];
20740 if next_highlight
20741 .range
20742 .start
20743 .cmp(&highlight.range.end, &snapshot)
20744 .is_le()
20745 {
20746 if next_highlight
20747 .range
20748 .end
20749 .cmp(&highlight.range.end, &snapshot)
20750 .is_gt()
20751 {
20752 row_highlights[ix].range.end = next_highlight.range.end;
20753 }
20754 row_highlights.remove(ix + 1);
20755 } else {
20756 break;
20757 }
20758 }
20759 }
20760 }
20761
20762 /// Remove any highlighted row ranges of the given type that intersect the
20763 /// given ranges.
20764 pub fn remove_highlighted_rows<T: 'static>(
20765 &mut self,
20766 ranges_to_remove: Vec<Range<Anchor>>,
20767 cx: &mut Context<Self>,
20768 ) {
20769 let snapshot = self.buffer().read(cx).snapshot(cx);
20770 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20771 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20772 row_highlights.retain(|highlight| {
20773 while let Some(range_to_remove) = ranges_to_remove.peek() {
20774 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20775 Ordering::Less | Ordering::Equal => {
20776 ranges_to_remove.next();
20777 }
20778 Ordering::Greater => {
20779 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20780 Ordering::Less | Ordering::Equal => {
20781 return false;
20782 }
20783 Ordering::Greater => break,
20784 }
20785 }
20786 }
20787 }
20788
20789 true
20790 })
20791 }
20792
20793 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20794 pub fn clear_row_highlights<T: 'static>(&mut self) {
20795 self.highlighted_rows.remove(&TypeId::of::<T>());
20796 }
20797
20798 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20799 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20800 self.highlighted_rows
20801 .get(&TypeId::of::<T>())
20802 .map_or(&[] as &[_], |vec| vec.as_slice())
20803 .iter()
20804 .map(|highlight| (highlight.range.clone(), highlight.color))
20805 }
20806
20807 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20808 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20809 /// Allows to ignore certain kinds of highlights.
20810 pub fn highlighted_display_rows(
20811 &self,
20812 window: &mut Window,
20813 cx: &mut App,
20814 ) -> BTreeMap<DisplayRow, LineHighlight> {
20815 let snapshot = self.snapshot(window, cx);
20816 let mut used_highlight_orders = HashMap::default();
20817 self.highlighted_rows
20818 .iter()
20819 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20820 .fold(
20821 BTreeMap::<DisplayRow, LineHighlight>::new(),
20822 |mut unique_rows, highlight| {
20823 let start = highlight.range.start.to_display_point(&snapshot);
20824 let end = highlight.range.end.to_display_point(&snapshot);
20825 let start_row = start.row().0;
20826 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20827 {
20828 end.row().0.saturating_sub(1)
20829 } else {
20830 end.row().0
20831 };
20832 for row in start_row..=end_row {
20833 let used_index =
20834 used_highlight_orders.entry(row).or_insert(highlight.index);
20835 if highlight.index >= *used_index {
20836 *used_index = highlight.index;
20837 unique_rows.insert(
20838 DisplayRow(row),
20839 LineHighlight {
20840 include_gutter: highlight.options.include_gutter,
20841 border: None,
20842 background: highlight.color.into(),
20843 type_id: Some(highlight.type_id),
20844 },
20845 );
20846 }
20847 }
20848 unique_rows
20849 },
20850 )
20851 }
20852
20853 pub fn highlighted_display_row_for_autoscroll(
20854 &self,
20855 snapshot: &DisplaySnapshot,
20856 ) -> Option<DisplayRow> {
20857 self.highlighted_rows
20858 .values()
20859 .flat_map(|highlighted_rows| highlighted_rows.iter())
20860 .filter_map(|highlight| {
20861 if highlight.options.autoscroll {
20862 Some(highlight.range.start.to_display_point(snapshot).row())
20863 } else {
20864 None
20865 }
20866 })
20867 .min()
20868 }
20869
20870 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20871 self.highlight_background::<SearchWithinRange>(
20872 ranges,
20873 |colors| colors.colors().editor_document_highlight_read_background,
20874 cx,
20875 )
20876 }
20877
20878 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20879 self.breadcrumb_header = Some(new_header);
20880 }
20881
20882 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20883 self.clear_background_highlights::<SearchWithinRange>(cx);
20884 }
20885
20886 pub fn highlight_background<T: 'static>(
20887 &mut self,
20888 ranges: &[Range<Anchor>],
20889 color_fetcher: fn(&Theme) -> Hsla,
20890 cx: &mut Context<Self>,
20891 ) {
20892 self.background_highlights.insert(
20893 HighlightKey::Type(TypeId::of::<T>()),
20894 (color_fetcher, Arc::from(ranges)),
20895 );
20896 self.scrollbar_marker_state.dirty = true;
20897 cx.notify();
20898 }
20899
20900 pub fn highlight_background_key<T: 'static>(
20901 &mut self,
20902 key: usize,
20903 ranges: &[Range<Anchor>],
20904 color_fetcher: fn(&Theme) -> Hsla,
20905 cx: &mut Context<Self>,
20906 ) {
20907 self.background_highlights.insert(
20908 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20909 (color_fetcher, Arc::from(ranges)),
20910 );
20911 self.scrollbar_marker_state.dirty = true;
20912 cx.notify();
20913 }
20914
20915 pub fn clear_background_highlights<T: 'static>(
20916 &mut self,
20917 cx: &mut Context<Self>,
20918 ) -> Option<BackgroundHighlight> {
20919 let text_highlights = self
20920 .background_highlights
20921 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20922 if !text_highlights.1.is_empty() {
20923 self.scrollbar_marker_state.dirty = true;
20924 cx.notify();
20925 }
20926 Some(text_highlights)
20927 }
20928
20929 pub fn highlight_gutter<T: 'static>(
20930 &mut self,
20931 ranges: impl Into<Vec<Range<Anchor>>>,
20932 color_fetcher: fn(&App) -> Hsla,
20933 cx: &mut Context<Self>,
20934 ) {
20935 self.gutter_highlights
20936 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20937 cx.notify();
20938 }
20939
20940 pub fn clear_gutter_highlights<T: 'static>(
20941 &mut self,
20942 cx: &mut Context<Self>,
20943 ) -> Option<GutterHighlight> {
20944 cx.notify();
20945 self.gutter_highlights.remove(&TypeId::of::<T>())
20946 }
20947
20948 pub fn insert_gutter_highlight<T: 'static>(
20949 &mut self,
20950 range: Range<Anchor>,
20951 color_fetcher: fn(&App) -> Hsla,
20952 cx: &mut Context<Self>,
20953 ) {
20954 let snapshot = self.buffer().read(cx).snapshot(cx);
20955 let mut highlights = self
20956 .gutter_highlights
20957 .remove(&TypeId::of::<T>())
20958 .map(|(_, highlights)| highlights)
20959 .unwrap_or_default();
20960 let ix = highlights.binary_search_by(|highlight| {
20961 Ordering::Equal
20962 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20963 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20964 });
20965 if let Err(ix) = ix {
20966 highlights.insert(ix, range);
20967 }
20968 self.gutter_highlights
20969 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20970 }
20971
20972 pub fn remove_gutter_highlights<T: 'static>(
20973 &mut self,
20974 ranges_to_remove: Vec<Range<Anchor>>,
20975 cx: &mut Context<Self>,
20976 ) {
20977 let snapshot = self.buffer().read(cx).snapshot(cx);
20978 let Some((color_fetcher, mut gutter_highlights)) =
20979 self.gutter_highlights.remove(&TypeId::of::<T>())
20980 else {
20981 return;
20982 };
20983 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20984 gutter_highlights.retain(|highlight| {
20985 while let Some(range_to_remove) = ranges_to_remove.peek() {
20986 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20987 Ordering::Less | Ordering::Equal => {
20988 ranges_to_remove.next();
20989 }
20990 Ordering::Greater => {
20991 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20992 Ordering::Less | Ordering::Equal => {
20993 return false;
20994 }
20995 Ordering::Greater => break,
20996 }
20997 }
20998 }
20999 }
21000
21001 true
21002 });
21003 self.gutter_highlights
21004 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21005 }
21006
21007 #[cfg(feature = "test-support")]
21008 pub fn all_text_highlights(
21009 &self,
21010 window: &mut Window,
21011 cx: &mut Context<Self>,
21012 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21013 let snapshot = self.snapshot(window, cx);
21014 self.display_map.update(cx, |display_map, _| {
21015 display_map
21016 .all_text_highlights()
21017 .map(|highlight| {
21018 let (style, ranges) = highlight.as_ref();
21019 (
21020 *style,
21021 ranges
21022 .iter()
21023 .map(|range| range.clone().to_display_points(&snapshot))
21024 .collect(),
21025 )
21026 })
21027 .collect()
21028 })
21029 }
21030
21031 #[cfg(feature = "test-support")]
21032 pub fn all_text_background_highlights(
21033 &self,
21034 window: &mut Window,
21035 cx: &mut Context<Self>,
21036 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21037 let snapshot = self.snapshot(window, cx);
21038 let buffer = &snapshot.buffer_snapshot();
21039 let start = buffer.anchor_before(MultiBufferOffset(0));
21040 let end = buffer.anchor_after(buffer.len());
21041 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21042 }
21043
21044 #[cfg(any(test, feature = "test-support"))]
21045 pub fn sorted_background_highlights_in_range(
21046 &self,
21047 search_range: Range<Anchor>,
21048 display_snapshot: &DisplaySnapshot,
21049 theme: &Theme,
21050 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21051 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21052 res.sort_by(|a, b| {
21053 a.0.start
21054 .cmp(&b.0.start)
21055 .then_with(|| a.0.end.cmp(&b.0.end))
21056 .then_with(|| a.1.cmp(&b.1))
21057 });
21058 res
21059 }
21060
21061 #[cfg(feature = "test-support")]
21062 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21063 let snapshot = self.buffer().read(cx).snapshot(cx);
21064
21065 let highlights = self
21066 .background_highlights
21067 .get(&HighlightKey::Type(TypeId::of::<
21068 items::BufferSearchHighlights,
21069 >()));
21070
21071 if let Some((_color, ranges)) = highlights {
21072 ranges
21073 .iter()
21074 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21075 .collect_vec()
21076 } else {
21077 vec![]
21078 }
21079 }
21080
21081 fn document_highlights_for_position<'a>(
21082 &'a self,
21083 position: Anchor,
21084 buffer: &'a MultiBufferSnapshot,
21085 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21086 let read_highlights = self
21087 .background_highlights
21088 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21089 .map(|h| &h.1);
21090 let write_highlights = self
21091 .background_highlights
21092 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21093 .map(|h| &h.1);
21094 let left_position = position.bias_left(buffer);
21095 let right_position = position.bias_right(buffer);
21096 read_highlights
21097 .into_iter()
21098 .chain(write_highlights)
21099 .flat_map(move |ranges| {
21100 let start_ix = match ranges.binary_search_by(|probe| {
21101 let cmp = probe.end.cmp(&left_position, buffer);
21102 if cmp.is_ge() {
21103 Ordering::Greater
21104 } else {
21105 Ordering::Less
21106 }
21107 }) {
21108 Ok(i) | Err(i) => i,
21109 };
21110
21111 ranges[start_ix..]
21112 .iter()
21113 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21114 })
21115 }
21116
21117 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21118 self.background_highlights
21119 .get(&HighlightKey::Type(TypeId::of::<T>()))
21120 .is_some_and(|(_, highlights)| !highlights.is_empty())
21121 }
21122
21123 /// Returns all background highlights for a given range.
21124 ///
21125 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21126 pub fn background_highlights_in_range(
21127 &self,
21128 search_range: Range<Anchor>,
21129 display_snapshot: &DisplaySnapshot,
21130 theme: &Theme,
21131 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21132 let mut results = Vec::new();
21133 for (color_fetcher, ranges) in self.background_highlights.values() {
21134 let color = color_fetcher(theme);
21135 let start_ix = match ranges.binary_search_by(|probe| {
21136 let cmp = probe
21137 .end
21138 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21139 if cmp.is_gt() {
21140 Ordering::Greater
21141 } else {
21142 Ordering::Less
21143 }
21144 }) {
21145 Ok(i) | Err(i) => i,
21146 };
21147 for range in &ranges[start_ix..] {
21148 if range
21149 .start
21150 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21151 .is_ge()
21152 {
21153 break;
21154 }
21155
21156 let start = range.start.to_display_point(display_snapshot);
21157 let end = range.end.to_display_point(display_snapshot);
21158 results.push((start..end, color))
21159 }
21160 }
21161 results
21162 }
21163
21164 pub fn gutter_highlights_in_range(
21165 &self,
21166 search_range: Range<Anchor>,
21167 display_snapshot: &DisplaySnapshot,
21168 cx: &App,
21169 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21170 let mut results = Vec::new();
21171 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21172 let color = color_fetcher(cx);
21173 let start_ix = match ranges.binary_search_by(|probe| {
21174 let cmp = probe
21175 .end
21176 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21177 if cmp.is_gt() {
21178 Ordering::Greater
21179 } else {
21180 Ordering::Less
21181 }
21182 }) {
21183 Ok(i) | Err(i) => i,
21184 };
21185 for range in &ranges[start_ix..] {
21186 if range
21187 .start
21188 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21189 .is_ge()
21190 {
21191 break;
21192 }
21193
21194 let start = range.start.to_display_point(display_snapshot);
21195 let end = range.end.to_display_point(display_snapshot);
21196 results.push((start..end, color))
21197 }
21198 }
21199 results
21200 }
21201
21202 /// Get the text ranges corresponding to the redaction query
21203 pub fn redacted_ranges(
21204 &self,
21205 search_range: Range<Anchor>,
21206 display_snapshot: &DisplaySnapshot,
21207 cx: &App,
21208 ) -> Vec<Range<DisplayPoint>> {
21209 display_snapshot
21210 .buffer_snapshot()
21211 .redacted_ranges(search_range, |file| {
21212 if let Some(file) = file {
21213 file.is_private()
21214 && EditorSettings::get(
21215 Some(SettingsLocation {
21216 worktree_id: file.worktree_id(cx),
21217 path: file.path().as_ref(),
21218 }),
21219 cx,
21220 )
21221 .redact_private_values
21222 } else {
21223 false
21224 }
21225 })
21226 .map(|range| {
21227 range.start.to_display_point(display_snapshot)
21228 ..range.end.to_display_point(display_snapshot)
21229 })
21230 .collect()
21231 }
21232
21233 pub fn highlight_text_key<T: 'static>(
21234 &mut self,
21235 key: usize,
21236 ranges: Vec<Range<Anchor>>,
21237 style: HighlightStyle,
21238 merge: bool,
21239 cx: &mut Context<Self>,
21240 ) {
21241 self.display_map.update(cx, |map, cx| {
21242 map.highlight_text(
21243 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21244 ranges,
21245 style,
21246 merge,
21247 cx,
21248 );
21249 });
21250 cx.notify();
21251 }
21252
21253 pub fn highlight_text<T: 'static>(
21254 &mut self,
21255 ranges: Vec<Range<Anchor>>,
21256 style: HighlightStyle,
21257 cx: &mut Context<Self>,
21258 ) {
21259 self.display_map.update(cx, |map, cx| {
21260 map.highlight_text(
21261 HighlightKey::Type(TypeId::of::<T>()),
21262 ranges,
21263 style,
21264 false,
21265 cx,
21266 )
21267 });
21268 cx.notify();
21269 }
21270
21271 pub fn text_highlights<'a, T: 'static>(
21272 &'a self,
21273 cx: &'a App,
21274 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21275 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21276 }
21277
21278 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21279 let cleared = self
21280 .display_map
21281 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21282 if cleared {
21283 cx.notify();
21284 }
21285 }
21286
21287 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21288 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21289 && self.focus_handle.is_focused(window)
21290 }
21291
21292 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21293 self.show_cursor_when_unfocused = is_enabled;
21294 cx.notify();
21295 }
21296
21297 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21298 cx.notify();
21299 }
21300
21301 fn on_debug_session_event(
21302 &mut self,
21303 _session: Entity<Session>,
21304 event: &SessionEvent,
21305 cx: &mut Context<Self>,
21306 ) {
21307 if let SessionEvent::InvalidateInlineValue = event {
21308 self.refresh_inline_values(cx);
21309 }
21310 }
21311
21312 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21313 let Some(project) = self.project.clone() else {
21314 return;
21315 };
21316
21317 if !self.inline_value_cache.enabled {
21318 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21319 self.splice_inlays(&inlays, Vec::new(), cx);
21320 return;
21321 }
21322
21323 let current_execution_position = self
21324 .highlighted_rows
21325 .get(&TypeId::of::<ActiveDebugLine>())
21326 .and_then(|lines| lines.last().map(|line| line.range.end));
21327
21328 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21329 let inline_values = editor
21330 .update(cx, |editor, cx| {
21331 let Some(current_execution_position) = current_execution_position else {
21332 return Some(Task::ready(Ok(Vec::new())));
21333 };
21334
21335 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21336 let snapshot = buffer.snapshot(cx);
21337
21338 let excerpt = snapshot.excerpt_containing(
21339 current_execution_position..current_execution_position,
21340 )?;
21341
21342 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21343 })?;
21344
21345 let range =
21346 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21347
21348 project.inline_values(buffer, range, cx)
21349 })
21350 .ok()
21351 .flatten()?
21352 .await
21353 .context("refreshing debugger inlays")
21354 .log_err()?;
21355
21356 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21357
21358 for (buffer_id, inline_value) in inline_values
21359 .into_iter()
21360 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21361 {
21362 buffer_inline_values
21363 .entry(buffer_id)
21364 .or_default()
21365 .push(inline_value);
21366 }
21367
21368 editor
21369 .update(cx, |editor, cx| {
21370 let snapshot = editor.buffer.read(cx).snapshot(cx);
21371 let mut new_inlays = Vec::default();
21372
21373 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21374 let buffer_id = buffer_snapshot.remote_id();
21375 buffer_inline_values
21376 .get(&buffer_id)
21377 .into_iter()
21378 .flatten()
21379 .for_each(|hint| {
21380 let inlay = Inlay::debugger(
21381 post_inc(&mut editor.next_inlay_id),
21382 Anchor::in_buffer(excerpt_id, hint.position),
21383 hint.text(),
21384 );
21385 if !inlay.text().chars().contains(&'\n') {
21386 new_inlays.push(inlay);
21387 }
21388 });
21389 }
21390
21391 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21392 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21393
21394 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21395 })
21396 .ok()?;
21397 Some(())
21398 });
21399 }
21400
21401 fn on_buffer_event(
21402 &mut self,
21403 multibuffer: &Entity<MultiBuffer>,
21404 event: &multi_buffer::Event,
21405 window: &mut Window,
21406 cx: &mut Context<Self>,
21407 ) {
21408 match event {
21409 multi_buffer::Event::Edited { edited_buffer } => {
21410 self.scrollbar_marker_state.dirty = true;
21411 self.active_indent_guides_state.dirty = true;
21412 self.refresh_active_diagnostics(cx);
21413 self.refresh_code_actions(window, cx);
21414 self.refresh_single_line_folds(window, cx);
21415 self.refresh_matching_bracket_highlights(window, cx);
21416 if self.has_active_edit_prediction() {
21417 self.update_visible_edit_prediction(window, cx);
21418 }
21419
21420 if let Some(buffer) = edited_buffer {
21421 if buffer.read(cx).file().is_none() {
21422 cx.emit(EditorEvent::TitleChanged);
21423 }
21424
21425 if self.project.is_some() {
21426 let buffer_id = buffer.read(cx).remote_id();
21427 self.register_buffer(buffer_id, cx);
21428 self.update_lsp_data(Some(buffer_id), window, cx);
21429 self.refresh_inlay_hints(
21430 InlayHintRefreshReason::BufferEdited(buffer_id),
21431 cx,
21432 );
21433 }
21434 }
21435
21436 cx.emit(EditorEvent::BufferEdited);
21437 cx.emit(SearchEvent::MatchesInvalidated);
21438
21439 let Some(project) = &self.project else { return };
21440 let (telemetry, is_via_ssh) = {
21441 let project = project.read(cx);
21442 let telemetry = project.client().telemetry().clone();
21443 let is_via_ssh = project.is_via_remote_server();
21444 (telemetry, is_via_ssh)
21445 };
21446 telemetry.log_edit_event("editor", is_via_ssh);
21447 }
21448 multi_buffer::Event::ExcerptsAdded {
21449 buffer,
21450 predecessor,
21451 excerpts,
21452 } => {
21453 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21454 let buffer_id = buffer.read(cx).remote_id();
21455 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21456 && let Some(project) = &self.project
21457 {
21458 update_uncommitted_diff_for_buffer(
21459 cx.entity(),
21460 project,
21461 [buffer.clone()],
21462 self.buffer.clone(),
21463 cx,
21464 )
21465 .detach();
21466 }
21467 self.update_lsp_data(Some(buffer_id), window, cx);
21468 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21469 self.colorize_brackets(false, cx);
21470 cx.emit(EditorEvent::ExcerptsAdded {
21471 buffer: buffer.clone(),
21472 predecessor: *predecessor,
21473 excerpts: excerpts.clone(),
21474 });
21475 }
21476 multi_buffer::Event::ExcerptsRemoved {
21477 ids,
21478 removed_buffer_ids,
21479 } => {
21480 if let Some(inlay_hints) = &mut self.inlay_hints {
21481 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21482 }
21483 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21484 for buffer_id in removed_buffer_ids {
21485 self.registered_buffers.remove(buffer_id);
21486 }
21487 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21488 cx.emit(EditorEvent::ExcerptsRemoved {
21489 ids: ids.clone(),
21490 removed_buffer_ids: removed_buffer_ids.clone(),
21491 });
21492 }
21493 multi_buffer::Event::ExcerptsEdited {
21494 excerpt_ids,
21495 buffer_ids,
21496 } => {
21497 self.display_map.update(cx, |map, cx| {
21498 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21499 });
21500 cx.emit(EditorEvent::ExcerptsEdited {
21501 ids: excerpt_ids.clone(),
21502 });
21503 }
21504 multi_buffer::Event::ExcerptsExpanded { ids } => {
21505 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21506 self.refresh_document_highlights(cx);
21507 for id in ids {
21508 self.fetched_tree_sitter_chunks.remove(id);
21509 }
21510 self.colorize_brackets(false, cx);
21511 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21512 }
21513 multi_buffer::Event::Reparsed(buffer_id) => {
21514 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21515 self.refresh_selected_text_highlights(true, window, cx);
21516 self.colorize_brackets(true, cx);
21517 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21518
21519 cx.emit(EditorEvent::Reparsed(*buffer_id));
21520 }
21521 multi_buffer::Event::DiffHunksToggled => {
21522 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21523 }
21524 multi_buffer::Event::LanguageChanged(buffer_id) => {
21525 self.registered_buffers.remove(&buffer_id);
21526 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21527 cx.emit(EditorEvent::Reparsed(*buffer_id));
21528 cx.notify();
21529 }
21530 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21531 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21532 multi_buffer::Event::FileHandleChanged
21533 | multi_buffer::Event::Reloaded
21534 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21535 multi_buffer::Event::DiagnosticsUpdated => {
21536 self.update_diagnostics_state(window, cx);
21537 }
21538 _ => {}
21539 };
21540 }
21541
21542 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21543 if !self.diagnostics_enabled() {
21544 return;
21545 }
21546 self.refresh_active_diagnostics(cx);
21547 self.refresh_inline_diagnostics(true, window, cx);
21548 self.scrollbar_marker_state.dirty = true;
21549 cx.notify();
21550 }
21551
21552 pub fn start_temporary_diff_override(&mut self) {
21553 self.load_diff_task.take();
21554 self.temporary_diff_override = true;
21555 }
21556
21557 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21558 self.temporary_diff_override = false;
21559 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21560 self.buffer.update(cx, |buffer, cx| {
21561 buffer.set_all_diff_hunks_collapsed(cx);
21562 });
21563
21564 if let Some(project) = self.project.clone() {
21565 self.load_diff_task = Some(
21566 update_uncommitted_diff_for_buffer(
21567 cx.entity(),
21568 &project,
21569 self.buffer.read(cx).all_buffers(),
21570 self.buffer.clone(),
21571 cx,
21572 )
21573 .shared(),
21574 );
21575 }
21576 }
21577
21578 fn on_display_map_changed(
21579 &mut self,
21580 _: Entity<DisplayMap>,
21581 _: &mut Window,
21582 cx: &mut Context<Self>,
21583 ) {
21584 cx.notify();
21585 }
21586
21587 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21588 if !self.mode.is_full() {
21589 return Vec::new();
21590 }
21591
21592 let theme_settings = theme::ThemeSettings::get_global(cx);
21593
21594 theme_settings
21595 .theme_overrides
21596 .get(cx.theme().name.as_ref())
21597 .map(|theme_style| &theme_style.accents)
21598 .into_iter()
21599 .flatten()
21600 .chain(
21601 theme_settings
21602 .experimental_theme_overrides
21603 .as_ref()
21604 .map(|overrides| &overrides.accents)
21605 .into_iter()
21606 .flatten(),
21607 )
21608 .flat_map(|accent| accent.0.clone())
21609 .collect()
21610 }
21611
21612 fn fetch_applicable_language_settings(
21613 &self,
21614 cx: &App,
21615 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21616 if !self.mode.is_full() {
21617 return HashMap::default();
21618 }
21619
21620 self.buffer().read(cx).all_buffers().into_iter().fold(
21621 HashMap::default(),
21622 |mut acc, buffer| {
21623 let buffer = buffer.read(cx);
21624 let language = buffer.language().map(|language| language.name());
21625 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21626 let file = buffer.file();
21627 v.insert(language_settings(language, file, cx).into_owned());
21628 }
21629 acc
21630 },
21631 )
21632 }
21633
21634 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21635 let new_language_settings = self.fetch_applicable_language_settings(cx);
21636 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21637 self.applicable_language_settings = new_language_settings;
21638
21639 let new_accent_overrides = self.fetch_accent_overrides(cx);
21640 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21641 self.accent_overrides = new_accent_overrides;
21642
21643 if self.diagnostics_enabled() {
21644 let new_severity = EditorSettings::get_global(cx)
21645 .diagnostics_max_severity
21646 .unwrap_or(DiagnosticSeverity::Hint);
21647 self.set_max_diagnostics_severity(new_severity, cx);
21648 }
21649 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21650 self.update_edit_prediction_settings(cx);
21651 self.refresh_edit_prediction(true, false, window, cx);
21652 self.refresh_inline_values(cx);
21653 self.refresh_inlay_hints(
21654 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21655 self.selections.newest_anchor().head(),
21656 &self.buffer.read(cx).snapshot(cx),
21657 cx,
21658 )),
21659 cx,
21660 );
21661
21662 let old_cursor_shape = self.cursor_shape;
21663 let old_show_breadcrumbs = self.show_breadcrumbs;
21664
21665 {
21666 let editor_settings = EditorSettings::get_global(cx);
21667 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21668 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21669 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21670 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21671 }
21672
21673 if old_cursor_shape != self.cursor_shape {
21674 cx.emit(EditorEvent::CursorShapeChanged);
21675 }
21676
21677 if old_show_breadcrumbs != self.show_breadcrumbs {
21678 cx.emit(EditorEvent::BreadcrumbsChanged);
21679 }
21680
21681 let project_settings = ProjectSettings::get_global(cx);
21682 self.buffer_serialization = self
21683 .should_serialize_buffer()
21684 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21685
21686 if self.mode.is_full() {
21687 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21688 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21689 if self.show_inline_diagnostics != show_inline_diagnostics {
21690 self.show_inline_diagnostics = show_inline_diagnostics;
21691 self.refresh_inline_diagnostics(false, window, cx);
21692 }
21693
21694 if self.git_blame_inline_enabled != inline_blame_enabled {
21695 self.toggle_git_blame_inline_internal(false, window, cx);
21696 }
21697
21698 let minimap_settings = EditorSettings::get_global(cx).minimap;
21699 if self.minimap_visibility != MinimapVisibility::Disabled {
21700 if self.minimap_visibility.settings_visibility()
21701 != minimap_settings.minimap_enabled()
21702 {
21703 self.set_minimap_visibility(
21704 MinimapVisibility::for_mode(self.mode(), cx),
21705 window,
21706 cx,
21707 );
21708 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21709 minimap_entity.update(cx, |minimap_editor, cx| {
21710 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21711 })
21712 }
21713 }
21714
21715 if language_settings_changed || accent_overrides_changed {
21716 self.colorize_brackets(true, cx);
21717 }
21718
21719 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21720 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21721 }) {
21722 if !inlay_splice.is_empty() {
21723 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21724 }
21725 self.refresh_colors_for_visible_range(None, window, cx);
21726 }
21727 }
21728
21729 cx.notify();
21730 }
21731
21732 pub fn set_searchable(&mut self, searchable: bool) {
21733 self.searchable = searchable;
21734 }
21735
21736 pub fn searchable(&self) -> bool {
21737 self.searchable
21738 }
21739
21740 pub fn open_excerpts_in_split(
21741 &mut self,
21742 _: &OpenExcerptsSplit,
21743 window: &mut Window,
21744 cx: &mut Context<Self>,
21745 ) {
21746 self.open_excerpts_common(None, true, window, cx)
21747 }
21748
21749 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21750 self.open_excerpts_common(None, false, window, cx)
21751 }
21752
21753 fn open_excerpts_common(
21754 &mut self,
21755 jump_data: Option<JumpData>,
21756 split: bool,
21757 window: &mut Window,
21758 cx: &mut Context<Self>,
21759 ) {
21760 let Some(workspace) = self.workspace() else {
21761 cx.propagate();
21762 return;
21763 };
21764
21765 if self.buffer.read(cx).is_singleton() {
21766 cx.propagate();
21767 return;
21768 }
21769
21770 let mut new_selections_by_buffer = HashMap::default();
21771 match &jump_data {
21772 Some(JumpData::MultiBufferPoint {
21773 excerpt_id,
21774 position,
21775 anchor,
21776 line_offset_from_top,
21777 }) => {
21778 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21779 if let Some(buffer) = multi_buffer_snapshot
21780 .buffer_id_for_excerpt(*excerpt_id)
21781 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21782 {
21783 let buffer_snapshot = buffer.read(cx).snapshot();
21784 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21785 language::ToPoint::to_point(anchor, &buffer_snapshot)
21786 } else {
21787 buffer_snapshot.clip_point(*position, Bias::Left)
21788 };
21789 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21790 new_selections_by_buffer.insert(
21791 buffer,
21792 (
21793 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21794 Some(*line_offset_from_top),
21795 ),
21796 );
21797 }
21798 }
21799 Some(JumpData::MultiBufferRow {
21800 row,
21801 line_offset_from_top,
21802 }) => {
21803 let point = MultiBufferPoint::new(row.0, 0);
21804 if let Some((buffer, buffer_point, _)) =
21805 self.buffer.read(cx).point_to_buffer_point(point, cx)
21806 {
21807 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21808 new_selections_by_buffer
21809 .entry(buffer)
21810 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21811 .0
21812 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21813 }
21814 }
21815 None => {
21816 let selections = self
21817 .selections
21818 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21819 let multi_buffer = self.buffer.read(cx);
21820 for selection in selections {
21821 for (snapshot, range, _, anchor) in multi_buffer
21822 .snapshot(cx)
21823 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21824 {
21825 if let Some(anchor) = anchor {
21826 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21827 else {
21828 continue;
21829 };
21830 let offset = text::ToOffset::to_offset(
21831 &anchor.text_anchor,
21832 &buffer_handle.read(cx).snapshot(),
21833 );
21834 let range = BufferOffset(offset)..BufferOffset(offset);
21835 new_selections_by_buffer
21836 .entry(buffer_handle)
21837 .or_insert((Vec::new(), None))
21838 .0
21839 .push(range)
21840 } else {
21841 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21842 else {
21843 continue;
21844 };
21845 new_selections_by_buffer
21846 .entry(buffer_handle)
21847 .or_insert((Vec::new(), None))
21848 .0
21849 .push(range)
21850 }
21851 }
21852 }
21853 }
21854 }
21855
21856 new_selections_by_buffer
21857 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21858
21859 if new_selections_by_buffer.is_empty() {
21860 return;
21861 }
21862
21863 // We defer the pane interaction because we ourselves are a workspace item
21864 // and activating a new item causes the pane to call a method on us reentrantly,
21865 // which panics if we're on the stack.
21866 window.defer(cx, move |window, cx| {
21867 workspace.update(cx, |workspace, cx| {
21868 let pane = if split {
21869 workspace.adjacent_pane(window, cx)
21870 } else {
21871 workspace.active_pane().clone()
21872 };
21873
21874 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21875 let editor = buffer
21876 .read(cx)
21877 .file()
21878 .is_none()
21879 .then(|| {
21880 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21881 // so `workspace.open_project_item` will never find them, always opening a new editor.
21882 // Instead, we try to activate the existing editor in the pane first.
21883 let (editor, pane_item_index) =
21884 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21885 let editor = item.downcast::<Editor>()?;
21886 let singleton_buffer =
21887 editor.read(cx).buffer().read(cx).as_singleton()?;
21888 if singleton_buffer == buffer {
21889 Some((editor, i))
21890 } else {
21891 None
21892 }
21893 })?;
21894 pane.update(cx, |pane, cx| {
21895 pane.activate_item(pane_item_index, true, true, window, cx)
21896 });
21897 Some(editor)
21898 })
21899 .flatten()
21900 .unwrap_or_else(|| {
21901 workspace.open_project_item::<Self>(
21902 pane.clone(),
21903 buffer,
21904 true,
21905 true,
21906 window,
21907 cx,
21908 )
21909 });
21910
21911 editor.update(cx, |editor, cx| {
21912 let autoscroll = match scroll_offset {
21913 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21914 None => Autoscroll::newest(),
21915 };
21916 let nav_history = editor.nav_history.take();
21917 editor.change_selections(
21918 SelectionEffects::scroll(autoscroll),
21919 window,
21920 cx,
21921 |s| {
21922 s.select_ranges(ranges.into_iter().map(|range| {
21923 // we checked that the editor is a singleton editor so the offsets are valid
21924 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21925 }));
21926 },
21927 );
21928 editor.nav_history = nav_history;
21929 });
21930 }
21931 })
21932 });
21933 }
21934
21935 // For now, don't allow opening excerpts in buffers that aren't backed by
21936 // regular project files.
21937 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21938 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21939 }
21940
21941 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21942 let snapshot = self.buffer.read(cx).read(cx);
21943 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21944 Some(
21945 ranges
21946 .iter()
21947 .map(move |range| {
21948 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21949 })
21950 .collect(),
21951 )
21952 }
21953
21954 fn selection_replacement_ranges(
21955 &self,
21956 range: Range<MultiBufferOffsetUtf16>,
21957 cx: &mut App,
21958 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21959 let selections = self
21960 .selections
21961 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21962 let newest_selection = selections
21963 .iter()
21964 .max_by_key(|selection| selection.id)
21965 .unwrap();
21966 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21967 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21968 let snapshot = self.buffer.read(cx).read(cx);
21969 selections
21970 .into_iter()
21971 .map(|mut selection| {
21972 selection.start.0.0 =
21973 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21974 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21975 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21976 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21977 })
21978 .collect()
21979 }
21980
21981 fn report_editor_event(
21982 &self,
21983 reported_event: ReportEditorEvent,
21984 file_extension: Option<String>,
21985 cx: &App,
21986 ) {
21987 if cfg!(any(test, feature = "test-support")) {
21988 return;
21989 }
21990
21991 let Some(project) = &self.project else { return };
21992
21993 // If None, we are in a file without an extension
21994 let file = self
21995 .buffer
21996 .read(cx)
21997 .as_singleton()
21998 .and_then(|b| b.read(cx).file());
21999 let file_extension = file_extension.or(file
22000 .as_ref()
22001 .and_then(|file| Path::new(file.file_name(cx)).extension())
22002 .and_then(|e| e.to_str())
22003 .map(|a| a.to_string()));
22004
22005 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22006 .map(|vim_mode| vim_mode.0)
22007 .unwrap_or(false);
22008
22009 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22010 let copilot_enabled = edit_predictions_provider
22011 == language::language_settings::EditPredictionProvider::Copilot;
22012 let copilot_enabled_for_language = self
22013 .buffer
22014 .read(cx)
22015 .language_settings(cx)
22016 .show_edit_predictions;
22017
22018 let project = project.read(cx);
22019 let event_type = reported_event.event_type();
22020
22021 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22022 telemetry::event!(
22023 event_type,
22024 type = if auto_saved {"autosave"} else {"manual"},
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 } else {
22033 telemetry::event!(
22034 event_type,
22035 file_extension,
22036 vim_mode,
22037 copilot_enabled,
22038 copilot_enabled_for_language,
22039 edit_predictions_provider,
22040 is_via_ssh = project.is_via_remote_server(),
22041 );
22042 };
22043 }
22044
22045 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22046 /// with each line being an array of {text, highlight} objects.
22047 fn copy_highlight_json(
22048 &mut self,
22049 _: &CopyHighlightJson,
22050 window: &mut Window,
22051 cx: &mut Context<Self>,
22052 ) {
22053 #[derive(Serialize)]
22054 struct Chunk<'a> {
22055 text: String,
22056 highlight: Option<&'a str>,
22057 }
22058
22059 let snapshot = self.buffer.read(cx).snapshot(cx);
22060 let range = self
22061 .selected_text_range(false, window, cx)
22062 .and_then(|selection| {
22063 if selection.range.is_empty() {
22064 None
22065 } else {
22066 Some(
22067 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22068 selection.range.start,
22069 )))
22070 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22071 selection.range.end,
22072 ))),
22073 )
22074 }
22075 })
22076 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22077
22078 let chunks = snapshot.chunks(range, true);
22079 let mut lines = Vec::new();
22080 let mut line: VecDeque<Chunk> = VecDeque::new();
22081
22082 let Some(style) = self.style.as_ref() else {
22083 return;
22084 };
22085
22086 for chunk in chunks {
22087 let highlight = chunk
22088 .syntax_highlight_id
22089 .and_then(|id| id.name(&style.syntax));
22090 let mut chunk_lines = chunk.text.split('\n').peekable();
22091 while let Some(text) = chunk_lines.next() {
22092 let mut merged_with_last_token = false;
22093 if let Some(last_token) = line.back_mut()
22094 && last_token.highlight == highlight
22095 {
22096 last_token.text.push_str(text);
22097 merged_with_last_token = true;
22098 }
22099
22100 if !merged_with_last_token {
22101 line.push_back(Chunk {
22102 text: text.into(),
22103 highlight,
22104 });
22105 }
22106
22107 if chunk_lines.peek().is_some() {
22108 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22109 line.pop_front();
22110 }
22111 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22112 line.pop_back();
22113 }
22114
22115 lines.push(mem::take(&mut line));
22116 }
22117 }
22118 }
22119
22120 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22121 return;
22122 };
22123 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22124 }
22125
22126 pub fn open_context_menu(
22127 &mut self,
22128 _: &OpenContextMenu,
22129 window: &mut Window,
22130 cx: &mut Context<Self>,
22131 ) {
22132 self.request_autoscroll(Autoscroll::newest(), cx);
22133 let position = self
22134 .selections
22135 .newest_display(&self.display_snapshot(cx))
22136 .start;
22137 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22138 }
22139
22140 pub fn replay_insert_event(
22141 &mut self,
22142 text: &str,
22143 relative_utf16_range: Option<Range<isize>>,
22144 window: &mut Window,
22145 cx: &mut Context<Self>,
22146 ) {
22147 if !self.input_enabled {
22148 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22149 return;
22150 }
22151 if let Some(relative_utf16_range) = relative_utf16_range {
22152 let selections = self
22153 .selections
22154 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22155 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22156 let new_ranges = selections.into_iter().map(|range| {
22157 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22158 range
22159 .head()
22160 .0
22161 .0
22162 .saturating_add_signed(relative_utf16_range.start),
22163 ));
22164 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22165 range
22166 .head()
22167 .0
22168 .0
22169 .saturating_add_signed(relative_utf16_range.end),
22170 ));
22171 start..end
22172 });
22173 s.select_ranges(new_ranges);
22174 });
22175 }
22176
22177 self.handle_input(text, window, cx);
22178 }
22179
22180 pub fn is_focused(&self, window: &Window) -> bool {
22181 self.focus_handle.is_focused(window)
22182 }
22183
22184 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22185 cx.emit(EditorEvent::Focused);
22186
22187 if let Some(descendant) = self
22188 .last_focused_descendant
22189 .take()
22190 .and_then(|descendant| descendant.upgrade())
22191 {
22192 window.focus(&descendant);
22193 } else {
22194 if let Some(blame) = self.blame.as_ref() {
22195 blame.update(cx, GitBlame::focus)
22196 }
22197
22198 self.blink_manager.update(cx, BlinkManager::enable);
22199 self.show_cursor_names(window, cx);
22200 self.buffer.update(cx, |buffer, cx| {
22201 buffer.finalize_last_transaction(cx);
22202 if self.leader_id.is_none() {
22203 buffer.set_active_selections(
22204 &self.selections.disjoint_anchors_arc(),
22205 self.selections.line_mode(),
22206 self.cursor_shape,
22207 cx,
22208 );
22209 }
22210 });
22211
22212 if let Some(position_map) = self.last_position_map.clone() {
22213 EditorElement::mouse_moved(
22214 self,
22215 &MouseMoveEvent {
22216 position: window.mouse_position(),
22217 pressed_button: None,
22218 modifiers: window.modifiers(),
22219 },
22220 &position_map,
22221 window,
22222 cx,
22223 );
22224 }
22225 }
22226 }
22227
22228 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22229 cx.emit(EditorEvent::FocusedIn)
22230 }
22231
22232 fn handle_focus_out(
22233 &mut self,
22234 event: FocusOutEvent,
22235 _window: &mut Window,
22236 cx: &mut Context<Self>,
22237 ) {
22238 if event.blurred != self.focus_handle {
22239 self.last_focused_descendant = Some(event.blurred);
22240 }
22241 self.selection_drag_state = SelectionDragState::None;
22242 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22243 }
22244
22245 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22246 self.blink_manager.update(cx, BlinkManager::disable);
22247 self.buffer
22248 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22249
22250 if let Some(blame) = self.blame.as_ref() {
22251 blame.update(cx, GitBlame::blur)
22252 }
22253 if !self.hover_state.focused(window, cx) {
22254 hide_hover(self, cx);
22255 }
22256 if !self
22257 .context_menu
22258 .borrow()
22259 .as_ref()
22260 .is_some_and(|context_menu| context_menu.focused(window, cx))
22261 {
22262 self.hide_context_menu(window, cx);
22263 }
22264 self.take_active_edit_prediction(cx);
22265 cx.emit(EditorEvent::Blurred);
22266 cx.notify();
22267 }
22268
22269 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22270 let mut pending: String = window
22271 .pending_input_keystrokes()
22272 .into_iter()
22273 .flatten()
22274 .filter_map(|keystroke| keystroke.key_char.clone())
22275 .collect();
22276
22277 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22278 pending = "".to_string();
22279 }
22280
22281 let existing_pending = self
22282 .text_highlights::<PendingInput>(cx)
22283 .map(|(_, ranges)| ranges.to_vec());
22284 if existing_pending.is_none() && pending.is_empty() {
22285 return;
22286 }
22287 let transaction =
22288 self.transact(window, cx, |this, window, cx| {
22289 let selections = this
22290 .selections
22291 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22292 let edits = selections
22293 .iter()
22294 .map(|selection| (selection.end..selection.end, pending.clone()));
22295 this.edit(edits, cx);
22296 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22297 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22298 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22299 }));
22300 });
22301 if let Some(existing_ranges) = existing_pending {
22302 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22303 this.edit(edits, cx);
22304 }
22305 });
22306
22307 let snapshot = self.snapshot(window, cx);
22308 let ranges = self
22309 .selections
22310 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22311 .into_iter()
22312 .map(|selection| {
22313 snapshot.buffer_snapshot().anchor_after(selection.end)
22314 ..snapshot
22315 .buffer_snapshot()
22316 .anchor_before(selection.end + pending.len())
22317 })
22318 .collect();
22319
22320 if pending.is_empty() {
22321 self.clear_highlights::<PendingInput>(cx);
22322 } else {
22323 self.highlight_text::<PendingInput>(
22324 ranges,
22325 HighlightStyle {
22326 underline: Some(UnderlineStyle {
22327 thickness: px(1.),
22328 color: None,
22329 wavy: false,
22330 }),
22331 ..Default::default()
22332 },
22333 cx,
22334 );
22335 }
22336
22337 self.ime_transaction = self.ime_transaction.or(transaction);
22338 if let Some(transaction) = self.ime_transaction {
22339 self.buffer.update(cx, |buffer, cx| {
22340 buffer.group_until_transaction(transaction, cx);
22341 });
22342 }
22343
22344 if self.text_highlights::<PendingInput>(cx).is_none() {
22345 self.ime_transaction.take();
22346 }
22347 }
22348
22349 pub fn register_action_renderer(
22350 &mut self,
22351 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22352 ) -> Subscription {
22353 let id = self.next_editor_action_id.post_inc();
22354 self.editor_actions
22355 .borrow_mut()
22356 .insert(id, Box::new(listener));
22357
22358 let editor_actions = self.editor_actions.clone();
22359 Subscription::new(move || {
22360 editor_actions.borrow_mut().remove(&id);
22361 })
22362 }
22363
22364 pub fn register_action<A: Action>(
22365 &mut self,
22366 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22367 ) -> Subscription {
22368 let id = self.next_editor_action_id.post_inc();
22369 let listener = Arc::new(listener);
22370 self.editor_actions.borrow_mut().insert(
22371 id,
22372 Box::new(move |_, window, _| {
22373 let listener = listener.clone();
22374 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22375 let action = action.downcast_ref().unwrap();
22376 if phase == DispatchPhase::Bubble {
22377 listener(action, window, cx)
22378 }
22379 })
22380 }),
22381 );
22382
22383 let editor_actions = self.editor_actions.clone();
22384 Subscription::new(move || {
22385 editor_actions.borrow_mut().remove(&id);
22386 })
22387 }
22388
22389 pub fn file_header_size(&self) -> u32 {
22390 FILE_HEADER_HEIGHT
22391 }
22392
22393 pub fn restore(
22394 &mut self,
22395 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22396 window: &mut Window,
22397 cx: &mut Context<Self>,
22398 ) {
22399 let workspace = self.workspace();
22400 let project = self.project();
22401 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22402 let mut tasks = Vec::new();
22403 for (buffer_id, changes) in revert_changes {
22404 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22405 buffer.update(cx, |buffer, cx| {
22406 buffer.edit(
22407 changes
22408 .into_iter()
22409 .map(|(range, text)| (range, text.to_string())),
22410 None,
22411 cx,
22412 );
22413 });
22414
22415 if let Some(project) =
22416 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22417 {
22418 project.update(cx, |project, cx| {
22419 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22420 })
22421 }
22422 }
22423 }
22424 tasks
22425 });
22426 cx.spawn_in(window, async move |_, cx| {
22427 for (buffer, task) in save_tasks {
22428 let result = task.await;
22429 if result.is_err() {
22430 let Some(path) = buffer
22431 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22432 .ok()
22433 else {
22434 continue;
22435 };
22436 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22437 let Some(task) = cx
22438 .update_window_entity(workspace, |workspace, window, cx| {
22439 workspace
22440 .open_path_preview(path, None, false, false, false, window, cx)
22441 })
22442 .ok()
22443 else {
22444 continue;
22445 };
22446 task.await.log_err();
22447 }
22448 }
22449 }
22450 })
22451 .detach();
22452 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22453 selections.refresh()
22454 });
22455 }
22456
22457 pub fn to_pixel_point(
22458 &self,
22459 source: multi_buffer::Anchor,
22460 editor_snapshot: &EditorSnapshot,
22461 window: &mut Window,
22462 ) -> Option<gpui::Point<Pixels>> {
22463 let source_point = source.to_display_point(editor_snapshot);
22464 self.display_to_pixel_point(source_point, editor_snapshot, window)
22465 }
22466
22467 pub fn display_to_pixel_point(
22468 &self,
22469 source: DisplayPoint,
22470 editor_snapshot: &EditorSnapshot,
22471 window: &mut Window,
22472 ) -> Option<gpui::Point<Pixels>> {
22473 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22474 let text_layout_details = self.text_layout_details(window);
22475 let scroll_top = text_layout_details
22476 .scroll_anchor
22477 .scroll_position(editor_snapshot)
22478 .y;
22479
22480 if source.row().as_f64() < scroll_top.floor() {
22481 return None;
22482 }
22483 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22484 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22485 Some(gpui::Point::new(source_x, source_y))
22486 }
22487
22488 pub fn has_visible_completions_menu(&self) -> bool {
22489 !self.edit_prediction_preview_is_active()
22490 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22491 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22492 })
22493 }
22494
22495 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22496 if self.mode.is_minimap() {
22497 return;
22498 }
22499 self.addons
22500 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22501 }
22502
22503 pub fn unregister_addon<T: Addon>(&mut self) {
22504 self.addons.remove(&std::any::TypeId::of::<T>());
22505 }
22506
22507 pub fn addon<T: Addon>(&self) -> Option<&T> {
22508 let type_id = std::any::TypeId::of::<T>();
22509 self.addons
22510 .get(&type_id)
22511 .and_then(|item| item.to_any().downcast_ref::<T>())
22512 }
22513
22514 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22515 let type_id = std::any::TypeId::of::<T>();
22516 self.addons
22517 .get_mut(&type_id)
22518 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22519 }
22520
22521 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22522 let text_layout_details = self.text_layout_details(window);
22523 let style = &text_layout_details.editor_style;
22524 let font_id = window.text_system().resolve_font(&style.text.font());
22525 let font_size = style.text.font_size.to_pixels(window.rem_size());
22526 let line_height = style.text.line_height_in_pixels(window.rem_size());
22527 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22528 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22529
22530 CharacterDimensions {
22531 em_width,
22532 em_advance,
22533 line_height,
22534 }
22535 }
22536
22537 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22538 self.load_diff_task.clone()
22539 }
22540
22541 fn read_metadata_from_db(
22542 &mut self,
22543 item_id: u64,
22544 workspace_id: WorkspaceId,
22545 window: &mut Window,
22546 cx: &mut Context<Editor>,
22547 ) {
22548 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22549 && !self.mode.is_minimap()
22550 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22551 {
22552 let buffer_snapshot = OnceCell::new();
22553
22554 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22555 && !folds.is_empty()
22556 {
22557 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22558 self.fold_ranges(
22559 folds
22560 .into_iter()
22561 .map(|(start, end)| {
22562 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22563 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22564 })
22565 .collect(),
22566 false,
22567 window,
22568 cx,
22569 );
22570 }
22571
22572 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22573 && !selections.is_empty()
22574 {
22575 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22576 // skip adding the initial selection to selection history
22577 self.selection_history.mode = SelectionHistoryMode::Skipping;
22578 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22579 s.select_ranges(selections.into_iter().map(|(start, end)| {
22580 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22581 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22582 }));
22583 });
22584 self.selection_history.mode = SelectionHistoryMode::Normal;
22585 };
22586 }
22587
22588 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22589 }
22590
22591 fn update_lsp_data(
22592 &mut self,
22593 for_buffer: Option<BufferId>,
22594 window: &mut Window,
22595 cx: &mut Context<'_, Self>,
22596 ) {
22597 self.pull_diagnostics(for_buffer, window, cx);
22598 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22599 }
22600
22601 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22602 if self.ignore_lsp_data() {
22603 return;
22604 }
22605 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22606 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22607 }
22608 }
22609
22610 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22611 if self.ignore_lsp_data() {
22612 return;
22613 }
22614
22615 if !self.registered_buffers.contains_key(&buffer_id)
22616 && let Some(project) = self.project.as_ref()
22617 {
22618 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22619 project.update(cx, |project, cx| {
22620 self.registered_buffers.insert(
22621 buffer_id,
22622 project.register_buffer_with_language_servers(&buffer, cx),
22623 );
22624 });
22625 } else {
22626 self.registered_buffers.remove(&buffer_id);
22627 }
22628 }
22629 }
22630
22631 fn ignore_lsp_data(&self) -> bool {
22632 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22633 // skip any LSP updates for it.
22634 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22635 }
22636}
22637
22638fn edit_for_markdown_paste<'a>(
22639 buffer: &MultiBufferSnapshot,
22640 range: Range<MultiBufferOffset>,
22641 to_insert: &'a str,
22642 url: Option<url::Url>,
22643) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22644 if url.is_none() {
22645 return (range, Cow::Borrowed(to_insert));
22646 };
22647
22648 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22649
22650 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22651 Cow::Borrowed(to_insert)
22652 } else {
22653 Cow::Owned(format!("[{old_text}]({to_insert})"))
22654 };
22655 (range, new_text)
22656}
22657
22658fn process_completion_for_edit(
22659 completion: &Completion,
22660 intent: CompletionIntent,
22661 buffer: &Entity<Buffer>,
22662 cursor_position: &text::Anchor,
22663 cx: &mut Context<Editor>,
22664) -> CompletionEdit {
22665 let buffer = buffer.read(cx);
22666 let buffer_snapshot = buffer.snapshot();
22667 let (snippet, new_text) = if completion.is_snippet() {
22668 let mut snippet_source = completion.new_text.clone();
22669 // Workaround for typescript language server issues so that methods don't expand within
22670 // strings and functions with type expressions. The previous point is used because the query
22671 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22672 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22673 let previous_point = if previous_point.column > 0 {
22674 cursor_position.to_previous_offset(&buffer_snapshot)
22675 } else {
22676 cursor_position.to_offset(&buffer_snapshot)
22677 };
22678 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22679 && scope.prefers_label_for_snippet_in_completion()
22680 && let Some(label) = completion.label()
22681 && matches!(
22682 completion.kind(),
22683 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22684 )
22685 {
22686 snippet_source = label;
22687 }
22688 match Snippet::parse(&snippet_source).log_err() {
22689 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22690 None => (None, completion.new_text.clone()),
22691 }
22692 } else {
22693 (None, completion.new_text.clone())
22694 };
22695
22696 let mut range_to_replace = {
22697 let replace_range = &completion.replace_range;
22698 if let CompletionSource::Lsp {
22699 insert_range: Some(insert_range),
22700 ..
22701 } = &completion.source
22702 {
22703 debug_assert_eq!(
22704 insert_range.start, replace_range.start,
22705 "insert_range and replace_range should start at the same position"
22706 );
22707 debug_assert!(
22708 insert_range
22709 .start
22710 .cmp(cursor_position, &buffer_snapshot)
22711 .is_le(),
22712 "insert_range should start before or at cursor position"
22713 );
22714 debug_assert!(
22715 replace_range
22716 .start
22717 .cmp(cursor_position, &buffer_snapshot)
22718 .is_le(),
22719 "replace_range should start before or at cursor position"
22720 );
22721
22722 let should_replace = match intent {
22723 CompletionIntent::CompleteWithInsert => false,
22724 CompletionIntent::CompleteWithReplace => true,
22725 CompletionIntent::Complete | CompletionIntent::Compose => {
22726 let insert_mode =
22727 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22728 .completions
22729 .lsp_insert_mode;
22730 match insert_mode {
22731 LspInsertMode::Insert => false,
22732 LspInsertMode::Replace => true,
22733 LspInsertMode::ReplaceSubsequence => {
22734 let mut text_to_replace = buffer.chars_for_range(
22735 buffer.anchor_before(replace_range.start)
22736 ..buffer.anchor_after(replace_range.end),
22737 );
22738 let mut current_needle = text_to_replace.next();
22739 for haystack_ch in completion.label.text.chars() {
22740 if let Some(needle_ch) = current_needle
22741 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22742 {
22743 current_needle = text_to_replace.next();
22744 }
22745 }
22746 current_needle.is_none()
22747 }
22748 LspInsertMode::ReplaceSuffix => {
22749 if replace_range
22750 .end
22751 .cmp(cursor_position, &buffer_snapshot)
22752 .is_gt()
22753 {
22754 let range_after_cursor = *cursor_position..replace_range.end;
22755 let text_after_cursor = buffer
22756 .text_for_range(
22757 buffer.anchor_before(range_after_cursor.start)
22758 ..buffer.anchor_after(range_after_cursor.end),
22759 )
22760 .collect::<String>()
22761 .to_ascii_lowercase();
22762 completion
22763 .label
22764 .text
22765 .to_ascii_lowercase()
22766 .ends_with(&text_after_cursor)
22767 } else {
22768 true
22769 }
22770 }
22771 }
22772 }
22773 };
22774
22775 if should_replace {
22776 replace_range.clone()
22777 } else {
22778 insert_range.clone()
22779 }
22780 } else {
22781 replace_range.clone()
22782 }
22783 };
22784
22785 if range_to_replace
22786 .end
22787 .cmp(cursor_position, &buffer_snapshot)
22788 .is_lt()
22789 {
22790 range_to_replace.end = *cursor_position;
22791 }
22792
22793 let replace_range = range_to_replace.to_offset(buffer);
22794 CompletionEdit {
22795 new_text,
22796 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22797 snippet,
22798 }
22799}
22800
22801struct CompletionEdit {
22802 new_text: String,
22803 replace_range: Range<BufferOffset>,
22804 snippet: Option<Snippet>,
22805}
22806
22807fn insert_extra_newline_brackets(
22808 buffer: &MultiBufferSnapshot,
22809 range: Range<MultiBufferOffset>,
22810 language: &language::LanguageScope,
22811) -> bool {
22812 let leading_whitespace_len = buffer
22813 .reversed_chars_at(range.start)
22814 .take_while(|c| c.is_whitespace() && *c != '\n')
22815 .map(|c| c.len_utf8())
22816 .sum::<usize>();
22817 let trailing_whitespace_len = buffer
22818 .chars_at(range.end)
22819 .take_while(|c| c.is_whitespace() && *c != '\n')
22820 .map(|c| c.len_utf8())
22821 .sum::<usize>();
22822 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22823
22824 language.brackets().any(|(pair, enabled)| {
22825 let pair_start = pair.start.trim_end();
22826 let pair_end = pair.end.trim_start();
22827
22828 enabled
22829 && pair.newline
22830 && buffer.contains_str_at(range.end, pair_end)
22831 && buffer.contains_str_at(
22832 range.start.saturating_sub_usize(pair_start.len()),
22833 pair_start,
22834 )
22835 })
22836}
22837
22838fn insert_extra_newline_tree_sitter(
22839 buffer: &MultiBufferSnapshot,
22840 range: Range<MultiBufferOffset>,
22841) -> bool {
22842 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22843 [(buffer, range, _)] => (*buffer, range.clone()),
22844 _ => return false,
22845 };
22846 let pair = {
22847 let mut result: Option<BracketMatch<usize>> = None;
22848
22849 for pair in buffer
22850 .all_bracket_ranges(range.start.0..range.end.0)
22851 .filter(move |pair| {
22852 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22853 })
22854 {
22855 let len = pair.close_range.end - pair.open_range.start;
22856
22857 if let Some(existing) = &result {
22858 let existing_len = existing.close_range.end - existing.open_range.start;
22859 if len > existing_len {
22860 continue;
22861 }
22862 }
22863
22864 result = Some(pair);
22865 }
22866
22867 result
22868 };
22869 let Some(pair) = pair else {
22870 return false;
22871 };
22872 pair.newline_only
22873 && buffer
22874 .chars_for_range(pair.open_range.end..range.start.0)
22875 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22876 .all(|c| c.is_whitespace() && c != '\n')
22877}
22878
22879fn update_uncommitted_diff_for_buffer(
22880 editor: Entity<Editor>,
22881 project: &Entity<Project>,
22882 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22883 buffer: Entity<MultiBuffer>,
22884 cx: &mut App,
22885) -> Task<()> {
22886 let mut tasks = Vec::new();
22887 project.update(cx, |project, cx| {
22888 for buffer in buffers {
22889 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22890 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22891 }
22892 }
22893 });
22894 cx.spawn(async move |cx| {
22895 let diffs = future::join_all(tasks).await;
22896 if editor
22897 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22898 .unwrap_or(false)
22899 {
22900 return;
22901 }
22902
22903 buffer
22904 .update(cx, |buffer, cx| {
22905 for diff in diffs.into_iter().flatten() {
22906 buffer.add_diff(diff, cx);
22907 }
22908 })
22909 .ok();
22910 })
22911}
22912
22913fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22914 let tab_size = tab_size.get() as usize;
22915 let mut width = offset;
22916
22917 for ch in text.chars() {
22918 width += if ch == '\t' {
22919 tab_size - (width % tab_size)
22920 } else {
22921 1
22922 };
22923 }
22924
22925 width - offset
22926}
22927
22928#[cfg(test)]
22929mod tests {
22930 use super::*;
22931
22932 #[test]
22933 fn test_string_size_with_expanded_tabs() {
22934 let nz = |val| NonZeroU32::new(val).unwrap();
22935 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22936 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22937 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22938 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22939 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22940 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22941 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22942 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22943 }
22944}
22945
22946/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22947struct WordBreakingTokenizer<'a> {
22948 input: &'a str,
22949}
22950
22951impl<'a> WordBreakingTokenizer<'a> {
22952 fn new(input: &'a str) -> Self {
22953 Self { input }
22954 }
22955}
22956
22957fn is_char_ideographic(ch: char) -> bool {
22958 use unicode_script::Script::*;
22959 use unicode_script::UnicodeScript;
22960 matches!(ch.script(), Han | Tangut | Yi)
22961}
22962
22963fn is_grapheme_ideographic(text: &str) -> bool {
22964 text.chars().any(is_char_ideographic)
22965}
22966
22967fn is_grapheme_whitespace(text: &str) -> bool {
22968 text.chars().any(|x| x.is_whitespace())
22969}
22970
22971fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22972 text.chars()
22973 .next()
22974 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22975}
22976
22977#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22978enum WordBreakToken<'a> {
22979 Word { token: &'a str, grapheme_len: usize },
22980 InlineWhitespace { token: &'a str, grapheme_len: usize },
22981 Newline,
22982}
22983
22984impl<'a> Iterator for WordBreakingTokenizer<'a> {
22985 /// Yields a span, the count of graphemes in the token, and whether it was
22986 /// whitespace. Note that it also breaks at word boundaries.
22987 type Item = WordBreakToken<'a>;
22988
22989 fn next(&mut self) -> Option<Self::Item> {
22990 use unicode_segmentation::UnicodeSegmentation;
22991 if self.input.is_empty() {
22992 return None;
22993 }
22994
22995 let mut iter = self.input.graphemes(true).peekable();
22996 let mut offset = 0;
22997 let mut grapheme_len = 0;
22998 if let Some(first_grapheme) = iter.next() {
22999 let is_newline = first_grapheme == "\n";
23000 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23001 offset += first_grapheme.len();
23002 grapheme_len += 1;
23003 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23004 if let Some(grapheme) = iter.peek().copied()
23005 && should_stay_with_preceding_ideograph(grapheme)
23006 {
23007 offset += grapheme.len();
23008 grapheme_len += 1;
23009 }
23010 } else {
23011 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23012 let mut next_word_bound = words.peek().copied();
23013 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23014 next_word_bound = words.next();
23015 }
23016 while let Some(grapheme) = iter.peek().copied() {
23017 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23018 break;
23019 };
23020 if is_grapheme_whitespace(grapheme) != is_whitespace
23021 || (grapheme == "\n") != is_newline
23022 {
23023 break;
23024 };
23025 offset += grapheme.len();
23026 grapheme_len += 1;
23027 iter.next();
23028 }
23029 }
23030 let token = &self.input[..offset];
23031 self.input = &self.input[offset..];
23032 if token == "\n" {
23033 Some(WordBreakToken::Newline)
23034 } else if is_whitespace {
23035 Some(WordBreakToken::InlineWhitespace {
23036 token,
23037 grapheme_len,
23038 })
23039 } else {
23040 Some(WordBreakToken::Word {
23041 token,
23042 grapheme_len,
23043 })
23044 }
23045 } else {
23046 None
23047 }
23048 }
23049}
23050
23051#[test]
23052fn test_word_breaking_tokenizer() {
23053 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23054 ("", &[]),
23055 (" ", &[whitespace(" ", 2)]),
23056 ("Ʒ", &[word("Ʒ", 1)]),
23057 ("Ǽ", &[word("Ǽ", 1)]),
23058 ("⋑", &[word("⋑", 1)]),
23059 ("⋑⋑", &[word("⋑⋑", 2)]),
23060 (
23061 "原理,进而",
23062 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23063 ),
23064 (
23065 "hello world",
23066 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23067 ),
23068 (
23069 "hello, world",
23070 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23071 ),
23072 (
23073 " hello world",
23074 &[
23075 whitespace(" ", 2),
23076 word("hello", 5),
23077 whitespace(" ", 1),
23078 word("world", 5),
23079 ],
23080 ),
23081 (
23082 "这是什么 \n 钢笔",
23083 &[
23084 word("这", 1),
23085 word("是", 1),
23086 word("什", 1),
23087 word("么", 1),
23088 whitespace(" ", 1),
23089 newline(),
23090 whitespace(" ", 1),
23091 word("钢", 1),
23092 word("笔", 1),
23093 ],
23094 ),
23095 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23096 ];
23097
23098 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23099 WordBreakToken::Word {
23100 token,
23101 grapheme_len,
23102 }
23103 }
23104
23105 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23106 WordBreakToken::InlineWhitespace {
23107 token,
23108 grapheme_len,
23109 }
23110 }
23111
23112 fn newline() -> WordBreakToken<'static> {
23113 WordBreakToken::Newline
23114 }
23115
23116 for (input, result) in tests {
23117 assert_eq!(
23118 WordBreakingTokenizer::new(input)
23119 .collect::<Vec<_>>()
23120 .as_slice(),
23121 *result,
23122 );
23123 }
23124}
23125
23126fn wrap_with_prefix(
23127 first_line_prefix: String,
23128 subsequent_lines_prefix: String,
23129 unwrapped_text: String,
23130 wrap_column: usize,
23131 tab_size: NonZeroU32,
23132 preserve_existing_whitespace: bool,
23133) -> String {
23134 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23135 let subsequent_lines_prefix_len =
23136 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23137 let mut wrapped_text = String::new();
23138 let mut current_line = first_line_prefix;
23139 let mut is_first_line = true;
23140
23141 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23142 let mut current_line_len = first_line_prefix_len;
23143 let mut in_whitespace = false;
23144 for token in tokenizer {
23145 let have_preceding_whitespace = in_whitespace;
23146 match token {
23147 WordBreakToken::Word {
23148 token,
23149 grapheme_len,
23150 } => {
23151 in_whitespace = false;
23152 let current_prefix_len = if is_first_line {
23153 first_line_prefix_len
23154 } else {
23155 subsequent_lines_prefix_len
23156 };
23157 if current_line_len + grapheme_len > wrap_column
23158 && current_line_len != current_prefix_len
23159 {
23160 wrapped_text.push_str(current_line.trim_end());
23161 wrapped_text.push('\n');
23162 is_first_line = false;
23163 current_line = subsequent_lines_prefix.clone();
23164 current_line_len = subsequent_lines_prefix_len;
23165 }
23166 current_line.push_str(token);
23167 current_line_len += grapheme_len;
23168 }
23169 WordBreakToken::InlineWhitespace {
23170 mut token,
23171 mut grapheme_len,
23172 } => {
23173 in_whitespace = true;
23174 if have_preceding_whitespace && !preserve_existing_whitespace {
23175 continue;
23176 }
23177 if !preserve_existing_whitespace {
23178 // Keep a single whitespace grapheme as-is
23179 if let Some(first) =
23180 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23181 {
23182 token = first;
23183 } else {
23184 token = " ";
23185 }
23186 grapheme_len = 1;
23187 }
23188 let current_prefix_len = if is_first_line {
23189 first_line_prefix_len
23190 } else {
23191 subsequent_lines_prefix_len
23192 };
23193 if current_line_len + grapheme_len > wrap_column {
23194 wrapped_text.push_str(current_line.trim_end());
23195 wrapped_text.push('\n');
23196 is_first_line = false;
23197 current_line = subsequent_lines_prefix.clone();
23198 current_line_len = subsequent_lines_prefix_len;
23199 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23200 current_line.push_str(token);
23201 current_line_len += grapheme_len;
23202 }
23203 }
23204 WordBreakToken::Newline => {
23205 in_whitespace = true;
23206 let current_prefix_len = if is_first_line {
23207 first_line_prefix_len
23208 } else {
23209 subsequent_lines_prefix_len
23210 };
23211 if preserve_existing_whitespace {
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 have_preceding_whitespace {
23218 continue;
23219 } else if current_line_len + 1 > wrap_column
23220 && current_line_len != current_prefix_len
23221 {
23222 wrapped_text.push_str(current_line.trim_end());
23223 wrapped_text.push('\n');
23224 is_first_line = false;
23225 current_line = subsequent_lines_prefix.clone();
23226 current_line_len = subsequent_lines_prefix_len;
23227 } else if current_line_len != current_prefix_len {
23228 current_line.push(' ');
23229 current_line_len += 1;
23230 }
23231 }
23232 }
23233 }
23234
23235 if !current_line.is_empty() {
23236 wrapped_text.push_str(¤t_line);
23237 }
23238 wrapped_text
23239}
23240
23241#[test]
23242fn test_wrap_with_prefix() {
23243 assert_eq!(
23244 wrap_with_prefix(
23245 "# ".to_string(),
23246 "# ".to_string(),
23247 "abcdefg".to_string(),
23248 4,
23249 NonZeroU32::new(4).unwrap(),
23250 false,
23251 ),
23252 "# abcdefg"
23253 );
23254 assert_eq!(
23255 wrap_with_prefix(
23256 "".to_string(),
23257 "".to_string(),
23258 "\thello world".to_string(),
23259 8,
23260 NonZeroU32::new(4).unwrap(),
23261 false,
23262 ),
23263 "hello\nworld"
23264 );
23265 assert_eq!(
23266 wrap_with_prefix(
23267 "// ".to_string(),
23268 "// ".to_string(),
23269 "xx \nyy zz aa bb cc".to_string(),
23270 12,
23271 NonZeroU32::new(4).unwrap(),
23272 false,
23273 ),
23274 "// xx yy zz\n// aa bb cc"
23275 );
23276 assert_eq!(
23277 wrap_with_prefix(
23278 String::new(),
23279 String::new(),
23280 "这是什么 \n 钢笔".to_string(),
23281 3,
23282 NonZeroU32::new(4).unwrap(),
23283 false,
23284 ),
23285 "这是什\n么 钢\n笔"
23286 );
23287 assert_eq!(
23288 wrap_with_prefix(
23289 String::new(),
23290 String::new(),
23291 format!("foo{}bar", '\u{2009}'), // thin space
23292 80,
23293 NonZeroU32::new(4).unwrap(),
23294 false,
23295 ),
23296 format!("foo{}bar", '\u{2009}')
23297 );
23298}
23299
23300pub trait CollaborationHub {
23301 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23302 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23303 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23304}
23305
23306impl CollaborationHub for Entity<Project> {
23307 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23308 self.read(cx).collaborators()
23309 }
23310
23311 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23312 self.read(cx).user_store().read(cx).participant_indices()
23313 }
23314
23315 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23316 let this = self.read(cx);
23317 let user_ids = this.collaborators().values().map(|c| c.user_id);
23318 this.user_store().read(cx).participant_names(user_ids, cx)
23319 }
23320}
23321
23322pub trait SemanticsProvider {
23323 fn hover(
23324 &self,
23325 buffer: &Entity<Buffer>,
23326 position: text::Anchor,
23327 cx: &mut App,
23328 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23329
23330 fn inline_values(
23331 &self,
23332 buffer_handle: Entity<Buffer>,
23333 range: Range<text::Anchor>,
23334 cx: &mut App,
23335 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23336
23337 fn applicable_inlay_chunks(
23338 &self,
23339 buffer: &Entity<Buffer>,
23340 ranges: &[Range<text::Anchor>],
23341 cx: &mut App,
23342 ) -> Vec<Range<BufferRow>>;
23343
23344 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23345
23346 fn inlay_hints(
23347 &self,
23348 invalidate: InvalidationStrategy,
23349 buffer: Entity<Buffer>,
23350 ranges: Vec<Range<text::Anchor>>,
23351 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23352 cx: &mut App,
23353 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23354
23355 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23356
23357 fn document_highlights(
23358 &self,
23359 buffer: &Entity<Buffer>,
23360 position: text::Anchor,
23361 cx: &mut App,
23362 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23363
23364 fn definitions(
23365 &self,
23366 buffer: &Entity<Buffer>,
23367 position: text::Anchor,
23368 kind: GotoDefinitionKind,
23369 cx: &mut App,
23370 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23371
23372 fn range_for_rename(
23373 &self,
23374 buffer: &Entity<Buffer>,
23375 position: text::Anchor,
23376 cx: &mut App,
23377 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23378
23379 fn perform_rename(
23380 &self,
23381 buffer: &Entity<Buffer>,
23382 position: text::Anchor,
23383 new_name: String,
23384 cx: &mut App,
23385 ) -> Option<Task<Result<ProjectTransaction>>>;
23386}
23387
23388pub trait CompletionProvider {
23389 fn completions(
23390 &self,
23391 excerpt_id: ExcerptId,
23392 buffer: &Entity<Buffer>,
23393 buffer_position: text::Anchor,
23394 trigger: CompletionContext,
23395 window: &mut Window,
23396 cx: &mut Context<Editor>,
23397 ) -> Task<Result<Vec<CompletionResponse>>>;
23398
23399 fn resolve_completions(
23400 &self,
23401 _buffer: Entity<Buffer>,
23402 _completion_indices: Vec<usize>,
23403 _completions: Rc<RefCell<Box<[Completion]>>>,
23404 _cx: &mut Context<Editor>,
23405 ) -> Task<Result<bool>> {
23406 Task::ready(Ok(false))
23407 }
23408
23409 fn apply_additional_edits_for_completion(
23410 &self,
23411 _buffer: Entity<Buffer>,
23412 _completions: Rc<RefCell<Box<[Completion]>>>,
23413 _completion_index: usize,
23414 _push_to_history: bool,
23415 _cx: &mut Context<Editor>,
23416 ) -> Task<Result<Option<language::Transaction>>> {
23417 Task::ready(Ok(None))
23418 }
23419
23420 fn is_completion_trigger(
23421 &self,
23422 buffer: &Entity<Buffer>,
23423 position: language::Anchor,
23424 text: &str,
23425 trigger_in_words: bool,
23426 menu_is_open: bool,
23427 cx: &mut Context<Editor>,
23428 ) -> bool;
23429
23430 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23431
23432 fn sort_completions(&self) -> bool {
23433 true
23434 }
23435
23436 fn filter_completions(&self) -> bool {
23437 true
23438 }
23439
23440 fn show_snippets(&self) -> bool {
23441 false
23442 }
23443}
23444
23445pub trait CodeActionProvider {
23446 fn id(&self) -> Arc<str>;
23447
23448 fn code_actions(
23449 &self,
23450 buffer: &Entity<Buffer>,
23451 range: Range<text::Anchor>,
23452 window: &mut Window,
23453 cx: &mut App,
23454 ) -> Task<Result<Vec<CodeAction>>>;
23455
23456 fn apply_code_action(
23457 &self,
23458 buffer_handle: Entity<Buffer>,
23459 action: CodeAction,
23460 excerpt_id: ExcerptId,
23461 push_to_history: bool,
23462 window: &mut Window,
23463 cx: &mut App,
23464 ) -> Task<Result<ProjectTransaction>>;
23465}
23466
23467impl CodeActionProvider for Entity<Project> {
23468 fn id(&self) -> Arc<str> {
23469 "project".into()
23470 }
23471
23472 fn code_actions(
23473 &self,
23474 buffer: &Entity<Buffer>,
23475 range: Range<text::Anchor>,
23476 _window: &mut Window,
23477 cx: &mut App,
23478 ) -> Task<Result<Vec<CodeAction>>> {
23479 self.update(cx, |project, cx| {
23480 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23481 let code_actions = project.code_actions(buffer, range, None, cx);
23482 cx.background_spawn(async move {
23483 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23484 Ok(code_lens_actions
23485 .context("code lens fetch")?
23486 .into_iter()
23487 .flatten()
23488 .chain(
23489 code_actions
23490 .context("code action fetch")?
23491 .into_iter()
23492 .flatten(),
23493 )
23494 .collect())
23495 })
23496 })
23497 }
23498
23499 fn apply_code_action(
23500 &self,
23501 buffer_handle: Entity<Buffer>,
23502 action: CodeAction,
23503 _excerpt_id: ExcerptId,
23504 push_to_history: bool,
23505 _window: &mut Window,
23506 cx: &mut App,
23507 ) -> Task<Result<ProjectTransaction>> {
23508 self.update(cx, |project, cx| {
23509 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23510 })
23511 }
23512}
23513
23514fn snippet_completions(
23515 project: &Project,
23516 buffer: &Entity<Buffer>,
23517 buffer_anchor: text::Anchor,
23518 classifier: CharClassifier,
23519 cx: &mut App,
23520) -> Task<Result<CompletionResponse>> {
23521 let languages = buffer.read(cx).languages_at(buffer_anchor);
23522 let snippet_store = project.snippets().read(cx);
23523
23524 let scopes: Vec<_> = languages
23525 .iter()
23526 .filter_map(|language| {
23527 let language_name = language.lsp_id();
23528 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23529
23530 if snippets.is_empty() {
23531 None
23532 } else {
23533 Some((language.default_scope(), snippets))
23534 }
23535 })
23536 .collect();
23537
23538 if scopes.is_empty() {
23539 return Task::ready(Ok(CompletionResponse {
23540 completions: vec![],
23541 display_options: CompletionDisplayOptions::default(),
23542 is_incomplete: false,
23543 }));
23544 }
23545
23546 let snapshot = buffer.read(cx).text_snapshot();
23547 let executor = cx.background_executor().clone();
23548
23549 cx.background_spawn(async move {
23550 let is_word_char = |c| classifier.is_word(c);
23551
23552 let mut is_incomplete = false;
23553 let mut completions: Vec<Completion> = Vec::new();
23554
23555 const MAX_PREFIX_LEN: usize = 128;
23556 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23557 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23558 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23559
23560 let max_buffer_window: String = snapshot
23561 .text_for_range(window_start..buffer_offset)
23562 .collect();
23563
23564 if max_buffer_window.is_empty() {
23565 return Ok(CompletionResponse {
23566 completions: vec![],
23567 display_options: CompletionDisplayOptions::default(),
23568 is_incomplete: true,
23569 });
23570 }
23571
23572 for (_scope, snippets) in scopes.into_iter() {
23573 // Sort snippets by word count to match longer snippet prefixes first.
23574 let mut sorted_snippet_candidates = snippets
23575 .iter()
23576 .enumerate()
23577 .flat_map(|(snippet_ix, snippet)| {
23578 snippet
23579 .prefix
23580 .iter()
23581 .enumerate()
23582 .map(move |(prefix_ix, prefix)| {
23583 let word_count =
23584 snippet_candidate_suffixes(prefix, is_word_char).count();
23585 ((snippet_ix, prefix_ix), prefix, word_count)
23586 })
23587 })
23588 .collect_vec();
23589 sorted_snippet_candidates
23590 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23591
23592 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23593
23594 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23595 .take(
23596 sorted_snippet_candidates
23597 .first()
23598 .map(|(_, _, word_count)| *word_count)
23599 .unwrap_or_default(),
23600 )
23601 .collect_vec();
23602
23603 const MAX_RESULTS: usize = 100;
23604 // Each match also remembers how many characters from the buffer it consumed
23605 let mut matches: Vec<(StringMatch, usize)> = vec![];
23606
23607 let mut snippet_list_cutoff_index = 0;
23608 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23609 let word_count = buffer_index + 1;
23610 // Increase `snippet_list_cutoff_index` until we have all of the
23611 // snippets with sufficiently many words.
23612 while sorted_snippet_candidates
23613 .get(snippet_list_cutoff_index)
23614 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23615 *snippet_word_count >= word_count
23616 })
23617 {
23618 snippet_list_cutoff_index += 1;
23619 }
23620
23621 // Take only the candidates with at least `word_count` many words
23622 let snippet_candidates_at_word_len =
23623 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23624
23625 let candidates = snippet_candidates_at_word_len
23626 .iter()
23627 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23628 .enumerate() // index in `sorted_snippet_candidates`
23629 // First char must match
23630 .filter(|(_ix, prefix)| {
23631 itertools::equal(
23632 prefix
23633 .chars()
23634 .next()
23635 .into_iter()
23636 .flat_map(|c| c.to_lowercase()),
23637 buffer_window
23638 .chars()
23639 .next()
23640 .into_iter()
23641 .flat_map(|c| c.to_lowercase()),
23642 )
23643 })
23644 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23645 .collect::<Vec<StringMatchCandidate>>();
23646
23647 matches.extend(
23648 fuzzy::match_strings(
23649 &candidates,
23650 &buffer_window,
23651 buffer_window.chars().any(|c| c.is_uppercase()),
23652 true,
23653 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23654 &Default::default(),
23655 executor.clone(),
23656 )
23657 .await
23658 .into_iter()
23659 .map(|string_match| (string_match, buffer_window.len())),
23660 );
23661
23662 if matches.len() >= MAX_RESULTS {
23663 break;
23664 }
23665 }
23666
23667 let to_lsp = |point: &text::Anchor| {
23668 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23669 point_to_lsp(end)
23670 };
23671 let lsp_end = to_lsp(&buffer_anchor);
23672
23673 if matches.len() >= MAX_RESULTS {
23674 is_incomplete = true;
23675 }
23676
23677 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23678 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23679 sorted_snippet_candidates[string_match.candidate_id];
23680 let snippet = &snippets[snippet_index];
23681 let start = buffer_offset - buffer_window_len;
23682 let start = snapshot.anchor_before(start);
23683 let range = start..buffer_anchor;
23684 let lsp_start = to_lsp(&start);
23685 let lsp_range = lsp::Range {
23686 start: lsp_start,
23687 end: lsp_end,
23688 };
23689 Completion {
23690 replace_range: range,
23691 new_text: snippet.body.clone(),
23692 source: CompletionSource::Lsp {
23693 insert_range: None,
23694 server_id: LanguageServerId(usize::MAX),
23695 resolved: true,
23696 lsp_completion: Box::new(lsp::CompletionItem {
23697 label: snippet.prefix.first().unwrap().clone(),
23698 kind: Some(CompletionItemKind::SNIPPET),
23699 label_details: snippet.description.as_ref().map(|description| {
23700 lsp::CompletionItemLabelDetails {
23701 detail: Some(description.clone()),
23702 description: None,
23703 }
23704 }),
23705 insert_text_format: Some(InsertTextFormat::SNIPPET),
23706 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23707 lsp::InsertReplaceEdit {
23708 new_text: snippet.body.clone(),
23709 insert: lsp_range,
23710 replace: lsp_range,
23711 },
23712 )),
23713 filter_text: Some(snippet.body.clone()),
23714 sort_text: Some(char::MAX.to_string()),
23715 ..lsp::CompletionItem::default()
23716 }),
23717 lsp_defaults: None,
23718 },
23719 label: CodeLabel {
23720 text: matching_prefix.clone(),
23721 runs: Vec::new(),
23722 filter_range: 0..matching_prefix.len(),
23723 },
23724 icon_path: None,
23725 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23726 single_line: snippet.name.clone().into(),
23727 plain_text: snippet
23728 .description
23729 .clone()
23730 .map(|description| description.into()),
23731 }),
23732 insert_text_mode: None,
23733 confirm: None,
23734 match_start: Some(start),
23735 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23736 }
23737 }));
23738 }
23739
23740 Ok(CompletionResponse {
23741 completions,
23742 display_options: CompletionDisplayOptions::default(),
23743 is_incomplete,
23744 })
23745 })
23746}
23747
23748impl CompletionProvider for Entity<Project> {
23749 fn completions(
23750 &self,
23751 _excerpt_id: ExcerptId,
23752 buffer: &Entity<Buffer>,
23753 buffer_position: text::Anchor,
23754 options: CompletionContext,
23755 _window: &mut Window,
23756 cx: &mut Context<Editor>,
23757 ) -> Task<Result<Vec<CompletionResponse>>> {
23758 self.update(cx, |project, cx| {
23759 let task = project.completions(buffer, buffer_position, options, cx);
23760 cx.background_spawn(task)
23761 })
23762 }
23763
23764 fn resolve_completions(
23765 &self,
23766 buffer: Entity<Buffer>,
23767 completion_indices: Vec<usize>,
23768 completions: Rc<RefCell<Box<[Completion]>>>,
23769 cx: &mut Context<Editor>,
23770 ) -> Task<Result<bool>> {
23771 self.update(cx, |project, cx| {
23772 project.lsp_store().update(cx, |lsp_store, cx| {
23773 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23774 })
23775 })
23776 }
23777
23778 fn apply_additional_edits_for_completion(
23779 &self,
23780 buffer: Entity<Buffer>,
23781 completions: Rc<RefCell<Box<[Completion]>>>,
23782 completion_index: usize,
23783 push_to_history: bool,
23784 cx: &mut Context<Editor>,
23785 ) -> Task<Result<Option<language::Transaction>>> {
23786 self.update(cx, |project, cx| {
23787 project.lsp_store().update(cx, |lsp_store, cx| {
23788 lsp_store.apply_additional_edits_for_completion(
23789 buffer,
23790 completions,
23791 completion_index,
23792 push_to_history,
23793 cx,
23794 )
23795 })
23796 })
23797 }
23798
23799 fn is_completion_trigger(
23800 &self,
23801 buffer: &Entity<Buffer>,
23802 position: language::Anchor,
23803 text: &str,
23804 trigger_in_words: bool,
23805 menu_is_open: bool,
23806 cx: &mut Context<Editor>,
23807 ) -> bool {
23808 let mut chars = text.chars();
23809 let char = if let Some(char) = chars.next() {
23810 char
23811 } else {
23812 return false;
23813 };
23814 if chars.next().is_some() {
23815 return false;
23816 }
23817
23818 let buffer = buffer.read(cx);
23819 let snapshot = buffer.snapshot();
23820 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23821 return false;
23822 }
23823 let classifier = snapshot
23824 .char_classifier_at(position)
23825 .scope_context(Some(CharScopeContext::Completion));
23826 if trigger_in_words && classifier.is_word(char) {
23827 return true;
23828 }
23829
23830 buffer.completion_triggers().contains(text)
23831 }
23832
23833 fn show_snippets(&self) -> bool {
23834 true
23835 }
23836}
23837
23838impl SemanticsProvider for Entity<Project> {
23839 fn hover(
23840 &self,
23841 buffer: &Entity<Buffer>,
23842 position: text::Anchor,
23843 cx: &mut App,
23844 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23845 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23846 }
23847
23848 fn document_highlights(
23849 &self,
23850 buffer: &Entity<Buffer>,
23851 position: text::Anchor,
23852 cx: &mut App,
23853 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23854 Some(self.update(cx, |project, cx| {
23855 project.document_highlights(buffer, position, cx)
23856 }))
23857 }
23858
23859 fn definitions(
23860 &self,
23861 buffer: &Entity<Buffer>,
23862 position: text::Anchor,
23863 kind: GotoDefinitionKind,
23864 cx: &mut App,
23865 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23866 Some(self.update(cx, |project, cx| match kind {
23867 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23868 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23869 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23870 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23871 }))
23872 }
23873
23874 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23875 self.update(cx, |project, cx| {
23876 if project
23877 .active_debug_session(cx)
23878 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23879 {
23880 return true;
23881 }
23882
23883 buffer.update(cx, |buffer, cx| {
23884 project.any_language_server_supports_inlay_hints(buffer, cx)
23885 })
23886 })
23887 }
23888
23889 fn inline_values(
23890 &self,
23891 buffer_handle: Entity<Buffer>,
23892 range: Range<text::Anchor>,
23893 cx: &mut App,
23894 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23895 self.update(cx, |project, cx| {
23896 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23897
23898 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23899 })
23900 }
23901
23902 fn applicable_inlay_chunks(
23903 &self,
23904 buffer: &Entity<Buffer>,
23905 ranges: &[Range<text::Anchor>],
23906 cx: &mut App,
23907 ) -> Vec<Range<BufferRow>> {
23908 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23909 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23910 })
23911 }
23912
23913 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23914 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23915 lsp_store.invalidate_inlay_hints(for_buffers)
23916 });
23917 }
23918
23919 fn inlay_hints(
23920 &self,
23921 invalidate: InvalidationStrategy,
23922 buffer: Entity<Buffer>,
23923 ranges: Vec<Range<text::Anchor>>,
23924 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23925 cx: &mut App,
23926 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23927 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23928 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23929 }))
23930 }
23931
23932 fn range_for_rename(
23933 &self,
23934 buffer: &Entity<Buffer>,
23935 position: text::Anchor,
23936 cx: &mut App,
23937 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23938 Some(self.update(cx, |project, cx| {
23939 let buffer = buffer.clone();
23940 let task = project.prepare_rename(buffer.clone(), position, cx);
23941 cx.spawn(async move |_, cx| {
23942 Ok(match task.await? {
23943 PrepareRenameResponse::Success(range) => Some(range),
23944 PrepareRenameResponse::InvalidPosition => None,
23945 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23946 // Fallback on using TreeSitter info to determine identifier range
23947 buffer.read_with(cx, |buffer, _| {
23948 let snapshot = buffer.snapshot();
23949 let (range, kind) = snapshot.surrounding_word(position, None);
23950 if kind != Some(CharKind::Word) {
23951 return None;
23952 }
23953 Some(
23954 snapshot.anchor_before(range.start)
23955 ..snapshot.anchor_after(range.end),
23956 )
23957 })?
23958 }
23959 })
23960 })
23961 }))
23962 }
23963
23964 fn perform_rename(
23965 &self,
23966 buffer: &Entity<Buffer>,
23967 position: text::Anchor,
23968 new_name: String,
23969 cx: &mut App,
23970 ) -> Option<Task<Result<ProjectTransaction>>> {
23971 Some(self.update(cx, |project, cx| {
23972 project.perform_rename(buffer.clone(), position, new_name, cx)
23973 }))
23974 }
23975}
23976
23977fn consume_contiguous_rows(
23978 contiguous_row_selections: &mut Vec<Selection<Point>>,
23979 selection: &Selection<Point>,
23980 display_map: &DisplaySnapshot,
23981 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23982) -> (MultiBufferRow, MultiBufferRow) {
23983 contiguous_row_selections.push(selection.clone());
23984 let start_row = starting_row(selection, display_map);
23985 let mut end_row = ending_row(selection, display_map);
23986
23987 while let Some(next_selection) = selections.peek() {
23988 if next_selection.start.row <= end_row.0 {
23989 end_row = ending_row(next_selection, display_map);
23990 contiguous_row_selections.push(selections.next().unwrap().clone());
23991 } else {
23992 break;
23993 }
23994 }
23995 (start_row, end_row)
23996}
23997
23998fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23999 if selection.start.column > 0 {
24000 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24001 } else {
24002 MultiBufferRow(selection.start.row)
24003 }
24004}
24005
24006fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24007 if next_selection.end.column > 0 || next_selection.is_empty() {
24008 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24009 } else {
24010 MultiBufferRow(next_selection.end.row)
24011 }
24012}
24013
24014impl EditorSnapshot {
24015 pub fn remote_selections_in_range<'a>(
24016 &'a self,
24017 range: &'a Range<Anchor>,
24018 collaboration_hub: &dyn CollaborationHub,
24019 cx: &'a App,
24020 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24021 let participant_names = collaboration_hub.user_names(cx);
24022 let participant_indices = collaboration_hub.user_participant_indices(cx);
24023 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24024 let collaborators_by_replica_id = collaborators_by_peer_id
24025 .values()
24026 .map(|collaborator| (collaborator.replica_id, collaborator))
24027 .collect::<HashMap<_, _>>();
24028 self.buffer_snapshot()
24029 .selections_in_range(range, false)
24030 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24031 if replica_id == ReplicaId::AGENT {
24032 Some(RemoteSelection {
24033 replica_id,
24034 selection,
24035 cursor_shape,
24036 line_mode,
24037 collaborator_id: CollaboratorId::Agent,
24038 user_name: Some("Agent".into()),
24039 color: cx.theme().players().agent(),
24040 })
24041 } else {
24042 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24043 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24044 let user_name = participant_names.get(&collaborator.user_id).cloned();
24045 Some(RemoteSelection {
24046 replica_id,
24047 selection,
24048 cursor_shape,
24049 line_mode,
24050 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24051 user_name,
24052 color: if let Some(index) = participant_index {
24053 cx.theme().players().color_for_participant(index.0)
24054 } else {
24055 cx.theme().players().absent()
24056 },
24057 })
24058 }
24059 })
24060 }
24061
24062 pub fn hunks_for_ranges(
24063 &self,
24064 ranges: impl IntoIterator<Item = Range<Point>>,
24065 ) -> Vec<MultiBufferDiffHunk> {
24066 let mut hunks = Vec::new();
24067 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24068 HashMap::default();
24069 for query_range in ranges {
24070 let query_rows =
24071 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24072 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24073 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24074 ) {
24075 // Include deleted hunks that are adjacent to the query range, because
24076 // otherwise they would be missed.
24077 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24078 if hunk.status().is_deleted() {
24079 intersects_range |= hunk.row_range.start == query_rows.end;
24080 intersects_range |= hunk.row_range.end == query_rows.start;
24081 }
24082 if intersects_range {
24083 if !processed_buffer_rows
24084 .entry(hunk.buffer_id)
24085 .or_default()
24086 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24087 {
24088 continue;
24089 }
24090 hunks.push(hunk);
24091 }
24092 }
24093 }
24094
24095 hunks
24096 }
24097
24098 fn display_diff_hunks_for_rows<'a>(
24099 &'a self,
24100 display_rows: Range<DisplayRow>,
24101 folded_buffers: &'a HashSet<BufferId>,
24102 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24103 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24104 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24105
24106 self.buffer_snapshot()
24107 .diff_hunks_in_range(buffer_start..buffer_end)
24108 .filter_map(|hunk| {
24109 if folded_buffers.contains(&hunk.buffer_id) {
24110 return None;
24111 }
24112
24113 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24114 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24115
24116 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24117 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24118
24119 let display_hunk = if hunk_display_start.column() != 0 {
24120 DisplayDiffHunk::Folded {
24121 display_row: hunk_display_start.row(),
24122 }
24123 } else {
24124 let mut end_row = hunk_display_end.row();
24125 if hunk_display_end.column() > 0 {
24126 end_row.0 += 1;
24127 }
24128 let is_created_file = hunk.is_created_file();
24129 DisplayDiffHunk::Unfolded {
24130 status: hunk.status(),
24131 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24132 ..hunk.diff_base_byte_range.end.0,
24133 display_row_range: hunk_display_start.row()..end_row,
24134 multi_buffer_range: Anchor::range_in_buffer(
24135 hunk.excerpt_id,
24136 hunk.buffer_range,
24137 ),
24138 is_created_file,
24139 }
24140 };
24141
24142 Some(display_hunk)
24143 })
24144 }
24145
24146 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24147 self.display_snapshot
24148 .buffer_snapshot()
24149 .language_at(position)
24150 }
24151
24152 pub fn is_focused(&self) -> bool {
24153 self.is_focused
24154 }
24155
24156 pub fn placeholder_text(&self) -> Option<String> {
24157 self.placeholder_display_snapshot
24158 .as_ref()
24159 .map(|display_map| display_map.text())
24160 }
24161
24162 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24163 self.scroll_anchor.scroll_position(&self.display_snapshot)
24164 }
24165
24166 fn gutter_dimensions(
24167 &self,
24168 font_id: FontId,
24169 font_size: Pixels,
24170 max_line_number_width: Pixels,
24171 cx: &App,
24172 ) -> Option<GutterDimensions> {
24173 if !self.show_gutter {
24174 return None;
24175 }
24176
24177 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24178 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24179
24180 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24181 matches!(
24182 ProjectSettings::get_global(cx).git.git_gutter,
24183 GitGutterSetting::TrackedFiles
24184 )
24185 });
24186 let gutter_settings = EditorSettings::get_global(cx).gutter;
24187 let show_line_numbers = self
24188 .show_line_numbers
24189 .unwrap_or(gutter_settings.line_numbers);
24190 let line_gutter_width = if show_line_numbers {
24191 // Avoid flicker-like gutter resizes when the line number gains another digit by
24192 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24193 let min_width_for_number_on_gutter =
24194 ch_advance * gutter_settings.min_line_number_digits as f32;
24195 max_line_number_width.max(min_width_for_number_on_gutter)
24196 } else {
24197 0.0.into()
24198 };
24199
24200 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24201 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24202
24203 let git_blame_entries_width =
24204 self.git_blame_gutter_max_author_length
24205 .map(|max_author_length| {
24206 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24207 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24208
24209 /// The number of characters to dedicate to gaps and margins.
24210 const SPACING_WIDTH: usize = 4;
24211
24212 let max_char_count = max_author_length.min(renderer.max_author_length())
24213 + ::git::SHORT_SHA_LENGTH
24214 + MAX_RELATIVE_TIMESTAMP.len()
24215 + SPACING_WIDTH;
24216
24217 ch_advance * max_char_count
24218 });
24219
24220 let is_singleton = self.buffer_snapshot().is_singleton();
24221
24222 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24223 left_padding += if !is_singleton {
24224 ch_width * 4.0
24225 } else if show_runnables || show_breakpoints {
24226 ch_width * 3.0
24227 } else if show_git_gutter && show_line_numbers {
24228 ch_width * 2.0
24229 } else if show_git_gutter || show_line_numbers {
24230 ch_width
24231 } else {
24232 px(0.)
24233 };
24234
24235 let shows_folds = is_singleton && gutter_settings.folds;
24236
24237 let right_padding = if shows_folds && show_line_numbers {
24238 ch_width * 4.0
24239 } else if shows_folds || (!is_singleton && show_line_numbers) {
24240 ch_width * 3.0
24241 } else if show_line_numbers {
24242 ch_width
24243 } else {
24244 px(0.)
24245 };
24246
24247 Some(GutterDimensions {
24248 left_padding,
24249 right_padding,
24250 width: line_gutter_width + left_padding + right_padding,
24251 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24252 git_blame_entries_width,
24253 })
24254 }
24255
24256 pub fn render_crease_toggle(
24257 &self,
24258 buffer_row: MultiBufferRow,
24259 row_contains_cursor: bool,
24260 editor: Entity<Editor>,
24261 window: &mut Window,
24262 cx: &mut App,
24263 ) -> Option<AnyElement> {
24264 let folded = self.is_line_folded(buffer_row);
24265 let mut is_foldable = false;
24266
24267 if let Some(crease) = self
24268 .crease_snapshot
24269 .query_row(buffer_row, self.buffer_snapshot())
24270 {
24271 is_foldable = true;
24272 match crease {
24273 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24274 if let Some(render_toggle) = render_toggle {
24275 let toggle_callback =
24276 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24277 if folded {
24278 editor.update(cx, |editor, cx| {
24279 editor.fold_at(buffer_row, window, cx)
24280 });
24281 } else {
24282 editor.update(cx, |editor, cx| {
24283 editor.unfold_at(buffer_row, window, cx)
24284 });
24285 }
24286 });
24287 return Some((render_toggle)(
24288 buffer_row,
24289 folded,
24290 toggle_callback,
24291 window,
24292 cx,
24293 ));
24294 }
24295 }
24296 }
24297 }
24298
24299 is_foldable |= self.starts_indent(buffer_row);
24300
24301 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24302 Some(
24303 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24304 .toggle_state(folded)
24305 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24306 if folded {
24307 this.unfold_at(buffer_row, window, cx);
24308 } else {
24309 this.fold_at(buffer_row, window, cx);
24310 }
24311 }))
24312 .into_any_element(),
24313 )
24314 } else {
24315 None
24316 }
24317 }
24318
24319 pub fn render_crease_trailer(
24320 &self,
24321 buffer_row: MultiBufferRow,
24322 window: &mut Window,
24323 cx: &mut App,
24324 ) -> Option<AnyElement> {
24325 let folded = self.is_line_folded(buffer_row);
24326 if let Crease::Inline { render_trailer, .. } = self
24327 .crease_snapshot
24328 .query_row(buffer_row, self.buffer_snapshot())?
24329 {
24330 let render_trailer = render_trailer.as_ref()?;
24331 Some(render_trailer(buffer_row, folded, window, cx))
24332 } else {
24333 None
24334 }
24335 }
24336}
24337
24338impl Deref for EditorSnapshot {
24339 type Target = DisplaySnapshot;
24340
24341 fn deref(&self) -> &Self::Target {
24342 &self.display_snapshot
24343 }
24344}
24345
24346#[derive(Clone, Debug, PartialEq, Eq)]
24347pub enum EditorEvent {
24348 InputIgnored {
24349 text: Arc<str>,
24350 },
24351 InputHandled {
24352 utf16_range_to_replace: Option<Range<isize>>,
24353 text: Arc<str>,
24354 },
24355 ExcerptsAdded {
24356 buffer: Entity<Buffer>,
24357 predecessor: ExcerptId,
24358 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24359 },
24360 ExcerptsRemoved {
24361 ids: Vec<ExcerptId>,
24362 removed_buffer_ids: Vec<BufferId>,
24363 },
24364 BufferFoldToggled {
24365 ids: Vec<ExcerptId>,
24366 folded: bool,
24367 },
24368 ExcerptsEdited {
24369 ids: Vec<ExcerptId>,
24370 },
24371 ExcerptsExpanded {
24372 ids: Vec<ExcerptId>,
24373 },
24374 BufferEdited,
24375 Edited {
24376 transaction_id: clock::Lamport,
24377 },
24378 Reparsed(BufferId),
24379 Focused,
24380 FocusedIn,
24381 Blurred,
24382 DirtyChanged,
24383 Saved,
24384 TitleChanged,
24385 SelectionsChanged {
24386 local: bool,
24387 },
24388 ScrollPositionChanged {
24389 local: bool,
24390 autoscroll: bool,
24391 },
24392 TransactionUndone {
24393 transaction_id: clock::Lamport,
24394 },
24395 TransactionBegun {
24396 transaction_id: clock::Lamport,
24397 },
24398 CursorShapeChanged,
24399 BreadcrumbsChanged,
24400 PushedToNavHistory {
24401 anchor: Anchor,
24402 is_deactivate: bool,
24403 },
24404}
24405
24406impl EventEmitter<EditorEvent> for Editor {}
24407
24408impl Focusable for Editor {
24409 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24410 self.focus_handle.clone()
24411 }
24412}
24413
24414impl Render for Editor {
24415 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24416 let settings = ThemeSettings::get_global(cx);
24417
24418 let mut text_style = match self.mode {
24419 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24420 color: cx.theme().colors().editor_foreground,
24421 font_family: settings.ui_font.family.clone(),
24422 font_features: settings.ui_font.features.clone(),
24423 font_fallbacks: settings.ui_font.fallbacks.clone(),
24424 font_size: rems(0.875).into(),
24425 font_weight: settings.ui_font.weight,
24426 line_height: relative(settings.buffer_line_height.value()),
24427 ..Default::default()
24428 },
24429 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24430 color: cx.theme().colors().editor_foreground,
24431 font_family: settings.buffer_font.family.clone(),
24432 font_features: settings.buffer_font.features.clone(),
24433 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24434 font_size: settings.buffer_font_size(cx).into(),
24435 font_weight: settings.buffer_font.weight,
24436 line_height: relative(settings.buffer_line_height.value()),
24437 ..Default::default()
24438 },
24439 };
24440 if let Some(text_style_refinement) = &self.text_style_refinement {
24441 text_style.refine(text_style_refinement)
24442 }
24443
24444 let background = match self.mode {
24445 EditorMode::SingleLine => cx.theme().system().transparent,
24446 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24447 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24448 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24449 };
24450
24451 EditorElement::new(
24452 &cx.entity(),
24453 EditorStyle {
24454 background,
24455 border: cx.theme().colors().border,
24456 local_player: cx.theme().players().local(),
24457 text: text_style,
24458 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24459 syntax: cx.theme().syntax().clone(),
24460 status: cx.theme().status().clone(),
24461 inlay_hints_style: make_inlay_hints_style(cx),
24462 edit_prediction_styles: make_suggestion_styles(cx),
24463 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24464 show_underlines: self.diagnostics_enabled(),
24465 },
24466 )
24467 }
24468}
24469
24470impl EntityInputHandler for Editor {
24471 fn text_for_range(
24472 &mut self,
24473 range_utf16: Range<usize>,
24474 adjusted_range: &mut Option<Range<usize>>,
24475 _: &mut Window,
24476 cx: &mut Context<Self>,
24477 ) -> Option<String> {
24478 let snapshot = self.buffer.read(cx).read(cx);
24479 let start = snapshot.clip_offset_utf16(
24480 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24481 Bias::Left,
24482 );
24483 let end = snapshot.clip_offset_utf16(
24484 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24485 Bias::Right,
24486 );
24487 if (start.0.0..end.0.0) != range_utf16 {
24488 adjusted_range.replace(start.0.0..end.0.0);
24489 }
24490 Some(snapshot.text_for_range(start..end).collect())
24491 }
24492
24493 fn selected_text_range(
24494 &mut self,
24495 ignore_disabled_input: bool,
24496 _: &mut Window,
24497 cx: &mut Context<Self>,
24498 ) -> Option<UTF16Selection> {
24499 // Prevent the IME menu from appearing when holding down an alphabetic key
24500 // while input is disabled.
24501 if !ignore_disabled_input && !self.input_enabled {
24502 return None;
24503 }
24504
24505 let selection = self
24506 .selections
24507 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24508 let range = selection.range();
24509
24510 Some(UTF16Selection {
24511 range: range.start.0.0..range.end.0.0,
24512 reversed: selection.reversed,
24513 })
24514 }
24515
24516 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24517 let snapshot = self.buffer.read(cx).read(cx);
24518 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24519 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24520 }
24521
24522 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24523 self.clear_highlights::<InputComposition>(cx);
24524 self.ime_transaction.take();
24525 }
24526
24527 fn replace_text_in_range(
24528 &mut self,
24529 range_utf16: Option<Range<usize>>,
24530 text: &str,
24531 window: &mut Window,
24532 cx: &mut Context<Self>,
24533 ) {
24534 if !self.input_enabled {
24535 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24536 return;
24537 }
24538
24539 self.transact(window, cx, |this, window, cx| {
24540 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24541 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24542 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24543 Some(this.selection_replacement_ranges(range_utf16, cx))
24544 } else {
24545 this.marked_text_ranges(cx)
24546 };
24547
24548 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24549 let newest_selection_id = this.selections.newest_anchor().id;
24550 this.selections
24551 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24552 .iter()
24553 .zip(ranges_to_replace.iter())
24554 .find_map(|(selection, range)| {
24555 if selection.id == newest_selection_id {
24556 Some(
24557 (range.start.0.0 as isize - selection.head().0.0 as isize)
24558 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24559 )
24560 } else {
24561 None
24562 }
24563 })
24564 });
24565
24566 cx.emit(EditorEvent::InputHandled {
24567 utf16_range_to_replace: range_to_replace,
24568 text: text.into(),
24569 });
24570
24571 if let Some(new_selected_ranges) = new_selected_ranges {
24572 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24573 selections.select_ranges(new_selected_ranges)
24574 });
24575 this.backspace(&Default::default(), window, cx);
24576 }
24577
24578 this.handle_input(text, window, cx);
24579 });
24580
24581 if let Some(transaction) = self.ime_transaction {
24582 self.buffer.update(cx, |buffer, cx| {
24583 buffer.group_until_transaction(transaction, cx);
24584 });
24585 }
24586
24587 self.unmark_text(window, cx);
24588 }
24589
24590 fn replace_and_mark_text_in_range(
24591 &mut self,
24592 range_utf16: Option<Range<usize>>,
24593 text: &str,
24594 new_selected_range_utf16: Option<Range<usize>>,
24595 window: &mut Window,
24596 cx: &mut Context<Self>,
24597 ) {
24598 if !self.input_enabled {
24599 return;
24600 }
24601
24602 let transaction = self.transact(window, cx, |this, window, cx| {
24603 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24604 let snapshot = this.buffer.read(cx).read(cx);
24605 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24606 for marked_range in &mut marked_ranges {
24607 marked_range.end = marked_range.start + relative_range_utf16.end;
24608 marked_range.start += relative_range_utf16.start;
24609 marked_range.start =
24610 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24611 marked_range.end =
24612 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24613 }
24614 }
24615 Some(marked_ranges)
24616 } else if let Some(range_utf16) = range_utf16 {
24617 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24618 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24619 Some(this.selection_replacement_ranges(range_utf16, cx))
24620 } else {
24621 None
24622 };
24623
24624 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24625 let newest_selection_id = this.selections.newest_anchor().id;
24626 this.selections
24627 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24628 .iter()
24629 .zip(ranges_to_replace.iter())
24630 .find_map(|(selection, range)| {
24631 if selection.id == newest_selection_id {
24632 Some(
24633 (range.start.0.0 as isize - selection.head().0.0 as isize)
24634 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24635 )
24636 } else {
24637 None
24638 }
24639 })
24640 });
24641
24642 cx.emit(EditorEvent::InputHandled {
24643 utf16_range_to_replace: range_to_replace,
24644 text: text.into(),
24645 });
24646
24647 if let Some(ranges) = ranges_to_replace {
24648 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24649 s.select_ranges(ranges)
24650 });
24651 }
24652
24653 let marked_ranges = {
24654 let snapshot = this.buffer.read(cx).read(cx);
24655 this.selections
24656 .disjoint_anchors_arc()
24657 .iter()
24658 .map(|selection| {
24659 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24660 })
24661 .collect::<Vec<_>>()
24662 };
24663
24664 if text.is_empty() {
24665 this.unmark_text(window, cx);
24666 } else {
24667 this.highlight_text::<InputComposition>(
24668 marked_ranges.clone(),
24669 HighlightStyle {
24670 underline: Some(UnderlineStyle {
24671 thickness: px(1.),
24672 color: None,
24673 wavy: false,
24674 }),
24675 ..Default::default()
24676 },
24677 cx,
24678 );
24679 }
24680
24681 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24682 let use_autoclose = this.use_autoclose;
24683 let use_auto_surround = this.use_auto_surround;
24684 this.set_use_autoclose(false);
24685 this.set_use_auto_surround(false);
24686 this.handle_input(text, window, cx);
24687 this.set_use_autoclose(use_autoclose);
24688 this.set_use_auto_surround(use_auto_surround);
24689
24690 if let Some(new_selected_range) = new_selected_range_utf16 {
24691 let snapshot = this.buffer.read(cx).read(cx);
24692 let new_selected_ranges = marked_ranges
24693 .into_iter()
24694 .map(|marked_range| {
24695 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24696 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24697 insertion_start.0 + new_selected_range.start,
24698 ));
24699 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24700 insertion_start.0 + new_selected_range.end,
24701 ));
24702 snapshot.clip_offset_utf16(new_start, Bias::Left)
24703 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24704 })
24705 .collect::<Vec<_>>();
24706
24707 drop(snapshot);
24708 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24709 selections.select_ranges(new_selected_ranges)
24710 });
24711 }
24712 });
24713
24714 self.ime_transaction = self.ime_transaction.or(transaction);
24715 if let Some(transaction) = self.ime_transaction {
24716 self.buffer.update(cx, |buffer, cx| {
24717 buffer.group_until_transaction(transaction, cx);
24718 });
24719 }
24720
24721 if self.text_highlights::<InputComposition>(cx).is_none() {
24722 self.ime_transaction.take();
24723 }
24724 }
24725
24726 fn bounds_for_range(
24727 &mut self,
24728 range_utf16: Range<usize>,
24729 element_bounds: gpui::Bounds<Pixels>,
24730 window: &mut Window,
24731 cx: &mut Context<Self>,
24732 ) -> Option<gpui::Bounds<Pixels>> {
24733 let text_layout_details = self.text_layout_details(window);
24734 let CharacterDimensions {
24735 em_width,
24736 em_advance,
24737 line_height,
24738 } = self.character_dimensions(window);
24739
24740 let snapshot = self.snapshot(window, cx);
24741 let scroll_position = snapshot.scroll_position();
24742 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24743
24744 let start =
24745 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24746 let x = Pixels::from(
24747 ScrollOffset::from(
24748 snapshot.x_for_display_point(start, &text_layout_details)
24749 + self.gutter_dimensions.full_width(),
24750 ) - scroll_left,
24751 );
24752 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24753
24754 Some(Bounds {
24755 origin: element_bounds.origin + point(x, y),
24756 size: size(em_width, line_height),
24757 })
24758 }
24759
24760 fn character_index_for_point(
24761 &mut self,
24762 point: gpui::Point<Pixels>,
24763 _window: &mut Window,
24764 _cx: &mut Context<Self>,
24765 ) -> Option<usize> {
24766 let position_map = self.last_position_map.as_ref()?;
24767 if !position_map.text_hitbox.contains(&point) {
24768 return None;
24769 }
24770 let display_point = position_map.point_for_position(point).previous_valid;
24771 let anchor = position_map
24772 .snapshot
24773 .display_point_to_anchor(display_point, Bias::Left);
24774 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24775 Some(utf16_offset.0.0)
24776 }
24777
24778 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24779 self.input_enabled
24780 }
24781}
24782
24783trait SelectionExt {
24784 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24785 fn spanned_rows(
24786 &self,
24787 include_end_if_at_line_start: bool,
24788 map: &DisplaySnapshot,
24789 ) -> Range<MultiBufferRow>;
24790}
24791
24792impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24793 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24794 let start = self
24795 .start
24796 .to_point(map.buffer_snapshot())
24797 .to_display_point(map);
24798 let end = self
24799 .end
24800 .to_point(map.buffer_snapshot())
24801 .to_display_point(map);
24802 if self.reversed {
24803 end..start
24804 } else {
24805 start..end
24806 }
24807 }
24808
24809 fn spanned_rows(
24810 &self,
24811 include_end_if_at_line_start: bool,
24812 map: &DisplaySnapshot,
24813 ) -> Range<MultiBufferRow> {
24814 let start = self.start.to_point(map.buffer_snapshot());
24815 let mut end = self.end.to_point(map.buffer_snapshot());
24816 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24817 end.row -= 1;
24818 }
24819
24820 let buffer_start = map.prev_line_boundary(start).0;
24821 let buffer_end = map.next_line_boundary(end).0;
24822 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24823 }
24824}
24825
24826impl<T: InvalidationRegion> InvalidationStack<T> {
24827 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24828 where
24829 S: Clone + ToOffset,
24830 {
24831 while let Some(region) = self.last() {
24832 let all_selections_inside_invalidation_ranges =
24833 if selections.len() == region.ranges().len() {
24834 selections
24835 .iter()
24836 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24837 .all(|(selection, invalidation_range)| {
24838 let head = selection.head().to_offset(buffer);
24839 invalidation_range.start <= head && invalidation_range.end >= head
24840 })
24841 } else {
24842 false
24843 };
24844
24845 if all_selections_inside_invalidation_ranges {
24846 break;
24847 } else {
24848 self.pop();
24849 }
24850 }
24851 }
24852}
24853
24854impl<T> Default for InvalidationStack<T> {
24855 fn default() -> Self {
24856 Self(Default::default())
24857 }
24858}
24859
24860impl<T> Deref for InvalidationStack<T> {
24861 type Target = Vec<T>;
24862
24863 fn deref(&self) -> &Self::Target {
24864 &self.0
24865 }
24866}
24867
24868impl<T> DerefMut for InvalidationStack<T> {
24869 fn deref_mut(&mut self) -> &mut Self::Target {
24870 &mut self.0
24871 }
24872}
24873
24874impl InvalidationRegion for SnippetState {
24875 fn ranges(&self) -> &[Range<Anchor>] {
24876 &self.ranges[self.active_index]
24877 }
24878}
24879
24880fn edit_prediction_edit_text(
24881 current_snapshot: &BufferSnapshot,
24882 edits: &[(Range<Anchor>, impl AsRef<str>)],
24883 edit_preview: &EditPreview,
24884 include_deletions: bool,
24885 cx: &App,
24886) -> HighlightedText {
24887 let edits = edits
24888 .iter()
24889 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24890 .collect::<Vec<_>>();
24891
24892 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24893}
24894
24895fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24896 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24897 // Just show the raw edit text with basic styling
24898 let mut text = String::new();
24899 let mut highlights = Vec::new();
24900
24901 let insertion_highlight_style = HighlightStyle {
24902 color: Some(cx.theme().colors().text),
24903 ..Default::default()
24904 };
24905
24906 for (_, edit_text) in edits {
24907 let start_offset = text.len();
24908 text.push_str(edit_text);
24909 let end_offset = text.len();
24910
24911 if start_offset < end_offset {
24912 highlights.push((start_offset..end_offset, insertion_highlight_style));
24913 }
24914 }
24915
24916 HighlightedText {
24917 text: text.into(),
24918 highlights,
24919 }
24920}
24921
24922pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24923 match severity {
24924 lsp::DiagnosticSeverity::ERROR => colors.error,
24925 lsp::DiagnosticSeverity::WARNING => colors.warning,
24926 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24927 lsp::DiagnosticSeverity::HINT => colors.info,
24928 _ => colors.ignored,
24929 }
24930}
24931
24932pub fn styled_runs_for_code_label<'a>(
24933 label: &'a CodeLabel,
24934 syntax_theme: &'a theme::SyntaxTheme,
24935 local_player: &'a theme::PlayerColor,
24936) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24937 let fade_out = HighlightStyle {
24938 fade_out: Some(0.35),
24939 ..Default::default()
24940 };
24941
24942 let mut prev_end = label.filter_range.end;
24943 label
24944 .runs
24945 .iter()
24946 .enumerate()
24947 .flat_map(move |(ix, (range, highlight_id))| {
24948 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
24949 HighlightStyle {
24950 color: Some(local_player.cursor),
24951 ..Default::default()
24952 }
24953 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
24954 HighlightStyle {
24955 background_color: Some(local_player.selection),
24956 ..Default::default()
24957 }
24958 } else if let Some(style) = highlight_id.style(syntax_theme) {
24959 style
24960 } else {
24961 return Default::default();
24962 };
24963 let muted_style = style.highlight(fade_out);
24964
24965 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24966 if range.start >= label.filter_range.end {
24967 if range.start > prev_end {
24968 runs.push((prev_end..range.start, fade_out));
24969 }
24970 runs.push((range.clone(), muted_style));
24971 } else if range.end <= label.filter_range.end {
24972 runs.push((range.clone(), style));
24973 } else {
24974 runs.push((range.start..label.filter_range.end, style));
24975 runs.push((label.filter_range.end..range.end, muted_style));
24976 }
24977 prev_end = cmp::max(prev_end, range.end);
24978
24979 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24980 runs.push((prev_end..label.text.len(), fade_out));
24981 }
24982
24983 runs
24984 })
24985}
24986
24987pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24988 let mut prev_index = 0;
24989 let mut prev_codepoint: Option<char> = None;
24990 text.char_indices()
24991 .chain([(text.len(), '\0')])
24992 .filter_map(move |(index, codepoint)| {
24993 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24994 let is_boundary = index == text.len()
24995 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24996 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24997 if is_boundary {
24998 let chunk = &text[prev_index..index];
24999 prev_index = index;
25000 Some(chunk)
25001 } else {
25002 None
25003 }
25004 })
25005}
25006
25007/// Given a string of text immediately before the cursor, iterates over possible
25008/// strings a snippet could match to. More precisely: returns an iterator over
25009/// suffixes of `text` created by splitting at word boundaries (before & after
25010/// every non-word character).
25011///
25012/// Shorter suffixes are returned first.
25013pub(crate) fn snippet_candidate_suffixes(
25014 text: &str,
25015 is_word_char: impl Fn(char) -> bool,
25016) -> impl std::iter::Iterator<Item = &str> {
25017 let mut prev_index = text.len();
25018 let mut prev_codepoint = None;
25019 text.char_indices()
25020 .rev()
25021 .chain([(0, '\0')])
25022 .filter_map(move |(index, codepoint)| {
25023 let prev_index = std::mem::replace(&mut prev_index, index);
25024 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25025 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25026 None
25027 } else {
25028 let chunk = &text[prev_index..]; // go to end of string
25029 Some(chunk)
25030 }
25031 })
25032}
25033
25034pub trait RangeToAnchorExt: Sized {
25035 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25036
25037 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25038 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25039 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25040 }
25041}
25042
25043impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25044 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25045 let start_offset = self.start.to_offset(snapshot);
25046 let end_offset = self.end.to_offset(snapshot);
25047 if start_offset == end_offset {
25048 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25049 } else {
25050 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25051 }
25052 }
25053}
25054
25055pub trait RowExt {
25056 fn as_f64(&self) -> f64;
25057
25058 fn next_row(&self) -> Self;
25059
25060 fn previous_row(&self) -> Self;
25061
25062 fn minus(&self, other: Self) -> u32;
25063}
25064
25065impl RowExt for DisplayRow {
25066 fn as_f64(&self) -> f64 {
25067 self.0 as _
25068 }
25069
25070 fn next_row(&self) -> Self {
25071 Self(self.0 + 1)
25072 }
25073
25074 fn previous_row(&self) -> Self {
25075 Self(self.0.saturating_sub(1))
25076 }
25077
25078 fn minus(&self, other: Self) -> u32 {
25079 self.0 - other.0
25080 }
25081}
25082
25083impl RowExt for MultiBufferRow {
25084 fn as_f64(&self) -> f64 {
25085 self.0 as _
25086 }
25087
25088 fn next_row(&self) -> Self {
25089 Self(self.0 + 1)
25090 }
25091
25092 fn previous_row(&self) -> Self {
25093 Self(self.0.saturating_sub(1))
25094 }
25095
25096 fn minus(&self, other: Self) -> u32 {
25097 self.0 - other.0
25098 }
25099}
25100
25101trait RowRangeExt {
25102 type Row;
25103
25104 fn len(&self) -> usize;
25105
25106 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25107}
25108
25109impl RowRangeExt for Range<MultiBufferRow> {
25110 type Row = MultiBufferRow;
25111
25112 fn len(&self) -> usize {
25113 (self.end.0 - self.start.0) as usize
25114 }
25115
25116 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25117 (self.start.0..self.end.0).map(MultiBufferRow)
25118 }
25119}
25120
25121impl RowRangeExt for Range<DisplayRow> {
25122 type Row = DisplayRow;
25123
25124 fn len(&self) -> usize {
25125 (self.end.0 - self.start.0) as usize
25126 }
25127
25128 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25129 (self.start.0..self.end.0).map(DisplayRow)
25130 }
25131}
25132
25133/// If select range has more than one line, we
25134/// just point the cursor to range.start.
25135fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25136 if range.start.row == range.end.row {
25137 range
25138 } else {
25139 range.start..range.start
25140 }
25141}
25142pub struct KillRing(ClipboardItem);
25143impl Global for KillRing {}
25144
25145const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25146
25147enum BreakpointPromptEditAction {
25148 Log,
25149 Condition,
25150 HitCondition,
25151}
25152
25153struct BreakpointPromptEditor {
25154 pub(crate) prompt: Entity<Editor>,
25155 editor: WeakEntity<Editor>,
25156 breakpoint_anchor: Anchor,
25157 breakpoint: Breakpoint,
25158 edit_action: BreakpointPromptEditAction,
25159 block_ids: HashSet<CustomBlockId>,
25160 editor_margins: Arc<Mutex<EditorMargins>>,
25161 _subscriptions: Vec<Subscription>,
25162}
25163
25164impl BreakpointPromptEditor {
25165 const MAX_LINES: u8 = 4;
25166
25167 fn new(
25168 editor: WeakEntity<Editor>,
25169 breakpoint_anchor: Anchor,
25170 breakpoint: Breakpoint,
25171 edit_action: BreakpointPromptEditAction,
25172 window: &mut Window,
25173 cx: &mut Context<Self>,
25174 ) -> Self {
25175 let base_text = match edit_action {
25176 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25177 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25178 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25179 }
25180 .map(|msg| msg.to_string())
25181 .unwrap_or_default();
25182
25183 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25184 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25185
25186 let prompt = cx.new(|cx| {
25187 let mut prompt = Editor::new(
25188 EditorMode::AutoHeight {
25189 min_lines: 1,
25190 max_lines: Some(Self::MAX_LINES as usize),
25191 },
25192 buffer,
25193 None,
25194 window,
25195 cx,
25196 );
25197 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25198 prompt.set_show_cursor_when_unfocused(false, cx);
25199 prompt.set_placeholder_text(
25200 match edit_action {
25201 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25202 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25203 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25204 },
25205 window,
25206 cx,
25207 );
25208
25209 prompt
25210 });
25211
25212 Self {
25213 prompt,
25214 editor,
25215 breakpoint_anchor,
25216 breakpoint,
25217 edit_action,
25218 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25219 block_ids: Default::default(),
25220 _subscriptions: vec![],
25221 }
25222 }
25223
25224 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25225 self.block_ids.extend(block_ids)
25226 }
25227
25228 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25229 if let Some(editor) = self.editor.upgrade() {
25230 let message = self
25231 .prompt
25232 .read(cx)
25233 .buffer
25234 .read(cx)
25235 .as_singleton()
25236 .expect("A multi buffer in breakpoint prompt isn't possible")
25237 .read(cx)
25238 .as_rope()
25239 .to_string();
25240
25241 editor.update(cx, |editor, cx| {
25242 editor.edit_breakpoint_at_anchor(
25243 self.breakpoint_anchor,
25244 self.breakpoint.clone(),
25245 match self.edit_action {
25246 BreakpointPromptEditAction::Log => {
25247 BreakpointEditAction::EditLogMessage(message.into())
25248 }
25249 BreakpointPromptEditAction::Condition => {
25250 BreakpointEditAction::EditCondition(message.into())
25251 }
25252 BreakpointPromptEditAction::HitCondition => {
25253 BreakpointEditAction::EditHitCondition(message.into())
25254 }
25255 },
25256 cx,
25257 );
25258
25259 editor.remove_blocks(self.block_ids.clone(), None, cx);
25260 cx.focus_self(window);
25261 });
25262 }
25263 }
25264
25265 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25266 self.editor
25267 .update(cx, |editor, cx| {
25268 editor.remove_blocks(self.block_ids.clone(), None, cx);
25269 window.focus(&editor.focus_handle);
25270 })
25271 .log_err();
25272 }
25273
25274 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25275 let settings = ThemeSettings::get_global(cx);
25276 let text_style = TextStyle {
25277 color: if self.prompt.read(cx).read_only(cx) {
25278 cx.theme().colors().text_disabled
25279 } else {
25280 cx.theme().colors().text
25281 },
25282 font_family: settings.buffer_font.family.clone(),
25283 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25284 font_size: settings.buffer_font_size(cx).into(),
25285 font_weight: settings.buffer_font.weight,
25286 line_height: relative(settings.buffer_line_height.value()),
25287 ..Default::default()
25288 };
25289 EditorElement::new(
25290 &self.prompt,
25291 EditorStyle {
25292 background: cx.theme().colors().editor_background,
25293 local_player: cx.theme().players().local(),
25294 text: text_style,
25295 ..Default::default()
25296 },
25297 )
25298 }
25299}
25300
25301impl Render for BreakpointPromptEditor {
25302 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25303 let editor_margins = *self.editor_margins.lock();
25304 let gutter_dimensions = editor_margins.gutter;
25305 h_flex()
25306 .key_context("Editor")
25307 .bg(cx.theme().colors().editor_background)
25308 .border_y_1()
25309 .border_color(cx.theme().status().info_border)
25310 .size_full()
25311 .py(window.line_height() / 2.5)
25312 .on_action(cx.listener(Self::confirm))
25313 .on_action(cx.listener(Self::cancel))
25314 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25315 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25316 }
25317}
25318
25319impl Focusable for BreakpointPromptEditor {
25320 fn focus_handle(&self, cx: &App) -> FocusHandle {
25321 self.prompt.focus_handle(cx)
25322 }
25323}
25324
25325fn all_edits_insertions_or_deletions(
25326 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25327 snapshot: &MultiBufferSnapshot,
25328) -> bool {
25329 let mut all_insertions = true;
25330 let mut all_deletions = true;
25331
25332 for (range, new_text) in edits.iter() {
25333 let range_is_empty = range.to_offset(snapshot).is_empty();
25334 let text_is_empty = new_text.is_empty();
25335
25336 if range_is_empty != text_is_empty {
25337 if range_is_empty {
25338 all_deletions = false;
25339 } else {
25340 all_insertions = false;
25341 }
25342 } else {
25343 return false;
25344 }
25345
25346 if !all_insertions && !all_deletions {
25347 return false;
25348 }
25349 }
25350 all_insertions || all_deletions
25351}
25352
25353struct MissingEditPredictionKeybindingTooltip;
25354
25355impl Render for MissingEditPredictionKeybindingTooltip {
25356 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25357 ui::tooltip_container(cx, |container, cx| {
25358 container
25359 .flex_shrink_0()
25360 .max_w_80()
25361 .min_h(rems_from_px(124.))
25362 .justify_between()
25363 .child(
25364 v_flex()
25365 .flex_1()
25366 .text_ui_sm(cx)
25367 .child(Label::new("Conflict with Accept Keybinding"))
25368 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25369 )
25370 .child(
25371 h_flex()
25372 .pb_1()
25373 .gap_1()
25374 .items_end()
25375 .w_full()
25376 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25377 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25378 }))
25379 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25380 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25381 })),
25382 )
25383 })
25384 }
25385}
25386
25387#[derive(Debug, Clone, Copy, PartialEq)]
25388pub struct LineHighlight {
25389 pub background: Background,
25390 pub border: Option<gpui::Hsla>,
25391 pub include_gutter: bool,
25392 pub type_id: Option<TypeId>,
25393}
25394
25395struct LineManipulationResult {
25396 pub new_text: String,
25397 pub line_count_before: usize,
25398 pub line_count_after: usize,
25399}
25400
25401fn render_diff_hunk_controls(
25402 row: u32,
25403 status: &DiffHunkStatus,
25404 hunk_range: Range<Anchor>,
25405 is_created_file: bool,
25406 line_height: Pixels,
25407 editor: &Entity<Editor>,
25408 _window: &mut Window,
25409 cx: &mut App,
25410) -> AnyElement {
25411 h_flex()
25412 .h(line_height)
25413 .mr_1()
25414 .gap_1()
25415 .px_0p5()
25416 .pb_1()
25417 .border_x_1()
25418 .border_b_1()
25419 .border_color(cx.theme().colors().border_variant)
25420 .rounded_b_lg()
25421 .bg(cx.theme().colors().editor_background)
25422 .gap_1()
25423 .block_mouse_except_scroll()
25424 .shadow_md()
25425 .child(if status.has_secondary_hunk() {
25426 Button::new(("stage", row as u64), "Stage")
25427 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25428 .tooltip({
25429 let focus_handle = editor.focus_handle(cx);
25430 move |_window, cx| {
25431 Tooltip::for_action_in(
25432 "Stage Hunk",
25433 &::git::ToggleStaged,
25434 &focus_handle,
25435 cx,
25436 )
25437 }
25438 })
25439 .on_click({
25440 let editor = editor.clone();
25441 move |_event, _window, cx| {
25442 editor.update(cx, |editor, cx| {
25443 editor.stage_or_unstage_diff_hunks(
25444 true,
25445 vec![hunk_range.start..hunk_range.start],
25446 cx,
25447 );
25448 });
25449 }
25450 })
25451 } else {
25452 Button::new(("unstage", row as u64), "Unstage")
25453 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25454 .tooltip({
25455 let focus_handle = editor.focus_handle(cx);
25456 move |_window, cx| {
25457 Tooltip::for_action_in(
25458 "Unstage Hunk",
25459 &::git::ToggleStaged,
25460 &focus_handle,
25461 cx,
25462 )
25463 }
25464 })
25465 .on_click({
25466 let editor = editor.clone();
25467 move |_event, _window, cx| {
25468 editor.update(cx, |editor, cx| {
25469 editor.stage_or_unstage_diff_hunks(
25470 false,
25471 vec![hunk_range.start..hunk_range.start],
25472 cx,
25473 );
25474 });
25475 }
25476 })
25477 })
25478 .child(
25479 Button::new(("restore", row as u64), "Restore")
25480 .tooltip({
25481 let focus_handle = editor.focus_handle(cx);
25482 move |_window, cx| {
25483 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25484 }
25485 })
25486 .on_click({
25487 let editor = editor.clone();
25488 move |_event, window, cx| {
25489 editor.update(cx, |editor, cx| {
25490 let snapshot = editor.snapshot(window, cx);
25491 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25492 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25493 });
25494 }
25495 })
25496 .disabled(is_created_file),
25497 )
25498 .when(
25499 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25500 |el| {
25501 el.child(
25502 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25503 .shape(IconButtonShape::Square)
25504 .icon_size(IconSize::Small)
25505 // .disabled(!has_multiple_hunks)
25506 .tooltip({
25507 let focus_handle = editor.focus_handle(cx);
25508 move |_window, cx| {
25509 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25510 }
25511 })
25512 .on_click({
25513 let editor = editor.clone();
25514 move |_event, window, cx| {
25515 editor.update(cx, |editor, cx| {
25516 let snapshot = editor.snapshot(window, cx);
25517 let position =
25518 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25519 editor.go_to_hunk_before_or_after_position(
25520 &snapshot,
25521 position,
25522 Direction::Next,
25523 window,
25524 cx,
25525 );
25526 editor.expand_selected_diff_hunks(cx);
25527 });
25528 }
25529 }),
25530 )
25531 .child(
25532 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25533 .shape(IconButtonShape::Square)
25534 .icon_size(IconSize::Small)
25535 // .disabled(!has_multiple_hunks)
25536 .tooltip({
25537 let focus_handle = editor.focus_handle(cx);
25538 move |_window, cx| {
25539 Tooltip::for_action_in(
25540 "Previous Hunk",
25541 &GoToPreviousHunk,
25542 &focus_handle,
25543 cx,
25544 )
25545 }
25546 })
25547 .on_click({
25548 let editor = editor.clone();
25549 move |_event, window, cx| {
25550 editor.update(cx, |editor, cx| {
25551 let snapshot = editor.snapshot(window, cx);
25552 let point =
25553 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25554 editor.go_to_hunk_before_or_after_position(
25555 &snapshot,
25556 point,
25557 Direction::Prev,
25558 window,
25559 cx,
25560 );
25561 editor.expand_selected_diff_hunks(cx);
25562 });
25563 }
25564 }),
25565 )
25566 },
25567 )
25568 .into_any_element()
25569}
25570
25571pub fn multibuffer_context_lines(cx: &App) -> u32 {
25572 EditorSettings::try_get(cx)
25573 .map(|settings| settings.excerpt_context_lines)
25574 .unwrap_or(2)
25575 .min(32)
25576}