1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79 status::FileStatus,
80};
81use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
82use anyhow::{Context as _, Result, anyhow};
83use blink_manager::BlinkManager;
84use buffer_diff::DiffHunkStatus;
85use client::{Collaborator, ParticipantIndex, parse_zed_link};
86use clock::ReplicaId;
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use collections::{BTreeMap, HashMap, HashSet, VecDeque};
92use convert_case::{Case, Casing};
93use dap::TelemetrySpawnLocation;
94use display_map::*;
95use edit_prediction_types::{EditPredictionDelegate, EditPredictionDelegateHandle};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
111 ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
112 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
113 WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
114};
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
125 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, Peekable},
183 mem,
184 num::NonZeroU32,
185 ops::{ControlFlow, 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 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287pub struct HunkAddedColor;
288pub struct HunkRemovedColor;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291pub enum Navigated {
292 Yes,
293 No,
294}
295
296impl Navigated {
297 pub fn from_bool(yes: bool) -> Navigated {
298 if yes { Navigated::Yes } else { Navigated::No }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303enum DisplayDiffHunk {
304 Folded {
305 display_row: DisplayRow,
306 },
307 Unfolded {
308 is_created_file: bool,
309 diff_base_byte_range: Range<usize>,
310 display_row_range: Range<DisplayRow>,
311 multi_buffer_range: Range<Anchor>,
312 status: DiffHunkStatus,
313 word_diffs: Vec<Range<MultiBufferOffset>>,
314 },
315}
316
317pub enum HideMouseCursorOrigin {
318 TypingAction,
319 MovementAction,
320}
321
322pub fn init(cx: &mut App) {
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 });
355 cx.on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 language_registry: Option<Arc<LanguageRegistry>>,
384 cx: &mut App,
385 ) -> Vec<BlockProperties<Anchor>>;
386
387 fn render_hover(
388 &self,
389 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
390 range: Range<Point>,
391 buffer_id: BufferId,
392 language_registry: Option<Arc<LanguageRegistry>>,
393 cx: &mut App,
394 ) -> Option<Entity<markdown::Markdown>>;
395
396 fn open_link(
397 &self,
398 editor: &mut Editor,
399 link: SharedString,
400 window: &mut Window,
401 cx: &mut Context<Editor>,
402 );
403}
404
405pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
406
407impl GlobalDiagnosticRenderer {
408 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
409 cx.try_global::<Self>().map(|g| g.0.clone())
410 }
411}
412
413impl gpui::Global for GlobalDiagnosticRenderer {}
414pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
415 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
416}
417
418pub struct SearchWithinRange;
419
420trait InvalidationRegion {
421 fn ranges(&self) -> &[Range<Anchor>];
422}
423
424#[derive(Clone, Debug, PartialEq)]
425pub enum SelectPhase {
426 Begin {
427 position: DisplayPoint,
428 add: bool,
429 click_count: usize,
430 },
431 BeginColumnar {
432 position: DisplayPoint,
433 reset: bool,
434 mode: ColumnarMode,
435 goal_column: u32,
436 },
437 Extend {
438 position: DisplayPoint,
439 click_count: usize,
440 },
441 Update {
442 position: DisplayPoint,
443 goal_column: u32,
444 scroll_delta: gpui::Point<f32>,
445 },
446 End,
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum ColumnarMode {
451 FromMouse,
452 FromSelection,
453}
454
455#[derive(Clone, Debug)]
456pub enum SelectMode {
457 Character,
458 Word(Range<Anchor>),
459 Line(Range<Anchor>),
460 All,
461}
462
463#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
464pub enum SizingBehavior {
465 /// The editor will layout itself using `size_full` and will include the vertical
466 /// scroll margin as requested by user settings.
467 #[default]
468 Default,
469 /// The editor will layout itself using `size_full`, but will not have any
470 /// vertical overscroll.
471 ExcludeOverscrollMargin,
472 /// The editor will request a vertical size according to its content and will be
473 /// layouted without a vertical scroll margin.
474 SizeByContent,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine,
480 AutoHeight {
481 min_lines: usize,
482 max_lines: Option<usize>,
483 },
484 Full {
485 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
486 scale_ui_elements_with_buffer_font_size: bool,
487 /// When set to `true`, the editor will render a background for the active line.
488 show_active_line_background: bool,
489 /// Determines the sizing behavior for this editor
490 sizing_behavior: SizingBehavior,
491 },
492 Minimap {
493 parent: WeakEntity<Editor>,
494 },
495}
496
497impl EditorMode {
498 pub fn full() -> Self {
499 Self::Full {
500 scale_ui_elements_with_buffer_font_size: true,
501 show_active_line_background: true,
502 sizing_behavior: SizingBehavior::Default,
503 }
504 }
505
506 #[inline]
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 #[inline]
512 pub fn is_single_line(&self) -> bool {
513 matches!(self, Self::SingleLine { .. })
514 }
515
516 #[inline]
517 fn is_minimap(&self) -> bool {
518 matches!(self, Self::Minimap { .. })
519 }
520}
521
522#[derive(Copy, Clone, Debug)]
523pub enum SoftWrap {
524 /// Prefer not to wrap at all.
525 ///
526 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
527 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
528 GitDiff,
529 /// Prefer a single line generally, unless an overly long line is encountered.
530 None,
531 /// Soft wrap lines that exceed the editor width.
532 EditorWidth,
533 /// Soft wrap lines at the preferred line length.
534 Column(u32),
535 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
536 Bounded(u32),
537}
538
539#[derive(Clone)]
540pub struct EditorStyle {
541 pub background: Hsla,
542 pub border: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub edit_prediction_styles: EditPredictionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 border: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 edit_prediction_styles: EditPredictionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 let mut style = cx.theme().syntax().get("hint");
584
585 if style.color.is_none() {
586 style.color = Some(cx.theme().status().hint);
587 }
588
589 if !show_background {
590 style.background_color = None;
591 return style;
592 }
593
594 if style.background_color.is_none() {
595 style.background_color = Some(cx.theme().status().hint_background);
596 }
597
598 style
599}
600
601pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
602 EditPredictionStyles {
603 insertion: HighlightStyle {
604 color: Some(cx.theme().status().predictive),
605 ..HighlightStyle::default()
606 },
607 whitespace: HighlightStyle {
608 background_color: Some(cx.theme().status().created_background),
609 ..HighlightStyle::default()
610 },
611 }
612}
613
614type CompletionId = usize;
615
616pub(crate) enum EditDisplayMode {
617 TabAccept,
618 DiffPopover,
619 Inline,
620}
621
622enum EditPrediction {
623 Edit {
624 edits: Vec<(Range<Anchor>, Arc<str>)>,
625 edit_preview: Option<EditPreview>,
626 display_mode: EditDisplayMode,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in the active editor
630 MoveWithin {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634 /// Move to a specific location in a different editor (not the active one)
635 MoveOutside {
636 target: language::Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Option<Range<Anchor>>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (
730 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
731 Arc<[Range<Anchor>]>,
732);
733type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
734
735#[derive(Default)]
736struct ScrollbarMarkerState {
737 scrollbar_size: Size<Pixels>,
738 dirty: bool,
739 markers: Arc<[PaintQuad]>,
740 pending_refresh: Option<Task<Result<()>>>,
741}
742
743impl ScrollbarMarkerState {
744 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
745 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
746 }
747}
748
749#[derive(Clone, Copy, PartialEq, Eq)]
750pub enum MinimapVisibility {
751 Disabled,
752 Enabled {
753 /// The configuration currently present in the users settings.
754 setting_configuration: bool,
755 /// Whether to override the currently set visibility from the users setting.
756 toggle_override: bool,
757 },
758}
759
760impl MinimapVisibility {
761 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
762 if mode.is_full() {
763 Self::Enabled {
764 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
765 toggle_override: false,
766 }
767 } else {
768 Self::Disabled
769 }
770 }
771
772 fn hidden(&self) -> Self {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => Self::Enabled {
778 setting_configuration,
779 toggle_override: setting_configuration,
780 },
781 Self::Disabled => Self::Disabled,
782 }
783 }
784
785 fn disabled(&self) -> bool {
786 matches!(*self, Self::Disabled)
787 }
788
789 fn settings_visibility(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 ..
794 } => setting_configuration,
795 _ => false,
796 }
797 }
798
799 fn visible(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 toggle_override,
804 } => setting_configuration ^ toggle_override,
805 _ => false,
806 }
807 }
808
809 fn toggle_visibility(&self) -> Self {
810 match *self {
811 Self::Enabled {
812 toggle_override,
813 setting_configuration,
814 } => Self::Enabled {
815 setting_configuration,
816 toggle_override: !toggle_override,
817 },
818 Self::Disabled => Self::Disabled,
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824pub enum BufferSerialization {
825 All,
826 NonDirtyBuffers,
827}
828
829impl BufferSerialization {
830 fn new(restore_unsaved_buffers: bool) -> Self {
831 if restore_unsaved_buffers {
832 Self::All
833 } else {
834 Self::NonDirtyBuffers
835 }
836 }
837}
838
839#[derive(Clone, Debug)]
840struct RunnableTasks {
841 templates: Vec<(TaskSourceKind, TaskTemplate)>,
842 offset: multi_buffer::Anchor,
843 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
844 column: u32,
845 // Values of all named captures, including those starting with '_'
846 extra_variables: HashMap<String, String>,
847 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
848 context_range: Range<BufferOffset>,
849}
850
851impl RunnableTasks {
852 fn resolve<'a>(
853 &'a self,
854 cx: &'a task::TaskContext,
855 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
856 self.templates.iter().filter_map(|(kind, template)| {
857 template
858 .resolve_task(&kind.to_id_base(), cx)
859 .map(|task| (kind.clone(), task))
860 })
861 }
862}
863
864#[derive(Clone)]
865pub struct ResolvedTasks {
866 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
867 position: Anchor,
868}
869
870/// Addons allow storing per-editor state in other crates (e.g. Vim)
871pub trait Addon: 'static {
872 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
873
874 fn render_buffer_header_controls(
875 &self,
876 _: &ExcerptInfo,
877 _: &Window,
878 _: &App,
879 ) -> Option<AnyElement> {
880 None
881 }
882
883 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
884 None
885 }
886
887 fn to_any(&self) -> &dyn std::any::Any;
888
889 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
890 None
891 }
892}
893
894struct ChangeLocation {
895 current: Option<Vec<Anchor>>,
896 original: Vec<Anchor>,
897}
898impl ChangeLocation {
899 fn locations(&self) -> &[Anchor] {
900 self.current.as_ref().unwrap_or(&self.original)
901 }
902}
903
904/// A set of caret positions, registered when the editor was edited.
905pub struct ChangeList {
906 changes: Vec<ChangeLocation>,
907 /// Currently "selected" change.
908 position: Option<usize>,
909}
910
911impl ChangeList {
912 pub fn new() -> Self {
913 Self {
914 changes: Vec::new(),
915 position: None,
916 }
917 }
918
919 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
920 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
921 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
922 if self.changes.is_empty() {
923 return None;
924 }
925
926 let prev = self.position.unwrap_or(self.changes.len());
927 let next = if direction == Direction::Prev {
928 prev.saturating_sub(count)
929 } else {
930 (prev + count).min(self.changes.len() - 1)
931 };
932 self.position = Some(next);
933 self.changes.get(next).map(|change| change.locations())
934 }
935
936 /// Adds a new change to the list, resetting the change list position.
937 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
938 self.position.take();
939 if let Some(last) = self.changes.last_mut()
940 && group
941 {
942 last.current = Some(new_positions)
943 } else {
944 self.changes.push(ChangeLocation {
945 original: new_positions,
946 current: None,
947 });
948 }
949 }
950
951 pub fn last(&self) -> Option<&[Anchor]> {
952 self.changes.last().map(|change| change.locations())
953 }
954
955 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
956 self.changes.last().map(|change| change.original.as_slice())
957 }
958
959 pub fn invert_last_group(&mut self) {
960 if let Some(last) = self.changes.last_mut()
961 && let Some(current) = last.current.as_mut()
962 {
963 mem::swap(&mut last.original, current);
964 }
965 }
966}
967
968#[derive(Clone)]
969struct InlineBlamePopoverState {
970 scroll_handle: ScrollHandle,
971 commit_message: Option<ParsedCommitMessage>,
972 markdown: Entity<Markdown>,
973}
974
975struct InlineBlamePopover {
976 position: gpui::Point<Pixels>,
977 hide_task: Option<Task<()>>,
978 popover_bounds: Option<Bounds<Pixels>>,
979 popover_state: InlineBlamePopoverState,
980 keyboard_grace: bool,
981}
982
983enum SelectionDragState {
984 /// State when no drag related activity is detected.
985 None,
986 /// State when the mouse is down on a selection that is about to be dragged.
987 ReadyToDrag {
988 selection: Selection<Anchor>,
989 click_position: gpui::Point<Pixels>,
990 mouse_down_time: Instant,
991 },
992 /// State when the mouse is dragging the selection in the editor.
993 Dragging {
994 selection: Selection<Anchor>,
995 drop_cursor: Selection<Anchor>,
996 hide_drop_cursor: bool,
997 },
998}
999
1000enum ColumnarSelectionState {
1001 FromMouse {
1002 selection_tail: Anchor,
1003 display_point: Option<DisplayPoint>,
1004 },
1005 FromSelection {
1006 selection_tail: Anchor,
1007 },
1008}
1009
1010/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1011/// a breakpoint on them.
1012#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1013struct PhantomBreakpointIndicator {
1014 display_row: DisplayRow,
1015 /// There's a small debounce between hovering over the line and showing the indicator.
1016 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1017 is_active: bool,
1018 collides_with_existing_breakpoint: bool,
1019}
1020
1021/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1022///
1023/// See the [module level documentation](self) for more information.
1024pub struct Editor {
1025 focus_handle: FocusHandle,
1026 last_focused_descendant: Option<WeakFocusHandle>,
1027 /// The text buffer being edited
1028 buffer: Entity<MultiBuffer>,
1029 /// Map of how text in the buffer should be displayed.
1030 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1031 pub display_map: Entity<DisplayMap>,
1032 placeholder_display_map: Option<Entity<DisplayMap>>,
1033 pub selections: SelectionsCollection,
1034 pub scroll_manager: ScrollManager,
1035 /// When inline assist editors are linked, they all render cursors because
1036 /// typing enters text into each of them, even the ones that aren't focused.
1037 pub(crate) show_cursor_when_unfocused: bool,
1038 columnar_selection_state: Option<ColumnarSelectionState>,
1039 add_selections_state: Option<AddSelectionsState>,
1040 select_next_state: Option<SelectNextState>,
1041 select_prev_state: Option<SelectNextState>,
1042 selection_history: SelectionHistory,
1043 defer_selection_effects: bool,
1044 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1045 autoclose_regions: Vec<AutocloseRegion>,
1046 snippet_stack: InvalidationStack<SnippetState>,
1047 select_syntax_node_history: SelectSyntaxNodeHistory,
1048 ime_transaction: Option<TransactionId>,
1049 pub diagnostics_max_severity: DiagnosticSeverity,
1050 active_diagnostics: ActiveDiagnostic,
1051 show_inline_diagnostics: bool,
1052 inline_diagnostics_update: Task<()>,
1053 inline_diagnostics_enabled: bool,
1054 diagnostics_enabled: bool,
1055 word_completions_enabled: bool,
1056 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1057 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1058 hard_wrap: Option<usize>,
1059 project: Option<Entity<Project>>,
1060 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1061 completion_provider: Option<Rc<dyn CompletionProvider>>,
1062 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1063 blink_manager: Entity<BlinkManager>,
1064 show_cursor_names: bool,
1065 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1066 pub show_local_selections: bool,
1067 mode: EditorMode,
1068 show_breadcrumbs: bool,
1069 show_gutter: bool,
1070 show_scrollbars: ScrollbarAxes,
1071 minimap_visibility: MinimapVisibility,
1072 offset_content: bool,
1073 disable_expand_excerpt_buttons: bool,
1074 show_line_numbers: Option<bool>,
1075 use_relative_line_numbers: Option<bool>,
1076 show_git_diff_gutter: Option<bool>,
1077 show_code_actions: Option<bool>,
1078 show_runnables: Option<bool>,
1079 show_breakpoints: Option<bool>,
1080 show_wrap_guides: Option<bool>,
1081 show_indent_guides: Option<bool>,
1082 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1083 highlight_order: usize,
1084 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1085 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1086 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1087 scrollbar_marker_state: ScrollbarMarkerState,
1088 active_indent_guides_state: ActiveIndentGuidesState,
1089 nav_history: Option<ItemNavHistory>,
1090 context_menu: RefCell<Option<CodeContextMenu>>,
1091 context_menu_options: Option<ContextMenuOptions>,
1092 mouse_context_menu: Option<MouseContextMenu>,
1093 completion_tasks: Vec<(CompletionId, Task<()>)>,
1094 inline_blame_popover: Option<InlineBlamePopover>,
1095 inline_blame_popover_show_task: Option<Task<()>>,
1096 signature_help_state: SignatureHelpState,
1097 auto_signature_help: Option<bool>,
1098 find_all_references_task_sources: Vec<Anchor>,
1099 next_completion_id: CompletionId,
1100 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1101 code_actions_task: Option<Task<Result<()>>>,
1102 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1103 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 document_highlights_task: Option<Task<()>>,
1105 linked_editing_range_task: Option<Task<Option<()>>>,
1106 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1107 pending_rename: Option<RenameState>,
1108 searchable: bool,
1109 cursor_shape: CursorShape,
1110 current_line_highlight: Option<CurrentLineHighlight>,
1111 pub collapse_matches: bool,
1112 autoindent_mode: Option<AutoindentMode>,
1113 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1114 input_enabled: bool,
1115 use_modal_editing: bool,
1116 read_only: bool,
1117 leader_id: Option<CollaboratorId>,
1118 remote_id: Option<ViewId>,
1119 pub hover_state: HoverState,
1120 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1121 gutter_hovered: bool,
1122 hovered_link_state: Option<HoveredLinkState>,
1123 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1124 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1125 active_edit_prediction: Option<EditPredictionState>,
1126 /// Used to prevent flickering as the user types while the menu is open
1127 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1128 edit_prediction_settings: EditPredictionSettings,
1129 edit_predictions_hidden_for_vim_mode: bool,
1130 show_edit_predictions_override: Option<bool>,
1131 show_completions_on_input_override: Option<bool>,
1132 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1133 edit_prediction_preview: EditPredictionPreview,
1134 edit_prediction_indent_conflict: bool,
1135 edit_prediction_requires_modifier_in_indent_conflict: bool,
1136 next_inlay_id: usize,
1137 next_color_inlay_id: usize,
1138 _subscriptions: Vec<Subscription>,
1139 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1140 gutter_dimensions: GutterDimensions,
1141 style: Option<EditorStyle>,
1142 text_style_refinement: Option<TextStyleRefinement>,
1143 next_editor_action_id: EditorActionId,
1144 editor_actions: Rc<
1145 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1146 >,
1147 use_autoclose: bool,
1148 use_auto_surround: bool,
1149 auto_replace_emoji_shortcode: bool,
1150 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1151 show_git_blame_gutter: bool,
1152 show_git_blame_inline: bool,
1153 show_git_blame_inline_delay_task: Option<Task<()>>,
1154 git_blame_inline_enabled: bool,
1155 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1156 buffer_serialization: Option<BufferSerialization>,
1157 show_selection_menu: Option<bool>,
1158 blame: Option<Entity<GitBlame>>,
1159 blame_subscription: Option<Subscription>,
1160 custom_context_menu: Option<
1161 Box<
1162 dyn 'static
1163 + Fn(
1164 &mut Self,
1165 DisplayPoint,
1166 &mut Window,
1167 &mut Context<Self>,
1168 ) -> Option<Entity<ui::ContextMenu>>,
1169 >,
1170 >,
1171 last_bounds: Option<Bounds<Pixels>>,
1172 last_position_map: Option<Rc<PositionMap>>,
1173 expect_bounds_change: Option<Bounds<Pixels>>,
1174 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1175 tasks_update_task: Option<Task<()>>,
1176 breakpoint_store: Option<Entity<BreakpointStore>>,
1177 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1178 hovered_diff_hunk_row: Option<DisplayRow>,
1179 pull_diagnostics_task: Task<()>,
1180 pull_diagnostics_background_task: Task<()>,
1181 in_project_search: bool,
1182 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1183 breadcrumb_header: Option<String>,
1184 focused_block: Option<FocusedBlock>,
1185 next_scroll_position: NextScrollCursorCenterTopBottom,
1186 addons: HashMap<TypeId, Box<dyn Addon>>,
1187 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1188 load_diff_task: Option<Shared<Task<()>>>,
1189 /// Whether we are temporarily displaying a diff other than git's
1190 temporary_diff_override: bool,
1191 selection_mark_mode: bool,
1192 toggle_fold_multiple_buffers: Task<()>,
1193 _scroll_cursor_center_top_bottom_task: Task<()>,
1194 serialize_selections: Task<()>,
1195 serialize_folds: Task<()>,
1196 mouse_cursor_hidden: bool,
1197 minimap: Option<Entity<Self>>,
1198 hide_mouse_mode: HideMouseMode,
1199 pub change_list: ChangeList,
1200 inline_value_cache: InlineValueCache,
1201
1202 selection_drag_state: SelectionDragState,
1203 colors: Option<LspColorData>,
1204 post_scroll_update: Task<()>,
1205 refresh_colors_task: Task<()>,
1206 inlay_hints: Option<LspInlayHintData>,
1207 folding_newlines: Task<()>,
1208 select_next_is_case_sensitive: Option<bool>,
1209 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1210 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1211 accent_data: Option<AccentData>,
1212 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1213 use_base_text_line_numbers: bool,
1214}
1215
1216#[derive(Debug, PartialEq)]
1217struct AccentData {
1218 colors: AccentColors,
1219 overrides: Vec<SharedString>,
1220}
1221
1222fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1223 if debounce_ms > 0 {
1224 Some(Duration::from_millis(debounce_ms))
1225 } else {
1226 None
1227 }
1228}
1229
1230#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1231enum NextScrollCursorCenterTopBottom {
1232 #[default]
1233 Center,
1234 Top,
1235 Bottom,
1236}
1237
1238impl NextScrollCursorCenterTopBottom {
1239 fn next(&self) -> Self {
1240 match self {
1241 Self::Center => Self::Top,
1242 Self::Top => Self::Bottom,
1243 Self::Bottom => Self::Center,
1244 }
1245 }
1246}
1247
1248#[derive(Clone)]
1249pub struct EditorSnapshot {
1250 pub mode: EditorMode,
1251 show_gutter: bool,
1252 show_line_numbers: Option<bool>,
1253 show_git_diff_gutter: Option<bool>,
1254 show_code_actions: Option<bool>,
1255 show_runnables: Option<bool>,
1256 show_breakpoints: Option<bool>,
1257 git_blame_gutter_max_author_length: Option<usize>,
1258 pub display_snapshot: DisplaySnapshot,
1259 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1260 is_focused: bool,
1261 scroll_anchor: ScrollAnchor,
1262 ongoing_scroll: OngoingScroll,
1263 current_line_highlight: CurrentLineHighlight,
1264 gutter_hovered: bool,
1265}
1266
1267#[derive(Default, Debug, Clone, Copy)]
1268pub struct GutterDimensions {
1269 pub left_padding: Pixels,
1270 pub right_padding: Pixels,
1271 pub width: Pixels,
1272 pub margin: Pixels,
1273 pub git_blame_entries_width: Option<Pixels>,
1274}
1275
1276impl GutterDimensions {
1277 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1278 Self {
1279 margin: Self::default_gutter_margin(font_id, font_size, cx),
1280 ..Default::default()
1281 }
1282 }
1283
1284 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1285 -cx.text_system().descent(font_id, font_size)
1286 }
1287 /// The full width of the space taken up by the gutter.
1288 pub fn full_width(&self) -> Pixels {
1289 self.margin + self.width
1290 }
1291
1292 /// The width of the space reserved for the fold indicators,
1293 /// use alongside 'justify_end' and `gutter_width` to
1294 /// right align content with the line numbers
1295 pub fn fold_area_width(&self) -> Pixels {
1296 self.margin + self.right_padding
1297 }
1298}
1299
1300struct CharacterDimensions {
1301 em_width: Pixels,
1302 em_advance: Pixels,
1303 line_height: Pixels,
1304}
1305
1306#[derive(Debug)]
1307pub struct RemoteSelection {
1308 pub replica_id: ReplicaId,
1309 pub selection: Selection<Anchor>,
1310 pub cursor_shape: CursorShape,
1311 pub collaborator_id: CollaboratorId,
1312 pub line_mode: bool,
1313 pub user_name: Option<SharedString>,
1314 pub color: PlayerColor,
1315}
1316
1317#[derive(Clone, Debug)]
1318struct SelectionHistoryEntry {
1319 selections: Arc<[Selection<Anchor>]>,
1320 select_next_state: Option<SelectNextState>,
1321 select_prev_state: Option<SelectNextState>,
1322 add_selections_state: Option<AddSelectionsState>,
1323}
1324
1325#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1326enum SelectionHistoryMode {
1327 #[default]
1328 Normal,
1329 Undoing,
1330 Redoing,
1331 Skipping,
1332}
1333
1334#[derive(Clone, PartialEq, Eq, Hash)]
1335struct HoveredCursor {
1336 replica_id: ReplicaId,
1337 selection_id: usize,
1338}
1339
1340#[derive(Debug)]
1341/// SelectionEffects controls the side-effects of updating the selection.
1342///
1343/// The default behaviour does "what you mostly want":
1344/// - it pushes to the nav history if the cursor moved by >10 lines
1345/// - it re-triggers completion requests
1346/// - it scrolls to fit
1347///
1348/// You might want to modify these behaviours. For example when doing a "jump"
1349/// like go to definition, we always want to add to nav history; but when scrolling
1350/// in vim mode we never do.
1351///
1352/// Similarly, you might want to disable scrolling if you don't want the viewport to
1353/// move.
1354#[derive(Clone)]
1355pub struct SelectionEffects {
1356 nav_history: Option<bool>,
1357 completions: bool,
1358 scroll: Option<Autoscroll>,
1359}
1360
1361impl Default for SelectionEffects {
1362 fn default() -> Self {
1363 Self {
1364 nav_history: None,
1365 completions: true,
1366 scroll: Some(Autoscroll::fit()),
1367 }
1368 }
1369}
1370impl SelectionEffects {
1371 pub fn scroll(scroll: Autoscroll) -> Self {
1372 Self {
1373 scroll: Some(scroll),
1374 ..Default::default()
1375 }
1376 }
1377
1378 pub fn no_scroll() -> Self {
1379 Self {
1380 scroll: None,
1381 ..Default::default()
1382 }
1383 }
1384
1385 pub fn completions(self, completions: bool) -> Self {
1386 Self {
1387 completions,
1388 ..self
1389 }
1390 }
1391
1392 pub fn nav_history(self, nav_history: bool) -> Self {
1393 Self {
1394 nav_history: Some(nav_history),
1395 ..self
1396 }
1397 }
1398}
1399
1400struct DeferredSelectionEffectsState {
1401 changed: bool,
1402 effects: SelectionEffects,
1403 old_cursor_position: Anchor,
1404 history_entry: SelectionHistoryEntry,
1405}
1406
1407#[derive(Default)]
1408struct SelectionHistory {
1409 #[allow(clippy::type_complexity)]
1410 selections_by_transaction:
1411 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1412 mode: SelectionHistoryMode,
1413 undo_stack: VecDeque<SelectionHistoryEntry>,
1414 redo_stack: VecDeque<SelectionHistoryEntry>,
1415}
1416
1417impl SelectionHistory {
1418 #[track_caller]
1419 fn insert_transaction(
1420 &mut self,
1421 transaction_id: TransactionId,
1422 selections: Arc<[Selection<Anchor>]>,
1423 ) {
1424 if selections.is_empty() {
1425 log::error!(
1426 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1427 std::panic::Location::caller()
1428 );
1429 return;
1430 }
1431 self.selections_by_transaction
1432 .insert(transaction_id, (selections, None));
1433 }
1434
1435 #[allow(clippy::type_complexity)]
1436 fn transaction(
1437 &self,
1438 transaction_id: TransactionId,
1439 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1440 self.selections_by_transaction.get(&transaction_id)
1441 }
1442
1443 #[allow(clippy::type_complexity)]
1444 fn transaction_mut(
1445 &mut self,
1446 transaction_id: TransactionId,
1447 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1448 self.selections_by_transaction.get_mut(&transaction_id)
1449 }
1450
1451 fn push(&mut self, entry: SelectionHistoryEntry) {
1452 if !entry.selections.is_empty() {
1453 match self.mode {
1454 SelectionHistoryMode::Normal => {
1455 self.push_undo(entry);
1456 self.redo_stack.clear();
1457 }
1458 SelectionHistoryMode::Undoing => self.push_redo(entry),
1459 SelectionHistoryMode::Redoing => self.push_undo(entry),
1460 SelectionHistoryMode::Skipping => {}
1461 }
1462 }
1463 }
1464
1465 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1466 if self
1467 .undo_stack
1468 .back()
1469 .is_none_or(|e| e.selections != entry.selections)
1470 {
1471 self.undo_stack.push_back(entry);
1472 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1473 self.undo_stack.pop_front();
1474 }
1475 }
1476 }
1477
1478 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1479 if self
1480 .redo_stack
1481 .back()
1482 .is_none_or(|e| e.selections != entry.selections)
1483 {
1484 self.redo_stack.push_back(entry);
1485 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1486 self.redo_stack.pop_front();
1487 }
1488 }
1489 }
1490}
1491
1492#[derive(Clone, Copy)]
1493pub struct RowHighlightOptions {
1494 pub autoscroll: bool,
1495 pub include_gutter: bool,
1496}
1497
1498impl Default for RowHighlightOptions {
1499 fn default() -> Self {
1500 Self {
1501 autoscroll: Default::default(),
1502 include_gutter: true,
1503 }
1504 }
1505}
1506
1507struct RowHighlight {
1508 index: usize,
1509 range: Range<Anchor>,
1510 color: Hsla,
1511 options: RowHighlightOptions,
1512 type_id: TypeId,
1513}
1514
1515#[derive(Clone, Debug)]
1516struct AddSelectionsState {
1517 groups: Vec<AddSelectionsGroup>,
1518}
1519
1520#[derive(Clone, Debug)]
1521struct AddSelectionsGroup {
1522 above: bool,
1523 stack: Vec<usize>,
1524}
1525
1526#[derive(Clone)]
1527struct SelectNextState {
1528 query: AhoCorasick,
1529 wordwise: bool,
1530 done: bool,
1531}
1532
1533impl std::fmt::Debug for SelectNextState {
1534 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1535 f.debug_struct(std::any::type_name::<Self>())
1536 .field("wordwise", &self.wordwise)
1537 .field("done", &self.done)
1538 .finish()
1539 }
1540}
1541
1542#[derive(Debug)]
1543struct AutocloseRegion {
1544 selection_id: usize,
1545 range: Range<Anchor>,
1546 pair: BracketPair,
1547}
1548
1549#[derive(Debug)]
1550struct SnippetState {
1551 ranges: Vec<Vec<Range<Anchor>>>,
1552 active_index: usize,
1553 choices: Vec<Option<Vec<String>>>,
1554}
1555
1556#[doc(hidden)]
1557pub struct RenameState {
1558 pub range: Range<Anchor>,
1559 pub old_name: Arc<str>,
1560 pub editor: Entity<Editor>,
1561 block_id: CustomBlockId,
1562}
1563
1564struct InvalidationStack<T>(Vec<T>);
1565
1566struct RegisteredEditPredictionDelegate {
1567 provider: Arc<dyn EditPredictionDelegateHandle>,
1568 _subscription: Subscription,
1569}
1570
1571#[derive(Debug, PartialEq, Eq)]
1572pub struct ActiveDiagnosticGroup {
1573 pub active_range: Range<Anchor>,
1574 pub active_message: String,
1575 pub group_id: usize,
1576 pub blocks: HashSet<CustomBlockId>,
1577}
1578
1579#[derive(Debug, PartialEq, Eq)]
1580
1581pub(crate) enum ActiveDiagnostic {
1582 None,
1583 All,
1584 Group(ActiveDiagnosticGroup),
1585}
1586
1587#[derive(Serialize, Deserialize, Clone, Debug)]
1588pub struct ClipboardSelection {
1589 /// The number of bytes in this selection.
1590 pub len: usize,
1591 /// Whether this was a full-line selection.
1592 pub is_entire_line: bool,
1593 /// The indentation of the first line when this content was originally copied.
1594 pub first_line_indent: u32,
1595}
1596
1597// selections, scroll behavior, was newest selection reversed
1598type SelectSyntaxNodeHistoryState = (
1599 Box<[Selection<MultiBufferOffset>]>,
1600 SelectSyntaxNodeScrollBehavior,
1601 bool,
1602);
1603
1604#[derive(Default)]
1605struct SelectSyntaxNodeHistory {
1606 stack: Vec<SelectSyntaxNodeHistoryState>,
1607 // disable temporarily to allow changing selections without losing the stack
1608 pub disable_clearing: bool,
1609}
1610
1611impl SelectSyntaxNodeHistory {
1612 pub fn try_clear(&mut self) {
1613 if !self.disable_clearing {
1614 self.stack.clear();
1615 }
1616 }
1617
1618 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1619 self.stack.push(selection);
1620 }
1621
1622 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1623 self.stack.pop()
1624 }
1625}
1626
1627enum SelectSyntaxNodeScrollBehavior {
1628 CursorTop,
1629 FitSelection,
1630 CursorBottom,
1631}
1632
1633#[derive(Debug)]
1634pub(crate) struct NavigationData {
1635 cursor_anchor: Anchor,
1636 cursor_position: Point,
1637 scroll_anchor: ScrollAnchor,
1638 scroll_top_row: u32,
1639}
1640
1641#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1642pub enum GotoDefinitionKind {
1643 Symbol,
1644 Declaration,
1645 Type,
1646 Implementation,
1647}
1648
1649pub enum FormatTarget {
1650 Buffers(HashSet<Entity<Buffer>>),
1651 Ranges(Vec<Range<MultiBufferPoint>>),
1652}
1653
1654pub(crate) struct FocusedBlock {
1655 id: BlockId,
1656 focus_handle: WeakFocusHandle,
1657}
1658
1659#[derive(Clone, Debug)]
1660enum JumpData {
1661 MultiBufferRow {
1662 row: MultiBufferRow,
1663 line_offset_from_top: u32,
1664 },
1665 MultiBufferPoint {
1666 excerpt_id: ExcerptId,
1667 position: Point,
1668 anchor: text::Anchor,
1669 line_offset_from_top: u32,
1670 },
1671}
1672
1673pub enum MultibufferSelectionMode {
1674 First,
1675 All,
1676}
1677
1678#[derive(Clone, Copy, Debug, Default)]
1679pub struct RewrapOptions {
1680 pub override_language_settings: bool,
1681 pub preserve_existing_whitespace: bool,
1682}
1683
1684impl Editor {
1685 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1689 }
1690
1691 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(EditorMode::full(), buffer, None, window, cx)
1695 }
1696
1697 pub fn auto_height(
1698 min_lines: usize,
1699 max_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: Some(max_lines),
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1718 /// The editor grows as tall as needed to fit its content.
1719 pub fn auto_height_unbounded(
1720 min_lines: usize,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| Buffer::local("", cx));
1725 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1726 Self::new(
1727 EditorMode::AutoHeight {
1728 min_lines,
1729 max_lines: None,
1730 },
1731 buffer,
1732 None,
1733 window,
1734 cx,
1735 )
1736 }
1737
1738 pub fn for_buffer(
1739 buffer: Entity<Buffer>,
1740 project: Option<Entity<Project>>,
1741 window: &mut Window,
1742 cx: &mut Context<Self>,
1743 ) -> Self {
1744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(EditorMode::full(), buffer, project, window, cx)
1746 }
1747
1748 pub fn for_multibuffer(
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Self::new(EditorMode::full(), buffer, project, window, cx)
1755 }
1756
1757 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1758 let mut clone = Self::new(
1759 self.mode.clone(),
1760 self.buffer.clone(),
1761 self.project.clone(),
1762 window,
1763 cx,
1764 );
1765 self.display_map.update(cx, |display_map, cx| {
1766 let snapshot = display_map.snapshot(cx);
1767 clone.display_map.update(cx, |display_map, cx| {
1768 display_map.set_state(&snapshot, cx);
1769 });
1770 });
1771 clone.folds_did_change(cx);
1772 clone.selections.clone_state(&self.selections);
1773 clone.scroll_manager.clone_state(&self.scroll_manager);
1774 clone.searchable = self.searchable;
1775 clone.read_only = self.read_only;
1776 clone
1777 }
1778
1779 pub fn new(
1780 mode: EditorMode,
1781 buffer: Entity<MultiBuffer>,
1782 project: Option<Entity<Project>>,
1783 window: &mut Window,
1784 cx: &mut Context<Self>,
1785 ) -> Self {
1786 Editor::new_internal(mode, buffer, project, None, window, cx)
1787 }
1788
1789 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1790 let multi_buffer = self.buffer().read(cx);
1791 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1792 let multi_buffer_visible_start = self
1793 .scroll_manager
1794 .anchor()
1795 .anchor
1796 .to_point(&multi_buffer_snapshot);
1797 let max_row = multi_buffer_snapshot.max_point().row;
1798
1799 let start_row = (multi_buffer_visible_start.row).min(max_row);
1800 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1801
1802 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1803 let outline_items = buffer
1804 .outline_items_containing(
1805 Point::new(start_row, 0)..Point::new(end_row, 0),
1806 true,
1807 self.style().map(|style| style.syntax.as_ref()),
1808 )
1809 .into_iter()
1810 .map(|outline_item| OutlineItem {
1811 depth: outline_item.depth,
1812 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1813 source_range_for_text: Anchor::range_in_buffer(
1814 *excerpt_id,
1815 outline_item.source_range_for_text,
1816 ),
1817 text: outline_item.text,
1818 highlight_ranges: outline_item.highlight_ranges,
1819 name_ranges: outline_item.name_ranges,
1820 body_range: outline_item
1821 .body_range
1822 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1823 annotation_range: outline_item
1824 .annotation_range
1825 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1826 });
1827 return Some(outline_items.collect());
1828 }
1829
1830 None
1831 }
1832
1833 fn new_internal(
1834 mode: EditorMode,
1835 multi_buffer: Entity<MultiBuffer>,
1836 project: Option<Entity<Project>>,
1837 display_map: Option<Entity<DisplayMap>>,
1838 window: &mut Window,
1839 cx: &mut Context<Self>,
1840 ) -> Self {
1841 debug_assert!(
1842 display_map.is_none() || mode.is_minimap(),
1843 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1844 );
1845
1846 let full_mode = mode.is_full();
1847 let is_minimap = mode.is_minimap();
1848 let diagnostics_max_severity = if full_mode {
1849 EditorSettings::get_global(cx)
1850 .diagnostics_max_severity
1851 .unwrap_or(DiagnosticSeverity::Hint)
1852 } else {
1853 DiagnosticSeverity::Off
1854 };
1855 let style = window.text_style();
1856 let font_size = style.font_size.to_pixels(window.rem_size());
1857 let editor = cx.entity().downgrade();
1858 let fold_placeholder = FoldPlaceholder {
1859 constrain_width: false,
1860 render: Arc::new(move |fold_id, fold_range, cx| {
1861 let editor = editor.clone();
1862 div()
1863 .id(fold_id)
1864 .bg(cx.theme().colors().ghost_element_background)
1865 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1866 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1867 .rounded_xs()
1868 .size_full()
1869 .cursor_pointer()
1870 .child("⋯")
1871 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1872 .on_click(move |_, _window, cx| {
1873 editor
1874 .update(cx, |editor, cx| {
1875 editor.unfold_ranges(
1876 &[fold_range.start..fold_range.end],
1877 true,
1878 false,
1879 cx,
1880 );
1881 cx.stop_propagation();
1882 })
1883 .ok();
1884 })
1885 .into_any()
1886 }),
1887 merge_adjacent: true,
1888 ..FoldPlaceholder::default()
1889 };
1890 let display_map = display_map.unwrap_or_else(|| {
1891 cx.new(|cx| {
1892 DisplayMap::new(
1893 multi_buffer.clone(),
1894 style.font(),
1895 font_size,
1896 None,
1897 FILE_HEADER_HEIGHT,
1898 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1899 fold_placeholder,
1900 diagnostics_max_severity,
1901 cx,
1902 )
1903 })
1904 });
1905
1906 let selections = SelectionsCollection::new();
1907
1908 let blink_manager = cx.new(|cx| {
1909 let mut blink_manager = BlinkManager::new(
1910 CURSOR_BLINK_INTERVAL,
1911 |cx| EditorSettings::get_global(cx).cursor_blink,
1912 cx,
1913 );
1914 if is_minimap {
1915 blink_manager.disable(cx);
1916 }
1917 blink_manager
1918 });
1919
1920 let soft_wrap_mode_override =
1921 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1922
1923 let mut project_subscriptions = Vec::new();
1924 if full_mode && let Some(project) = project.as_ref() {
1925 project_subscriptions.push(cx.subscribe_in(
1926 project,
1927 window,
1928 |editor, _, event, window, cx| match event {
1929 project::Event::RefreshCodeLens => {
1930 // we always query lens with actions, without storing them, always refreshing them
1931 }
1932 project::Event::RefreshInlayHints {
1933 server_id,
1934 request_id,
1935 } => {
1936 editor.refresh_inlay_hints(
1937 InlayHintRefreshReason::RefreshRequested {
1938 server_id: *server_id,
1939 request_id: *request_id,
1940 },
1941 cx,
1942 );
1943 }
1944 project::Event::LanguageServerRemoved(..) => {
1945 if editor.tasks_update_task.is_none() {
1946 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1947 }
1948 editor.registered_buffers.clear();
1949 editor.register_visible_buffers(cx);
1950 }
1951 project::Event::LanguageServerAdded(..) => {
1952 if editor.tasks_update_task.is_none() {
1953 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1954 }
1955 }
1956 project::Event::SnippetEdit(id, snippet_edits) => {
1957 // todo(lw): Non singletons
1958 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1959 let snapshot = buffer.read(cx).snapshot();
1960 let focus_handle = editor.focus_handle(cx);
1961 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1962 for (range, snippet) in snippet_edits {
1963 let buffer_range =
1964 language::range_from_lsp(*range).to_offset(&snapshot);
1965 editor
1966 .insert_snippet(
1967 &[MultiBufferOffset(buffer_range.start)
1968 ..MultiBufferOffset(buffer_range.end)],
1969 snippet.clone(),
1970 window,
1971 cx,
1972 )
1973 .ok();
1974 }
1975 }
1976 }
1977 }
1978 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1979 let buffer_id = *buffer_id;
1980 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1981 editor.register_buffer(buffer_id, cx);
1982 editor.update_lsp_data(Some(buffer_id), window, cx);
1983 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1984 refresh_linked_ranges(editor, window, cx);
1985 editor.refresh_code_actions(window, cx);
1986 editor.refresh_document_highlights(cx);
1987 }
1988 }
1989
1990 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1991 let Some(workspace) = editor.workspace() else {
1992 return;
1993 };
1994 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1995 else {
1996 return;
1997 };
1998
1999 if active_editor.entity_id() == cx.entity_id() {
2000 let entity_id = cx.entity_id();
2001 workspace.update(cx, |this, cx| {
2002 this.panes_mut()
2003 .iter_mut()
2004 .filter(|pane| pane.entity_id() != entity_id)
2005 .for_each(|p| {
2006 p.update(cx, |pane, _| {
2007 pane.nav_history_mut().rename_item(
2008 entity_id,
2009 project_path.clone(),
2010 abs_path.clone().into(),
2011 );
2012 })
2013 });
2014 });
2015 let edited_buffers_already_open = {
2016 let other_editors: Vec<Entity<Editor>> = workspace
2017 .read(cx)
2018 .panes()
2019 .iter()
2020 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2021 .filter(|editor| editor.entity_id() != cx.entity_id())
2022 .collect();
2023
2024 transaction.0.keys().all(|buffer| {
2025 other_editors.iter().any(|editor| {
2026 let multi_buffer = editor.read(cx).buffer();
2027 multi_buffer.read(cx).is_singleton()
2028 && multi_buffer.read(cx).as_singleton().map_or(
2029 false,
2030 |singleton| {
2031 singleton.entity_id() == buffer.entity_id()
2032 },
2033 )
2034 })
2035 })
2036 };
2037 if !edited_buffers_already_open {
2038 let workspace = workspace.downgrade();
2039 let transaction = transaction.clone();
2040 cx.defer_in(window, move |_, window, cx| {
2041 cx.spawn_in(window, async move |editor, cx| {
2042 Self::open_project_transaction(
2043 &editor,
2044 workspace,
2045 transaction,
2046 "Rename".to_string(),
2047 cx,
2048 )
2049 .await
2050 .ok()
2051 })
2052 .detach();
2053 });
2054 }
2055 }
2056 }
2057
2058 _ => {}
2059 },
2060 ));
2061 if let Some(task_inventory) = project
2062 .read(cx)
2063 .task_store()
2064 .read(cx)
2065 .task_inventory()
2066 .cloned()
2067 {
2068 project_subscriptions.push(cx.observe_in(
2069 &task_inventory,
2070 window,
2071 |editor, _, window, cx| {
2072 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2073 },
2074 ));
2075 };
2076
2077 project_subscriptions.push(cx.subscribe_in(
2078 &project.read(cx).breakpoint_store(),
2079 window,
2080 |editor, _, event, window, cx| match event {
2081 BreakpointStoreEvent::ClearDebugLines => {
2082 editor.clear_row_highlights::<ActiveDebugLine>();
2083 editor.refresh_inline_values(cx);
2084 }
2085 BreakpointStoreEvent::SetDebugLine => {
2086 if editor.go_to_active_debug_line(window, cx) {
2087 cx.stop_propagation();
2088 }
2089
2090 editor.refresh_inline_values(cx);
2091 }
2092 _ => {}
2093 },
2094 ));
2095 let git_store = project.read(cx).git_store().clone();
2096 let project = project.clone();
2097 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2098 if let GitStoreEvent::RepositoryAdded = event {
2099 this.load_diff_task = Some(
2100 update_uncommitted_diff_for_buffer(
2101 cx.entity(),
2102 &project,
2103 this.buffer.read(cx).all_buffers(),
2104 this.buffer.clone(),
2105 cx,
2106 )
2107 .shared(),
2108 );
2109 }
2110 }));
2111 }
2112
2113 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2114
2115 let inlay_hint_settings =
2116 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2117 let focus_handle = cx.focus_handle();
2118 if !is_minimap {
2119 cx.on_focus(&focus_handle, window, Self::handle_focus)
2120 .detach();
2121 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2122 .detach();
2123 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2124 .detach();
2125 cx.on_blur(&focus_handle, window, Self::handle_blur)
2126 .detach();
2127 cx.observe_pending_input(window, Self::observe_pending_input)
2128 .detach();
2129 }
2130
2131 let show_indent_guides =
2132 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2133 Some(false)
2134 } else {
2135 None
2136 };
2137
2138 let breakpoint_store = match (&mode, project.as_ref()) {
2139 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2140 _ => None,
2141 };
2142
2143 let mut code_action_providers = Vec::new();
2144 let mut load_uncommitted_diff = None;
2145 if let Some(project) = project.clone() {
2146 load_uncommitted_diff = Some(
2147 update_uncommitted_diff_for_buffer(
2148 cx.entity(),
2149 &project,
2150 multi_buffer.read(cx).all_buffers(),
2151 multi_buffer.clone(),
2152 cx,
2153 )
2154 .shared(),
2155 );
2156 code_action_providers.push(Rc::new(project) as Rc<_>);
2157 }
2158
2159 let mut editor = Self {
2160 focus_handle,
2161 show_cursor_when_unfocused: false,
2162 last_focused_descendant: None,
2163 buffer: multi_buffer.clone(),
2164 display_map: display_map.clone(),
2165 placeholder_display_map: None,
2166 selections,
2167 scroll_manager: ScrollManager::new(cx),
2168 columnar_selection_state: None,
2169 add_selections_state: None,
2170 select_next_state: None,
2171 select_prev_state: None,
2172 selection_history: SelectionHistory::default(),
2173 defer_selection_effects: false,
2174 deferred_selection_effects_state: None,
2175 autoclose_regions: Vec::new(),
2176 snippet_stack: InvalidationStack::default(),
2177 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2178 ime_transaction: None,
2179 active_diagnostics: ActiveDiagnostic::None,
2180 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2181 inline_diagnostics_update: Task::ready(()),
2182 inline_diagnostics: Vec::new(),
2183 soft_wrap_mode_override,
2184 diagnostics_max_severity,
2185 hard_wrap: None,
2186 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2187 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2188 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2189 project,
2190 blink_manager: blink_manager.clone(),
2191 show_local_selections: true,
2192 show_scrollbars: ScrollbarAxes {
2193 horizontal: full_mode,
2194 vertical: full_mode,
2195 },
2196 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2197 offset_content: !matches!(mode, EditorMode::SingleLine),
2198 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2199 show_gutter: full_mode,
2200 show_line_numbers: (!full_mode).then_some(false),
2201 use_relative_line_numbers: None,
2202 disable_expand_excerpt_buttons: !full_mode,
2203 show_git_diff_gutter: None,
2204 show_code_actions: None,
2205 show_runnables: None,
2206 show_breakpoints: None,
2207 show_wrap_guides: None,
2208 show_indent_guides,
2209 buffers_with_disabled_indent_guides: HashSet::default(),
2210 highlight_order: 0,
2211 highlighted_rows: HashMap::default(),
2212 background_highlights: HashMap::default(),
2213 gutter_highlights: HashMap::default(),
2214 scrollbar_marker_state: ScrollbarMarkerState::default(),
2215 active_indent_guides_state: ActiveIndentGuidesState::default(),
2216 nav_history: None,
2217 context_menu: RefCell::new(None),
2218 context_menu_options: None,
2219 mouse_context_menu: None,
2220 completion_tasks: Vec::new(),
2221 inline_blame_popover: None,
2222 inline_blame_popover_show_task: None,
2223 signature_help_state: SignatureHelpState::default(),
2224 auto_signature_help: None,
2225 find_all_references_task_sources: Vec::new(),
2226 next_completion_id: 0,
2227 next_inlay_id: 0,
2228 code_action_providers,
2229 available_code_actions: None,
2230 code_actions_task: None,
2231 quick_selection_highlight_task: None,
2232 debounced_selection_highlight_task: None,
2233 document_highlights_task: None,
2234 linked_editing_range_task: None,
2235 pending_rename: None,
2236 searchable: !is_minimap,
2237 cursor_shape: EditorSettings::get_global(cx)
2238 .cursor_shape
2239 .unwrap_or_default(),
2240 current_line_highlight: None,
2241 autoindent_mode: Some(AutoindentMode::EachLine),
2242 collapse_matches: false,
2243 workspace: None,
2244 input_enabled: !is_minimap,
2245 use_modal_editing: full_mode,
2246 read_only: is_minimap,
2247 use_autoclose: true,
2248 use_auto_surround: true,
2249 auto_replace_emoji_shortcode: false,
2250 jsx_tag_auto_close_enabled_in_any_buffer: false,
2251 leader_id: None,
2252 remote_id: None,
2253 hover_state: HoverState::default(),
2254 pending_mouse_down: None,
2255 hovered_link_state: None,
2256 edit_prediction_provider: None,
2257 active_edit_prediction: None,
2258 stale_edit_prediction_in_menu: None,
2259 edit_prediction_preview: EditPredictionPreview::Inactive {
2260 released_too_fast: false,
2261 },
2262 inline_diagnostics_enabled: full_mode,
2263 diagnostics_enabled: full_mode,
2264 word_completions_enabled: full_mode,
2265 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2266 gutter_hovered: false,
2267 pixel_position_of_newest_cursor: None,
2268 last_bounds: None,
2269 last_position_map: None,
2270 expect_bounds_change: None,
2271 gutter_dimensions: GutterDimensions::default(),
2272 style: None,
2273 show_cursor_names: false,
2274 hovered_cursors: HashMap::default(),
2275 next_editor_action_id: EditorActionId::default(),
2276 editor_actions: Rc::default(),
2277 edit_predictions_hidden_for_vim_mode: false,
2278 show_edit_predictions_override: None,
2279 show_completions_on_input_override: None,
2280 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2281 edit_prediction_settings: EditPredictionSettings::Disabled,
2282 edit_prediction_indent_conflict: false,
2283 edit_prediction_requires_modifier_in_indent_conflict: true,
2284 custom_context_menu: None,
2285 show_git_blame_gutter: false,
2286 show_git_blame_inline: false,
2287 show_selection_menu: None,
2288 show_git_blame_inline_delay_task: None,
2289 git_blame_inline_enabled: full_mode
2290 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2291 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2292 buffer_serialization: is_minimap.not().then(|| {
2293 BufferSerialization::new(
2294 ProjectSettings::get_global(cx)
2295 .session
2296 .restore_unsaved_buffers,
2297 )
2298 }),
2299 blame: None,
2300 blame_subscription: None,
2301 tasks: BTreeMap::default(),
2302
2303 breakpoint_store,
2304 gutter_breakpoint_indicator: (None, None),
2305 hovered_diff_hunk_row: None,
2306 _subscriptions: (!is_minimap)
2307 .then(|| {
2308 vec![
2309 cx.observe(&multi_buffer, Self::on_buffer_changed),
2310 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2311 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2312 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2313 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2314 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2315 cx.observe_window_activation(window, |editor, window, cx| {
2316 let active = window.is_window_active();
2317 editor.blink_manager.update(cx, |blink_manager, cx| {
2318 if active {
2319 blink_manager.enable(cx);
2320 } else {
2321 blink_manager.disable(cx);
2322 }
2323 });
2324 if active {
2325 editor.show_mouse_cursor(cx);
2326 }
2327 }),
2328 ]
2329 })
2330 .unwrap_or_default(),
2331 tasks_update_task: None,
2332 pull_diagnostics_task: Task::ready(()),
2333 pull_diagnostics_background_task: Task::ready(()),
2334 colors: None,
2335 refresh_colors_task: Task::ready(()),
2336 inlay_hints: None,
2337 next_color_inlay_id: 0,
2338 post_scroll_update: Task::ready(()),
2339 linked_edit_ranges: Default::default(),
2340 in_project_search: false,
2341 previous_search_ranges: None,
2342 breadcrumb_header: None,
2343 focused_block: None,
2344 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2345 addons: HashMap::default(),
2346 registered_buffers: HashMap::default(),
2347 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2348 selection_mark_mode: false,
2349 toggle_fold_multiple_buffers: Task::ready(()),
2350 serialize_selections: Task::ready(()),
2351 serialize_folds: Task::ready(()),
2352 text_style_refinement: None,
2353 load_diff_task: load_uncommitted_diff,
2354 temporary_diff_override: false,
2355 mouse_cursor_hidden: false,
2356 minimap: None,
2357 hide_mouse_mode: EditorSettings::get_global(cx)
2358 .hide_mouse
2359 .unwrap_or_default(),
2360 change_list: ChangeList::new(),
2361 mode,
2362 selection_drag_state: SelectionDragState::None,
2363 folding_newlines: Task::ready(()),
2364 lookup_key: None,
2365 select_next_is_case_sensitive: None,
2366 applicable_language_settings: HashMap::default(),
2367 accent_data: None,
2368 fetched_tree_sitter_chunks: HashMap::default(),
2369 use_base_text_line_numbers: false,
2370 };
2371
2372 if is_minimap {
2373 return editor;
2374 }
2375
2376 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2377 editor.accent_data = editor.fetch_accent_data(cx);
2378
2379 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2380 editor
2381 ._subscriptions
2382 .push(cx.observe(breakpoints, |_, _, cx| {
2383 cx.notify();
2384 }));
2385 }
2386 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2387 editor._subscriptions.extend(project_subscriptions);
2388
2389 editor._subscriptions.push(cx.subscribe_in(
2390 &cx.entity(),
2391 window,
2392 |editor, _, e: &EditorEvent, window, cx| match e {
2393 EditorEvent::ScrollPositionChanged { local, .. } => {
2394 if *local {
2395 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2396 editor.inline_blame_popover.take();
2397 let new_anchor = editor.scroll_manager.anchor();
2398 let snapshot = editor.snapshot(window, cx);
2399 editor.update_restoration_data(cx, move |data| {
2400 data.scroll_position = (
2401 new_anchor.top_row(snapshot.buffer_snapshot()),
2402 new_anchor.offset,
2403 );
2404 });
2405
2406 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2407 cx.background_executor()
2408 .timer(Duration::from_millis(50))
2409 .await;
2410 editor
2411 .update_in(cx, |editor, window, cx| {
2412 editor.register_visible_buffers(cx);
2413 editor.refresh_colors_for_visible_range(None, window, cx);
2414 editor.refresh_inlay_hints(
2415 InlayHintRefreshReason::NewLinesShown,
2416 cx,
2417 );
2418 editor.colorize_brackets(false, cx);
2419 })
2420 .ok();
2421 });
2422 }
2423 }
2424 EditorEvent::Edited { .. } => {
2425 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2426 .map(|vim_mode| vim_mode.0)
2427 .unwrap_or(false);
2428 if !vim_mode {
2429 let display_map = editor.display_snapshot(cx);
2430 let selections = editor.selections.all_adjusted_display(&display_map);
2431 let pop_state = editor
2432 .change_list
2433 .last()
2434 .map(|previous| {
2435 previous.len() == selections.len()
2436 && previous.iter().enumerate().all(|(ix, p)| {
2437 p.to_display_point(&display_map).row()
2438 == selections[ix].head().row()
2439 })
2440 })
2441 .unwrap_or(false);
2442 let new_positions = selections
2443 .into_iter()
2444 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2445 .collect();
2446 editor
2447 .change_list
2448 .push_to_change_list(pop_state, new_positions);
2449 }
2450 }
2451 _ => (),
2452 },
2453 ));
2454
2455 if let Some(dap_store) = editor
2456 .project
2457 .as_ref()
2458 .map(|project| project.read(cx).dap_store())
2459 {
2460 let weak_editor = cx.weak_entity();
2461
2462 editor
2463 ._subscriptions
2464 .push(
2465 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2466 let session_entity = cx.entity();
2467 weak_editor
2468 .update(cx, |editor, cx| {
2469 editor._subscriptions.push(
2470 cx.subscribe(&session_entity, Self::on_debug_session_event),
2471 );
2472 })
2473 .ok();
2474 }),
2475 );
2476
2477 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2478 editor
2479 ._subscriptions
2480 .push(cx.subscribe(&session, Self::on_debug_session_event));
2481 }
2482 }
2483
2484 // skip adding the initial selection to selection history
2485 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2486 editor.end_selection(window, cx);
2487 editor.selection_history.mode = SelectionHistoryMode::Normal;
2488
2489 editor.scroll_manager.show_scrollbars(window, cx);
2490 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2491
2492 if full_mode {
2493 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2494 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2495
2496 if editor.git_blame_inline_enabled {
2497 editor.start_git_blame_inline(false, window, cx);
2498 }
2499
2500 editor.go_to_active_debug_line(window, cx);
2501
2502 editor.minimap =
2503 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2504 editor.colors = Some(LspColorData::new(cx));
2505 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2506
2507 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2508 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2509 }
2510 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2511 }
2512
2513 editor
2514 }
2515
2516 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2517 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2518 }
2519
2520 pub fn deploy_mouse_context_menu(
2521 &mut self,
2522 position: gpui::Point<Pixels>,
2523 context_menu: Entity<ContextMenu>,
2524 window: &mut Window,
2525 cx: &mut Context<Self>,
2526 ) {
2527 self.mouse_context_menu = Some(MouseContextMenu::new(
2528 self,
2529 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2530 context_menu,
2531 window,
2532 cx,
2533 ));
2534 }
2535
2536 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2537 self.mouse_context_menu
2538 .as_ref()
2539 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2540 }
2541
2542 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2543 if self
2544 .selections
2545 .pending_anchor()
2546 .is_some_and(|pending_selection| {
2547 let snapshot = self.buffer().read(cx).snapshot(cx);
2548 pending_selection.range().includes(range, &snapshot)
2549 })
2550 {
2551 return true;
2552 }
2553
2554 self.selections
2555 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2556 .into_iter()
2557 .any(|selection| {
2558 // This is needed to cover a corner case, if we just check for an existing
2559 // selection in the fold range, having a cursor at the start of the fold
2560 // marks it as selected. Non-empty selections don't cause this.
2561 let length = selection.end - selection.start;
2562 length > 0
2563 })
2564 }
2565
2566 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2567 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2568 }
2569
2570 fn key_context_internal(
2571 &self,
2572 has_active_edit_prediction: bool,
2573 window: &mut Window,
2574 cx: &mut App,
2575 ) -> KeyContext {
2576 let mut key_context = KeyContext::new_with_defaults();
2577 key_context.add("Editor");
2578 let mode = match self.mode {
2579 EditorMode::SingleLine => "single_line",
2580 EditorMode::AutoHeight { .. } => "auto_height",
2581 EditorMode::Minimap { .. } => "minimap",
2582 EditorMode::Full { .. } => "full",
2583 };
2584
2585 if EditorSettings::jupyter_enabled(cx) {
2586 key_context.add("jupyter");
2587 }
2588
2589 key_context.set("mode", mode);
2590 if self.pending_rename.is_some() {
2591 key_context.add("renaming");
2592 }
2593
2594 if let Some(snippet_stack) = self.snippet_stack.last() {
2595 key_context.add("in_snippet");
2596
2597 if snippet_stack.active_index > 0 {
2598 key_context.add("has_previous_tabstop");
2599 }
2600
2601 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2602 key_context.add("has_next_tabstop");
2603 }
2604 }
2605
2606 match self.context_menu.borrow().as_ref() {
2607 Some(CodeContextMenu::Completions(menu)) => {
2608 if menu.visible() {
2609 key_context.add("menu");
2610 key_context.add("showing_completions");
2611 }
2612 }
2613 Some(CodeContextMenu::CodeActions(menu)) => {
2614 if menu.visible() {
2615 key_context.add("menu");
2616 key_context.add("showing_code_actions")
2617 }
2618 }
2619 None => {}
2620 }
2621
2622 if self.signature_help_state.has_multiple_signatures() {
2623 key_context.add("showing_signature_help");
2624 }
2625
2626 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2627 if !self.focus_handle(cx).contains_focused(window, cx)
2628 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2629 {
2630 for addon in self.addons.values() {
2631 addon.extend_key_context(&mut key_context, cx)
2632 }
2633 }
2634
2635 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2636 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2637 Some(
2638 file.full_path(cx)
2639 .extension()?
2640 .to_string_lossy()
2641 .into_owned(),
2642 )
2643 }) {
2644 key_context.set("extension", extension);
2645 }
2646 } else {
2647 key_context.add("multibuffer");
2648 }
2649
2650 if has_active_edit_prediction {
2651 if self.edit_prediction_in_conflict() {
2652 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2653 } else {
2654 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2655 key_context.add("copilot_suggestion");
2656 }
2657 }
2658
2659 if self.selection_mark_mode {
2660 key_context.add("selection_mode");
2661 }
2662
2663 let disjoint = self.selections.disjoint_anchors();
2664 let snapshot = self.snapshot(window, cx);
2665 let snapshot = snapshot.buffer_snapshot();
2666 if self.mode == EditorMode::SingleLine
2667 && let [selection] = disjoint
2668 && selection.start == selection.end
2669 && selection.end.to_offset(snapshot) == snapshot.len()
2670 {
2671 key_context.add("end_of_input");
2672 }
2673
2674 if self.has_any_expanded_diff_hunks(cx) {
2675 key_context.add("diffs_expanded");
2676 }
2677
2678 key_context
2679 }
2680
2681 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2682 self.last_bounds.as_ref()
2683 }
2684
2685 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2686 if self.mouse_cursor_hidden {
2687 self.mouse_cursor_hidden = false;
2688 cx.notify();
2689 }
2690 }
2691
2692 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2693 let hide_mouse_cursor = match origin {
2694 HideMouseCursorOrigin::TypingAction => {
2695 matches!(
2696 self.hide_mouse_mode,
2697 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2698 )
2699 }
2700 HideMouseCursorOrigin::MovementAction => {
2701 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2702 }
2703 };
2704 if self.mouse_cursor_hidden != hide_mouse_cursor {
2705 self.mouse_cursor_hidden = hide_mouse_cursor;
2706 cx.notify();
2707 }
2708 }
2709
2710 pub fn edit_prediction_in_conflict(&self) -> bool {
2711 if !self.show_edit_predictions_in_menu() {
2712 return false;
2713 }
2714
2715 let showing_completions = self
2716 .context_menu
2717 .borrow()
2718 .as_ref()
2719 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2720
2721 showing_completions
2722 || self.edit_prediction_requires_modifier()
2723 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2724 // bindings to insert tab characters.
2725 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2726 }
2727
2728 pub fn accept_edit_prediction_keybind(
2729 &self,
2730 accept_partial: bool,
2731 window: &mut Window,
2732 cx: &mut App,
2733 ) -> AcceptEditPredictionBinding {
2734 let key_context = self.key_context_internal(true, window, cx);
2735 let in_conflict = self.edit_prediction_in_conflict();
2736
2737 let bindings = if accept_partial {
2738 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2739 } else {
2740 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2741 };
2742
2743 // TODO: if the binding contains multiple keystrokes, display all of them, not
2744 // just the first one.
2745 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2746 !in_conflict
2747 || binding
2748 .keystrokes()
2749 .first()
2750 .is_some_and(|keystroke| keystroke.modifiers().modified())
2751 }))
2752 }
2753
2754 pub fn new_file(
2755 workspace: &mut Workspace,
2756 _: &workspace::NewFile,
2757 window: &mut Window,
2758 cx: &mut Context<Workspace>,
2759 ) {
2760 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2761 "Failed to create buffer",
2762 window,
2763 cx,
2764 |e, _, _| match e.error_code() {
2765 ErrorCode::RemoteUpgradeRequired => Some(format!(
2766 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2767 e.error_tag("required").unwrap_or("the latest version")
2768 )),
2769 _ => None,
2770 },
2771 );
2772 }
2773
2774 pub fn new_in_workspace(
2775 workspace: &mut Workspace,
2776 window: &mut Window,
2777 cx: &mut Context<Workspace>,
2778 ) -> Task<Result<Entity<Editor>>> {
2779 let project = workspace.project().clone();
2780 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2781
2782 cx.spawn_in(window, async move |workspace, cx| {
2783 let buffer = create.await?;
2784 workspace.update_in(cx, |workspace, window, cx| {
2785 let editor =
2786 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2787 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2788 editor
2789 })
2790 })
2791 }
2792
2793 fn new_file_vertical(
2794 workspace: &mut Workspace,
2795 _: &workspace::NewFileSplitVertical,
2796 window: &mut Window,
2797 cx: &mut Context<Workspace>,
2798 ) {
2799 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2800 }
2801
2802 fn new_file_horizontal(
2803 workspace: &mut Workspace,
2804 _: &workspace::NewFileSplitHorizontal,
2805 window: &mut Window,
2806 cx: &mut Context<Workspace>,
2807 ) {
2808 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2809 }
2810
2811 fn new_file_split(
2812 workspace: &mut Workspace,
2813 action: &workspace::NewFileSplit,
2814 window: &mut Window,
2815 cx: &mut Context<Workspace>,
2816 ) {
2817 Self::new_file_in_direction(workspace, action.0, window, cx)
2818 }
2819
2820 fn new_file_in_direction(
2821 workspace: &mut Workspace,
2822 direction: SplitDirection,
2823 window: &mut Window,
2824 cx: &mut Context<Workspace>,
2825 ) {
2826 let project = workspace.project().clone();
2827 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2828
2829 cx.spawn_in(window, async move |workspace, cx| {
2830 let buffer = create.await?;
2831 workspace.update_in(cx, move |workspace, window, cx| {
2832 workspace.split_item(
2833 direction,
2834 Box::new(
2835 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2836 ),
2837 window,
2838 cx,
2839 )
2840 })?;
2841 anyhow::Ok(())
2842 })
2843 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2844 match e.error_code() {
2845 ErrorCode::RemoteUpgradeRequired => Some(format!(
2846 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2847 e.error_tag("required").unwrap_or("the latest version")
2848 )),
2849 _ => None,
2850 }
2851 });
2852 }
2853
2854 pub fn leader_id(&self) -> Option<CollaboratorId> {
2855 self.leader_id
2856 }
2857
2858 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2859 &self.buffer
2860 }
2861
2862 pub fn project(&self) -> Option<&Entity<Project>> {
2863 self.project.as_ref()
2864 }
2865
2866 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2867 self.workspace.as_ref()?.0.upgrade()
2868 }
2869
2870 /// Returns the workspace serialization ID if this editor should be serialized.
2871 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2872 self.workspace
2873 .as_ref()
2874 .filter(|_| self.should_serialize_buffer())
2875 .and_then(|workspace| workspace.1)
2876 }
2877
2878 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2879 self.buffer().read(cx).title(cx)
2880 }
2881
2882 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2883 let git_blame_gutter_max_author_length = self
2884 .render_git_blame_gutter(cx)
2885 .then(|| {
2886 if let Some(blame) = self.blame.as_ref() {
2887 let max_author_length =
2888 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2889 Some(max_author_length)
2890 } else {
2891 None
2892 }
2893 })
2894 .flatten();
2895
2896 EditorSnapshot {
2897 mode: self.mode.clone(),
2898 show_gutter: self.show_gutter,
2899 show_line_numbers: self.show_line_numbers,
2900 show_git_diff_gutter: self.show_git_diff_gutter,
2901 show_code_actions: self.show_code_actions,
2902 show_runnables: self.show_runnables,
2903 show_breakpoints: self.show_breakpoints,
2904 git_blame_gutter_max_author_length,
2905 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2906 placeholder_display_snapshot: self
2907 .placeholder_display_map
2908 .as_ref()
2909 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2910 scroll_anchor: self.scroll_manager.anchor(),
2911 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2912 is_focused: self.focus_handle.is_focused(window),
2913 current_line_highlight: self
2914 .current_line_highlight
2915 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2916 gutter_hovered: self.gutter_hovered,
2917 }
2918 }
2919
2920 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2921 self.buffer.read(cx).language_at(point, cx)
2922 }
2923
2924 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2925 self.buffer.read(cx).read(cx).file_at(point).cloned()
2926 }
2927
2928 pub fn active_excerpt(
2929 &self,
2930 cx: &App,
2931 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2932 self.buffer
2933 .read(cx)
2934 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2935 }
2936
2937 pub fn mode(&self) -> &EditorMode {
2938 &self.mode
2939 }
2940
2941 pub fn set_mode(&mut self, mode: EditorMode) {
2942 self.mode = mode;
2943 }
2944
2945 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2946 self.collaboration_hub.as_deref()
2947 }
2948
2949 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2950 self.collaboration_hub = Some(hub);
2951 }
2952
2953 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2954 self.in_project_search = in_project_search;
2955 }
2956
2957 pub fn set_custom_context_menu(
2958 &mut self,
2959 f: impl 'static
2960 + Fn(
2961 &mut Self,
2962 DisplayPoint,
2963 &mut Window,
2964 &mut Context<Self>,
2965 ) -> Option<Entity<ui::ContextMenu>>,
2966 ) {
2967 self.custom_context_menu = Some(Box::new(f))
2968 }
2969
2970 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2971 self.completion_provider = provider;
2972 }
2973
2974 #[cfg(any(test, feature = "test-support"))]
2975 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2976 self.completion_provider.clone()
2977 }
2978
2979 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2980 self.semantics_provider.clone()
2981 }
2982
2983 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2984 self.semantics_provider = provider;
2985 }
2986
2987 pub fn set_edit_prediction_provider<T>(
2988 &mut self,
2989 provider: Option<Entity<T>>,
2990 window: &mut Window,
2991 cx: &mut Context<Self>,
2992 ) where
2993 T: EditPredictionDelegate,
2994 {
2995 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
2996 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2997 if this.focus_handle.is_focused(window) {
2998 this.update_visible_edit_prediction(window, cx);
2999 }
3000 }),
3001 provider: Arc::new(provider),
3002 });
3003 self.update_edit_prediction_settings(cx);
3004 self.refresh_edit_prediction(false, false, window, cx);
3005 }
3006
3007 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3008 self.placeholder_display_map
3009 .as_ref()
3010 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3011 }
3012
3013 pub fn set_placeholder_text(
3014 &mut self,
3015 placeholder_text: &str,
3016 window: &mut Window,
3017 cx: &mut Context<Self>,
3018 ) {
3019 let multibuffer = cx
3020 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3021
3022 let style = window.text_style();
3023
3024 self.placeholder_display_map = Some(cx.new(|cx| {
3025 DisplayMap::new(
3026 multibuffer,
3027 style.font(),
3028 style.font_size.to_pixels(window.rem_size()),
3029 None,
3030 FILE_HEADER_HEIGHT,
3031 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3032 Default::default(),
3033 DiagnosticSeverity::Off,
3034 cx,
3035 )
3036 }));
3037 cx.notify();
3038 }
3039
3040 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3041 self.cursor_shape = cursor_shape;
3042
3043 // Disrupt blink for immediate user feedback that the cursor shape has changed
3044 self.blink_manager.update(cx, BlinkManager::show_cursor);
3045
3046 cx.notify();
3047 }
3048
3049 pub fn cursor_shape(&self) -> CursorShape {
3050 self.cursor_shape
3051 }
3052
3053 pub fn set_current_line_highlight(
3054 &mut self,
3055 current_line_highlight: Option<CurrentLineHighlight>,
3056 ) {
3057 self.current_line_highlight = current_line_highlight;
3058 }
3059
3060 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3061 self.collapse_matches = collapse_matches;
3062 }
3063
3064 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3065 if self.collapse_matches {
3066 return range.start..range.start;
3067 }
3068 range.clone()
3069 }
3070
3071 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3072 self.display_map.read(cx).clip_at_line_ends
3073 }
3074
3075 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3076 if self.display_map.read(cx).clip_at_line_ends != clip {
3077 self.display_map
3078 .update(cx, |map, _| map.clip_at_line_ends = clip);
3079 }
3080 }
3081
3082 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3083 self.input_enabled = input_enabled;
3084 }
3085
3086 pub fn set_edit_predictions_hidden_for_vim_mode(
3087 &mut self,
3088 hidden: bool,
3089 window: &mut Window,
3090 cx: &mut Context<Self>,
3091 ) {
3092 if hidden != self.edit_predictions_hidden_for_vim_mode {
3093 self.edit_predictions_hidden_for_vim_mode = hidden;
3094 if hidden {
3095 self.update_visible_edit_prediction(window, cx);
3096 } else {
3097 self.refresh_edit_prediction(true, false, window, cx);
3098 }
3099 }
3100 }
3101
3102 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3103 self.menu_edit_predictions_policy = value;
3104 }
3105
3106 pub fn set_autoindent(&mut self, autoindent: bool) {
3107 if autoindent {
3108 self.autoindent_mode = Some(AutoindentMode::EachLine);
3109 } else {
3110 self.autoindent_mode = None;
3111 }
3112 }
3113
3114 pub fn read_only(&self, cx: &App) -> bool {
3115 self.read_only || self.buffer.read(cx).read_only()
3116 }
3117
3118 pub fn set_read_only(&mut self, read_only: bool) {
3119 self.read_only = read_only;
3120 }
3121
3122 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3123 self.use_autoclose = autoclose;
3124 }
3125
3126 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3127 self.use_auto_surround = auto_surround;
3128 }
3129
3130 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3131 self.auto_replace_emoji_shortcode = auto_replace;
3132 }
3133
3134 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3135 self.buffer_serialization = should_serialize.then(|| {
3136 BufferSerialization::new(
3137 ProjectSettings::get_global(cx)
3138 .session
3139 .restore_unsaved_buffers,
3140 )
3141 })
3142 }
3143
3144 fn should_serialize_buffer(&self) -> bool {
3145 self.buffer_serialization.is_some()
3146 }
3147
3148 pub fn toggle_edit_predictions(
3149 &mut self,
3150 _: &ToggleEditPrediction,
3151 window: &mut Window,
3152 cx: &mut Context<Self>,
3153 ) {
3154 if self.show_edit_predictions_override.is_some() {
3155 self.set_show_edit_predictions(None, window, cx);
3156 } else {
3157 let show_edit_predictions = !self.edit_predictions_enabled();
3158 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3159 }
3160 }
3161
3162 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3163 self.show_completions_on_input_override = show_completions_on_input;
3164 }
3165
3166 pub fn set_show_edit_predictions(
3167 &mut self,
3168 show_edit_predictions: Option<bool>,
3169 window: &mut Window,
3170 cx: &mut Context<Self>,
3171 ) {
3172 self.show_edit_predictions_override = show_edit_predictions;
3173 self.update_edit_prediction_settings(cx);
3174
3175 if let Some(false) = show_edit_predictions {
3176 self.discard_edit_prediction(false, cx);
3177 } else {
3178 self.refresh_edit_prediction(false, true, window, cx);
3179 }
3180 }
3181
3182 fn edit_predictions_disabled_in_scope(
3183 &self,
3184 buffer: &Entity<Buffer>,
3185 buffer_position: language::Anchor,
3186 cx: &App,
3187 ) -> bool {
3188 let snapshot = buffer.read(cx).snapshot();
3189 let settings = snapshot.settings_at(buffer_position, cx);
3190
3191 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3192 return false;
3193 };
3194
3195 scope.override_name().is_some_and(|scope_name| {
3196 settings
3197 .edit_predictions_disabled_in
3198 .iter()
3199 .any(|s| s == scope_name)
3200 })
3201 }
3202
3203 pub fn set_use_modal_editing(&mut self, to: bool) {
3204 self.use_modal_editing = to;
3205 }
3206
3207 pub fn use_modal_editing(&self) -> bool {
3208 self.use_modal_editing
3209 }
3210
3211 fn selections_did_change(
3212 &mut self,
3213 local: bool,
3214 old_cursor_position: &Anchor,
3215 effects: SelectionEffects,
3216 window: &mut Window,
3217 cx: &mut Context<Self>,
3218 ) {
3219 window.invalidate_character_coordinates();
3220
3221 // Copy selections to primary selection buffer
3222 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3223 if local {
3224 let selections = self
3225 .selections
3226 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3227 let buffer_handle = self.buffer.read(cx).read(cx);
3228
3229 let mut text = String::new();
3230 for (index, selection) in selections.iter().enumerate() {
3231 let text_for_selection = buffer_handle
3232 .text_for_range(selection.start..selection.end)
3233 .collect::<String>();
3234
3235 text.push_str(&text_for_selection);
3236 if index != selections.len() - 1 {
3237 text.push('\n');
3238 }
3239 }
3240
3241 if !text.is_empty() {
3242 cx.write_to_primary(ClipboardItem::new_string(text));
3243 }
3244 }
3245
3246 let selection_anchors = self.selections.disjoint_anchors_arc();
3247
3248 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3249 self.buffer.update(cx, |buffer, cx| {
3250 buffer.set_active_selections(
3251 &selection_anchors,
3252 self.selections.line_mode(),
3253 self.cursor_shape,
3254 cx,
3255 )
3256 });
3257 }
3258 let display_map = self
3259 .display_map
3260 .update(cx, |display_map, cx| display_map.snapshot(cx));
3261 let buffer = display_map.buffer_snapshot();
3262 if self.selections.count() == 1 {
3263 self.add_selections_state = None;
3264 }
3265 self.select_next_state = None;
3266 self.select_prev_state = None;
3267 self.select_syntax_node_history.try_clear();
3268 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3269 self.snippet_stack.invalidate(&selection_anchors, buffer);
3270 self.take_rename(false, window, cx);
3271
3272 let newest_selection = self.selections.newest_anchor();
3273 let new_cursor_position = newest_selection.head();
3274 let selection_start = newest_selection.start;
3275
3276 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3277 self.push_to_nav_history(
3278 *old_cursor_position,
3279 Some(new_cursor_position.to_point(buffer)),
3280 false,
3281 effects.nav_history == Some(true),
3282 cx,
3283 );
3284 }
3285
3286 if local {
3287 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3288 self.register_buffer(buffer_id, cx);
3289 }
3290
3291 let mut context_menu = self.context_menu.borrow_mut();
3292 let completion_menu = match context_menu.as_ref() {
3293 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3294 Some(CodeContextMenu::CodeActions(_)) => {
3295 *context_menu = None;
3296 None
3297 }
3298 None => None,
3299 };
3300 let completion_position = completion_menu.map(|menu| menu.initial_position);
3301 drop(context_menu);
3302
3303 if effects.completions
3304 && let Some(completion_position) = completion_position
3305 {
3306 let start_offset = selection_start.to_offset(buffer);
3307 let position_matches = start_offset == completion_position.to_offset(buffer);
3308 let continue_showing = if position_matches {
3309 if self.snippet_stack.is_empty() {
3310 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3311 == Some(CharKind::Word)
3312 } else {
3313 // Snippet choices can be shown even when the cursor is in whitespace.
3314 // Dismissing the menu with actions like backspace is handled by
3315 // invalidation regions.
3316 true
3317 }
3318 } else {
3319 false
3320 };
3321
3322 if continue_showing {
3323 self.open_or_update_completions_menu(None, None, false, window, cx);
3324 } else {
3325 self.hide_context_menu(window, cx);
3326 }
3327 }
3328
3329 hide_hover(self, cx);
3330
3331 if old_cursor_position.to_display_point(&display_map).row()
3332 != new_cursor_position.to_display_point(&display_map).row()
3333 {
3334 self.available_code_actions.take();
3335 }
3336 self.refresh_code_actions(window, cx);
3337 self.refresh_document_highlights(cx);
3338 refresh_linked_ranges(self, window, cx);
3339
3340 self.refresh_selected_text_highlights(false, window, cx);
3341 self.refresh_matching_bracket_highlights(window, cx);
3342 self.update_visible_edit_prediction(window, cx);
3343 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3344 self.inline_blame_popover.take();
3345 if self.git_blame_inline_enabled {
3346 self.start_inline_blame_timer(window, cx);
3347 }
3348 }
3349
3350 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3351 cx.emit(EditorEvent::SelectionsChanged { local });
3352
3353 let selections = &self.selections.disjoint_anchors_arc();
3354 if selections.len() == 1 {
3355 cx.emit(SearchEvent::ActiveMatchChanged)
3356 }
3357 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3358 let inmemory_selections = selections
3359 .iter()
3360 .map(|s| {
3361 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3362 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3363 })
3364 .collect();
3365 self.update_restoration_data(cx, |data| {
3366 data.selections = inmemory_selections;
3367 });
3368
3369 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3370 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3371 {
3372 let snapshot = self.buffer().read(cx).snapshot(cx);
3373 let selections = selections.clone();
3374 let background_executor = cx.background_executor().clone();
3375 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3376 self.serialize_selections = cx.background_spawn(async move {
3377 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3378 let db_selections = selections
3379 .iter()
3380 .map(|selection| {
3381 (
3382 selection.start.to_offset(&snapshot).0,
3383 selection.end.to_offset(&snapshot).0,
3384 )
3385 })
3386 .collect();
3387
3388 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3389 .await
3390 .with_context(|| {
3391 format!(
3392 "persisting editor selections for editor {editor_id}, \
3393 workspace {workspace_id:?}"
3394 )
3395 })
3396 .log_err();
3397 });
3398 }
3399 }
3400
3401 cx.notify();
3402 }
3403
3404 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3405 use text::ToOffset as _;
3406 use text::ToPoint as _;
3407
3408 if self.mode.is_minimap()
3409 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3410 {
3411 return;
3412 }
3413
3414 if !self.buffer().read(cx).is_singleton() {
3415 return;
3416 }
3417
3418 let display_snapshot = self
3419 .display_map
3420 .update(cx, |display_map, cx| display_map.snapshot(cx));
3421 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3422 return;
3423 };
3424 let inmemory_folds = display_snapshot
3425 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3426 .map(|fold| {
3427 fold.range.start.text_anchor.to_point(&snapshot)
3428 ..fold.range.end.text_anchor.to_point(&snapshot)
3429 })
3430 .collect();
3431 self.update_restoration_data(cx, |data| {
3432 data.folds = inmemory_folds;
3433 });
3434
3435 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3436 return;
3437 };
3438 let background_executor = cx.background_executor().clone();
3439 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3440 let db_folds = display_snapshot
3441 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3442 .map(|fold| {
3443 (
3444 fold.range.start.text_anchor.to_offset(&snapshot),
3445 fold.range.end.text_anchor.to_offset(&snapshot),
3446 )
3447 })
3448 .collect();
3449 self.serialize_folds = cx.background_spawn(async move {
3450 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3451 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3452 .await
3453 .with_context(|| {
3454 format!(
3455 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3456 )
3457 })
3458 .log_err();
3459 });
3460 }
3461
3462 pub fn sync_selections(
3463 &mut self,
3464 other: Entity<Editor>,
3465 cx: &mut Context<Self>,
3466 ) -> gpui::Subscription {
3467 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3468 if !other_selections.is_empty() {
3469 self.selections
3470 .change_with(&self.display_snapshot(cx), |selections| {
3471 selections.select_anchors(other_selections);
3472 });
3473 }
3474
3475 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3476 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3477 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3478 if other_selections.is_empty() {
3479 return;
3480 }
3481 let snapshot = this.display_snapshot(cx);
3482 this.selections.change_with(&snapshot, |selections| {
3483 selections.select_anchors(other_selections);
3484 });
3485 }
3486 });
3487
3488 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3489 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3490 let these_selections = this.selections.disjoint_anchors().to_vec();
3491 if these_selections.is_empty() {
3492 return;
3493 }
3494 other.update(cx, |other_editor, cx| {
3495 let snapshot = other_editor.display_snapshot(cx);
3496 other_editor
3497 .selections
3498 .change_with(&snapshot, |selections| {
3499 selections.select_anchors(these_selections);
3500 })
3501 });
3502 }
3503 });
3504
3505 Subscription::join(other_subscription, this_subscription)
3506 }
3507
3508 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3509 if self.buffer().read(cx).is_singleton() {
3510 return;
3511 }
3512 let snapshot = self.buffer.read(cx).snapshot(cx);
3513 let buffer_ids: HashSet<BufferId> = self
3514 .selections
3515 .disjoint_anchor_ranges()
3516 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3517 .collect();
3518 for buffer_id in buffer_ids {
3519 self.unfold_buffer(buffer_id, cx);
3520 }
3521 }
3522
3523 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3524 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3525 /// effects of selection change occur at the end of the transaction.
3526 pub fn change_selections<R>(
3527 &mut self,
3528 effects: SelectionEffects,
3529 window: &mut Window,
3530 cx: &mut Context<Self>,
3531 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3532 ) -> R {
3533 let snapshot = self.display_snapshot(cx);
3534 if let Some(state) = &mut self.deferred_selection_effects_state {
3535 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3536 state.effects.completions = effects.completions;
3537 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3538 let (changed, result) = self.selections.change_with(&snapshot, change);
3539 state.changed |= changed;
3540 return result;
3541 }
3542 let mut state = DeferredSelectionEffectsState {
3543 changed: false,
3544 effects,
3545 old_cursor_position: self.selections.newest_anchor().head(),
3546 history_entry: SelectionHistoryEntry {
3547 selections: self.selections.disjoint_anchors_arc(),
3548 select_next_state: self.select_next_state.clone(),
3549 select_prev_state: self.select_prev_state.clone(),
3550 add_selections_state: self.add_selections_state.clone(),
3551 },
3552 };
3553 let (changed, result) = self.selections.change_with(&snapshot, change);
3554 state.changed = state.changed || changed;
3555 if self.defer_selection_effects {
3556 self.deferred_selection_effects_state = Some(state);
3557 } else {
3558 self.apply_selection_effects(state, window, cx);
3559 }
3560 result
3561 }
3562
3563 /// Defers the effects of selection change, so that the effects of multiple calls to
3564 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3565 /// to selection history and the state of popovers based on selection position aren't
3566 /// erroneously updated.
3567 pub fn with_selection_effects_deferred<R>(
3568 &mut self,
3569 window: &mut Window,
3570 cx: &mut Context<Self>,
3571 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3572 ) -> R {
3573 let already_deferred = self.defer_selection_effects;
3574 self.defer_selection_effects = true;
3575 let result = update(self, window, cx);
3576 if !already_deferred {
3577 self.defer_selection_effects = false;
3578 if let Some(state) = self.deferred_selection_effects_state.take() {
3579 self.apply_selection_effects(state, window, cx);
3580 }
3581 }
3582 result
3583 }
3584
3585 fn apply_selection_effects(
3586 &mut self,
3587 state: DeferredSelectionEffectsState,
3588 window: &mut Window,
3589 cx: &mut Context<Self>,
3590 ) {
3591 if state.changed {
3592 self.selection_history.push(state.history_entry);
3593
3594 if let Some(autoscroll) = state.effects.scroll {
3595 self.request_autoscroll(autoscroll, cx);
3596 }
3597
3598 let old_cursor_position = &state.old_cursor_position;
3599
3600 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3601
3602 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3603 self.show_signature_help(&ShowSignatureHelp, window, cx);
3604 }
3605 }
3606 }
3607
3608 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3609 where
3610 I: IntoIterator<Item = (Range<S>, T)>,
3611 S: ToOffset,
3612 T: Into<Arc<str>>,
3613 {
3614 if self.read_only(cx) {
3615 return;
3616 }
3617
3618 self.buffer
3619 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3620 }
3621
3622 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3623 where
3624 I: IntoIterator<Item = (Range<S>, T)>,
3625 S: ToOffset,
3626 T: Into<Arc<str>>,
3627 {
3628 if self.read_only(cx) {
3629 return;
3630 }
3631
3632 self.buffer.update(cx, |buffer, cx| {
3633 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3634 });
3635 }
3636
3637 pub fn edit_with_block_indent<I, S, T>(
3638 &mut self,
3639 edits: I,
3640 original_indent_columns: Vec<Option<u32>>,
3641 cx: &mut Context<Self>,
3642 ) where
3643 I: IntoIterator<Item = (Range<S>, T)>,
3644 S: ToOffset,
3645 T: Into<Arc<str>>,
3646 {
3647 if self.read_only(cx) {
3648 return;
3649 }
3650
3651 self.buffer.update(cx, |buffer, cx| {
3652 buffer.edit(
3653 edits,
3654 Some(AutoindentMode::Block {
3655 original_indent_columns,
3656 }),
3657 cx,
3658 )
3659 });
3660 }
3661
3662 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3663 self.hide_context_menu(window, cx);
3664
3665 match phase {
3666 SelectPhase::Begin {
3667 position,
3668 add,
3669 click_count,
3670 } => self.begin_selection(position, add, click_count, window, cx),
3671 SelectPhase::BeginColumnar {
3672 position,
3673 goal_column,
3674 reset,
3675 mode,
3676 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3677 SelectPhase::Extend {
3678 position,
3679 click_count,
3680 } => self.extend_selection(position, click_count, window, cx),
3681 SelectPhase::Update {
3682 position,
3683 goal_column,
3684 scroll_delta,
3685 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3686 SelectPhase::End => self.end_selection(window, cx),
3687 }
3688 }
3689
3690 fn extend_selection(
3691 &mut self,
3692 position: DisplayPoint,
3693 click_count: usize,
3694 window: &mut Window,
3695 cx: &mut Context<Self>,
3696 ) {
3697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3698 let tail = self
3699 .selections
3700 .newest::<MultiBufferOffset>(&display_map)
3701 .tail();
3702 let click_count = click_count.max(match self.selections.select_mode() {
3703 SelectMode::Character => 1,
3704 SelectMode::Word(_) => 2,
3705 SelectMode::Line(_) => 3,
3706 SelectMode::All => 4,
3707 });
3708 self.begin_selection(position, false, click_count, window, cx);
3709
3710 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3711
3712 let current_selection = match self.selections.select_mode() {
3713 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3714 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3715 };
3716
3717 let mut pending_selection = self
3718 .selections
3719 .pending_anchor()
3720 .cloned()
3721 .expect("extend_selection not called with pending selection");
3722
3723 if pending_selection
3724 .start
3725 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3726 == Ordering::Greater
3727 {
3728 pending_selection.start = current_selection.start;
3729 }
3730 if pending_selection
3731 .end
3732 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3733 == Ordering::Less
3734 {
3735 pending_selection.end = current_selection.end;
3736 pending_selection.reversed = true;
3737 }
3738
3739 let mut pending_mode = self.selections.pending_mode().unwrap();
3740 match &mut pending_mode {
3741 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3742 _ => {}
3743 }
3744
3745 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3746 SelectionEffects::scroll(Autoscroll::fit())
3747 } else {
3748 SelectionEffects::no_scroll()
3749 };
3750
3751 self.change_selections(effects, window, cx, |s| {
3752 s.set_pending(pending_selection.clone(), pending_mode);
3753 s.set_is_extending(true);
3754 });
3755 }
3756
3757 fn begin_selection(
3758 &mut self,
3759 position: DisplayPoint,
3760 add: bool,
3761 click_count: usize,
3762 window: &mut Window,
3763 cx: &mut Context<Self>,
3764 ) {
3765 if !self.focus_handle.is_focused(window) {
3766 self.last_focused_descendant = None;
3767 window.focus(&self.focus_handle);
3768 }
3769
3770 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3771 let buffer = display_map.buffer_snapshot();
3772 let position = display_map.clip_point(position, Bias::Left);
3773
3774 let start;
3775 let end;
3776 let mode;
3777 let mut auto_scroll;
3778 match click_count {
3779 1 => {
3780 start = buffer.anchor_before(position.to_point(&display_map));
3781 end = start;
3782 mode = SelectMode::Character;
3783 auto_scroll = true;
3784 }
3785 2 => {
3786 let position = display_map
3787 .clip_point(position, Bias::Left)
3788 .to_offset(&display_map, Bias::Left);
3789 let (range, _) = buffer.surrounding_word(position, None);
3790 start = buffer.anchor_before(range.start);
3791 end = buffer.anchor_before(range.end);
3792 mode = SelectMode::Word(start..end);
3793 auto_scroll = true;
3794 }
3795 3 => {
3796 let position = display_map
3797 .clip_point(position, Bias::Left)
3798 .to_point(&display_map);
3799 let line_start = display_map.prev_line_boundary(position).0;
3800 let next_line_start = buffer.clip_point(
3801 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3802 Bias::Left,
3803 );
3804 start = buffer.anchor_before(line_start);
3805 end = buffer.anchor_before(next_line_start);
3806 mode = SelectMode::Line(start..end);
3807 auto_scroll = true;
3808 }
3809 _ => {
3810 start = buffer.anchor_before(MultiBufferOffset(0));
3811 end = buffer.anchor_before(buffer.len());
3812 mode = SelectMode::All;
3813 auto_scroll = false;
3814 }
3815 }
3816 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3817
3818 let point_to_delete: Option<usize> = {
3819 let selected_points: Vec<Selection<Point>> =
3820 self.selections.disjoint_in_range(start..end, &display_map);
3821
3822 if !add || click_count > 1 {
3823 None
3824 } else if !selected_points.is_empty() {
3825 Some(selected_points[0].id)
3826 } else {
3827 let clicked_point_already_selected =
3828 self.selections.disjoint_anchors().iter().find(|selection| {
3829 selection.start.to_point(buffer) == start.to_point(buffer)
3830 || selection.end.to_point(buffer) == end.to_point(buffer)
3831 });
3832
3833 clicked_point_already_selected.map(|selection| selection.id)
3834 }
3835 };
3836
3837 let selections_count = self.selections.count();
3838 let effects = if auto_scroll {
3839 SelectionEffects::default()
3840 } else {
3841 SelectionEffects::no_scroll()
3842 };
3843
3844 self.change_selections(effects, window, cx, |s| {
3845 if let Some(point_to_delete) = point_to_delete {
3846 s.delete(point_to_delete);
3847
3848 if selections_count == 1 {
3849 s.set_pending_anchor_range(start..end, mode);
3850 }
3851 } else {
3852 if !add {
3853 s.clear_disjoint();
3854 }
3855
3856 s.set_pending_anchor_range(start..end, mode);
3857 }
3858 });
3859 }
3860
3861 fn begin_columnar_selection(
3862 &mut self,
3863 position: DisplayPoint,
3864 goal_column: u32,
3865 reset: bool,
3866 mode: ColumnarMode,
3867 window: &mut Window,
3868 cx: &mut Context<Self>,
3869 ) {
3870 if !self.focus_handle.is_focused(window) {
3871 self.last_focused_descendant = None;
3872 window.focus(&self.focus_handle);
3873 }
3874
3875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3876
3877 if reset {
3878 let pointer_position = display_map
3879 .buffer_snapshot()
3880 .anchor_before(position.to_point(&display_map));
3881
3882 self.change_selections(
3883 SelectionEffects::scroll(Autoscroll::newest()),
3884 window,
3885 cx,
3886 |s| {
3887 s.clear_disjoint();
3888 s.set_pending_anchor_range(
3889 pointer_position..pointer_position,
3890 SelectMode::Character,
3891 );
3892 },
3893 );
3894 };
3895
3896 let tail = self.selections.newest::<Point>(&display_map).tail();
3897 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3898 self.columnar_selection_state = match mode {
3899 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3900 selection_tail: selection_anchor,
3901 display_point: if reset {
3902 if position.column() != goal_column {
3903 Some(DisplayPoint::new(position.row(), goal_column))
3904 } else {
3905 None
3906 }
3907 } else {
3908 None
3909 },
3910 }),
3911 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3912 selection_tail: selection_anchor,
3913 }),
3914 };
3915
3916 if !reset {
3917 self.select_columns(position, goal_column, &display_map, window, cx);
3918 }
3919 }
3920
3921 fn update_selection(
3922 &mut self,
3923 position: DisplayPoint,
3924 goal_column: u32,
3925 scroll_delta: gpui::Point<f32>,
3926 window: &mut Window,
3927 cx: &mut Context<Self>,
3928 ) {
3929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3930
3931 if self.columnar_selection_state.is_some() {
3932 self.select_columns(position, goal_column, &display_map, window, cx);
3933 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3934 let buffer = display_map.buffer_snapshot();
3935 let head;
3936 let tail;
3937 let mode = self.selections.pending_mode().unwrap();
3938 match &mode {
3939 SelectMode::Character => {
3940 head = position.to_point(&display_map);
3941 tail = pending.tail().to_point(buffer);
3942 }
3943 SelectMode::Word(original_range) => {
3944 let offset = display_map
3945 .clip_point(position, Bias::Left)
3946 .to_offset(&display_map, Bias::Left);
3947 let original_range = original_range.to_offset(buffer);
3948
3949 let head_offset = if buffer.is_inside_word(offset, None)
3950 || original_range.contains(&offset)
3951 {
3952 let (word_range, _) = buffer.surrounding_word(offset, None);
3953 if word_range.start < original_range.start {
3954 word_range.start
3955 } else {
3956 word_range.end
3957 }
3958 } else {
3959 offset
3960 };
3961
3962 head = head_offset.to_point(buffer);
3963 if head_offset <= original_range.start {
3964 tail = original_range.end.to_point(buffer);
3965 } else {
3966 tail = original_range.start.to_point(buffer);
3967 }
3968 }
3969 SelectMode::Line(original_range) => {
3970 let original_range = original_range.to_point(display_map.buffer_snapshot());
3971
3972 let position = display_map
3973 .clip_point(position, Bias::Left)
3974 .to_point(&display_map);
3975 let line_start = display_map.prev_line_boundary(position).0;
3976 let next_line_start = buffer.clip_point(
3977 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3978 Bias::Left,
3979 );
3980
3981 if line_start < original_range.start {
3982 head = line_start
3983 } else {
3984 head = next_line_start
3985 }
3986
3987 if head <= original_range.start {
3988 tail = original_range.end;
3989 } else {
3990 tail = original_range.start;
3991 }
3992 }
3993 SelectMode::All => {
3994 return;
3995 }
3996 };
3997
3998 if head < tail {
3999 pending.start = buffer.anchor_before(head);
4000 pending.end = buffer.anchor_before(tail);
4001 pending.reversed = true;
4002 } else {
4003 pending.start = buffer.anchor_before(tail);
4004 pending.end = buffer.anchor_before(head);
4005 pending.reversed = false;
4006 }
4007
4008 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4009 s.set_pending(pending.clone(), mode);
4010 });
4011 } else {
4012 log::error!("update_selection dispatched with no pending selection");
4013 return;
4014 }
4015
4016 self.apply_scroll_delta(scroll_delta, window, cx);
4017 cx.notify();
4018 }
4019
4020 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4021 self.columnar_selection_state.take();
4022 if let Some(pending_mode) = self.selections.pending_mode() {
4023 let selections = self
4024 .selections
4025 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4026 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4027 s.select(selections);
4028 s.clear_pending();
4029 if s.is_extending() {
4030 s.set_is_extending(false);
4031 } else {
4032 s.set_select_mode(pending_mode);
4033 }
4034 });
4035 }
4036 }
4037
4038 fn select_columns(
4039 &mut self,
4040 head: DisplayPoint,
4041 goal_column: u32,
4042 display_map: &DisplaySnapshot,
4043 window: &mut Window,
4044 cx: &mut Context<Self>,
4045 ) {
4046 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4047 return;
4048 };
4049
4050 let tail = match columnar_state {
4051 ColumnarSelectionState::FromMouse {
4052 selection_tail,
4053 display_point,
4054 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4055 ColumnarSelectionState::FromSelection { selection_tail } => {
4056 selection_tail.to_display_point(display_map)
4057 }
4058 };
4059
4060 let start_row = cmp::min(tail.row(), head.row());
4061 let end_row = cmp::max(tail.row(), head.row());
4062 let start_column = cmp::min(tail.column(), goal_column);
4063 let end_column = cmp::max(tail.column(), goal_column);
4064 let reversed = start_column < tail.column();
4065
4066 let selection_ranges = (start_row.0..=end_row.0)
4067 .map(DisplayRow)
4068 .filter_map(|row| {
4069 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4070 || start_column <= display_map.line_len(row))
4071 && !display_map.is_block_line(row)
4072 {
4073 let start = display_map
4074 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4075 .to_point(display_map);
4076 let end = display_map
4077 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4078 .to_point(display_map);
4079 if reversed {
4080 Some(end..start)
4081 } else {
4082 Some(start..end)
4083 }
4084 } else {
4085 None
4086 }
4087 })
4088 .collect::<Vec<_>>();
4089 if selection_ranges.is_empty() {
4090 return;
4091 }
4092
4093 let ranges = match columnar_state {
4094 ColumnarSelectionState::FromMouse { .. } => {
4095 let mut non_empty_ranges = selection_ranges
4096 .iter()
4097 .filter(|selection_range| selection_range.start != selection_range.end)
4098 .peekable();
4099 if non_empty_ranges.peek().is_some() {
4100 non_empty_ranges.cloned().collect()
4101 } else {
4102 selection_ranges
4103 }
4104 }
4105 _ => selection_ranges,
4106 };
4107
4108 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4109 s.select_ranges(ranges);
4110 });
4111 cx.notify();
4112 }
4113
4114 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4115 self.selections
4116 .all_adjusted(snapshot)
4117 .iter()
4118 .any(|selection| !selection.is_empty())
4119 }
4120
4121 pub fn has_pending_nonempty_selection(&self) -> bool {
4122 let pending_nonempty_selection = match self.selections.pending_anchor() {
4123 Some(Selection { start, end, .. }) => start != end,
4124 None => false,
4125 };
4126
4127 pending_nonempty_selection
4128 || (self.columnar_selection_state.is_some()
4129 && self.selections.disjoint_anchors().len() > 1)
4130 }
4131
4132 pub fn has_pending_selection(&self) -> bool {
4133 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4134 }
4135
4136 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4137 self.selection_mark_mode = false;
4138 self.selection_drag_state = SelectionDragState::None;
4139
4140 if self.dismiss_menus_and_popups(true, window, cx) {
4141 cx.notify();
4142 return;
4143 }
4144 if self.clear_expanded_diff_hunks(cx) {
4145 cx.notify();
4146 return;
4147 }
4148 if self.show_git_blame_gutter {
4149 self.show_git_blame_gutter = false;
4150 cx.notify();
4151 return;
4152 }
4153
4154 if self.mode.is_full()
4155 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4156 {
4157 cx.notify();
4158 return;
4159 }
4160
4161 cx.propagate();
4162 }
4163
4164 pub fn dismiss_menus_and_popups(
4165 &mut self,
4166 is_user_requested: bool,
4167 window: &mut Window,
4168 cx: &mut Context<Self>,
4169 ) -> bool {
4170 let mut dismissed = false;
4171
4172 dismissed |= self.take_rename(false, window, cx).is_some();
4173 dismissed |= self.hide_blame_popover(true, cx);
4174 dismissed |= hide_hover(self, cx);
4175 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4176 dismissed |= self.hide_context_menu(window, cx).is_some();
4177 dismissed |= self.mouse_context_menu.take().is_some();
4178 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4179 dismissed |= self.snippet_stack.pop().is_some();
4180
4181 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4182 self.dismiss_diagnostics(cx);
4183 dismissed = true;
4184 }
4185
4186 dismissed
4187 }
4188
4189 fn linked_editing_ranges_for(
4190 &self,
4191 selection: Range<text::Anchor>,
4192 cx: &App,
4193 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4194 if self.linked_edit_ranges.is_empty() {
4195 return None;
4196 }
4197 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4198 selection.end.buffer_id.and_then(|end_buffer_id| {
4199 if selection.start.buffer_id != Some(end_buffer_id) {
4200 return None;
4201 }
4202 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4203 let snapshot = buffer.read(cx).snapshot();
4204 self.linked_edit_ranges
4205 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4206 .map(|ranges| (ranges, snapshot, buffer))
4207 })?;
4208 use text::ToOffset as TO;
4209 // find offset from the start of current range to current cursor position
4210 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4211
4212 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4213 let start_difference = start_offset - start_byte_offset;
4214 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4215 let end_difference = end_offset - start_byte_offset;
4216 // Current range has associated linked ranges.
4217 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4218 for range in linked_ranges.iter() {
4219 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4220 let end_offset = start_offset + end_difference;
4221 let start_offset = start_offset + start_difference;
4222 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4223 continue;
4224 }
4225 if self.selections.disjoint_anchor_ranges().any(|s| {
4226 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4227 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4228 {
4229 return false;
4230 }
4231 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4232 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4233 }) {
4234 continue;
4235 }
4236 let start = buffer_snapshot.anchor_after(start_offset);
4237 let end = buffer_snapshot.anchor_after(end_offset);
4238 linked_edits
4239 .entry(buffer.clone())
4240 .or_default()
4241 .push(start..end);
4242 }
4243 Some(linked_edits)
4244 }
4245
4246 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4247 let text: Arc<str> = text.into();
4248
4249 if self.read_only(cx) {
4250 return;
4251 }
4252
4253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4254
4255 self.unfold_buffers_with_selections(cx);
4256
4257 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4258 let mut bracket_inserted = false;
4259 let mut edits = Vec::new();
4260 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4261 let mut new_selections = Vec::with_capacity(selections.len());
4262 let mut new_autoclose_regions = Vec::new();
4263 let snapshot = self.buffer.read(cx).read(cx);
4264 let mut clear_linked_edit_ranges = false;
4265
4266 for (selection, autoclose_region) in
4267 self.selections_with_autoclose_regions(selections, &snapshot)
4268 {
4269 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4270 // Determine if the inserted text matches the opening or closing
4271 // bracket of any of this language's bracket pairs.
4272 let mut bracket_pair = None;
4273 let mut is_bracket_pair_start = false;
4274 let mut is_bracket_pair_end = false;
4275 if !text.is_empty() {
4276 let mut bracket_pair_matching_end = None;
4277 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4278 // and they are removing the character that triggered IME popup.
4279 for (pair, enabled) in scope.brackets() {
4280 if !pair.close && !pair.surround {
4281 continue;
4282 }
4283
4284 if enabled && pair.start.ends_with(text.as_ref()) {
4285 let prefix_len = pair.start.len() - text.len();
4286 let preceding_text_matches_prefix = prefix_len == 0
4287 || (selection.start.column >= (prefix_len as u32)
4288 && snapshot.contains_str_at(
4289 Point::new(
4290 selection.start.row,
4291 selection.start.column - (prefix_len as u32),
4292 ),
4293 &pair.start[..prefix_len],
4294 ));
4295 if preceding_text_matches_prefix {
4296 bracket_pair = Some(pair.clone());
4297 is_bracket_pair_start = true;
4298 break;
4299 }
4300 }
4301 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4302 {
4303 // take first bracket pair matching end, but don't break in case a later bracket
4304 // pair matches start
4305 bracket_pair_matching_end = Some(pair.clone());
4306 }
4307 }
4308 if let Some(end) = bracket_pair_matching_end
4309 && bracket_pair.is_none()
4310 {
4311 bracket_pair = Some(end);
4312 is_bracket_pair_end = true;
4313 }
4314 }
4315
4316 if let Some(bracket_pair) = bracket_pair {
4317 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4318 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4319 let auto_surround =
4320 self.use_auto_surround && snapshot_settings.use_auto_surround;
4321 if selection.is_empty() {
4322 if is_bracket_pair_start {
4323 // If the inserted text is a suffix of an opening bracket and the
4324 // selection is preceded by the rest of the opening bracket, then
4325 // insert the closing bracket.
4326 let following_text_allows_autoclose = snapshot
4327 .chars_at(selection.start)
4328 .next()
4329 .is_none_or(|c| scope.should_autoclose_before(c));
4330
4331 let preceding_text_allows_autoclose = selection.start.column == 0
4332 || snapshot
4333 .reversed_chars_at(selection.start)
4334 .next()
4335 .is_none_or(|c| {
4336 bracket_pair.start != bracket_pair.end
4337 || !snapshot
4338 .char_classifier_at(selection.start)
4339 .is_word(c)
4340 });
4341
4342 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4343 && bracket_pair.start.len() == 1
4344 {
4345 let target = bracket_pair.start.chars().next().unwrap();
4346 let current_line_count = snapshot
4347 .reversed_chars_at(selection.start)
4348 .take_while(|&c| c != '\n')
4349 .filter(|&c| c == target)
4350 .count();
4351 current_line_count % 2 == 1
4352 } else {
4353 false
4354 };
4355
4356 if autoclose
4357 && bracket_pair.close
4358 && following_text_allows_autoclose
4359 && preceding_text_allows_autoclose
4360 && !is_closing_quote
4361 {
4362 let anchor = snapshot.anchor_before(selection.end);
4363 new_selections.push((selection.map(|_| anchor), text.len()));
4364 new_autoclose_regions.push((
4365 anchor,
4366 text.len(),
4367 selection.id,
4368 bracket_pair.clone(),
4369 ));
4370 edits.push((
4371 selection.range(),
4372 format!("{}{}", text, bracket_pair.end).into(),
4373 ));
4374 bracket_inserted = true;
4375 continue;
4376 }
4377 }
4378
4379 if let Some(region) = autoclose_region {
4380 // If the selection is followed by an auto-inserted closing bracket,
4381 // then don't insert that closing bracket again; just move the selection
4382 // past the closing bracket.
4383 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4384 && text.as_ref() == region.pair.end.as_str()
4385 && snapshot.contains_str_at(region.range.end, text.as_ref());
4386 if should_skip {
4387 let anchor = snapshot.anchor_after(selection.end);
4388 new_selections
4389 .push((selection.map(|_| anchor), region.pair.end.len()));
4390 continue;
4391 }
4392 }
4393
4394 let always_treat_brackets_as_autoclosed = snapshot
4395 .language_settings_at(selection.start, cx)
4396 .always_treat_brackets_as_autoclosed;
4397 if always_treat_brackets_as_autoclosed
4398 && is_bracket_pair_end
4399 && snapshot.contains_str_at(selection.end, text.as_ref())
4400 {
4401 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4402 // and the inserted text is a closing bracket and the selection is followed
4403 // by the closing bracket then move the selection past the closing bracket.
4404 let anchor = snapshot.anchor_after(selection.end);
4405 new_selections.push((selection.map(|_| anchor), text.len()));
4406 continue;
4407 }
4408 }
4409 // If an opening bracket is 1 character long and is typed while
4410 // text is selected, then surround that text with the bracket pair.
4411 else if auto_surround
4412 && bracket_pair.surround
4413 && is_bracket_pair_start
4414 && bracket_pair.start.chars().count() == 1
4415 {
4416 edits.push((selection.start..selection.start, text.clone()));
4417 edits.push((
4418 selection.end..selection.end,
4419 bracket_pair.end.as_str().into(),
4420 ));
4421 bracket_inserted = true;
4422 new_selections.push((
4423 Selection {
4424 id: selection.id,
4425 start: snapshot.anchor_after(selection.start),
4426 end: snapshot.anchor_before(selection.end),
4427 reversed: selection.reversed,
4428 goal: selection.goal,
4429 },
4430 0,
4431 ));
4432 continue;
4433 }
4434 }
4435 }
4436
4437 if self.auto_replace_emoji_shortcode
4438 && selection.is_empty()
4439 && text.as_ref().ends_with(':')
4440 && let Some(possible_emoji_short_code) =
4441 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4442 && !possible_emoji_short_code.is_empty()
4443 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4444 {
4445 let emoji_shortcode_start = Point::new(
4446 selection.start.row,
4447 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4448 );
4449
4450 // Remove shortcode from buffer
4451 edits.push((
4452 emoji_shortcode_start..selection.start,
4453 "".to_string().into(),
4454 ));
4455 new_selections.push((
4456 Selection {
4457 id: selection.id,
4458 start: snapshot.anchor_after(emoji_shortcode_start),
4459 end: snapshot.anchor_before(selection.start),
4460 reversed: selection.reversed,
4461 goal: selection.goal,
4462 },
4463 0,
4464 ));
4465
4466 // Insert emoji
4467 let selection_start_anchor = snapshot.anchor_after(selection.start);
4468 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4469 edits.push((selection.start..selection.end, emoji.to_string().into()));
4470
4471 continue;
4472 }
4473
4474 // If not handling any auto-close operation, then just replace the selected
4475 // text with the given input and move the selection to the end of the
4476 // newly inserted text.
4477 let anchor = snapshot.anchor_after(selection.end);
4478 if !self.linked_edit_ranges.is_empty() {
4479 let start_anchor = snapshot.anchor_before(selection.start);
4480
4481 let is_word_char = text.chars().next().is_none_or(|char| {
4482 let classifier = snapshot
4483 .char_classifier_at(start_anchor.to_offset(&snapshot))
4484 .scope_context(Some(CharScopeContext::LinkedEdit));
4485 classifier.is_word(char)
4486 });
4487
4488 if is_word_char {
4489 if let Some(ranges) = self
4490 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4491 {
4492 for (buffer, edits) in ranges {
4493 linked_edits
4494 .entry(buffer.clone())
4495 .or_default()
4496 .extend(edits.into_iter().map(|range| (range, text.clone())));
4497 }
4498 }
4499 } else {
4500 clear_linked_edit_ranges = true;
4501 }
4502 }
4503
4504 new_selections.push((selection.map(|_| anchor), 0));
4505 edits.push((selection.start..selection.end, text.clone()));
4506 }
4507
4508 drop(snapshot);
4509
4510 self.transact(window, cx, |this, window, cx| {
4511 if clear_linked_edit_ranges {
4512 this.linked_edit_ranges.clear();
4513 }
4514 let initial_buffer_versions =
4515 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4516
4517 this.buffer.update(cx, |buffer, cx| {
4518 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4519 });
4520 for (buffer, edits) in linked_edits {
4521 buffer.update(cx, |buffer, cx| {
4522 let snapshot = buffer.snapshot();
4523 let edits = edits
4524 .into_iter()
4525 .map(|(range, text)| {
4526 use text::ToPoint as TP;
4527 let end_point = TP::to_point(&range.end, &snapshot);
4528 let start_point = TP::to_point(&range.start, &snapshot);
4529 (start_point..end_point, text)
4530 })
4531 .sorted_by_key(|(range, _)| range.start);
4532 buffer.edit(edits, None, cx);
4533 })
4534 }
4535 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4536 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4537 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4538 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4539 new_anchor_selections,
4540 &map,
4541 )
4542 .zip(new_selection_deltas)
4543 .map(|(selection, delta)| Selection {
4544 id: selection.id,
4545 start: selection.start + delta,
4546 end: selection.end + delta,
4547 reversed: selection.reversed,
4548 goal: SelectionGoal::None,
4549 })
4550 .collect::<Vec<_>>();
4551
4552 let mut i = 0;
4553 for (position, delta, selection_id, pair) in new_autoclose_regions {
4554 let position = position.to_offset(map.buffer_snapshot()) + delta;
4555 let start = map.buffer_snapshot().anchor_before(position);
4556 let end = map.buffer_snapshot().anchor_after(position);
4557 while let Some(existing_state) = this.autoclose_regions.get(i) {
4558 match existing_state
4559 .range
4560 .start
4561 .cmp(&start, map.buffer_snapshot())
4562 {
4563 Ordering::Less => i += 1,
4564 Ordering::Greater => break,
4565 Ordering::Equal => {
4566 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4567 Ordering::Less => i += 1,
4568 Ordering::Equal => break,
4569 Ordering::Greater => break,
4570 }
4571 }
4572 }
4573 }
4574 this.autoclose_regions.insert(
4575 i,
4576 AutocloseRegion {
4577 selection_id,
4578 range: start..end,
4579 pair,
4580 },
4581 );
4582 }
4583
4584 let had_active_edit_prediction = this.has_active_edit_prediction();
4585 this.change_selections(
4586 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4587 window,
4588 cx,
4589 |s| s.select(new_selections),
4590 );
4591
4592 if !bracket_inserted
4593 && let Some(on_type_format_task) =
4594 this.trigger_on_type_formatting(text.to_string(), window, cx)
4595 {
4596 on_type_format_task.detach_and_log_err(cx);
4597 }
4598
4599 let editor_settings = EditorSettings::get_global(cx);
4600 if bracket_inserted
4601 && (editor_settings.auto_signature_help
4602 || editor_settings.show_signature_help_after_edits)
4603 {
4604 this.show_signature_help(&ShowSignatureHelp, window, cx);
4605 }
4606
4607 let trigger_in_words =
4608 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4609 if this.hard_wrap.is_some() {
4610 let latest: Range<Point> = this.selections.newest(&map).range();
4611 if latest.is_empty()
4612 && this
4613 .buffer()
4614 .read(cx)
4615 .snapshot(cx)
4616 .line_len(MultiBufferRow(latest.start.row))
4617 == latest.start.column
4618 {
4619 this.rewrap_impl(
4620 RewrapOptions {
4621 override_language_settings: true,
4622 preserve_existing_whitespace: true,
4623 },
4624 cx,
4625 )
4626 }
4627 }
4628 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4629 refresh_linked_ranges(this, window, cx);
4630 this.refresh_edit_prediction(true, false, window, cx);
4631 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4632 });
4633 }
4634
4635 fn find_possible_emoji_shortcode_at_position(
4636 snapshot: &MultiBufferSnapshot,
4637 position: Point,
4638 ) -> Option<String> {
4639 let mut chars = Vec::new();
4640 let mut found_colon = false;
4641 for char in snapshot.reversed_chars_at(position).take(100) {
4642 // Found a possible emoji shortcode in the middle of the buffer
4643 if found_colon {
4644 if char.is_whitespace() {
4645 chars.reverse();
4646 return Some(chars.iter().collect());
4647 }
4648 // If the previous character is not a whitespace, we are in the middle of a word
4649 // and we only want to complete the shortcode if the word is made up of other emojis
4650 let mut containing_word = String::new();
4651 for ch in snapshot
4652 .reversed_chars_at(position)
4653 .skip(chars.len() + 1)
4654 .take(100)
4655 {
4656 if ch.is_whitespace() {
4657 break;
4658 }
4659 containing_word.push(ch);
4660 }
4661 let containing_word = containing_word.chars().rev().collect::<String>();
4662 if util::word_consists_of_emojis(containing_word.as_str()) {
4663 chars.reverse();
4664 return Some(chars.iter().collect());
4665 }
4666 }
4667
4668 if char.is_whitespace() || !char.is_ascii() {
4669 return None;
4670 }
4671 if char == ':' {
4672 found_colon = true;
4673 } else {
4674 chars.push(char);
4675 }
4676 }
4677 // Found a possible emoji shortcode at the beginning of the buffer
4678 chars.reverse();
4679 Some(chars.iter().collect())
4680 }
4681
4682 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4684 self.transact(window, cx, |this, window, cx| {
4685 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4686 let selections = this
4687 .selections
4688 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4689 let multi_buffer = this.buffer.read(cx);
4690 let buffer = multi_buffer.snapshot(cx);
4691 selections
4692 .iter()
4693 .map(|selection| {
4694 let start_point = selection.start.to_point(&buffer);
4695 let mut existing_indent =
4696 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4697 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4698 let start = selection.start;
4699 let end = selection.end;
4700 let selection_is_empty = start == end;
4701 let language_scope = buffer.language_scope_at(start);
4702 let (
4703 comment_delimiter,
4704 doc_delimiter,
4705 insert_extra_newline,
4706 indent_on_newline,
4707 indent_on_extra_newline,
4708 ) = if let Some(language) = &language_scope {
4709 let mut insert_extra_newline =
4710 insert_extra_newline_brackets(&buffer, start..end, language)
4711 || insert_extra_newline_tree_sitter(&buffer, start..end);
4712
4713 // Comment extension on newline is allowed only for cursor selections
4714 let comment_delimiter = maybe!({
4715 if !selection_is_empty {
4716 return None;
4717 }
4718
4719 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4720 return None;
4721 }
4722
4723 let delimiters = language.line_comment_prefixes();
4724 let max_len_of_delimiter =
4725 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4726 let (snapshot, range) =
4727 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4728
4729 let num_of_whitespaces = snapshot
4730 .chars_for_range(range.clone())
4731 .take_while(|c| c.is_whitespace())
4732 .count();
4733 let comment_candidate = snapshot
4734 .chars_for_range(range.clone())
4735 .skip(num_of_whitespaces)
4736 .take(max_len_of_delimiter)
4737 .collect::<String>();
4738 let (delimiter, trimmed_len) = delimiters
4739 .iter()
4740 .filter_map(|delimiter| {
4741 let prefix = delimiter.trim_end();
4742 if comment_candidate.starts_with(prefix) {
4743 Some((delimiter, prefix.len()))
4744 } else {
4745 None
4746 }
4747 })
4748 .max_by_key(|(_, len)| *len)?;
4749
4750 if let Some(BlockCommentConfig {
4751 start: block_start, ..
4752 }) = language.block_comment()
4753 {
4754 let block_start_trimmed = block_start.trim_end();
4755 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4756 let line_content = snapshot
4757 .chars_for_range(range)
4758 .skip(num_of_whitespaces)
4759 .take(block_start_trimmed.len())
4760 .collect::<String>();
4761
4762 if line_content.starts_with(block_start_trimmed) {
4763 return None;
4764 }
4765 }
4766 }
4767
4768 let cursor_is_placed_after_comment_marker =
4769 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4770 if cursor_is_placed_after_comment_marker {
4771 Some(delimiter.clone())
4772 } else {
4773 None
4774 }
4775 });
4776
4777 let mut indent_on_newline = IndentSize::spaces(0);
4778 let mut indent_on_extra_newline = IndentSize::spaces(0);
4779
4780 let doc_delimiter = maybe!({
4781 if !selection_is_empty {
4782 return None;
4783 }
4784
4785 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4786 return None;
4787 }
4788
4789 let BlockCommentConfig {
4790 start: start_tag,
4791 end: end_tag,
4792 prefix: delimiter,
4793 tab_size: len,
4794 } = language.documentation_comment()?;
4795 let is_within_block_comment = buffer
4796 .language_scope_at(start_point)
4797 .is_some_and(|scope| scope.override_name() == Some("comment"));
4798 if !is_within_block_comment {
4799 return None;
4800 }
4801
4802 let (snapshot, range) =
4803 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4804
4805 let num_of_whitespaces = snapshot
4806 .chars_for_range(range.clone())
4807 .take_while(|c| c.is_whitespace())
4808 .count();
4809
4810 // 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.
4811 let column = start_point.column;
4812 let cursor_is_after_start_tag = {
4813 let start_tag_len = start_tag.len();
4814 let start_tag_line = snapshot
4815 .chars_for_range(range.clone())
4816 .skip(num_of_whitespaces)
4817 .take(start_tag_len)
4818 .collect::<String>();
4819 if start_tag_line.starts_with(start_tag.as_ref()) {
4820 num_of_whitespaces + start_tag_len <= column as usize
4821 } else {
4822 false
4823 }
4824 };
4825
4826 let cursor_is_after_delimiter = {
4827 let delimiter_trim = delimiter.trim_end();
4828 let delimiter_line = snapshot
4829 .chars_for_range(range.clone())
4830 .skip(num_of_whitespaces)
4831 .take(delimiter_trim.len())
4832 .collect::<String>();
4833 if delimiter_line.starts_with(delimiter_trim) {
4834 num_of_whitespaces + delimiter_trim.len() <= column as usize
4835 } else {
4836 false
4837 }
4838 };
4839
4840 let cursor_is_before_end_tag_if_exists = {
4841 let mut char_position = 0u32;
4842 let mut end_tag_offset = None;
4843
4844 'outer: for chunk in snapshot.text_for_range(range) {
4845 if let Some(byte_pos) = chunk.find(&**end_tag) {
4846 let chars_before_match =
4847 chunk[..byte_pos].chars().count() as u32;
4848 end_tag_offset =
4849 Some(char_position + chars_before_match);
4850 break 'outer;
4851 }
4852 char_position += chunk.chars().count() as u32;
4853 }
4854
4855 if let Some(end_tag_offset) = end_tag_offset {
4856 let cursor_is_before_end_tag = column <= end_tag_offset;
4857 if cursor_is_after_start_tag {
4858 if cursor_is_before_end_tag {
4859 insert_extra_newline = true;
4860 }
4861 let cursor_is_at_start_of_end_tag =
4862 column == end_tag_offset;
4863 if cursor_is_at_start_of_end_tag {
4864 indent_on_extra_newline.len = *len;
4865 }
4866 }
4867 cursor_is_before_end_tag
4868 } else {
4869 true
4870 }
4871 };
4872
4873 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4874 && cursor_is_before_end_tag_if_exists
4875 {
4876 if cursor_is_after_start_tag {
4877 indent_on_newline.len = *len;
4878 }
4879 Some(delimiter.clone())
4880 } else {
4881 None
4882 }
4883 });
4884
4885 (
4886 comment_delimiter,
4887 doc_delimiter,
4888 insert_extra_newline,
4889 indent_on_newline,
4890 indent_on_extra_newline,
4891 )
4892 } else {
4893 (
4894 None,
4895 None,
4896 false,
4897 IndentSize::default(),
4898 IndentSize::default(),
4899 )
4900 };
4901
4902 let prevent_auto_indent = doc_delimiter.is_some();
4903 let delimiter = comment_delimiter.or(doc_delimiter);
4904
4905 let capacity_for_delimiter =
4906 delimiter.as_deref().map(str::len).unwrap_or_default();
4907 let mut new_text = String::with_capacity(
4908 1 + capacity_for_delimiter
4909 + existing_indent.len as usize
4910 + indent_on_newline.len as usize
4911 + indent_on_extra_newline.len as usize,
4912 );
4913 new_text.push('\n');
4914 new_text.extend(existing_indent.chars());
4915 new_text.extend(indent_on_newline.chars());
4916
4917 if let Some(delimiter) = &delimiter {
4918 new_text.push_str(delimiter);
4919 }
4920
4921 if insert_extra_newline {
4922 new_text.push('\n');
4923 new_text.extend(existing_indent.chars());
4924 new_text.extend(indent_on_extra_newline.chars());
4925 }
4926
4927 let anchor = buffer.anchor_after(end);
4928 let new_selection = selection.map(|_| anchor);
4929 (
4930 ((start..end, new_text), prevent_auto_indent),
4931 (insert_extra_newline, new_selection),
4932 )
4933 })
4934 .unzip()
4935 };
4936
4937 let mut auto_indent_edits = Vec::new();
4938 let mut edits = Vec::new();
4939 for (edit, prevent_auto_indent) in edits_with_flags {
4940 if prevent_auto_indent {
4941 edits.push(edit);
4942 } else {
4943 auto_indent_edits.push(edit);
4944 }
4945 }
4946 if !edits.is_empty() {
4947 this.edit(edits, cx);
4948 }
4949 if !auto_indent_edits.is_empty() {
4950 this.edit_with_autoindent(auto_indent_edits, cx);
4951 }
4952
4953 let buffer = this.buffer.read(cx).snapshot(cx);
4954 let new_selections = selection_info
4955 .into_iter()
4956 .map(|(extra_newline_inserted, new_selection)| {
4957 let mut cursor = new_selection.end.to_point(&buffer);
4958 if extra_newline_inserted {
4959 cursor.row -= 1;
4960 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4961 }
4962 new_selection.map(|_| cursor)
4963 })
4964 .collect();
4965
4966 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4967 this.refresh_edit_prediction(true, false, window, cx);
4968 });
4969 }
4970
4971 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4973
4974 let buffer = self.buffer.read(cx);
4975 let snapshot = buffer.snapshot(cx);
4976
4977 let mut edits = Vec::new();
4978 let mut rows = Vec::new();
4979
4980 for (rows_inserted, selection) in self
4981 .selections
4982 .all_adjusted(&self.display_snapshot(cx))
4983 .into_iter()
4984 .enumerate()
4985 {
4986 let cursor = selection.head();
4987 let row = cursor.row;
4988
4989 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4990
4991 let newline = "\n".to_string();
4992 edits.push((start_of_line..start_of_line, newline));
4993
4994 rows.push(row + rows_inserted as u32);
4995 }
4996
4997 self.transact(window, cx, |editor, window, cx| {
4998 editor.edit(edits, cx);
4999
5000 editor.change_selections(Default::default(), window, cx, |s| {
5001 let mut index = 0;
5002 s.move_cursors_with(|map, _, _| {
5003 let row = rows[index];
5004 index += 1;
5005
5006 let point = Point::new(row, 0);
5007 let boundary = map.next_line_boundary(point).1;
5008 let clipped = map.clip_point(boundary, Bias::Left);
5009
5010 (clipped, SelectionGoal::None)
5011 });
5012 });
5013
5014 let mut indent_edits = Vec::new();
5015 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5016 for row in rows {
5017 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5018 for (row, indent) in indents {
5019 if indent.len == 0 {
5020 continue;
5021 }
5022
5023 let text = match indent.kind {
5024 IndentKind::Space => " ".repeat(indent.len as usize),
5025 IndentKind::Tab => "\t".repeat(indent.len as usize),
5026 };
5027 let point = Point::new(row.0, 0);
5028 indent_edits.push((point..point, text));
5029 }
5030 }
5031 editor.edit(indent_edits, cx);
5032 });
5033 }
5034
5035 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5037
5038 let buffer = self.buffer.read(cx);
5039 let snapshot = buffer.snapshot(cx);
5040
5041 let mut edits = Vec::new();
5042 let mut rows = Vec::new();
5043 let mut rows_inserted = 0;
5044
5045 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5046 let cursor = selection.head();
5047 let row = cursor.row;
5048
5049 let point = Point::new(row + 1, 0);
5050 let start_of_line = snapshot.clip_point(point, Bias::Left);
5051
5052 let newline = "\n".to_string();
5053 edits.push((start_of_line..start_of_line, newline));
5054
5055 rows_inserted += 1;
5056 rows.push(row + rows_inserted);
5057 }
5058
5059 self.transact(window, cx, |editor, window, cx| {
5060 editor.edit(edits, cx);
5061
5062 editor.change_selections(Default::default(), window, cx, |s| {
5063 let mut index = 0;
5064 s.move_cursors_with(|map, _, _| {
5065 let row = rows[index];
5066 index += 1;
5067
5068 let point = Point::new(row, 0);
5069 let boundary = map.next_line_boundary(point).1;
5070 let clipped = map.clip_point(boundary, Bias::Left);
5071
5072 (clipped, SelectionGoal::None)
5073 });
5074 });
5075
5076 let mut indent_edits = Vec::new();
5077 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5078 for row in rows {
5079 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5080 for (row, indent) in indents {
5081 if indent.len == 0 {
5082 continue;
5083 }
5084
5085 let text = match indent.kind {
5086 IndentKind::Space => " ".repeat(indent.len as usize),
5087 IndentKind::Tab => "\t".repeat(indent.len as usize),
5088 };
5089 let point = Point::new(row.0, 0);
5090 indent_edits.push((point..point, text));
5091 }
5092 }
5093 editor.edit(indent_edits, cx);
5094 });
5095 }
5096
5097 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5098 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5099 original_indent_columns: Vec::new(),
5100 });
5101 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5102 }
5103
5104 fn insert_with_autoindent_mode(
5105 &mut self,
5106 text: &str,
5107 autoindent_mode: Option<AutoindentMode>,
5108 window: &mut Window,
5109 cx: &mut Context<Self>,
5110 ) {
5111 if self.read_only(cx) {
5112 return;
5113 }
5114
5115 let text: Arc<str> = text.into();
5116 self.transact(window, cx, |this, window, cx| {
5117 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5118 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5119 let anchors = {
5120 let snapshot = buffer.read(cx);
5121 old_selections
5122 .iter()
5123 .map(|s| {
5124 let anchor = snapshot.anchor_after(s.head());
5125 s.map(|_| anchor)
5126 })
5127 .collect::<Vec<_>>()
5128 };
5129 buffer.edit(
5130 old_selections
5131 .iter()
5132 .map(|s| (s.start..s.end, text.clone())),
5133 autoindent_mode,
5134 cx,
5135 );
5136 anchors
5137 });
5138
5139 this.change_selections(Default::default(), window, cx, |s| {
5140 s.select_anchors(selection_anchors);
5141 });
5142
5143 cx.notify();
5144 });
5145 }
5146
5147 fn trigger_completion_on_input(
5148 &mut self,
5149 text: &str,
5150 trigger_in_words: bool,
5151 window: &mut Window,
5152 cx: &mut Context<Self>,
5153 ) {
5154 let completions_source = self
5155 .context_menu
5156 .borrow()
5157 .as_ref()
5158 .and_then(|menu| match menu {
5159 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5160 CodeContextMenu::CodeActions(_) => None,
5161 });
5162
5163 match completions_source {
5164 Some(CompletionsMenuSource::Words { .. }) => {
5165 self.open_or_update_completions_menu(
5166 Some(CompletionsMenuSource::Words {
5167 ignore_threshold: false,
5168 }),
5169 None,
5170 trigger_in_words,
5171 window,
5172 cx,
5173 );
5174 }
5175 _ => self.open_or_update_completions_menu(
5176 None,
5177 Some(text.to_owned()).filter(|x| !x.is_empty()),
5178 true,
5179 window,
5180 cx,
5181 ),
5182 }
5183 }
5184
5185 /// If any empty selections is touching the start of its innermost containing autoclose
5186 /// region, expand it to select the brackets.
5187 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5188 let selections = self
5189 .selections
5190 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5191 let buffer = self.buffer.read(cx).read(cx);
5192 let new_selections = self
5193 .selections_with_autoclose_regions(selections, &buffer)
5194 .map(|(mut selection, region)| {
5195 if !selection.is_empty() {
5196 return selection;
5197 }
5198
5199 if let Some(region) = region {
5200 let mut range = region.range.to_offset(&buffer);
5201 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5202 range.start -= region.pair.start.len();
5203 if buffer.contains_str_at(range.start, ®ion.pair.start)
5204 && buffer.contains_str_at(range.end, ®ion.pair.end)
5205 {
5206 range.end += region.pair.end.len();
5207 selection.start = range.start;
5208 selection.end = range.end;
5209
5210 return selection;
5211 }
5212 }
5213 }
5214
5215 let always_treat_brackets_as_autoclosed = buffer
5216 .language_settings_at(selection.start, cx)
5217 .always_treat_brackets_as_autoclosed;
5218
5219 if !always_treat_brackets_as_autoclosed {
5220 return selection;
5221 }
5222
5223 if let Some(scope) = buffer.language_scope_at(selection.start) {
5224 for (pair, enabled) in scope.brackets() {
5225 if !enabled || !pair.close {
5226 continue;
5227 }
5228
5229 if buffer.contains_str_at(selection.start, &pair.end) {
5230 let pair_start_len = pair.start.len();
5231 if buffer.contains_str_at(
5232 selection.start.saturating_sub_usize(pair_start_len),
5233 &pair.start,
5234 ) {
5235 selection.start -= pair_start_len;
5236 selection.end += pair.end.len();
5237
5238 return selection;
5239 }
5240 }
5241 }
5242 }
5243
5244 selection
5245 })
5246 .collect();
5247
5248 drop(buffer);
5249 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5250 selections.select(new_selections)
5251 });
5252 }
5253
5254 /// Iterate the given selections, and for each one, find the smallest surrounding
5255 /// autoclose region. This uses the ordering of the selections and the autoclose
5256 /// regions to avoid repeated comparisons.
5257 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5258 &'a self,
5259 selections: impl IntoIterator<Item = Selection<D>>,
5260 buffer: &'a MultiBufferSnapshot,
5261 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5262 let mut i = 0;
5263 let mut regions = self.autoclose_regions.as_slice();
5264 selections.into_iter().map(move |selection| {
5265 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5266
5267 let mut enclosing = None;
5268 while let Some(pair_state) = regions.get(i) {
5269 if pair_state.range.end.to_offset(buffer) < range.start {
5270 regions = ®ions[i + 1..];
5271 i = 0;
5272 } else if pair_state.range.start.to_offset(buffer) > range.end {
5273 break;
5274 } else {
5275 if pair_state.selection_id == selection.id {
5276 enclosing = Some(pair_state);
5277 }
5278 i += 1;
5279 }
5280 }
5281
5282 (selection, enclosing)
5283 })
5284 }
5285
5286 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5287 fn invalidate_autoclose_regions(
5288 &mut self,
5289 mut selections: &[Selection<Anchor>],
5290 buffer: &MultiBufferSnapshot,
5291 ) {
5292 self.autoclose_regions.retain(|state| {
5293 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5294 return false;
5295 }
5296
5297 let mut i = 0;
5298 while let Some(selection) = selections.get(i) {
5299 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5300 selections = &selections[1..];
5301 continue;
5302 }
5303 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5304 break;
5305 }
5306 if selection.id == state.selection_id {
5307 return true;
5308 } else {
5309 i += 1;
5310 }
5311 }
5312 false
5313 });
5314 }
5315
5316 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5317 let offset = position.to_offset(buffer);
5318 let (word_range, kind) =
5319 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5320 if offset > word_range.start && kind == Some(CharKind::Word) {
5321 Some(
5322 buffer
5323 .text_for_range(word_range.start..offset)
5324 .collect::<String>(),
5325 )
5326 } else {
5327 None
5328 }
5329 }
5330
5331 pub fn visible_excerpts(
5332 &self,
5333 lsp_related_only: bool,
5334 cx: &mut Context<Editor>,
5335 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5336 let project = self.project().cloned();
5337 let multi_buffer = self.buffer().read(cx);
5338 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5339 let multi_buffer_visible_start = self
5340 .scroll_manager
5341 .anchor()
5342 .anchor
5343 .to_point(&multi_buffer_snapshot);
5344 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5345 multi_buffer_visible_start
5346 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5347 Bias::Left,
5348 );
5349 multi_buffer_snapshot
5350 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5351 .into_iter()
5352 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5353 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5354 if !lsp_related_only {
5355 return Some((
5356 excerpt_id,
5357 (
5358 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5359 buffer.version().clone(),
5360 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5361 ),
5362 ));
5363 }
5364
5365 let project = project.as_ref()?.read(cx);
5366 let buffer_file = project::File::from_dyn(buffer.file())?;
5367 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5368 let worktree_entry = buffer_worktree
5369 .read(cx)
5370 .entry_for_id(buffer_file.project_entry_id()?)?;
5371 if worktree_entry.is_ignored {
5372 None
5373 } else {
5374 Some((
5375 excerpt_id,
5376 (
5377 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5378 buffer.version().clone(),
5379 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5380 ),
5381 ))
5382 }
5383 })
5384 .collect()
5385 }
5386
5387 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5388 TextLayoutDetails {
5389 text_system: window.text_system().clone(),
5390 editor_style: self.style.clone().unwrap(),
5391 rem_size: window.rem_size(),
5392 scroll_anchor: self.scroll_manager.anchor(),
5393 visible_rows: self.visible_line_count(),
5394 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5395 }
5396 }
5397
5398 fn trigger_on_type_formatting(
5399 &self,
5400 input: String,
5401 window: &mut Window,
5402 cx: &mut Context<Self>,
5403 ) -> Option<Task<Result<()>>> {
5404 if input.len() != 1 {
5405 return None;
5406 }
5407
5408 let project = self.project()?;
5409 let position = self.selections.newest_anchor().head();
5410 let (buffer, buffer_position) = self
5411 .buffer
5412 .read(cx)
5413 .text_anchor_for_position(position, cx)?;
5414
5415 let settings = language_settings::language_settings(
5416 buffer
5417 .read(cx)
5418 .language_at(buffer_position)
5419 .map(|l| l.name()),
5420 buffer.read(cx).file(),
5421 cx,
5422 );
5423 if !settings.use_on_type_format {
5424 return None;
5425 }
5426
5427 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5428 // hence we do LSP request & edit on host side only — add formats to host's history.
5429 let push_to_lsp_host_history = true;
5430 // If this is not the host, append its history with new edits.
5431 let push_to_client_history = project.read(cx).is_via_collab();
5432
5433 let on_type_formatting = project.update(cx, |project, cx| {
5434 project.on_type_format(
5435 buffer.clone(),
5436 buffer_position,
5437 input,
5438 push_to_lsp_host_history,
5439 cx,
5440 )
5441 });
5442 Some(cx.spawn_in(window, async move |editor, cx| {
5443 if let Some(transaction) = on_type_formatting.await? {
5444 if push_to_client_history {
5445 buffer
5446 .update(cx, |buffer, _| {
5447 buffer.push_transaction(transaction, Instant::now());
5448 buffer.finalize_last_transaction();
5449 })
5450 .ok();
5451 }
5452 editor.update(cx, |editor, cx| {
5453 editor.refresh_document_highlights(cx);
5454 })?;
5455 }
5456 Ok(())
5457 }))
5458 }
5459
5460 pub fn show_word_completions(
5461 &mut self,
5462 _: &ShowWordCompletions,
5463 window: &mut Window,
5464 cx: &mut Context<Self>,
5465 ) {
5466 self.open_or_update_completions_menu(
5467 Some(CompletionsMenuSource::Words {
5468 ignore_threshold: true,
5469 }),
5470 None,
5471 false,
5472 window,
5473 cx,
5474 );
5475 }
5476
5477 pub fn show_completions(
5478 &mut self,
5479 _: &ShowCompletions,
5480 window: &mut Window,
5481 cx: &mut Context<Self>,
5482 ) {
5483 self.open_or_update_completions_menu(None, None, false, window, cx);
5484 }
5485
5486 fn open_or_update_completions_menu(
5487 &mut self,
5488 requested_source: Option<CompletionsMenuSource>,
5489 trigger: Option<String>,
5490 trigger_in_words: bool,
5491 window: &mut Window,
5492 cx: &mut Context<Self>,
5493 ) {
5494 if self.pending_rename.is_some() {
5495 return;
5496 }
5497
5498 let completions_source = self
5499 .context_menu
5500 .borrow()
5501 .as_ref()
5502 .and_then(|menu| match menu {
5503 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5504 CodeContextMenu::CodeActions(_) => None,
5505 });
5506
5507 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5508
5509 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5510 // inserted and selected. To handle that case, the start of the selection is used so that
5511 // the menu starts with all choices.
5512 let position = self
5513 .selections
5514 .newest_anchor()
5515 .start
5516 .bias_right(&multibuffer_snapshot);
5517 if position.diff_base_anchor.is_some() {
5518 return;
5519 }
5520 let buffer_position = multibuffer_snapshot.anchor_before(position);
5521 let Some(buffer) = buffer_position
5522 .text_anchor
5523 .buffer_id
5524 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5525 else {
5526 return;
5527 };
5528 let buffer_snapshot = buffer.read(cx).snapshot();
5529
5530 let menu_is_open = matches!(
5531 self.context_menu.borrow().as_ref(),
5532 Some(CodeContextMenu::Completions(_))
5533 );
5534
5535 let language = buffer_snapshot
5536 .language_at(buffer_position.text_anchor)
5537 .map(|language| language.name());
5538
5539 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5540 let completion_settings = language_settings.completions.clone();
5541
5542 let show_completions_on_input = self
5543 .show_completions_on_input_override
5544 .unwrap_or(language_settings.show_completions_on_input);
5545 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5546 return;
5547 }
5548
5549 let query: Option<Arc<String>> =
5550 Self::completion_query(&multibuffer_snapshot, buffer_position)
5551 .map(|query| query.into());
5552
5553 drop(multibuffer_snapshot);
5554
5555 // Hide the current completions menu when query is empty. Without this, cached
5556 // completions from before the trigger char may be reused (#32774).
5557 if query.is_none() && menu_is_open {
5558 self.hide_context_menu(window, cx);
5559 }
5560
5561 let mut ignore_word_threshold = false;
5562 let provider = match requested_source {
5563 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5564 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5565 ignore_word_threshold = ignore_threshold;
5566 None
5567 }
5568 Some(CompletionsMenuSource::SnippetChoices)
5569 | Some(CompletionsMenuSource::SnippetsOnly) => {
5570 log::error!("bug: SnippetChoices requested_source is not handled");
5571 None
5572 }
5573 };
5574
5575 let sort_completions = provider
5576 .as_ref()
5577 .is_some_and(|provider| provider.sort_completions());
5578
5579 let filter_completions = provider
5580 .as_ref()
5581 .is_none_or(|provider| provider.filter_completions());
5582
5583 let was_snippets_only = matches!(
5584 completions_source,
5585 Some(CompletionsMenuSource::SnippetsOnly)
5586 );
5587
5588 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5589 if filter_completions {
5590 menu.filter(
5591 query.clone().unwrap_or_default(),
5592 buffer_position.text_anchor,
5593 &buffer,
5594 provider.clone(),
5595 window,
5596 cx,
5597 );
5598 }
5599 // When `is_incomplete` is false, no need to re-query completions when the current query
5600 // is a suffix of the initial query.
5601 let was_complete = !menu.is_incomplete;
5602 if was_complete && !was_snippets_only {
5603 // If the new query is a suffix of the old query (typing more characters) and
5604 // the previous result was complete, the existing completions can be filtered.
5605 //
5606 // Note that snippet completions are always complete.
5607 let query_matches = match (&menu.initial_query, &query) {
5608 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5609 (None, _) => true,
5610 _ => false,
5611 };
5612 if query_matches {
5613 let position_matches = if menu.initial_position == position {
5614 true
5615 } else {
5616 let snapshot = self.buffer.read(cx).read(cx);
5617 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5618 };
5619 if position_matches {
5620 return;
5621 }
5622 }
5623 }
5624 };
5625
5626 let Anchor {
5627 excerpt_id: buffer_excerpt_id,
5628 text_anchor: buffer_position,
5629 ..
5630 } = buffer_position;
5631
5632 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5633 buffer_snapshot.surrounding_word(buffer_position, None)
5634 {
5635 let word_to_exclude = buffer_snapshot
5636 .text_for_range(word_range.clone())
5637 .collect::<String>();
5638 (
5639 buffer_snapshot.anchor_before(word_range.start)
5640 ..buffer_snapshot.anchor_after(buffer_position),
5641 Some(word_to_exclude),
5642 )
5643 } else {
5644 (buffer_position..buffer_position, None)
5645 };
5646
5647 let show_completion_documentation = buffer_snapshot
5648 .settings_at(buffer_position, cx)
5649 .show_completion_documentation;
5650
5651 // The document can be large, so stay in reasonable bounds when searching for words,
5652 // otherwise completion pop-up might be slow to appear.
5653 const WORD_LOOKUP_ROWS: u32 = 5_000;
5654 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5655 let min_word_search = buffer_snapshot.clip_point(
5656 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5657 Bias::Left,
5658 );
5659 let max_word_search = buffer_snapshot.clip_point(
5660 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5661 Bias::Right,
5662 );
5663 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5664 ..buffer_snapshot.point_to_offset(max_word_search);
5665
5666 let skip_digits = query
5667 .as_ref()
5668 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5669
5670 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5671 trigger.as_ref().is_none_or(|trigger| {
5672 provider.is_completion_trigger(
5673 &buffer,
5674 position.text_anchor,
5675 trigger,
5676 trigger_in_words,
5677 cx,
5678 )
5679 })
5680 });
5681
5682 let provider_responses = if let Some(provider) = &provider
5683 && load_provider_completions
5684 {
5685 let trigger_character =
5686 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5687 let completion_context = CompletionContext {
5688 trigger_kind: match &trigger_character {
5689 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5690 None => CompletionTriggerKind::INVOKED,
5691 },
5692 trigger_character,
5693 };
5694
5695 provider.completions(
5696 buffer_excerpt_id,
5697 &buffer,
5698 buffer_position,
5699 completion_context,
5700 window,
5701 cx,
5702 )
5703 } else {
5704 Task::ready(Ok(Vec::new()))
5705 };
5706
5707 let load_word_completions = if !self.word_completions_enabled {
5708 false
5709 } else if requested_source
5710 == Some(CompletionsMenuSource::Words {
5711 ignore_threshold: true,
5712 })
5713 {
5714 true
5715 } else {
5716 load_provider_completions
5717 && completion_settings.words != WordsCompletionMode::Disabled
5718 && (ignore_word_threshold || {
5719 let words_min_length = completion_settings.words_min_length;
5720 // check whether word has at least `words_min_length` characters
5721 let query_chars = query.iter().flat_map(|q| q.chars());
5722 query_chars.take(words_min_length).count() == words_min_length
5723 })
5724 };
5725
5726 let mut words = if load_word_completions {
5727 cx.background_spawn({
5728 let buffer_snapshot = buffer_snapshot.clone();
5729 async move {
5730 buffer_snapshot.words_in_range(WordsQuery {
5731 fuzzy_contents: None,
5732 range: word_search_range,
5733 skip_digits,
5734 })
5735 }
5736 })
5737 } else {
5738 Task::ready(BTreeMap::default())
5739 };
5740
5741 let snippets = if let Some(provider) = &provider
5742 && provider.show_snippets()
5743 && let Some(project) = self.project()
5744 {
5745 let char_classifier = buffer_snapshot
5746 .char_classifier_at(buffer_position)
5747 .scope_context(Some(CharScopeContext::Completion));
5748 project.update(cx, |project, cx| {
5749 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5750 })
5751 } else {
5752 Task::ready(Ok(CompletionResponse {
5753 completions: Vec::new(),
5754 display_options: Default::default(),
5755 is_incomplete: false,
5756 }))
5757 };
5758
5759 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5760
5761 let id = post_inc(&mut self.next_completion_id);
5762 let task = cx.spawn_in(window, async move |editor, cx| {
5763 let Ok(()) = editor.update(cx, |this, _| {
5764 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5765 }) else {
5766 return;
5767 };
5768
5769 // TODO: Ideally completions from different sources would be selectively re-queried, so
5770 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5771 let mut completions = Vec::new();
5772 let mut is_incomplete = false;
5773 let mut display_options: Option<CompletionDisplayOptions> = None;
5774 if let Some(provider_responses) = provider_responses.await.log_err()
5775 && !provider_responses.is_empty()
5776 {
5777 for response in provider_responses {
5778 completions.extend(response.completions);
5779 is_incomplete = is_incomplete || response.is_incomplete;
5780 match display_options.as_mut() {
5781 None => {
5782 display_options = Some(response.display_options);
5783 }
5784 Some(options) => options.merge(&response.display_options),
5785 }
5786 }
5787 if completion_settings.words == WordsCompletionMode::Fallback {
5788 words = Task::ready(BTreeMap::default());
5789 }
5790 }
5791 let display_options = display_options.unwrap_or_default();
5792
5793 let mut words = words.await;
5794 if let Some(word_to_exclude) = &word_to_exclude {
5795 words.remove(word_to_exclude);
5796 }
5797 for lsp_completion in &completions {
5798 words.remove(&lsp_completion.new_text);
5799 }
5800 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5801 replace_range: word_replace_range.clone(),
5802 new_text: word.clone(),
5803 label: CodeLabel::plain(word, None),
5804 match_start: None,
5805 snippet_deduplication_key: None,
5806 icon_path: None,
5807 documentation: None,
5808 source: CompletionSource::BufferWord {
5809 word_range,
5810 resolved: false,
5811 },
5812 insert_text_mode: Some(InsertTextMode::AS_IS),
5813 confirm: None,
5814 }));
5815
5816 completions.extend(
5817 snippets
5818 .await
5819 .into_iter()
5820 .flat_map(|response| response.completions),
5821 );
5822
5823 let menu = if completions.is_empty() {
5824 None
5825 } else {
5826 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5827 let languages = editor
5828 .workspace
5829 .as_ref()
5830 .and_then(|(workspace, _)| workspace.upgrade())
5831 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5832 let menu = CompletionsMenu::new(
5833 id,
5834 requested_source.unwrap_or(if load_provider_completions {
5835 CompletionsMenuSource::Normal
5836 } else {
5837 CompletionsMenuSource::SnippetsOnly
5838 }),
5839 sort_completions,
5840 show_completion_documentation,
5841 position,
5842 query.clone(),
5843 is_incomplete,
5844 buffer.clone(),
5845 completions.into(),
5846 display_options,
5847 snippet_sort_order,
5848 languages,
5849 language,
5850 cx,
5851 );
5852
5853 let query = if filter_completions { query } else { None };
5854 let matches_task = menu.do_async_filtering(
5855 query.unwrap_or_default(),
5856 buffer_position,
5857 &buffer,
5858 cx,
5859 );
5860 (menu, matches_task)
5861 }) else {
5862 return;
5863 };
5864
5865 let matches = matches_task.await;
5866
5867 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5868 // Newer menu already set, so exit.
5869 if let Some(CodeContextMenu::Completions(prev_menu)) =
5870 editor.context_menu.borrow().as_ref()
5871 && prev_menu.id > id
5872 {
5873 return;
5874 };
5875
5876 // Only valid to take prev_menu because either the new menu is immediately set
5877 // below, or the menu is hidden.
5878 if let Some(CodeContextMenu::Completions(prev_menu)) =
5879 editor.context_menu.borrow_mut().take()
5880 {
5881 let position_matches =
5882 if prev_menu.initial_position == menu.initial_position {
5883 true
5884 } else {
5885 let snapshot = editor.buffer.read(cx).read(cx);
5886 prev_menu.initial_position.to_offset(&snapshot)
5887 == menu.initial_position.to_offset(&snapshot)
5888 };
5889 if position_matches {
5890 // Preserve markdown cache before `set_filter_results` because it will
5891 // try to populate the documentation cache.
5892 menu.preserve_markdown_cache(prev_menu);
5893 }
5894 };
5895
5896 menu.set_filter_results(matches, provider, window, cx);
5897 }) else {
5898 return;
5899 };
5900
5901 menu.visible().then_some(menu)
5902 };
5903
5904 editor
5905 .update_in(cx, |editor, window, cx| {
5906 if editor.focus_handle.is_focused(window)
5907 && let Some(menu) = menu
5908 {
5909 *editor.context_menu.borrow_mut() =
5910 Some(CodeContextMenu::Completions(menu));
5911
5912 crate::hover_popover::hide_hover(editor, cx);
5913 if editor.show_edit_predictions_in_menu() {
5914 editor.update_visible_edit_prediction(window, cx);
5915 } else {
5916 editor.discard_edit_prediction(false, cx);
5917 }
5918
5919 cx.notify();
5920 return;
5921 }
5922
5923 if editor.completion_tasks.len() <= 1 {
5924 // If there are no more completion tasks and the last menu was empty, we should hide it.
5925 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5926 // If it was already hidden and we don't show edit predictions in the menu,
5927 // we should also show the edit prediction when available.
5928 if was_hidden && editor.show_edit_predictions_in_menu() {
5929 editor.update_visible_edit_prediction(window, cx);
5930 }
5931 }
5932 })
5933 .ok();
5934 });
5935
5936 self.completion_tasks.push((id, task));
5937 }
5938
5939 #[cfg(feature = "test-support")]
5940 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5941 let menu = self.context_menu.borrow();
5942 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5943 let completions = menu.completions.borrow();
5944 Some(completions.to_vec())
5945 } else {
5946 None
5947 }
5948 }
5949
5950 pub fn with_completions_menu_matching_id<R>(
5951 &self,
5952 id: CompletionId,
5953 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5954 ) -> R {
5955 let mut context_menu = self.context_menu.borrow_mut();
5956 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5957 return f(None);
5958 };
5959 if completions_menu.id != id {
5960 return f(None);
5961 }
5962 f(Some(completions_menu))
5963 }
5964
5965 pub fn confirm_completion(
5966 &mut self,
5967 action: &ConfirmCompletion,
5968 window: &mut Window,
5969 cx: &mut Context<Self>,
5970 ) -> Option<Task<Result<()>>> {
5971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5972 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5973 }
5974
5975 pub fn confirm_completion_insert(
5976 &mut self,
5977 _: &ConfirmCompletionInsert,
5978 window: &mut Window,
5979 cx: &mut Context<Self>,
5980 ) -> Option<Task<Result<()>>> {
5981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5982 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5983 }
5984
5985 pub fn confirm_completion_replace(
5986 &mut self,
5987 _: &ConfirmCompletionReplace,
5988 window: &mut Window,
5989 cx: &mut Context<Self>,
5990 ) -> Option<Task<Result<()>>> {
5991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5992 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5993 }
5994
5995 pub fn compose_completion(
5996 &mut self,
5997 action: &ComposeCompletion,
5998 window: &mut Window,
5999 cx: &mut Context<Self>,
6000 ) -> Option<Task<Result<()>>> {
6001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6002 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6003 }
6004
6005 fn do_completion(
6006 &mut self,
6007 item_ix: Option<usize>,
6008 intent: CompletionIntent,
6009 window: &mut Window,
6010 cx: &mut Context<Editor>,
6011 ) -> Option<Task<Result<()>>> {
6012 use language::ToOffset as _;
6013
6014 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6015 else {
6016 return None;
6017 };
6018
6019 let candidate_id = {
6020 let entries = completions_menu.entries.borrow();
6021 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6022 if self.show_edit_predictions_in_menu() {
6023 self.discard_edit_prediction(true, cx);
6024 }
6025 mat.candidate_id
6026 };
6027
6028 let completion = completions_menu
6029 .completions
6030 .borrow()
6031 .get(candidate_id)?
6032 .clone();
6033 cx.stop_propagation();
6034
6035 let buffer_handle = completions_menu.buffer.clone();
6036
6037 let CompletionEdit {
6038 new_text,
6039 snippet,
6040 replace_range,
6041 } = process_completion_for_edit(
6042 &completion,
6043 intent,
6044 &buffer_handle,
6045 &completions_menu.initial_position.text_anchor,
6046 cx,
6047 );
6048
6049 let buffer = buffer_handle.read(cx);
6050 let snapshot = self.buffer.read(cx).snapshot(cx);
6051 let newest_anchor = self.selections.newest_anchor();
6052 let replace_range_multibuffer = {
6053 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6054 excerpt.map_range_from_buffer(replace_range.clone())
6055 };
6056 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6057 return None;
6058 }
6059
6060 let old_text = buffer
6061 .text_for_range(replace_range.clone())
6062 .collect::<String>();
6063 let lookbehind = newest_anchor
6064 .start
6065 .text_anchor
6066 .to_offset(buffer)
6067 .saturating_sub(replace_range.start.0);
6068 let lookahead = replace_range
6069 .end
6070 .0
6071 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6072 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6073 let suffix = &old_text[lookbehind.min(old_text.len())..];
6074
6075 let selections = self
6076 .selections
6077 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6078 let mut ranges = Vec::new();
6079 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6080
6081 for selection in &selections {
6082 let range = if selection.id == newest_anchor.id {
6083 replace_range_multibuffer.clone()
6084 } else {
6085 let mut range = selection.range();
6086
6087 // if prefix is present, don't duplicate it
6088 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6089 range.start = range.start.saturating_sub_usize(lookbehind);
6090
6091 // if suffix is also present, mimic the newest cursor and replace it
6092 if selection.id != newest_anchor.id
6093 && snapshot.contains_str_at(range.end, suffix)
6094 {
6095 range.end += lookahead;
6096 }
6097 }
6098 range
6099 };
6100
6101 ranges.push(range.clone());
6102
6103 if !self.linked_edit_ranges.is_empty() {
6104 let start_anchor = snapshot.anchor_before(range.start);
6105 let end_anchor = snapshot.anchor_after(range.end);
6106 if let Some(ranges) = self
6107 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6108 {
6109 for (buffer, edits) in ranges {
6110 linked_edits
6111 .entry(buffer.clone())
6112 .or_default()
6113 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6114 }
6115 }
6116 }
6117 }
6118
6119 let common_prefix_len = old_text
6120 .chars()
6121 .zip(new_text.chars())
6122 .take_while(|(a, b)| a == b)
6123 .map(|(a, _)| a.len_utf8())
6124 .sum::<usize>();
6125
6126 cx.emit(EditorEvent::InputHandled {
6127 utf16_range_to_replace: None,
6128 text: new_text[common_prefix_len..].into(),
6129 });
6130
6131 self.transact(window, cx, |editor, window, cx| {
6132 if let Some(mut snippet) = snippet {
6133 snippet.text = new_text.to_string();
6134 editor
6135 .insert_snippet(&ranges, snippet, window, cx)
6136 .log_err();
6137 } else {
6138 editor.buffer.update(cx, |multi_buffer, cx| {
6139 let auto_indent = match completion.insert_text_mode {
6140 Some(InsertTextMode::AS_IS) => None,
6141 _ => editor.autoindent_mode.clone(),
6142 };
6143 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6144 multi_buffer.edit(edits, auto_indent, cx);
6145 });
6146 }
6147 for (buffer, edits) in linked_edits {
6148 buffer.update(cx, |buffer, cx| {
6149 let snapshot = buffer.snapshot();
6150 let edits = edits
6151 .into_iter()
6152 .map(|(range, text)| {
6153 use text::ToPoint as TP;
6154 let end_point = TP::to_point(&range.end, &snapshot);
6155 let start_point = TP::to_point(&range.start, &snapshot);
6156 (start_point..end_point, text)
6157 })
6158 .sorted_by_key(|(range, _)| range.start);
6159 buffer.edit(edits, None, cx);
6160 })
6161 }
6162
6163 editor.refresh_edit_prediction(true, false, window, cx);
6164 });
6165 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6166
6167 let show_new_completions_on_confirm = completion
6168 .confirm
6169 .as_ref()
6170 .is_some_and(|confirm| confirm(intent, window, cx));
6171 if show_new_completions_on_confirm {
6172 self.open_or_update_completions_menu(None, None, false, window, cx);
6173 }
6174
6175 let provider = self.completion_provider.as_ref()?;
6176
6177 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6178 let command = lsp_store.as_ref().and_then(|lsp_store| {
6179 let CompletionSource::Lsp {
6180 lsp_completion,
6181 server_id,
6182 ..
6183 } = &completion.source
6184 else {
6185 return None;
6186 };
6187 let lsp_command = lsp_completion.command.as_ref()?;
6188 let available_commands = lsp_store
6189 .read(cx)
6190 .lsp_server_capabilities
6191 .get(server_id)
6192 .and_then(|server_capabilities| {
6193 server_capabilities
6194 .execute_command_provider
6195 .as_ref()
6196 .map(|options| options.commands.as_slice())
6197 })?;
6198 if available_commands.contains(&lsp_command.command) {
6199 Some(CodeAction {
6200 server_id: *server_id,
6201 range: language::Anchor::MIN..language::Anchor::MIN,
6202 lsp_action: LspAction::Command(lsp_command.clone()),
6203 resolved: false,
6204 })
6205 } else {
6206 None
6207 }
6208 });
6209
6210 drop(completion);
6211 let apply_edits = provider.apply_additional_edits_for_completion(
6212 buffer_handle.clone(),
6213 completions_menu.completions.clone(),
6214 candidate_id,
6215 true,
6216 cx,
6217 );
6218
6219 let editor_settings = EditorSettings::get_global(cx);
6220 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6221 // After the code completion is finished, users often want to know what signatures are needed.
6222 // so we should automatically call signature_help
6223 self.show_signature_help(&ShowSignatureHelp, window, cx);
6224 }
6225
6226 Some(cx.spawn_in(window, async move |editor, cx| {
6227 apply_edits.await?;
6228
6229 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6230 let title = command.lsp_action.title().to_owned();
6231 let project_transaction = lsp_store
6232 .update(cx, |lsp_store, cx| {
6233 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6234 })?
6235 .await
6236 .context("applying post-completion command")?;
6237 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6238 Self::open_project_transaction(
6239 &editor,
6240 workspace.downgrade(),
6241 project_transaction,
6242 title,
6243 cx,
6244 )
6245 .await?;
6246 }
6247 }
6248
6249 Ok(())
6250 }))
6251 }
6252
6253 pub fn toggle_code_actions(
6254 &mut self,
6255 action: &ToggleCodeActions,
6256 window: &mut Window,
6257 cx: &mut Context<Self>,
6258 ) {
6259 let quick_launch = action.quick_launch;
6260 let mut context_menu = self.context_menu.borrow_mut();
6261 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6262 if code_actions.deployed_from == action.deployed_from {
6263 // Toggle if we're selecting the same one
6264 *context_menu = None;
6265 cx.notify();
6266 return;
6267 } else {
6268 // Otherwise, clear it and start a new one
6269 *context_menu = None;
6270 cx.notify();
6271 }
6272 }
6273 drop(context_menu);
6274 let snapshot = self.snapshot(window, cx);
6275 let deployed_from = action.deployed_from.clone();
6276 let action = action.clone();
6277 self.completion_tasks.clear();
6278 self.discard_edit_prediction(false, cx);
6279
6280 let multibuffer_point = match &action.deployed_from {
6281 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6282 DisplayPoint::new(*row, 0).to_point(&snapshot)
6283 }
6284 _ => self
6285 .selections
6286 .newest::<Point>(&snapshot.display_snapshot)
6287 .head(),
6288 };
6289 let Some((buffer, buffer_row)) = snapshot
6290 .buffer_snapshot()
6291 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6292 .and_then(|(buffer_snapshot, range)| {
6293 self.buffer()
6294 .read(cx)
6295 .buffer(buffer_snapshot.remote_id())
6296 .map(|buffer| (buffer, range.start.row))
6297 })
6298 else {
6299 return;
6300 };
6301 let buffer_id = buffer.read(cx).remote_id();
6302 let tasks = self
6303 .tasks
6304 .get(&(buffer_id, buffer_row))
6305 .map(|t| Arc::new(t.to_owned()));
6306
6307 if !self.focus_handle.is_focused(window) {
6308 return;
6309 }
6310 let project = self.project.clone();
6311
6312 let code_actions_task = match deployed_from {
6313 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6314 _ => self.code_actions(buffer_row, window, cx),
6315 };
6316
6317 let runnable_task = match deployed_from {
6318 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6319 _ => {
6320 let mut task_context_task = Task::ready(None);
6321 if let Some(tasks) = &tasks
6322 && let Some(project) = project
6323 {
6324 task_context_task =
6325 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6326 }
6327
6328 cx.spawn_in(window, {
6329 let buffer = buffer.clone();
6330 async move |editor, cx| {
6331 let task_context = task_context_task.await;
6332
6333 let resolved_tasks =
6334 tasks
6335 .zip(task_context.clone())
6336 .map(|(tasks, task_context)| ResolvedTasks {
6337 templates: tasks.resolve(&task_context).collect(),
6338 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6339 multibuffer_point.row,
6340 tasks.column,
6341 )),
6342 });
6343 let debug_scenarios = editor
6344 .update(cx, |editor, cx| {
6345 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6346 })?
6347 .await;
6348 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6349 }
6350 })
6351 }
6352 };
6353
6354 cx.spawn_in(window, async move |editor, cx| {
6355 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6356 let code_actions = code_actions_task.await;
6357 let spawn_straight_away = quick_launch
6358 && resolved_tasks
6359 .as_ref()
6360 .is_some_and(|tasks| tasks.templates.len() == 1)
6361 && code_actions
6362 .as_ref()
6363 .is_none_or(|actions| actions.is_empty())
6364 && debug_scenarios.is_empty();
6365
6366 editor.update_in(cx, |editor, window, cx| {
6367 crate::hover_popover::hide_hover(editor, cx);
6368 let actions = CodeActionContents::new(
6369 resolved_tasks,
6370 code_actions,
6371 debug_scenarios,
6372 task_context.unwrap_or_default(),
6373 );
6374
6375 // Don't show the menu if there are no actions available
6376 if actions.is_empty() {
6377 cx.notify();
6378 return Task::ready(Ok(()));
6379 }
6380
6381 *editor.context_menu.borrow_mut() =
6382 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6383 buffer,
6384 actions,
6385 selected_item: Default::default(),
6386 scroll_handle: UniformListScrollHandle::default(),
6387 deployed_from,
6388 }));
6389 cx.notify();
6390 if spawn_straight_away
6391 && let Some(task) = editor.confirm_code_action(
6392 &ConfirmCodeAction { item_ix: Some(0) },
6393 window,
6394 cx,
6395 )
6396 {
6397 return task;
6398 }
6399
6400 Task::ready(Ok(()))
6401 })
6402 })
6403 .detach_and_log_err(cx);
6404 }
6405
6406 fn debug_scenarios(
6407 &mut self,
6408 resolved_tasks: &Option<ResolvedTasks>,
6409 buffer: &Entity<Buffer>,
6410 cx: &mut App,
6411 ) -> Task<Vec<task::DebugScenario>> {
6412 maybe!({
6413 let project = self.project()?;
6414 let dap_store = project.read(cx).dap_store();
6415 let mut scenarios = vec![];
6416 let resolved_tasks = resolved_tasks.as_ref()?;
6417 let buffer = buffer.read(cx);
6418 let language = buffer.language()?;
6419 let file = buffer.file();
6420 let debug_adapter = language_settings(language.name().into(), file, cx)
6421 .debuggers
6422 .first()
6423 .map(SharedString::from)
6424 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6425
6426 dap_store.update(cx, |dap_store, cx| {
6427 for (_, task) in &resolved_tasks.templates {
6428 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6429 task.original_task().clone(),
6430 debug_adapter.clone().into(),
6431 task.display_label().to_owned().into(),
6432 cx,
6433 );
6434 scenarios.push(maybe_scenario);
6435 }
6436 });
6437 Some(cx.background_spawn(async move {
6438 futures::future::join_all(scenarios)
6439 .await
6440 .into_iter()
6441 .flatten()
6442 .collect::<Vec<_>>()
6443 }))
6444 })
6445 .unwrap_or_else(|| Task::ready(vec![]))
6446 }
6447
6448 fn code_actions(
6449 &mut self,
6450 buffer_row: u32,
6451 window: &mut Window,
6452 cx: &mut Context<Self>,
6453 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6454 let mut task = self.code_actions_task.take();
6455 cx.spawn_in(window, async move |editor, cx| {
6456 while let Some(prev_task) = task {
6457 prev_task.await.log_err();
6458 task = editor
6459 .update(cx, |this, _| this.code_actions_task.take())
6460 .ok()?;
6461 }
6462
6463 editor
6464 .update(cx, |editor, cx| {
6465 editor
6466 .available_code_actions
6467 .clone()
6468 .and_then(|(location, code_actions)| {
6469 let snapshot = location.buffer.read(cx).snapshot();
6470 let point_range = location.range.to_point(&snapshot);
6471 let point_range = point_range.start.row..=point_range.end.row;
6472 if point_range.contains(&buffer_row) {
6473 Some(code_actions)
6474 } else {
6475 None
6476 }
6477 })
6478 })
6479 .ok()
6480 .flatten()
6481 })
6482 }
6483
6484 pub fn confirm_code_action(
6485 &mut self,
6486 action: &ConfirmCodeAction,
6487 window: &mut Window,
6488 cx: &mut Context<Self>,
6489 ) -> Option<Task<Result<()>>> {
6490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6491
6492 let actions_menu =
6493 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6494 menu
6495 } else {
6496 return None;
6497 };
6498
6499 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6500 let action = actions_menu.actions.get(action_ix)?;
6501 let title = action.label();
6502 let buffer = actions_menu.buffer;
6503 let workspace = self.workspace()?;
6504
6505 match action {
6506 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6507 workspace.update(cx, |workspace, cx| {
6508 workspace.schedule_resolved_task(
6509 task_source_kind,
6510 resolved_task,
6511 false,
6512 window,
6513 cx,
6514 );
6515
6516 Some(Task::ready(Ok(())))
6517 })
6518 }
6519 CodeActionsItem::CodeAction {
6520 excerpt_id,
6521 action,
6522 provider,
6523 } => {
6524 let apply_code_action =
6525 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6526 let workspace = workspace.downgrade();
6527 Some(cx.spawn_in(window, async move |editor, cx| {
6528 let project_transaction = apply_code_action.await?;
6529 Self::open_project_transaction(
6530 &editor,
6531 workspace,
6532 project_transaction,
6533 title,
6534 cx,
6535 )
6536 .await
6537 }))
6538 }
6539 CodeActionsItem::DebugScenario(scenario) => {
6540 let context = actions_menu.actions.context;
6541
6542 workspace.update(cx, |workspace, cx| {
6543 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6544 workspace.start_debug_session(
6545 scenario,
6546 context,
6547 Some(buffer),
6548 None,
6549 window,
6550 cx,
6551 );
6552 });
6553 Some(Task::ready(Ok(())))
6554 }
6555 }
6556 }
6557
6558 pub async fn open_project_transaction(
6559 editor: &WeakEntity<Editor>,
6560 workspace: WeakEntity<Workspace>,
6561 transaction: ProjectTransaction,
6562 title: String,
6563 cx: &mut AsyncWindowContext,
6564 ) -> Result<()> {
6565 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6566 cx.update(|_, cx| {
6567 entries.sort_unstable_by_key(|(buffer, _)| {
6568 buffer.read(cx).file().map(|f| f.path().clone())
6569 });
6570 })?;
6571 if entries.is_empty() {
6572 return Ok(());
6573 }
6574
6575 // If the project transaction's edits are all contained within this editor, then
6576 // avoid opening a new editor to display them.
6577
6578 if let [(buffer, transaction)] = &*entries {
6579 let excerpt = editor.update(cx, |editor, cx| {
6580 editor
6581 .buffer()
6582 .read(cx)
6583 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6584 })?;
6585 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6586 && excerpted_buffer == *buffer
6587 {
6588 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6589 let excerpt_range = excerpt_range.to_offset(buffer);
6590 buffer
6591 .edited_ranges_for_transaction::<usize>(transaction)
6592 .all(|range| {
6593 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6594 })
6595 })?;
6596
6597 if all_edits_within_excerpt {
6598 return Ok(());
6599 }
6600 }
6601 }
6602
6603 let mut ranges_to_highlight = Vec::new();
6604 let excerpt_buffer = cx.new(|cx| {
6605 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6606 for (buffer_handle, transaction) in &entries {
6607 let edited_ranges = buffer_handle
6608 .read(cx)
6609 .edited_ranges_for_transaction::<Point>(transaction)
6610 .collect::<Vec<_>>();
6611 let (ranges, _) = multibuffer.set_excerpts_for_path(
6612 PathKey::for_buffer(buffer_handle, cx),
6613 buffer_handle.clone(),
6614 edited_ranges,
6615 multibuffer_context_lines(cx),
6616 cx,
6617 );
6618
6619 ranges_to_highlight.extend(ranges);
6620 }
6621 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6622 multibuffer
6623 })?;
6624
6625 workspace.update_in(cx, |workspace, window, cx| {
6626 let project = workspace.project().clone();
6627 let editor =
6628 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6630 editor.update(cx, |editor, cx| {
6631 editor.highlight_background::<Self>(
6632 &ranges_to_highlight,
6633 |_, theme| theme.colors().editor_highlighted_line_background,
6634 cx,
6635 );
6636 });
6637 })?;
6638
6639 Ok(())
6640 }
6641
6642 pub fn clear_code_action_providers(&mut self) {
6643 self.code_action_providers.clear();
6644 self.available_code_actions.take();
6645 }
6646
6647 pub fn add_code_action_provider(
6648 &mut self,
6649 provider: Rc<dyn CodeActionProvider>,
6650 window: &mut Window,
6651 cx: &mut Context<Self>,
6652 ) {
6653 if self
6654 .code_action_providers
6655 .iter()
6656 .any(|existing_provider| existing_provider.id() == provider.id())
6657 {
6658 return;
6659 }
6660
6661 self.code_action_providers.push(provider);
6662 self.refresh_code_actions(window, cx);
6663 }
6664
6665 pub fn remove_code_action_provider(
6666 &mut self,
6667 id: Arc<str>,
6668 window: &mut Window,
6669 cx: &mut Context<Self>,
6670 ) {
6671 self.code_action_providers
6672 .retain(|provider| provider.id() != id);
6673 self.refresh_code_actions(window, cx);
6674 }
6675
6676 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6677 !self.code_action_providers.is_empty()
6678 && EditorSettings::get_global(cx).toolbar.code_actions
6679 }
6680
6681 pub fn has_available_code_actions(&self) -> bool {
6682 self.available_code_actions
6683 .as_ref()
6684 .is_some_and(|(_, actions)| !actions.is_empty())
6685 }
6686
6687 fn render_inline_code_actions(
6688 &self,
6689 icon_size: ui::IconSize,
6690 display_row: DisplayRow,
6691 is_active: bool,
6692 cx: &mut Context<Self>,
6693 ) -> AnyElement {
6694 let show_tooltip = !self.context_menu_visible();
6695 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6696 .icon_size(icon_size)
6697 .shape(ui::IconButtonShape::Square)
6698 .icon_color(ui::Color::Hidden)
6699 .toggle_state(is_active)
6700 .when(show_tooltip, |this| {
6701 this.tooltip({
6702 let focus_handle = self.focus_handle.clone();
6703 move |_window, cx| {
6704 Tooltip::for_action_in(
6705 "Toggle Code Actions",
6706 &ToggleCodeActions {
6707 deployed_from: None,
6708 quick_launch: false,
6709 },
6710 &focus_handle,
6711 cx,
6712 )
6713 }
6714 })
6715 })
6716 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6717 window.focus(&editor.focus_handle(cx));
6718 editor.toggle_code_actions(
6719 &crate::actions::ToggleCodeActions {
6720 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6721 display_row,
6722 )),
6723 quick_launch: false,
6724 },
6725 window,
6726 cx,
6727 );
6728 }))
6729 .into_any_element()
6730 }
6731
6732 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6733 &self.context_menu
6734 }
6735
6736 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6737 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6738 cx.background_executor()
6739 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6740 .await;
6741
6742 let (start_buffer, start, _, end, newest_selection) = this
6743 .update(cx, |this, cx| {
6744 let newest_selection = this.selections.newest_anchor().clone();
6745 if newest_selection.head().diff_base_anchor.is_some() {
6746 return None;
6747 }
6748 let display_snapshot = this.display_snapshot(cx);
6749 let newest_selection_adjusted =
6750 this.selections.newest_adjusted(&display_snapshot);
6751 let buffer = this.buffer.read(cx);
6752
6753 let (start_buffer, start) =
6754 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6755 let (end_buffer, end) =
6756 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6757
6758 Some((start_buffer, start, end_buffer, end, newest_selection))
6759 })?
6760 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6761 .context(
6762 "Expected selection to lie in a single buffer when refreshing code actions",
6763 )?;
6764 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6765 let providers = this.code_action_providers.clone();
6766 let tasks = this
6767 .code_action_providers
6768 .iter()
6769 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6770 .collect::<Vec<_>>();
6771 (providers, tasks)
6772 })?;
6773
6774 let mut actions = Vec::new();
6775 for (provider, provider_actions) in
6776 providers.into_iter().zip(future::join_all(tasks).await)
6777 {
6778 if let Some(provider_actions) = provider_actions.log_err() {
6779 actions.extend(provider_actions.into_iter().map(|action| {
6780 AvailableCodeAction {
6781 excerpt_id: newest_selection.start.excerpt_id,
6782 action,
6783 provider: provider.clone(),
6784 }
6785 }));
6786 }
6787 }
6788
6789 this.update(cx, |this, cx| {
6790 this.available_code_actions = if actions.is_empty() {
6791 None
6792 } else {
6793 Some((
6794 Location {
6795 buffer: start_buffer,
6796 range: start..end,
6797 },
6798 actions.into(),
6799 ))
6800 };
6801 cx.notify();
6802 })
6803 }));
6804 }
6805
6806 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6807 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6808 self.show_git_blame_inline = false;
6809
6810 self.show_git_blame_inline_delay_task =
6811 Some(cx.spawn_in(window, async move |this, cx| {
6812 cx.background_executor().timer(delay).await;
6813
6814 this.update(cx, |this, cx| {
6815 this.show_git_blame_inline = true;
6816 cx.notify();
6817 })
6818 .log_err();
6819 }));
6820 }
6821 }
6822
6823 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6824 let snapshot = self.snapshot(window, cx);
6825 let cursor = self
6826 .selections
6827 .newest::<Point>(&snapshot.display_snapshot)
6828 .head();
6829 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6830 else {
6831 return;
6832 };
6833
6834 if self.blame.is_none() {
6835 self.start_git_blame(true, window, cx);
6836 }
6837 let Some(blame) = self.blame.as_ref() else {
6838 return;
6839 };
6840
6841 let row_info = RowInfo {
6842 buffer_id: Some(buffer.remote_id()),
6843 buffer_row: Some(point.row),
6844 ..Default::default()
6845 };
6846 let Some((buffer, blame_entry)) = blame
6847 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6848 .flatten()
6849 else {
6850 return;
6851 };
6852
6853 let anchor = self.selections.newest_anchor().head();
6854 let position = self.to_pixel_point(anchor, &snapshot, window);
6855 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6856 self.show_blame_popover(
6857 buffer,
6858 &blame_entry,
6859 position + last_bounds.origin,
6860 true,
6861 cx,
6862 );
6863 };
6864 }
6865
6866 fn show_blame_popover(
6867 &mut self,
6868 buffer: BufferId,
6869 blame_entry: &BlameEntry,
6870 position: gpui::Point<Pixels>,
6871 ignore_timeout: bool,
6872 cx: &mut Context<Self>,
6873 ) {
6874 if let Some(state) = &mut self.inline_blame_popover {
6875 state.hide_task.take();
6876 } else {
6877 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6878 let blame_entry = blame_entry.clone();
6879 let show_task = cx.spawn(async move |editor, cx| {
6880 if !ignore_timeout {
6881 cx.background_executor()
6882 .timer(std::time::Duration::from_millis(blame_popover_delay))
6883 .await;
6884 }
6885 editor
6886 .update(cx, |editor, cx| {
6887 editor.inline_blame_popover_show_task.take();
6888 let Some(blame) = editor.blame.as_ref() else {
6889 return;
6890 };
6891 let blame = blame.read(cx);
6892 let details = blame.details_for_entry(buffer, &blame_entry);
6893 let markdown = cx.new(|cx| {
6894 Markdown::new(
6895 details
6896 .as_ref()
6897 .map(|message| message.message.clone())
6898 .unwrap_or_default(),
6899 None,
6900 None,
6901 cx,
6902 )
6903 });
6904 editor.inline_blame_popover = Some(InlineBlamePopover {
6905 position,
6906 hide_task: None,
6907 popover_bounds: None,
6908 popover_state: InlineBlamePopoverState {
6909 scroll_handle: ScrollHandle::new(),
6910 commit_message: details,
6911 markdown,
6912 },
6913 keyboard_grace: ignore_timeout,
6914 });
6915 cx.notify();
6916 })
6917 .ok();
6918 });
6919 self.inline_blame_popover_show_task = Some(show_task);
6920 }
6921 }
6922
6923 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6924 self.inline_blame_popover_show_task.take();
6925 if let Some(state) = &mut self.inline_blame_popover {
6926 let hide_task = cx.spawn(async move |editor, cx| {
6927 if !ignore_timeout {
6928 cx.background_executor()
6929 .timer(std::time::Duration::from_millis(100))
6930 .await;
6931 }
6932 editor
6933 .update(cx, |editor, cx| {
6934 editor.inline_blame_popover.take();
6935 cx.notify();
6936 })
6937 .ok();
6938 });
6939 state.hide_task = Some(hide_task);
6940 true
6941 } else {
6942 false
6943 }
6944 }
6945
6946 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6947 if self.pending_rename.is_some() {
6948 return None;
6949 }
6950
6951 let provider = self.semantics_provider.clone()?;
6952 let buffer = self.buffer.read(cx);
6953 let newest_selection = self.selections.newest_anchor().clone();
6954 let cursor_position = newest_selection.head();
6955 let (cursor_buffer, cursor_buffer_position) =
6956 buffer.text_anchor_for_position(cursor_position, cx)?;
6957 let (tail_buffer, tail_buffer_position) =
6958 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6959 if cursor_buffer != tail_buffer {
6960 return None;
6961 }
6962
6963 let snapshot = cursor_buffer.read(cx).snapshot();
6964 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6965 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6966 if start_word_range != end_word_range {
6967 self.document_highlights_task.take();
6968 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6969 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6970 return None;
6971 }
6972
6973 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6974 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6975 cx.background_executor()
6976 .timer(Duration::from_millis(debounce))
6977 .await;
6978
6979 let highlights = if let Some(highlights) = cx
6980 .update(|cx| {
6981 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6982 })
6983 .ok()
6984 .flatten()
6985 {
6986 highlights.await.log_err()
6987 } else {
6988 None
6989 };
6990
6991 if let Some(highlights) = highlights {
6992 this.update(cx, |this, cx| {
6993 if this.pending_rename.is_some() {
6994 return;
6995 }
6996
6997 let buffer = this.buffer.read(cx);
6998 if buffer
6999 .text_anchor_for_position(cursor_position, cx)
7000 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7001 {
7002 return;
7003 }
7004
7005 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7006 let mut write_ranges = Vec::new();
7007 let mut read_ranges = Vec::new();
7008 for highlight in highlights {
7009 let buffer_id = cursor_buffer.read(cx).remote_id();
7010 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7011 {
7012 let start = highlight
7013 .range
7014 .start
7015 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7016 let end = highlight
7017 .range
7018 .end
7019 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7020 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7021 continue;
7022 }
7023
7024 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7025 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7026 write_ranges.push(range);
7027 } else {
7028 read_ranges.push(range);
7029 }
7030 }
7031 }
7032
7033 this.highlight_background::<DocumentHighlightRead>(
7034 &read_ranges,
7035 |_, theme| theme.colors().editor_document_highlight_read_background,
7036 cx,
7037 );
7038 this.highlight_background::<DocumentHighlightWrite>(
7039 &write_ranges,
7040 |_, theme| theme.colors().editor_document_highlight_write_background,
7041 cx,
7042 );
7043 cx.notify();
7044 })
7045 .log_err();
7046 }
7047 }));
7048 None
7049 }
7050
7051 fn prepare_highlight_query_from_selection(
7052 &mut self,
7053 window: &Window,
7054 cx: &mut Context<Editor>,
7055 ) -> Option<(String, Range<Anchor>)> {
7056 if matches!(self.mode, EditorMode::SingleLine) {
7057 return None;
7058 }
7059 if !EditorSettings::get_global(cx).selection_highlight {
7060 return None;
7061 }
7062 if self.selections.count() != 1 || self.selections.line_mode() {
7063 return None;
7064 }
7065 let snapshot = self.snapshot(window, cx);
7066 let selection = self.selections.newest::<Point>(&snapshot);
7067 // If the selection spans multiple rows OR it is empty
7068 if selection.start.row != selection.end.row
7069 || selection.start.column == selection.end.column
7070 {
7071 return None;
7072 }
7073 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7074 let query = snapshot
7075 .buffer_snapshot()
7076 .text_for_range(selection_anchor_range.clone())
7077 .collect::<String>();
7078 if query.trim().is_empty() {
7079 return None;
7080 }
7081 Some((query, selection_anchor_range))
7082 }
7083
7084 fn update_selection_occurrence_highlights(
7085 &mut self,
7086 query_text: String,
7087 query_range: Range<Anchor>,
7088 multi_buffer_range_to_query: Range<Point>,
7089 use_debounce: bool,
7090 window: &mut Window,
7091 cx: &mut Context<Editor>,
7092 ) -> Task<()> {
7093 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7094 cx.spawn_in(window, async move |editor, cx| {
7095 if use_debounce {
7096 cx.background_executor()
7097 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7098 .await;
7099 }
7100 let match_task = cx.background_spawn(async move {
7101 let buffer_ranges = multi_buffer_snapshot
7102 .range_to_buffer_ranges(multi_buffer_range_to_query)
7103 .into_iter()
7104 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7105 let mut match_ranges = Vec::new();
7106 let Ok(regex) = project::search::SearchQuery::text(
7107 query_text.clone(),
7108 false,
7109 false,
7110 false,
7111 Default::default(),
7112 Default::default(),
7113 false,
7114 None,
7115 ) else {
7116 return Vec::default();
7117 };
7118 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7119 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7120 match_ranges.extend(
7121 regex
7122 .search(
7123 buffer_snapshot,
7124 Some(search_range.start.0..search_range.end.0),
7125 )
7126 .await
7127 .into_iter()
7128 .filter_map(|match_range| {
7129 let match_start = buffer_snapshot
7130 .anchor_after(search_range.start + match_range.start);
7131 let match_end = buffer_snapshot
7132 .anchor_before(search_range.start + match_range.end);
7133 let match_anchor_range =
7134 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7135 (match_anchor_range != query_range).then_some(match_anchor_range)
7136 }),
7137 );
7138 }
7139 match_ranges
7140 });
7141 let match_ranges = match_task.await;
7142 editor
7143 .update_in(cx, |editor, _, cx| {
7144 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7145 if !match_ranges.is_empty() {
7146 editor.highlight_background::<SelectedTextHighlight>(
7147 &match_ranges,
7148 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7149 cx,
7150 )
7151 }
7152 })
7153 .log_err();
7154 })
7155 }
7156
7157 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7158 struct NewlineFold;
7159 let type_id = std::any::TypeId::of::<NewlineFold>();
7160 if !self.mode.is_single_line() {
7161 return;
7162 }
7163 let snapshot = self.snapshot(window, cx);
7164 if snapshot.buffer_snapshot().max_point().row == 0 {
7165 return;
7166 }
7167 let task = cx.background_spawn(async move {
7168 let new_newlines = snapshot
7169 .buffer_chars_at(MultiBufferOffset(0))
7170 .filter_map(|(c, i)| {
7171 if c == '\n' {
7172 Some(
7173 snapshot.buffer_snapshot().anchor_after(i)
7174 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7175 )
7176 } else {
7177 None
7178 }
7179 })
7180 .collect::<Vec<_>>();
7181 let existing_newlines = snapshot
7182 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7183 .filter_map(|fold| {
7184 if fold.placeholder.type_tag == Some(type_id) {
7185 Some(fold.range.start..fold.range.end)
7186 } else {
7187 None
7188 }
7189 })
7190 .collect::<Vec<_>>();
7191
7192 (new_newlines, existing_newlines)
7193 });
7194 self.folding_newlines = cx.spawn(async move |this, cx| {
7195 let (new_newlines, existing_newlines) = task.await;
7196 if new_newlines == existing_newlines {
7197 return;
7198 }
7199 let placeholder = FoldPlaceholder {
7200 render: Arc::new(move |_, _, cx| {
7201 div()
7202 .bg(cx.theme().status().hint_background)
7203 .border_b_1()
7204 .size_full()
7205 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7206 .border_color(cx.theme().status().hint)
7207 .child("\\n")
7208 .into_any()
7209 }),
7210 constrain_width: false,
7211 merge_adjacent: false,
7212 type_tag: Some(type_id),
7213 };
7214 let creases = new_newlines
7215 .into_iter()
7216 .map(|range| Crease::simple(range, placeholder.clone()))
7217 .collect();
7218 this.update(cx, |this, cx| {
7219 this.display_map.update(cx, |display_map, cx| {
7220 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7221 display_map.fold(creases, cx);
7222 });
7223 })
7224 .ok();
7225 });
7226 }
7227
7228 fn refresh_selected_text_highlights(
7229 &mut self,
7230 on_buffer_edit: bool,
7231 window: &mut Window,
7232 cx: &mut Context<Editor>,
7233 ) {
7234 let Some((query_text, query_range)) =
7235 self.prepare_highlight_query_from_selection(window, cx)
7236 else {
7237 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7238 self.quick_selection_highlight_task.take();
7239 self.debounced_selection_highlight_task.take();
7240 return;
7241 };
7242 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7243 if on_buffer_edit
7244 || self
7245 .quick_selection_highlight_task
7246 .as_ref()
7247 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7248 {
7249 let multi_buffer_visible_start = self
7250 .scroll_manager
7251 .anchor()
7252 .anchor
7253 .to_point(&multi_buffer_snapshot);
7254 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7255 multi_buffer_visible_start
7256 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7257 Bias::Left,
7258 );
7259 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7260 self.quick_selection_highlight_task = Some((
7261 query_range.clone(),
7262 self.update_selection_occurrence_highlights(
7263 query_text.clone(),
7264 query_range.clone(),
7265 multi_buffer_visible_range,
7266 false,
7267 window,
7268 cx,
7269 ),
7270 ));
7271 }
7272 if on_buffer_edit
7273 || self
7274 .debounced_selection_highlight_task
7275 .as_ref()
7276 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7277 {
7278 let multi_buffer_start = multi_buffer_snapshot
7279 .anchor_before(MultiBufferOffset(0))
7280 .to_point(&multi_buffer_snapshot);
7281 let multi_buffer_end = multi_buffer_snapshot
7282 .anchor_after(multi_buffer_snapshot.len())
7283 .to_point(&multi_buffer_snapshot);
7284 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7285 self.debounced_selection_highlight_task = Some((
7286 query_range.clone(),
7287 self.update_selection_occurrence_highlights(
7288 query_text,
7289 query_range,
7290 multi_buffer_full_range,
7291 true,
7292 window,
7293 cx,
7294 ),
7295 ));
7296 }
7297 }
7298
7299 pub fn refresh_edit_prediction(
7300 &mut self,
7301 debounce: bool,
7302 user_requested: bool,
7303 window: &mut Window,
7304 cx: &mut Context<Self>,
7305 ) -> Option<()> {
7306 if DisableAiSettings::get_global(cx).disable_ai {
7307 return None;
7308 }
7309
7310 let provider = self.edit_prediction_provider()?;
7311 let cursor = self.selections.newest_anchor().head();
7312 let (buffer, cursor_buffer_position) =
7313 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7314
7315 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7316 self.discard_edit_prediction(false, cx);
7317 return None;
7318 }
7319
7320 self.update_visible_edit_prediction(window, cx);
7321
7322 if !user_requested
7323 && (!self.should_show_edit_predictions()
7324 || !self.is_focused(window)
7325 || buffer.read(cx).is_empty())
7326 {
7327 self.discard_edit_prediction(false, cx);
7328 return None;
7329 }
7330
7331 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7332 Some(())
7333 }
7334
7335 fn show_edit_predictions_in_menu(&self) -> bool {
7336 match self.edit_prediction_settings {
7337 EditPredictionSettings::Disabled => false,
7338 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7339 }
7340 }
7341
7342 pub fn edit_predictions_enabled(&self) -> bool {
7343 match self.edit_prediction_settings {
7344 EditPredictionSettings::Disabled => false,
7345 EditPredictionSettings::Enabled { .. } => true,
7346 }
7347 }
7348
7349 fn edit_prediction_requires_modifier(&self) -> bool {
7350 match self.edit_prediction_settings {
7351 EditPredictionSettings::Disabled => false,
7352 EditPredictionSettings::Enabled {
7353 preview_requires_modifier,
7354 ..
7355 } => preview_requires_modifier,
7356 }
7357 }
7358
7359 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7360 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7361 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7362 self.discard_edit_prediction(false, cx);
7363 } else {
7364 let selection = self.selections.newest_anchor();
7365 let cursor = selection.head();
7366
7367 if let Some((buffer, cursor_buffer_position)) =
7368 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7369 {
7370 self.edit_prediction_settings =
7371 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7372 }
7373 }
7374 }
7375
7376 fn edit_prediction_settings_at_position(
7377 &self,
7378 buffer: &Entity<Buffer>,
7379 buffer_position: language::Anchor,
7380 cx: &App,
7381 ) -> EditPredictionSettings {
7382 if !self.mode.is_full()
7383 || !self.show_edit_predictions_override.unwrap_or(true)
7384 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7385 {
7386 return EditPredictionSettings::Disabled;
7387 }
7388
7389 let buffer = buffer.read(cx);
7390
7391 let file = buffer.file();
7392
7393 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7394 return EditPredictionSettings::Disabled;
7395 };
7396
7397 let by_provider = matches!(
7398 self.menu_edit_predictions_policy,
7399 MenuEditPredictionsPolicy::ByProvider
7400 );
7401
7402 let show_in_menu = by_provider
7403 && self
7404 .edit_prediction_provider
7405 .as_ref()
7406 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7407
7408 let preview_requires_modifier =
7409 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7410
7411 EditPredictionSettings::Enabled {
7412 show_in_menu,
7413 preview_requires_modifier,
7414 }
7415 }
7416
7417 fn should_show_edit_predictions(&self) -> bool {
7418 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7419 }
7420
7421 pub fn edit_prediction_preview_is_active(&self) -> bool {
7422 matches!(
7423 self.edit_prediction_preview,
7424 EditPredictionPreview::Active { .. }
7425 )
7426 }
7427
7428 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7429 let cursor = self.selections.newest_anchor().head();
7430 if let Some((buffer, cursor_position)) =
7431 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7432 {
7433 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7434 } else {
7435 false
7436 }
7437 }
7438
7439 pub fn supports_minimap(&self, cx: &App) -> bool {
7440 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7441 }
7442
7443 fn edit_predictions_enabled_in_buffer(
7444 &self,
7445 buffer: &Entity<Buffer>,
7446 buffer_position: language::Anchor,
7447 cx: &App,
7448 ) -> bool {
7449 maybe!({
7450 if self.read_only(cx) {
7451 return Some(false);
7452 }
7453 let provider = self.edit_prediction_provider()?;
7454 if !provider.is_enabled(buffer, buffer_position, cx) {
7455 return Some(false);
7456 }
7457 let buffer = buffer.read(cx);
7458 let Some(file) = buffer.file() else {
7459 return Some(true);
7460 };
7461 let settings = all_language_settings(Some(file), cx);
7462 Some(settings.edit_predictions_enabled_for_file(file, cx))
7463 })
7464 .unwrap_or(false)
7465 }
7466
7467 fn cycle_edit_prediction(
7468 &mut self,
7469 direction: Direction,
7470 window: &mut Window,
7471 cx: &mut Context<Self>,
7472 ) -> Option<()> {
7473 let provider = self.edit_prediction_provider()?;
7474 let cursor = self.selections.newest_anchor().head();
7475 let (buffer, cursor_buffer_position) =
7476 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7477 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7478 return None;
7479 }
7480
7481 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7482 self.update_visible_edit_prediction(window, cx);
7483
7484 Some(())
7485 }
7486
7487 pub fn show_edit_prediction(
7488 &mut self,
7489 _: &ShowEditPrediction,
7490 window: &mut Window,
7491 cx: &mut Context<Self>,
7492 ) {
7493 if !self.has_active_edit_prediction() {
7494 self.refresh_edit_prediction(false, true, window, cx);
7495 return;
7496 }
7497
7498 self.update_visible_edit_prediction(window, cx);
7499 }
7500
7501 pub fn display_cursor_names(
7502 &mut self,
7503 _: &DisplayCursorNames,
7504 window: &mut Window,
7505 cx: &mut Context<Self>,
7506 ) {
7507 self.show_cursor_names(window, cx);
7508 }
7509
7510 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7511 self.show_cursor_names = true;
7512 cx.notify();
7513 cx.spawn_in(window, async move |this, cx| {
7514 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7515 this.update(cx, |this, cx| {
7516 this.show_cursor_names = false;
7517 cx.notify()
7518 })
7519 .ok()
7520 })
7521 .detach();
7522 }
7523
7524 pub fn next_edit_prediction(
7525 &mut self,
7526 _: &NextEditPrediction,
7527 window: &mut Window,
7528 cx: &mut Context<Self>,
7529 ) {
7530 if self.has_active_edit_prediction() {
7531 self.cycle_edit_prediction(Direction::Next, window, cx);
7532 } else {
7533 let is_copilot_disabled = self
7534 .refresh_edit_prediction(false, true, window, cx)
7535 .is_none();
7536 if is_copilot_disabled {
7537 cx.propagate();
7538 }
7539 }
7540 }
7541
7542 pub fn previous_edit_prediction(
7543 &mut self,
7544 _: &PreviousEditPrediction,
7545 window: &mut Window,
7546 cx: &mut Context<Self>,
7547 ) {
7548 if self.has_active_edit_prediction() {
7549 self.cycle_edit_prediction(Direction::Prev, window, cx);
7550 } else {
7551 let is_copilot_disabled = self
7552 .refresh_edit_prediction(false, true, window, cx)
7553 .is_none();
7554 if is_copilot_disabled {
7555 cx.propagate();
7556 }
7557 }
7558 }
7559
7560 pub fn accept_edit_prediction(
7561 &mut self,
7562 _: &AcceptEditPrediction,
7563 window: &mut Window,
7564 cx: &mut Context<Self>,
7565 ) {
7566 if self.show_edit_predictions_in_menu() {
7567 self.hide_context_menu(window, cx);
7568 }
7569
7570 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7571 return;
7572 };
7573
7574 match &active_edit_prediction.completion {
7575 EditPrediction::MoveWithin { target, .. } => {
7576 let target = *target;
7577
7578 if let Some(position_map) = &self.last_position_map {
7579 if position_map
7580 .visible_row_range
7581 .contains(&target.to_display_point(&position_map.snapshot).row())
7582 || !self.edit_prediction_requires_modifier()
7583 {
7584 self.unfold_ranges(&[target..target], true, false, cx);
7585 // Note that this is also done in vim's handler of the Tab action.
7586 self.change_selections(
7587 SelectionEffects::scroll(Autoscroll::newest()),
7588 window,
7589 cx,
7590 |selections| {
7591 selections.select_anchor_ranges([target..target]);
7592 },
7593 );
7594 self.clear_row_highlights::<EditPredictionPreview>();
7595
7596 self.edit_prediction_preview
7597 .set_previous_scroll_position(None);
7598 } else {
7599 self.edit_prediction_preview
7600 .set_previous_scroll_position(Some(
7601 position_map.snapshot.scroll_anchor,
7602 ));
7603
7604 self.highlight_rows::<EditPredictionPreview>(
7605 target..target,
7606 cx.theme().colors().editor_highlighted_line_background,
7607 RowHighlightOptions {
7608 autoscroll: true,
7609 ..Default::default()
7610 },
7611 cx,
7612 );
7613 self.request_autoscroll(Autoscroll::fit(), cx);
7614 }
7615 }
7616 }
7617 EditPrediction::MoveOutside { snapshot, target } => {
7618 if let Some(workspace) = self.workspace() {
7619 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7620 .detach_and_log_err(cx);
7621 }
7622 }
7623 EditPrediction::Edit { edits, .. } => {
7624 self.report_edit_prediction_event(
7625 active_edit_prediction.completion_id.clone(),
7626 true,
7627 cx,
7628 );
7629
7630 if let Some(provider) = self.edit_prediction_provider() {
7631 provider.accept(cx);
7632 }
7633
7634 // Store the transaction ID and selections before applying the edit
7635 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7636
7637 let snapshot = self.buffer.read(cx).snapshot(cx);
7638 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7639
7640 self.buffer.update(cx, |buffer, cx| {
7641 buffer.edit(edits.iter().cloned(), None, cx)
7642 });
7643
7644 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7645 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7646 });
7647
7648 let selections = self.selections.disjoint_anchors_arc();
7649 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7650 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7651 if has_new_transaction {
7652 self.selection_history
7653 .insert_transaction(transaction_id_now, selections);
7654 }
7655 }
7656
7657 self.update_visible_edit_prediction(window, cx);
7658 if self.active_edit_prediction.is_none() {
7659 self.refresh_edit_prediction(true, true, window, cx);
7660 }
7661
7662 cx.notify();
7663 }
7664 }
7665
7666 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7667 }
7668
7669 pub fn accept_partial_edit_prediction(
7670 &mut self,
7671 _: &AcceptPartialEditPrediction,
7672 window: &mut Window,
7673 cx: &mut Context<Self>,
7674 ) {
7675 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7676 return;
7677 };
7678 if self.selections.count() != 1 {
7679 return;
7680 }
7681
7682 match &active_edit_prediction.completion {
7683 EditPrediction::MoveWithin { target, .. } => {
7684 let target = *target;
7685 self.change_selections(
7686 SelectionEffects::scroll(Autoscroll::newest()),
7687 window,
7688 cx,
7689 |selections| {
7690 selections.select_anchor_ranges([target..target]);
7691 },
7692 );
7693 }
7694 EditPrediction::MoveOutside { snapshot, target } => {
7695 if let Some(workspace) = self.workspace() {
7696 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7697 .detach_and_log_err(cx);
7698 }
7699 }
7700 EditPrediction::Edit { edits, .. } => {
7701 self.report_edit_prediction_event(
7702 active_edit_prediction.completion_id.clone(),
7703 true,
7704 cx,
7705 );
7706
7707 // Find an insertion that starts at the cursor position.
7708 let snapshot = self.buffer.read(cx).snapshot(cx);
7709 let cursor_offset = self
7710 .selections
7711 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7712 .head();
7713 let insertion = edits.iter().find_map(|(range, text)| {
7714 let range = range.to_offset(&snapshot);
7715 if range.is_empty() && range.start == cursor_offset {
7716 Some(text)
7717 } else {
7718 None
7719 }
7720 });
7721
7722 if let Some(text) = insertion {
7723 let mut partial_completion = text
7724 .chars()
7725 .by_ref()
7726 .take_while(|c| c.is_alphabetic())
7727 .collect::<String>();
7728 if partial_completion.is_empty() {
7729 partial_completion = text
7730 .chars()
7731 .by_ref()
7732 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7733 .collect::<String>();
7734 }
7735
7736 cx.emit(EditorEvent::InputHandled {
7737 utf16_range_to_replace: None,
7738 text: partial_completion.clone().into(),
7739 });
7740
7741 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7742
7743 self.refresh_edit_prediction(true, true, window, cx);
7744 cx.notify();
7745 } else {
7746 self.accept_edit_prediction(&Default::default(), window, cx);
7747 }
7748 }
7749 }
7750 }
7751
7752 fn discard_edit_prediction(
7753 &mut self,
7754 should_report_edit_prediction_event: bool,
7755 cx: &mut Context<Self>,
7756 ) -> bool {
7757 if should_report_edit_prediction_event {
7758 let completion_id = self
7759 .active_edit_prediction
7760 .as_ref()
7761 .and_then(|active_completion| active_completion.completion_id.clone());
7762
7763 self.report_edit_prediction_event(completion_id, false, cx);
7764 }
7765
7766 if let Some(provider) = self.edit_prediction_provider() {
7767 provider.discard(cx);
7768 }
7769
7770 self.take_active_edit_prediction(cx)
7771 }
7772
7773 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7774 let Some(provider) = self.edit_prediction_provider() else {
7775 return;
7776 };
7777
7778 let Some((_, buffer, _)) = self
7779 .buffer
7780 .read(cx)
7781 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7782 else {
7783 return;
7784 };
7785
7786 let extension = buffer
7787 .read(cx)
7788 .file()
7789 .and_then(|file| Some(file.path().extension()?.to_string()));
7790
7791 let event_type = match accepted {
7792 true => "Edit Prediction Accepted",
7793 false => "Edit Prediction Discarded",
7794 };
7795 telemetry::event!(
7796 event_type,
7797 provider = provider.name(),
7798 prediction_id = id,
7799 suggestion_accepted = accepted,
7800 file_extension = extension,
7801 );
7802 }
7803
7804 fn open_editor_at_anchor(
7805 snapshot: &language::BufferSnapshot,
7806 target: language::Anchor,
7807 workspace: &Entity<Workspace>,
7808 window: &mut Window,
7809 cx: &mut App,
7810 ) -> Task<Result<()>> {
7811 workspace.update(cx, |workspace, cx| {
7812 let path = snapshot.file().map(|file| file.full_path(cx));
7813 let Some(path) =
7814 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7815 else {
7816 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7817 };
7818 let target = text::ToPoint::to_point(&target, snapshot);
7819 let item = workspace.open_path(path, None, true, window, cx);
7820 window.spawn(cx, async move |cx| {
7821 let Some(editor) = item.await?.downcast::<Editor>() else {
7822 return Ok(());
7823 };
7824 editor
7825 .update_in(cx, |editor, window, cx| {
7826 editor.go_to_singleton_buffer_point(target, window, cx);
7827 })
7828 .ok();
7829 anyhow::Ok(())
7830 })
7831 })
7832 }
7833
7834 pub fn has_active_edit_prediction(&self) -> bool {
7835 self.active_edit_prediction.is_some()
7836 }
7837
7838 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7839 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7840 return false;
7841 };
7842
7843 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7844 self.clear_highlights::<EditPredictionHighlight>(cx);
7845 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7846 true
7847 }
7848
7849 /// Returns true when we're displaying the edit prediction popover below the cursor
7850 /// like we are not previewing and the LSP autocomplete menu is visible
7851 /// or we are in `when_holding_modifier` mode.
7852 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7853 if self.edit_prediction_preview_is_active()
7854 || !self.show_edit_predictions_in_menu()
7855 || !self.edit_predictions_enabled()
7856 {
7857 return false;
7858 }
7859
7860 if self.has_visible_completions_menu() {
7861 return true;
7862 }
7863
7864 has_completion && self.edit_prediction_requires_modifier()
7865 }
7866
7867 fn handle_modifiers_changed(
7868 &mut self,
7869 modifiers: Modifiers,
7870 position_map: &PositionMap,
7871 window: &mut Window,
7872 cx: &mut Context<Self>,
7873 ) {
7874 // Ensure that the edit prediction preview is updated, even when not
7875 // enabled, if there's an active edit prediction preview.
7876 if self.show_edit_predictions_in_menu()
7877 || matches!(
7878 self.edit_prediction_preview,
7879 EditPredictionPreview::Active { .. }
7880 )
7881 {
7882 self.update_edit_prediction_preview(&modifiers, window, cx);
7883 }
7884
7885 self.update_selection_mode(&modifiers, position_map, window, cx);
7886
7887 let mouse_position = window.mouse_position();
7888 if !position_map.text_hitbox.is_hovered(window) {
7889 return;
7890 }
7891
7892 self.update_hovered_link(
7893 position_map.point_for_position(mouse_position),
7894 &position_map.snapshot,
7895 modifiers,
7896 window,
7897 cx,
7898 )
7899 }
7900
7901 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7902 match EditorSettings::get_global(cx).multi_cursor_modifier {
7903 MultiCursorModifier::Alt => modifiers.secondary(),
7904 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7905 }
7906 }
7907
7908 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7909 match EditorSettings::get_global(cx).multi_cursor_modifier {
7910 MultiCursorModifier::Alt => modifiers.alt,
7911 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7912 }
7913 }
7914
7915 fn columnar_selection_mode(
7916 modifiers: &Modifiers,
7917 cx: &mut Context<Self>,
7918 ) -> Option<ColumnarMode> {
7919 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7920 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7921 Some(ColumnarMode::FromMouse)
7922 } else if Self::is_alt_pressed(modifiers, cx) {
7923 Some(ColumnarMode::FromSelection)
7924 } else {
7925 None
7926 }
7927 } else {
7928 None
7929 }
7930 }
7931
7932 fn update_selection_mode(
7933 &mut self,
7934 modifiers: &Modifiers,
7935 position_map: &PositionMap,
7936 window: &mut Window,
7937 cx: &mut Context<Self>,
7938 ) {
7939 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7940 return;
7941 };
7942 if self.selections.pending_anchor().is_none() {
7943 return;
7944 }
7945
7946 let mouse_position = window.mouse_position();
7947 let point_for_position = position_map.point_for_position(mouse_position);
7948 let position = point_for_position.previous_valid;
7949
7950 self.select(
7951 SelectPhase::BeginColumnar {
7952 position,
7953 reset: false,
7954 mode,
7955 goal_column: point_for_position.exact_unclipped.column(),
7956 },
7957 window,
7958 cx,
7959 );
7960 }
7961
7962 fn update_edit_prediction_preview(
7963 &mut self,
7964 modifiers: &Modifiers,
7965 window: &mut Window,
7966 cx: &mut Context<Self>,
7967 ) {
7968 let mut modifiers_held = false;
7969 if let Some(accept_keystroke) = self
7970 .accept_edit_prediction_keybind(false, window, cx)
7971 .keystroke()
7972 {
7973 modifiers_held = modifiers_held
7974 || (accept_keystroke.modifiers() == modifiers
7975 && accept_keystroke.modifiers().modified());
7976 };
7977 if let Some(accept_partial_keystroke) = self
7978 .accept_edit_prediction_keybind(true, window, cx)
7979 .keystroke()
7980 {
7981 modifiers_held = modifiers_held
7982 || (accept_partial_keystroke.modifiers() == modifiers
7983 && accept_partial_keystroke.modifiers().modified());
7984 }
7985
7986 if modifiers_held {
7987 if matches!(
7988 self.edit_prediction_preview,
7989 EditPredictionPreview::Inactive { .. }
7990 ) {
7991 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7992 provider.provider.did_show(cx)
7993 }
7994
7995 self.edit_prediction_preview = EditPredictionPreview::Active {
7996 previous_scroll_position: None,
7997 since: Instant::now(),
7998 };
7999
8000 self.update_visible_edit_prediction(window, cx);
8001 cx.notify();
8002 }
8003 } else if let EditPredictionPreview::Active {
8004 previous_scroll_position,
8005 since,
8006 } = self.edit_prediction_preview
8007 {
8008 if let (Some(previous_scroll_position), Some(position_map)) =
8009 (previous_scroll_position, self.last_position_map.as_ref())
8010 {
8011 self.set_scroll_position(
8012 previous_scroll_position
8013 .scroll_position(&position_map.snapshot.display_snapshot),
8014 window,
8015 cx,
8016 );
8017 }
8018
8019 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8020 released_too_fast: since.elapsed() < Duration::from_millis(200),
8021 };
8022 self.clear_row_highlights::<EditPredictionPreview>();
8023 self.update_visible_edit_prediction(window, cx);
8024 cx.notify();
8025 }
8026 }
8027
8028 fn update_visible_edit_prediction(
8029 &mut self,
8030 _window: &mut Window,
8031 cx: &mut Context<Self>,
8032 ) -> Option<()> {
8033 if DisableAiSettings::get_global(cx).disable_ai {
8034 return None;
8035 }
8036
8037 if self.ime_transaction.is_some() {
8038 self.discard_edit_prediction(false, cx);
8039 return None;
8040 }
8041
8042 let selection = self.selections.newest_anchor();
8043 let cursor = selection.head();
8044 let multibuffer = self.buffer.read(cx).snapshot(cx);
8045 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8046 let excerpt_id = cursor.excerpt_id;
8047
8048 let show_in_menu = self.show_edit_predictions_in_menu();
8049 let completions_menu_has_precedence = !show_in_menu
8050 && (self.context_menu.borrow().is_some()
8051 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8052
8053 if completions_menu_has_precedence
8054 || !offset_selection.is_empty()
8055 || self
8056 .active_edit_prediction
8057 .as_ref()
8058 .is_some_and(|completion| {
8059 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8060 return false;
8061 };
8062 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8063 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8064 !invalidation_range.contains(&offset_selection.head())
8065 })
8066 {
8067 self.discard_edit_prediction(false, cx);
8068 return None;
8069 }
8070
8071 self.take_active_edit_prediction(cx);
8072 let Some(provider) = self.edit_prediction_provider() else {
8073 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8074 return None;
8075 };
8076
8077 let (buffer, cursor_buffer_position) =
8078 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8079
8080 self.edit_prediction_settings =
8081 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8082
8083 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8084
8085 if self.edit_prediction_indent_conflict {
8086 let cursor_point = cursor.to_point(&multibuffer);
8087 let mut suggested_indent = None;
8088 multibuffer.suggested_indents_callback(
8089 cursor_point.row..cursor_point.row + 1,
8090 |_, indent| {
8091 suggested_indent = Some(indent);
8092 ControlFlow::Break(())
8093 },
8094 cx,
8095 );
8096
8097 if let Some(indent) = suggested_indent
8098 && indent.len == cursor_point.column
8099 {
8100 self.edit_prediction_indent_conflict = false;
8101 }
8102 }
8103
8104 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8105
8106 let (completion_id, edits, edit_preview) = match edit_prediction {
8107 edit_prediction_types::EditPrediction::Local {
8108 id,
8109 edits,
8110 edit_preview,
8111 } => (id, edits, edit_preview),
8112 edit_prediction_types::EditPrediction::Jump {
8113 id,
8114 snapshot,
8115 target,
8116 } => {
8117 self.stale_edit_prediction_in_menu = None;
8118 self.active_edit_prediction = Some(EditPredictionState {
8119 inlay_ids: vec![],
8120 completion: EditPrediction::MoveOutside { snapshot, target },
8121 completion_id: id,
8122 invalidation_range: None,
8123 });
8124 cx.notify();
8125 return Some(());
8126 }
8127 };
8128
8129 let edits = edits
8130 .into_iter()
8131 .flat_map(|(range, new_text)| {
8132 Some((
8133 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8134 new_text,
8135 ))
8136 })
8137 .collect::<Vec<_>>();
8138 if edits.is_empty() {
8139 return None;
8140 }
8141
8142 let first_edit_start = edits.first().unwrap().0.start;
8143 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8144 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8145
8146 let last_edit_end = edits.last().unwrap().0.end;
8147 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8148 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8149
8150 let cursor_row = cursor.to_point(&multibuffer).row;
8151
8152 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8153
8154 let mut inlay_ids = Vec::new();
8155 let invalidation_row_range;
8156 let move_invalidation_row_range = if cursor_row < edit_start_row {
8157 Some(cursor_row..edit_end_row)
8158 } else if cursor_row > edit_end_row {
8159 Some(edit_start_row..cursor_row)
8160 } else {
8161 None
8162 };
8163 let supports_jump = self
8164 .edit_prediction_provider
8165 .as_ref()
8166 .map(|provider| provider.provider.supports_jump_to_edit())
8167 .unwrap_or(true);
8168
8169 let is_move = supports_jump
8170 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8171 let completion = if is_move {
8172 invalidation_row_range =
8173 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8174 let target = first_edit_start;
8175 EditPrediction::MoveWithin { target, snapshot }
8176 } else {
8177 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8178 && !self.edit_predictions_hidden_for_vim_mode;
8179
8180 if show_completions_in_buffer {
8181 if let Some(provider) = &self.edit_prediction_provider {
8182 provider.provider.did_show(cx);
8183 }
8184 if edits
8185 .iter()
8186 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8187 {
8188 let mut inlays = Vec::new();
8189 for (range, new_text) in &edits {
8190 let inlay = Inlay::edit_prediction(
8191 post_inc(&mut self.next_inlay_id),
8192 range.start,
8193 new_text.as_ref(),
8194 );
8195 inlay_ids.push(inlay.id);
8196 inlays.push(inlay);
8197 }
8198
8199 self.splice_inlays(&[], inlays, cx);
8200 } else {
8201 let background_color = cx.theme().status().deleted_background;
8202 self.highlight_text::<EditPredictionHighlight>(
8203 edits.iter().map(|(range, _)| range.clone()).collect(),
8204 HighlightStyle {
8205 background_color: Some(background_color),
8206 ..Default::default()
8207 },
8208 cx,
8209 );
8210 }
8211 }
8212
8213 invalidation_row_range = edit_start_row..edit_end_row;
8214
8215 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8216 if provider.show_tab_accept_marker() {
8217 EditDisplayMode::TabAccept
8218 } else {
8219 EditDisplayMode::Inline
8220 }
8221 } else {
8222 EditDisplayMode::DiffPopover
8223 };
8224
8225 EditPrediction::Edit {
8226 edits,
8227 edit_preview,
8228 display_mode,
8229 snapshot,
8230 }
8231 };
8232
8233 let invalidation_range = multibuffer
8234 .anchor_before(Point::new(invalidation_row_range.start, 0))
8235 ..multibuffer.anchor_after(Point::new(
8236 invalidation_row_range.end,
8237 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8238 ));
8239
8240 self.stale_edit_prediction_in_menu = None;
8241 self.active_edit_prediction = Some(EditPredictionState {
8242 inlay_ids,
8243 completion,
8244 completion_id,
8245 invalidation_range: Some(invalidation_range),
8246 });
8247
8248 cx.notify();
8249
8250 Some(())
8251 }
8252
8253 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8254 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8255 }
8256
8257 fn clear_tasks(&mut self) {
8258 self.tasks.clear()
8259 }
8260
8261 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8262 if self.tasks.insert(key, value).is_some() {
8263 // This case should hopefully be rare, but just in case...
8264 log::error!(
8265 "multiple different run targets found on a single line, only the last target will be rendered"
8266 )
8267 }
8268 }
8269
8270 /// Get all display points of breakpoints that will be rendered within editor
8271 ///
8272 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8273 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8274 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8275 fn active_breakpoints(
8276 &self,
8277 range: Range<DisplayRow>,
8278 window: &mut Window,
8279 cx: &mut Context<Self>,
8280 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8281 let mut breakpoint_display_points = HashMap::default();
8282
8283 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8284 return breakpoint_display_points;
8285 };
8286
8287 let snapshot = self.snapshot(window, cx);
8288
8289 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8290 let Some(project) = self.project() else {
8291 return breakpoint_display_points;
8292 };
8293
8294 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8295 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8296
8297 for (buffer_snapshot, range, excerpt_id) in
8298 multi_buffer_snapshot.range_to_buffer_ranges(range)
8299 {
8300 let Some(buffer) = project
8301 .read(cx)
8302 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8303 else {
8304 continue;
8305 };
8306 let breakpoints = breakpoint_store.read(cx).breakpoints(
8307 &buffer,
8308 Some(
8309 buffer_snapshot.anchor_before(range.start)
8310 ..buffer_snapshot.anchor_after(range.end),
8311 ),
8312 buffer_snapshot,
8313 cx,
8314 );
8315 for (breakpoint, state) in breakpoints {
8316 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8317 let position = multi_buffer_anchor
8318 .to_point(&multi_buffer_snapshot)
8319 .to_display_point(&snapshot);
8320
8321 breakpoint_display_points.insert(
8322 position.row(),
8323 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8324 );
8325 }
8326 }
8327
8328 breakpoint_display_points
8329 }
8330
8331 fn breakpoint_context_menu(
8332 &self,
8333 anchor: Anchor,
8334 window: &mut Window,
8335 cx: &mut Context<Self>,
8336 ) -> Entity<ui::ContextMenu> {
8337 let weak_editor = cx.weak_entity();
8338 let focus_handle = self.focus_handle(cx);
8339
8340 let row = self
8341 .buffer
8342 .read(cx)
8343 .snapshot(cx)
8344 .summary_for_anchor::<Point>(&anchor)
8345 .row;
8346
8347 let breakpoint = self
8348 .breakpoint_at_row(row, window, cx)
8349 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8350
8351 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8352 "Edit Log Breakpoint"
8353 } else {
8354 "Set Log Breakpoint"
8355 };
8356
8357 let condition_breakpoint_msg = if breakpoint
8358 .as_ref()
8359 .is_some_and(|bp| bp.1.condition.is_some())
8360 {
8361 "Edit Condition Breakpoint"
8362 } else {
8363 "Set Condition Breakpoint"
8364 };
8365
8366 let hit_condition_breakpoint_msg = if breakpoint
8367 .as_ref()
8368 .is_some_and(|bp| bp.1.hit_condition.is_some())
8369 {
8370 "Edit Hit Condition Breakpoint"
8371 } else {
8372 "Set Hit Condition Breakpoint"
8373 };
8374
8375 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8376 "Unset Breakpoint"
8377 } else {
8378 "Set Breakpoint"
8379 };
8380
8381 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8382
8383 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8384 BreakpointState::Enabled => Some("Disable"),
8385 BreakpointState::Disabled => Some("Enable"),
8386 });
8387
8388 let (anchor, breakpoint) =
8389 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8390
8391 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8392 menu.on_blur_subscription(Subscription::new(|| {}))
8393 .context(focus_handle)
8394 .when(run_to_cursor, |this| {
8395 let weak_editor = weak_editor.clone();
8396 this.entry("Run to cursor", None, move |window, cx| {
8397 weak_editor
8398 .update(cx, |editor, cx| {
8399 editor.change_selections(
8400 SelectionEffects::no_scroll(),
8401 window,
8402 cx,
8403 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8404 );
8405 })
8406 .ok();
8407
8408 window.dispatch_action(Box::new(RunToCursor), cx);
8409 })
8410 .separator()
8411 })
8412 .when_some(toggle_state_msg, |this, msg| {
8413 this.entry(msg, None, {
8414 let weak_editor = weak_editor.clone();
8415 let breakpoint = breakpoint.clone();
8416 move |_window, cx| {
8417 weak_editor
8418 .update(cx, |this, cx| {
8419 this.edit_breakpoint_at_anchor(
8420 anchor,
8421 breakpoint.as_ref().clone(),
8422 BreakpointEditAction::InvertState,
8423 cx,
8424 );
8425 })
8426 .log_err();
8427 }
8428 })
8429 })
8430 .entry(set_breakpoint_msg, None, {
8431 let weak_editor = weak_editor.clone();
8432 let breakpoint = breakpoint.clone();
8433 move |_window, cx| {
8434 weak_editor
8435 .update(cx, |this, cx| {
8436 this.edit_breakpoint_at_anchor(
8437 anchor,
8438 breakpoint.as_ref().clone(),
8439 BreakpointEditAction::Toggle,
8440 cx,
8441 );
8442 })
8443 .log_err();
8444 }
8445 })
8446 .entry(log_breakpoint_msg, None, {
8447 let breakpoint = breakpoint.clone();
8448 let weak_editor = weak_editor.clone();
8449 move |window, cx| {
8450 weak_editor
8451 .update(cx, |this, cx| {
8452 this.add_edit_breakpoint_block(
8453 anchor,
8454 breakpoint.as_ref(),
8455 BreakpointPromptEditAction::Log,
8456 window,
8457 cx,
8458 );
8459 })
8460 .log_err();
8461 }
8462 })
8463 .entry(condition_breakpoint_msg, None, {
8464 let breakpoint = breakpoint.clone();
8465 let weak_editor = weak_editor.clone();
8466 move |window, cx| {
8467 weak_editor
8468 .update(cx, |this, cx| {
8469 this.add_edit_breakpoint_block(
8470 anchor,
8471 breakpoint.as_ref(),
8472 BreakpointPromptEditAction::Condition,
8473 window,
8474 cx,
8475 );
8476 })
8477 .log_err();
8478 }
8479 })
8480 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8481 weak_editor
8482 .update(cx, |this, cx| {
8483 this.add_edit_breakpoint_block(
8484 anchor,
8485 breakpoint.as_ref(),
8486 BreakpointPromptEditAction::HitCondition,
8487 window,
8488 cx,
8489 );
8490 })
8491 .log_err();
8492 })
8493 })
8494 }
8495
8496 fn render_breakpoint(
8497 &self,
8498 position: Anchor,
8499 row: DisplayRow,
8500 breakpoint: &Breakpoint,
8501 state: Option<BreakpointSessionState>,
8502 cx: &mut Context<Self>,
8503 ) -> IconButton {
8504 let is_rejected = state.is_some_and(|s| !s.verified);
8505 // Is it a breakpoint that shows up when hovering over gutter?
8506 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8507 (false, false),
8508 |PhantomBreakpointIndicator {
8509 is_active,
8510 display_row,
8511 collides_with_existing_breakpoint,
8512 }| {
8513 (
8514 is_active && display_row == row,
8515 collides_with_existing_breakpoint,
8516 )
8517 },
8518 );
8519
8520 let (color, icon) = {
8521 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8522 (false, false) => ui::IconName::DebugBreakpoint,
8523 (true, false) => ui::IconName::DebugLogBreakpoint,
8524 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8525 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8526 };
8527
8528 let color = cx.theme().colors();
8529
8530 let color = if is_phantom {
8531 if collides_with_existing {
8532 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8533 } else {
8534 Color::Hint
8535 }
8536 } else if is_rejected {
8537 Color::Disabled
8538 } else {
8539 Color::Debugger
8540 };
8541
8542 (color, icon)
8543 };
8544
8545 let breakpoint = Arc::from(breakpoint.clone());
8546
8547 let alt_as_text = gpui::Keystroke {
8548 modifiers: Modifiers::secondary_key(),
8549 ..Default::default()
8550 };
8551 let primary_action_text = if breakpoint.is_disabled() {
8552 "Enable breakpoint"
8553 } else if is_phantom && !collides_with_existing {
8554 "Set breakpoint"
8555 } else {
8556 "Unset breakpoint"
8557 };
8558 let focus_handle = self.focus_handle.clone();
8559
8560 let meta = if is_rejected {
8561 SharedString::from("No executable code is associated with this line.")
8562 } else if collides_with_existing && !breakpoint.is_disabled() {
8563 SharedString::from(format!(
8564 "{alt_as_text}-click to disable,\nright-click for more options."
8565 ))
8566 } else {
8567 SharedString::from("Right-click for more options.")
8568 };
8569 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8570 .icon_size(IconSize::XSmall)
8571 .size(ui::ButtonSize::None)
8572 .when(is_rejected, |this| {
8573 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8574 })
8575 .icon_color(color)
8576 .style(ButtonStyle::Transparent)
8577 .on_click(cx.listener({
8578 move |editor, event: &ClickEvent, window, cx| {
8579 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8580 BreakpointEditAction::InvertState
8581 } else {
8582 BreakpointEditAction::Toggle
8583 };
8584
8585 window.focus(&editor.focus_handle(cx));
8586 editor.edit_breakpoint_at_anchor(
8587 position,
8588 breakpoint.as_ref().clone(),
8589 edit_action,
8590 cx,
8591 );
8592 }
8593 }))
8594 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8595 editor.set_breakpoint_context_menu(
8596 row,
8597 Some(position),
8598 event.position(),
8599 window,
8600 cx,
8601 );
8602 }))
8603 .tooltip(move |_window, cx| {
8604 Tooltip::with_meta_in(
8605 primary_action_text,
8606 Some(&ToggleBreakpoint),
8607 meta.clone(),
8608 &focus_handle,
8609 cx,
8610 )
8611 })
8612 }
8613
8614 fn build_tasks_context(
8615 project: &Entity<Project>,
8616 buffer: &Entity<Buffer>,
8617 buffer_row: u32,
8618 tasks: &Arc<RunnableTasks>,
8619 cx: &mut Context<Self>,
8620 ) -> Task<Option<task::TaskContext>> {
8621 let position = Point::new(buffer_row, tasks.column);
8622 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8623 let location = Location {
8624 buffer: buffer.clone(),
8625 range: range_start..range_start,
8626 };
8627 // Fill in the environmental variables from the tree-sitter captures
8628 let mut captured_task_variables = TaskVariables::default();
8629 for (capture_name, value) in tasks.extra_variables.clone() {
8630 captured_task_variables.insert(
8631 task::VariableName::Custom(capture_name.into()),
8632 value.clone(),
8633 );
8634 }
8635 project.update(cx, |project, cx| {
8636 project.task_store().update(cx, |task_store, cx| {
8637 task_store.task_context_for_location(captured_task_variables, location, cx)
8638 })
8639 })
8640 }
8641
8642 pub fn spawn_nearest_task(
8643 &mut self,
8644 action: &SpawnNearestTask,
8645 window: &mut Window,
8646 cx: &mut Context<Self>,
8647 ) {
8648 let Some((workspace, _)) = self.workspace.clone() else {
8649 return;
8650 };
8651 let Some(project) = self.project.clone() else {
8652 return;
8653 };
8654
8655 // Try to find a closest, enclosing node using tree-sitter that has a task
8656 let Some((buffer, buffer_row, tasks)) = self
8657 .find_enclosing_node_task(cx)
8658 // Or find the task that's closest in row-distance.
8659 .or_else(|| self.find_closest_task(cx))
8660 else {
8661 return;
8662 };
8663
8664 let reveal_strategy = action.reveal;
8665 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8666 cx.spawn_in(window, async move |_, cx| {
8667 let context = task_context.await?;
8668 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8669
8670 let resolved = &mut resolved_task.resolved;
8671 resolved.reveal = reveal_strategy;
8672
8673 workspace
8674 .update_in(cx, |workspace, window, cx| {
8675 workspace.schedule_resolved_task(
8676 task_source_kind,
8677 resolved_task,
8678 false,
8679 window,
8680 cx,
8681 );
8682 })
8683 .ok()
8684 })
8685 .detach();
8686 }
8687
8688 fn find_closest_task(
8689 &mut self,
8690 cx: &mut Context<Self>,
8691 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8692 let cursor_row = self
8693 .selections
8694 .newest_adjusted(&self.display_snapshot(cx))
8695 .head()
8696 .row;
8697
8698 let ((buffer_id, row), tasks) = self
8699 .tasks
8700 .iter()
8701 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8702
8703 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8704 let tasks = Arc::new(tasks.to_owned());
8705 Some((buffer, *row, tasks))
8706 }
8707
8708 fn find_enclosing_node_task(
8709 &mut self,
8710 cx: &mut Context<Self>,
8711 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8712 let snapshot = self.buffer.read(cx).snapshot(cx);
8713 let offset = self
8714 .selections
8715 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8716 .head();
8717 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8718 let offset = excerpt.map_offset_to_buffer(offset);
8719 let buffer_id = excerpt.buffer().remote_id();
8720
8721 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8722 let mut cursor = layer.node().walk();
8723
8724 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8725 if cursor.node().end_byte() == offset.0 {
8726 cursor.goto_next_sibling();
8727 }
8728 }
8729
8730 // Ascend to the smallest ancestor that contains the range and has a task.
8731 loop {
8732 let node = cursor.node();
8733 let node_range = node.byte_range();
8734 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8735
8736 // Check if this node contains our offset
8737 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8738 // If it contains offset, check for task
8739 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8740 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8741 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8742 }
8743 }
8744
8745 if !cursor.goto_parent() {
8746 break;
8747 }
8748 }
8749 None
8750 }
8751
8752 fn render_run_indicator(
8753 &self,
8754 _style: &EditorStyle,
8755 is_active: bool,
8756 row: DisplayRow,
8757 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8758 cx: &mut Context<Self>,
8759 ) -> IconButton {
8760 let color = Color::Muted;
8761 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8762
8763 IconButton::new(
8764 ("run_indicator", row.0 as usize),
8765 ui::IconName::PlayOutlined,
8766 )
8767 .shape(ui::IconButtonShape::Square)
8768 .icon_size(IconSize::XSmall)
8769 .icon_color(color)
8770 .toggle_state(is_active)
8771 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8772 let quick_launch = match e {
8773 ClickEvent::Keyboard(_) => true,
8774 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8775 };
8776
8777 window.focus(&editor.focus_handle(cx));
8778 editor.toggle_code_actions(
8779 &ToggleCodeActions {
8780 deployed_from: Some(CodeActionSource::RunMenu(row)),
8781 quick_launch,
8782 },
8783 window,
8784 cx,
8785 );
8786 }))
8787 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8788 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8789 }))
8790 }
8791
8792 pub fn context_menu_visible(&self) -> bool {
8793 !self.edit_prediction_preview_is_active()
8794 && self
8795 .context_menu
8796 .borrow()
8797 .as_ref()
8798 .is_some_and(|menu| menu.visible())
8799 }
8800
8801 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8802 self.context_menu
8803 .borrow()
8804 .as_ref()
8805 .map(|menu| menu.origin())
8806 }
8807
8808 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8809 self.context_menu_options = Some(options);
8810 }
8811
8812 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8813 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8814
8815 fn render_edit_prediction_popover(
8816 &mut self,
8817 text_bounds: &Bounds<Pixels>,
8818 content_origin: gpui::Point<Pixels>,
8819 right_margin: Pixels,
8820 editor_snapshot: &EditorSnapshot,
8821 visible_row_range: Range<DisplayRow>,
8822 scroll_top: ScrollOffset,
8823 scroll_bottom: ScrollOffset,
8824 line_layouts: &[LineWithInvisibles],
8825 line_height: Pixels,
8826 scroll_position: gpui::Point<ScrollOffset>,
8827 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8828 newest_selection_head: Option<DisplayPoint>,
8829 editor_width: Pixels,
8830 style: &EditorStyle,
8831 window: &mut Window,
8832 cx: &mut App,
8833 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8834 if self.mode().is_minimap() {
8835 return None;
8836 }
8837 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8838
8839 if self.edit_prediction_visible_in_cursor_popover(true) {
8840 return None;
8841 }
8842
8843 match &active_edit_prediction.completion {
8844 EditPrediction::MoveWithin { target, .. } => {
8845 let target_display_point = target.to_display_point(editor_snapshot);
8846
8847 if self.edit_prediction_requires_modifier() {
8848 if !self.edit_prediction_preview_is_active() {
8849 return None;
8850 }
8851
8852 self.render_edit_prediction_modifier_jump_popover(
8853 text_bounds,
8854 content_origin,
8855 visible_row_range,
8856 line_layouts,
8857 line_height,
8858 scroll_pixel_position,
8859 newest_selection_head,
8860 target_display_point,
8861 window,
8862 cx,
8863 )
8864 } else {
8865 self.render_edit_prediction_eager_jump_popover(
8866 text_bounds,
8867 content_origin,
8868 editor_snapshot,
8869 visible_row_range,
8870 scroll_top,
8871 scroll_bottom,
8872 line_height,
8873 scroll_pixel_position,
8874 target_display_point,
8875 editor_width,
8876 window,
8877 cx,
8878 )
8879 }
8880 }
8881 EditPrediction::Edit {
8882 display_mode: EditDisplayMode::Inline,
8883 ..
8884 } => None,
8885 EditPrediction::Edit {
8886 display_mode: EditDisplayMode::TabAccept,
8887 edits,
8888 ..
8889 } => {
8890 let range = &edits.first()?.0;
8891 let target_display_point = range.end.to_display_point(editor_snapshot);
8892
8893 self.render_edit_prediction_end_of_line_popover(
8894 "Accept",
8895 editor_snapshot,
8896 visible_row_range,
8897 target_display_point,
8898 line_height,
8899 scroll_pixel_position,
8900 content_origin,
8901 editor_width,
8902 window,
8903 cx,
8904 )
8905 }
8906 EditPrediction::Edit {
8907 edits,
8908 edit_preview,
8909 display_mode: EditDisplayMode::DiffPopover,
8910 snapshot,
8911 } => self.render_edit_prediction_diff_popover(
8912 text_bounds,
8913 content_origin,
8914 right_margin,
8915 editor_snapshot,
8916 visible_row_range,
8917 line_layouts,
8918 line_height,
8919 scroll_position,
8920 scroll_pixel_position,
8921 newest_selection_head,
8922 editor_width,
8923 style,
8924 edits,
8925 edit_preview,
8926 snapshot,
8927 window,
8928 cx,
8929 ),
8930 EditPrediction::MoveOutside { snapshot, .. } => {
8931 let mut element = self
8932 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8933 .into_any();
8934
8935 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8936 let origin_x = text_bounds.size.width - size.width - px(30.);
8937 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8938 element.prepaint_at(origin, window, cx);
8939
8940 Some((element, origin))
8941 }
8942 }
8943 }
8944
8945 fn render_edit_prediction_modifier_jump_popover(
8946 &mut self,
8947 text_bounds: &Bounds<Pixels>,
8948 content_origin: gpui::Point<Pixels>,
8949 visible_row_range: Range<DisplayRow>,
8950 line_layouts: &[LineWithInvisibles],
8951 line_height: Pixels,
8952 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8953 newest_selection_head: Option<DisplayPoint>,
8954 target_display_point: DisplayPoint,
8955 window: &mut Window,
8956 cx: &mut App,
8957 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8958 let scrolled_content_origin =
8959 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8960
8961 const SCROLL_PADDING_Y: Pixels = px(12.);
8962
8963 if target_display_point.row() < visible_row_range.start {
8964 return self.render_edit_prediction_scroll_popover(
8965 |_| SCROLL_PADDING_Y,
8966 IconName::ArrowUp,
8967 visible_row_range,
8968 line_layouts,
8969 newest_selection_head,
8970 scrolled_content_origin,
8971 window,
8972 cx,
8973 );
8974 } else if target_display_point.row() >= visible_row_range.end {
8975 return self.render_edit_prediction_scroll_popover(
8976 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8977 IconName::ArrowDown,
8978 visible_row_range,
8979 line_layouts,
8980 newest_selection_head,
8981 scrolled_content_origin,
8982 window,
8983 cx,
8984 );
8985 }
8986
8987 const POLE_WIDTH: Pixels = px(2.);
8988
8989 let line_layout =
8990 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8991 let target_column = target_display_point.column() as usize;
8992
8993 let target_x = line_layout.x_for_index(target_column);
8994 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8995 - scroll_pixel_position.y;
8996
8997 let flag_on_right = target_x < text_bounds.size.width / 2.;
8998
8999 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9000 border_color.l += 0.001;
9001
9002 let mut element = v_flex()
9003 .items_end()
9004 .when(flag_on_right, |el| el.items_start())
9005 .child(if flag_on_right {
9006 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9007 .rounded_bl(px(0.))
9008 .rounded_tl(px(0.))
9009 .border_l_2()
9010 .border_color(border_color)
9011 } else {
9012 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9013 .rounded_br(px(0.))
9014 .rounded_tr(px(0.))
9015 .border_r_2()
9016 .border_color(border_color)
9017 })
9018 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9019 .into_any();
9020
9021 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9022
9023 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9024 - point(
9025 if flag_on_right {
9026 POLE_WIDTH
9027 } else {
9028 size.width - POLE_WIDTH
9029 },
9030 size.height - line_height,
9031 );
9032
9033 origin.x = origin.x.max(content_origin.x);
9034
9035 element.prepaint_at(origin, window, cx);
9036
9037 Some((element, origin))
9038 }
9039
9040 fn render_edit_prediction_scroll_popover(
9041 &mut self,
9042 to_y: impl Fn(Size<Pixels>) -> Pixels,
9043 scroll_icon: IconName,
9044 visible_row_range: Range<DisplayRow>,
9045 line_layouts: &[LineWithInvisibles],
9046 newest_selection_head: Option<DisplayPoint>,
9047 scrolled_content_origin: gpui::Point<Pixels>,
9048 window: &mut Window,
9049 cx: &mut App,
9050 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9051 let mut element = self
9052 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9053 .into_any();
9054
9055 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9056
9057 let cursor = newest_selection_head?;
9058 let cursor_row_layout =
9059 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9060 let cursor_column = cursor.column() as usize;
9061
9062 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9063
9064 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9065
9066 element.prepaint_at(origin, window, cx);
9067 Some((element, origin))
9068 }
9069
9070 fn render_edit_prediction_eager_jump_popover(
9071 &mut self,
9072 text_bounds: &Bounds<Pixels>,
9073 content_origin: gpui::Point<Pixels>,
9074 editor_snapshot: &EditorSnapshot,
9075 visible_row_range: Range<DisplayRow>,
9076 scroll_top: ScrollOffset,
9077 scroll_bottom: ScrollOffset,
9078 line_height: Pixels,
9079 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9080 target_display_point: DisplayPoint,
9081 editor_width: Pixels,
9082 window: &mut Window,
9083 cx: &mut App,
9084 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9085 if target_display_point.row().as_f64() < scroll_top {
9086 let mut element = self
9087 .render_edit_prediction_line_popover(
9088 "Jump to Edit",
9089 Some(IconName::ArrowUp),
9090 window,
9091 cx,
9092 )
9093 .into_any();
9094
9095 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9096 let offset = point(
9097 (text_bounds.size.width - size.width) / 2.,
9098 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9099 );
9100
9101 let origin = text_bounds.origin + offset;
9102 element.prepaint_at(origin, window, cx);
9103 Some((element, origin))
9104 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9105 let mut element = self
9106 .render_edit_prediction_line_popover(
9107 "Jump to Edit",
9108 Some(IconName::ArrowDown),
9109 window,
9110 cx,
9111 )
9112 .into_any();
9113
9114 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9115 let offset = point(
9116 (text_bounds.size.width - size.width) / 2.,
9117 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9118 );
9119
9120 let origin = text_bounds.origin + offset;
9121 element.prepaint_at(origin, window, cx);
9122 Some((element, origin))
9123 } else {
9124 self.render_edit_prediction_end_of_line_popover(
9125 "Jump to Edit",
9126 editor_snapshot,
9127 visible_row_range,
9128 target_display_point,
9129 line_height,
9130 scroll_pixel_position,
9131 content_origin,
9132 editor_width,
9133 window,
9134 cx,
9135 )
9136 }
9137 }
9138
9139 fn render_edit_prediction_end_of_line_popover(
9140 self: &mut Editor,
9141 label: &'static str,
9142 editor_snapshot: &EditorSnapshot,
9143 visible_row_range: Range<DisplayRow>,
9144 target_display_point: DisplayPoint,
9145 line_height: Pixels,
9146 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9147 content_origin: gpui::Point<Pixels>,
9148 editor_width: Pixels,
9149 window: &mut Window,
9150 cx: &mut App,
9151 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9152 let target_line_end = DisplayPoint::new(
9153 target_display_point.row(),
9154 editor_snapshot.line_len(target_display_point.row()),
9155 );
9156
9157 let mut element = self
9158 .render_edit_prediction_line_popover(label, None, window, cx)
9159 .into_any();
9160
9161 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9162
9163 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9164
9165 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9166 let mut origin = start_point
9167 + line_origin
9168 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9169 origin.x = origin.x.max(content_origin.x);
9170
9171 let max_x = content_origin.x + editor_width - size.width;
9172
9173 if origin.x > max_x {
9174 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9175
9176 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9177 origin.y += offset;
9178 IconName::ArrowUp
9179 } else {
9180 origin.y -= offset;
9181 IconName::ArrowDown
9182 };
9183
9184 element = self
9185 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9186 .into_any();
9187
9188 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9189
9190 origin.x = content_origin.x + editor_width - size.width - px(2.);
9191 }
9192
9193 element.prepaint_at(origin, window, cx);
9194 Some((element, origin))
9195 }
9196
9197 fn render_edit_prediction_diff_popover(
9198 self: &Editor,
9199 text_bounds: &Bounds<Pixels>,
9200 content_origin: gpui::Point<Pixels>,
9201 right_margin: Pixels,
9202 editor_snapshot: &EditorSnapshot,
9203 visible_row_range: Range<DisplayRow>,
9204 line_layouts: &[LineWithInvisibles],
9205 line_height: Pixels,
9206 scroll_position: gpui::Point<ScrollOffset>,
9207 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9208 newest_selection_head: Option<DisplayPoint>,
9209 editor_width: Pixels,
9210 style: &EditorStyle,
9211 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9212 edit_preview: &Option<language::EditPreview>,
9213 snapshot: &language::BufferSnapshot,
9214 window: &mut Window,
9215 cx: &mut App,
9216 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9217 let edit_start = edits
9218 .first()
9219 .unwrap()
9220 .0
9221 .start
9222 .to_display_point(editor_snapshot);
9223 let edit_end = edits
9224 .last()
9225 .unwrap()
9226 .0
9227 .end
9228 .to_display_point(editor_snapshot);
9229
9230 let is_visible = visible_row_range.contains(&edit_start.row())
9231 || visible_row_range.contains(&edit_end.row());
9232 if !is_visible {
9233 return None;
9234 }
9235
9236 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9237 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9238 } else {
9239 // Fallback for providers without edit_preview
9240 crate::edit_prediction_fallback_text(edits, cx)
9241 };
9242
9243 let styled_text = highlighted_edits.to_styled_text(&style.text);
9244 let line_count = highlighted_edits.text.lines().count();
9245
9246 const BORDER_WIDTH: Pixels = px(1.);
9247
9248 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9249 let has_keybind = keybind.is_some();
9250
9251 let mut element = h_flex()
9252 .items_start()
9253 .child(
9254 h_flex()
9255 .bg(cx.theme().colors().editor_background)
9256 .border(BORDER_WIDTH)
9257 .shadow_xs()
9258 .border_color(cx.theme().colors().border)
9259 .rounded_l_lg()
9260 .when(line_count > 1, |el| el.rounded_br_lg())
9261 .pr_1()
9262 .child(styled_text),
9263 )
9264 .child(
9265 h_flex()
9266 .h(line_height + BORDER_WIDTH * 2.)
9267 .px_1p5()
9268 .gap_1()
9269 // Workaround: For some reason, there's a gap if we don't do this
9270 .ml(-BORDER_WIDTH)
9271 .shadow(vec![gpui::BoxShadow {
9272 color: gpui::black().opacity(0.05),
9273 offset: point(px(1.), px(1.)),
9274 blur_radius: px(2.),
9275 spread_radius: px(0.),
9276 }])
9277 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9278 .border(BORDER_WIDTH)
9279 .border_color(cx.theme().colors().border)
9280 .rounded_r_lg()
9281 .id("edit_prediction_diff_popover_keybind")
9282 .when(!has_keybind, |el| {
9283 let status_colors = cx.theme().status();
9284
9285 el.bg(status_colors.error_background)
9286 .border_color(status_colors.error.opacity(0.6))
9287 .child(Icon::new(IconName::Info).color(Color::Error))
9288 .cursor_default()
9289 .hoverable_tooltip(move |_window, cx| {
9290 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9291 })
9292 })
9293 .children(keybind),
9294 )
9295 .into_any();
9296
9297 let longest_row =
9298 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9299 let longest_line_width = if visible_row_range.contains(&longest_row) {
9300 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9301 } else {
9302 layout_line(
9303 longest_row,
9304 editor_snapshot,
9305 style,
9306 editor_width,
9307 |_| false,
9308 window,
9309 cx,
9310 )
9311 .width
9312 };
9313
9314 let viewport_bounds =
9315 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9316 right: -right_margin,
9317 ..Default::default()
9318 });
9319
9320 let x_after_longest = Pixels::from(
9321 ScrollPixelOffset::from(
9322 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9323 ) - scroll_pixel_position.x,
9324 );
9325
9326 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9327
9328 // Fully visible if it can be displayed within the window (allow overlapping other
9329 // panes). However, this is only allowed if the popover starts within text_bounds.
9330 let can_position_to_the_right = x_after_longest < text_bounds.right()
9331 && x_after_longest + element_bounds.width < viewport_bounds.right();
9332
9333 let mut origin = if can_position_to_the_right {
9334 point(
9335 x_after_longest,
9336 text_bounds.origin.y
9337 + Pixels::from(
9338 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9339 - scroll_pixel_position.y,
9340 ),
9341 )
9342 } else {
9343 let cursor_row = newest_selection_head.map(|head| head.row());
9344 let above_edit = edit_start
9345 .row()
9346 .0
9347 .checked_sub(line_count as u32)
9348 .map(DisplayRow);
9349 let below_edit = Some(edit_end.row() + 1);
9350 let above_cursor =
9351 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9352 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9353
9354 // Place the edit popover adjacent to the edit if there is a location
9355 // available that is onscreen and does not obscure the cursor. Otherwise,
9356 // place it adjacent to the cursor.
9357 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9358 .into_iter()
9359 .flatten()
9360 .find(|&start_row| {
9361 let end_row = start_row + line_count as u32;
9362 visible_row_range.contains(&start_row)
9363 && visible_row_range.contains(&end_row)
9364 && cursor_row
9365 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9366 })?;
9367
9368 content_origin
9369 + point(
9370 Pixels::from(-scroll_pixel_position.x),
9371 Pixels::from(
9372 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9373 ),
9374 )
9375 };
9376
9377 origin.x -= BORDER_WIDTH;
9378
9379 window.defer_draw(element, origin, 1);
9380
9381 // Do not return an element, since it will already be drawn due to defer_draw.
9382 None
9383 }
9384
9385 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9386 px(30.)
9387 }
9388
9389 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9390 if self.read_only(cx) {
9391 cx.theme().players().read_only()
9392 } else {
9393 self.style.as_ref().unwrap().local_player
9394 }
9395 }
9396
9397 fn render_edit_prediction_accept_keybind(
9398 &self,
9399 window: &mut Window,
9400 cx: &mut App,
9401 ) -> Option<AnyElement> {
9402 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9403 let accept_keystroke = accept_binding.keystroke()?;
9404
9405 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9406
9407 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9408 Color::Accent
9409 } else {
9410 Color::Muted
9411 };
9412
9413 h_flex()
9414 .px_0p5()
9415 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9416 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9417 .text_size(TextSize::XSmall.rems(cx))
9418 .child(h_flex().children(ui::render_modifiers(
9419 accept_keystroke.modifiers(),
9420 PlatformStyle::platform(),
9421 Some(modifiers_color),
9422 Some(IconSize::XSmall.rems().into()),
9423 true,
9424 )))
9425 .when(is_platform_style_mac, |parent| {
9426 parent.child(accept_keystroke.key().to_string())
9427 })
9428 .when(!is_platform_style_mac, |parent| {
9429 parent.child(
9430 Key::new(
9431 util::capitalize(accept_keystroke.key()),
9432 Some(Color::Default),
9433 )
9434 .size(Some(IconSize::XSmall.rems().into())),
9435 )
9436 })
9437 .into_any()
9438 .into()
9439 }
9440
9441 fn render_edit_prediction_line_popover(
9442 &self,
9443 label: impl Into<SharedString>,
9444 icon: Option<IconName>,
9445 window: &mut Window,
9446 cx: &mut App,
9447 ) -> Stateful<Div> {
9448 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9449
9450 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9451 let has_keybind = keybind.is_some();
9452
9453 h_flex()
9454 .id("ep-line-popover")
9455 .py_0p5()
9456 .pl_1()
9457 .pr(padding_right)
9458 .gap_1()
9459 .rounded_md()
9460 .border_1()
9461 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9462 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9463 .shadow_xs()
9464 .when(!has_keybind, |el| {
9465 let status_colors = cx.theme().status();
9466
9467 el.bg(status_colors.error_background)
9468 .border_color(status_colors.error.opacity(0.6))
9469 .pl_2()
9470 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9471 .cursor_default()
9472 .hoverable_tooltip(move |_window, cx| {
9473 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9474 })
9475 })
9476 .children(keybind)
9477 .child(
9478 Label::new(label)
9479 .size(LabelSize::Small)
9480 .when(!has_keybind, |el| {
9481 el.color(cx.theme().status().error.into()).strikethrough()
9482 }),
9483 )
9484 .when(!has_keybind, |el| {
9485 el.child(
9486 h_flex().ml_1().child(
9487 Icon::new(IconName::Info)
9488 .size(IconSize::Small)
9489 .color(cx.theme().status().error.into()),
9490 ),
9491 )
9492 })
9493 .when_some(icon, |element, icon| {
9494 element.child(
9495 div()
9496 .mt(px(1.5))
9497 .child(Icon::new(icon).size(IconSize::Small)),
9498 )
9499 })
9500 }
9501
9502 fn render_edit_prediction_jump_outside_popover(
9503 &self,
9504 snapshot: &BufferSnapshot,
9505 window: &mut Window,
9506 cx: &mut App,
9507 ) -> Stateful<Div> {
9508 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9509 let has_keybind = keybind.is_some();
9510
9511 let file_name = snapshot
9512 .file()
9513 .map(|file| SharedString::new(file.file_name(cx)))
9514 .unwrap_or(SharedString::new_static("untitled"));
9515
9516 h_flex()
9517 .id("ep-jump-outside-popover")
9518 .py_1()
9519 .px_2()
9520 .gap_1()
9521 .rounded_md()
9522 .border_1()
9523 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9524 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9525 .shadow_xs()
9526 .when(!has_keybind, |el| {
9527 let status_colors = cx.theme().status();
9528
9529 el.bg(status_colors.error_background)
9530 .border_color(status_colors.error.opacity(0.6))
9531 .pl_2()
9532 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9533 .cursor_default()
9534 .hoverable_tooltip(move |_window, cx| {
9535 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9536 })
9537 })
9538 .children(keybind)
9539 .child(
9540 Label::new(file_name)
9541 .size(LabelSize::Small)
9542 .buffer_font(cx)
9543 .when(!has_keybind, |el| {
9544 el.color(cx.theme().status().error.into()).strikethrough()
9545 }),
9546 )
9547 .when(!has_keybind, |el| {
9548 el.child(
9549 h_flex().ml_1().child(
9550 Icon::new(IconName::Info)
9551 .size(IconSize::Small)
9552 .color(cx.theme().status().error.into()),
9553 ),
9554 )
9555 })
9556 .child(
9557 div()
9558 .mt(px(1.5))
9559 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9560 )
9561 }
9562
9563 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9564 let accent_color = cx.theme().colors().text_accent;
9565 let editor_bg_color = cx.theme().colors().editor_background;
9566 editor_bg_color.blend(accent_color.opacity(0.1))
9567 }
9568
9569 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9570 let accent_color = cx.theme().colors().text_accent;
9571 let editor_bg_color = cx.theme().colors().editor_background;
9572 editor_bg_color.blend(accent_color.opacity(0.6))
9573 }
9574 fn get_prediction_provider_icon_name(
9575 provider: &Option<RegisteredEditPredictionDelegate>,
9576 ) -> IconName {
9577 match provider {
9578 Some(provider) => match provider.provider.name() {
9579 "copilot" => IconName::Copilot,
9580 "supermaven" => IconName::Supermaven,
9581 _ => IconName::ZedPredict,
9582 },
9583 None => IconName::ZedPredict,
9584 }
9585 }
9586
9587 fn render_edit_prediction_cursor_popover(
9588 &self,
9589 min_width: Pixels,
9590 max_width: Pixels,
9591 cursor_point: Point,
9592 style: &EditorStyle,
9593 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9594 _window: &Window,
9595 cx: &mut Context<Editor>,
9596 ) -> Option<AnyElement> {
9597 let provider = self.edit_prediction_provider.as_ref()?;
9598 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9599
9600 let is_refreshing = provider.provider.is_refreshing(cx);
9601
9602 fn pending_completion_container(icon: IconName) -> Div {
9603 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9604 }
9605
9606 let completion = match &self.active_edit_prediction {
9607 Some(prediction) => {
9608 if !self.has_visible_completions_menu() {
9609 const RADIUS: Pixels = px(6.);
9610 const BORDER_WIDTH: Pixels = px(1.);
9611
9612 return Some(
9613 h_flex()
9614 .elevation_2(cx)
9615 .border(BORDER_WIDTH)
9616 .border_color(cx.theme().colors().border)
9617 .when(accept_keystroke.is_none(), |el| {
9618 el.border_color(cx.theme().status().error)
9619 })
9620 .rounded(RADIUS)
9621 .rounded_tl(px(0.))
9622 .overflow_hidden()
9623 .child(div().px_1p5().child(match &prediction.completion {
9624 EditPrediction::MoveWithin { target, snapshot } => {
9625 use text::ToPoint as _;
9626 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9627 {
9628 Icon::new(IconName::ZedPredictDown)
9629 } else {
9630 Icon::new(IconName::ZedPredictUp)
9631 }
9632 }
9633 EditPrediction::MoveOutside { .. } => {
9634 // TODO [zeta2] custom icon for external jump?
9635 Icon::new(provider_icon)
9636 }
9637 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9638 }))
9639 .child(
9640 h_flex()
9641 .gap_1()
9642 .py_1()
9643 .px_2()
9644 .rounded_r(RADIUS - BORDER_WIDTH)
9645 .border_l_1()
9646 .border_color(cx.theme().colors().border)
9647 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9648 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9649 el.child(
9650 Label::new("Hold")
9651 .size(LabelSize::Small)
9652 .when(accept_keystroke.is_none(), |el| {
9653 el.strikethrough()
9654 })
9655 .line_height_style(LineHeightStyle::UiLabel),
9656 )
9657 })
9658 .id("edit_prediction_cursor_popover_keybind")
9659 .when(accept_keystroke.is_none(), |el| {
9660 let status_colors = cx.theme().status();
9661
9662 el.bg(status_colors.error_background)
9663 .border_color(status_colors.error.opacity(0.6))
9664 .child(Icon::new(IconName::Info).color(Color::Error))
9665 .cursor_default()
9666 .hoverable_tooltip(move |_window, cx| {
9667 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9668 .into()
9669 })
9670 })
9671 .when_some(
9672 accept_keystroke.as_ref(),
9673 |el, accept_keystroke| {
9674 el.child(h_flex().children(ui::render_modifiers(
9675 accept_keystroke.modifiers(),
9676 PlatformStyle::platform(),
9677 Some(Color::Default),
9678 Some(IconSize::XSmall.rems().into()),
9679 false,
9680 )))
9681 },
9682 ),
9683 )
9684 .into_any(),
9685 );
9686 }
9687
9688 self.render_edit_prediction_cursor_popover_preview(
9689 prediction,
9690 cursor_point,
9691 style,
9692 cx,
9693 )?
9694 }
9695
9696 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9697 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9698 stale_completion,
9699 cursor_point,
9700 style,
9701 cx,
9702 )?,
9703
9704 None => pending_completion_container(provider_icon)
9705 .child(Label::new("...").size(LabelSize::Small)),
9706 },
9707
9708 None => pending_completion_container(provider_icon)
9709 .child(Label::new("...").size(LabelSize::Small)),
9710 };
9711
9712 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9713 completion
9714 .with_animation(
9715 "loading-completion",
9716 Animation::new(Duration::from_secs(2))
9717 .repeat()
9718 .with_easing(pulsating_between(0.4, 0.8)),
9719 |label, delta| label.opacity(delta),
9720 )
9721 .into_any_element()
9722 } else {
9723 completion.into_any_element()
9724 };
9725
9726 let has_completion = self.active_edit_prediction.is_some();
9727
9728 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9729 Some(
9730 h_flex()
9731 .min_w(min_width)
9732 .max_w(max_width)
9733 .flex_1()
9734 .elevation_2(cx)
9735 .border_color(cx.theme().colors().border)
9736 .child(
9737 div()
9738 .flex_1()
9739 .py_1()
9740 .px_2()
9741 .overflow_hidden()
9742 .child(completion),
9743 )
9744 .when_some(accept_keystroke, |el, accept_keystroke| {
9745 if !accept_keystroke.modifiers().modified() {
9746 return el;
9747 }
9748
9749 el.child(
9750 h_flex()
9751 .h_full()
9752 .border_l_1()
9753 .rounded_r_lg()
9754 .border_color(cx.theme().colors().border)
9755 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9756 .gap_1()
9757 .py_1()
9758 .px_2()
9759 .child(
9760 h_flex()
9761 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9762 .when(is_platform_style_mac, |parent| parent.gap_1())
9763 .child(h_flex().children(ui::render_modifiers(
9764 accept_keystroke.modifiers(),
9765 PlatformStyle::platform(),
9766 Some(if !has_completion {
9767 Color::Muted
9768 } else {
9769 Color::Default
9770 }),
9771 None,
9772 false,
9773 ))),
9774 )
9775 .child(Label::new("Preview").into_any_element())
9776 .opacity(if has_completion { 1.0 } else { 0.4 }),
9777 )
9778 })
9779 .into_any(),
9780 )
9781 }
9782
9783 fn render_edit_prediction_cursor_popover_preview(
9784 &self,
9785 completion: &EditPredictionState,
9786 cursor_point: Point,
9787 style: &EditorStyle,
9788 cx: &mut Context<Editor>,
9789 ) -> Option<Div> {
9790 use text::ToPoint as _;
9791
9792 fn render_relative_row_jump(
9793 prefix: impl Into<String>,
9794 current_row: u32,
9795 target_row: u32,
9796 ) -> Div {
9797 let (row_diff, arrow) = if target_row < current_row {
9798 (current_row - target_row, IconName::ArrowUp)
9799 } else {
9800 (target_row - current_row, IconName::ArrowDown)
9801 };
9802
9803 h_flex()
9804 .child(
9805 Label::new(format!("{}{}", prefix.into(), row_diff))
9806 .color(Color::Muted)
9807 .size(LabelSize::Small),
9808 )
9809 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9810 }
9811
9812 let supports_jump = self
9813 .edit_prediction_provider
9814 .as_ref()
9815 .map(|provider| provider.provider.supports_jump_to_edit())
9816 .unwrap_or(true);
9817
9818 match &completion.completion {
9819 EditPrediction::MoveWithin {
9820 target, snapshot, ..
9821 } => {
9822 if !supports_jump {
9823 return None;
9824 }
9825
9826 Some(
9827 h_flex()
9828 .px_2()
9829 .gap_2()
9830 .flex_1()
9831 .child(
9832 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9833 Icon::new(IconName::ZedPredictDown)
9834 } else {
9835 Icon::new(IconName::ZedPredictUp)
9836 },
9837 )
9838 .child(Label::new("Jump to Edit")),
9839 )
9840 }
9841 EditPrediction::MoveOutside { snapshot, .. } => {
9842 let file_name = snapshot
9843 .file()
9844 .map(|file| file.file_name(cx))
9845 .unwrap_or("untitled");
9846 Some(
9847 h_flex()
9848 .px_2()
9849 .gap_2()
9850 .flex_1()
9851 .child(Icon::new(IconName::ZedPredict))
9852 .child(Label::new(format!("Jump to {file_name}"))),
9853 )
9854 }
9855 EditPrediction::Edit {
9856 edits,
9857 edit_preview,
9858 snapshot,
9859 display_mode: _,
9860 } => {
9861 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9862
9863 let (highlighted_edits, has_more_lines) =
9864 if let Some(edit_preview) = edit_preview.as_ref() {
9865 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9866 .first_line_preview()
9867 } else {
9868 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9869 };
9870
9871 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9872 .with_default_highlights(&style.text, highlighted_edits.highlights);
9873
9874 let preview = h_flex()
9875 .gap_1()
9876 .min_w_16()
9877 .child(styled_text)
9878 .when(has_more_lines, |parent| parent.child("…"));
9879
9880 let left = if supports_jump && first_edit_row != cursor_point.row {
9881 render_relative_row_jump("", cursor_point.row, first_edit_row)
9882 .into_any_element()
9883 } else {
9884 let icon_name =
9885 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9886 Icon::new(icon_name).into_any_element()
9887 };
9888
9889 Some(
9890 h_flex()
9891 .h_full()
9892 .flex_1()
9893 .gap_2()
9894 .pr_1()
9895 .overflow_x_hidden()
9896 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9897 .child(left)
9898 .child(preview),
9899 )
9900 }
9901 }
9902 }
9903
9904 pub fn render_context_menu(
9905 &self,
9906 style: &EditorStyle,
9907 max_height_in_lines: u32,
9908 window: &mut Window,
9909 cx: &mut Context<Editor>,
9910 ) -> Option<AnyElement> {
9911 let menu = self.context_menu.borrow();
9912 let menu = menu.as_ref()?;
9913 if !menu.visible() {
9914 return None;
9915 };
9916 Some(menu.render(style, max_height_in_lines, window, cx))
9917 }
9918
9919 fn render_context_menu_aside(
9920 &mut self,
9921 max_size: Size<Pixels>,
9922 window: &mut Window,
9923 cx: &mut Context<Editor>,
9924 ) -> Option<AnyElement> {
9925 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9926 if menu.visible() {
9927 menu.render_aside(max_size, window, cx)
9928 } else {
9929 None
9930 }
9931 })
9932 }
9933
9934 fn hide_context_menu(
9935 &mut self,
9936 window: &mut Window,
9937 cx: &mut Context<Self>,
9938 ) -> Option<CodeContextMenu> {
9939 cx.notify();
9940 self.completion_tasks.clear();
9941 let context_menu = self.context_menu.borrow_mut().take();
9942 self.stale_edit_prediction_in_menu.take();
9943 self.update_visible_edit_prediction(window, cx);
9944 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9945 && let Some(completion_provider) = &self.completion_provider
9946 {
9947 completion_provider.selection_changed(None, window, cx);
9948 }
9949 context_menu
9950 }
9951
9952 fn show_snippet_choices(
9953 &mut self,
9954 choices: &Vec<String>,
9955 selection: Range<Anchor>,
9956 cx: &mut Context<Self>,
9957 ) {
9958 let Some((_, buffer, _)) = self
9959 .buffer()
9960 .read(cx)
9961 .excerpt_containing(selection.start, cx)
9962 else {
9963 return;
9964 };
9965 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9966 else {
9967 return;
9968 };
9969 if buffer != end_buffer {
9970 log::error!("expected anchor range to have matching buffer IDs");
9971 return;
9972 }
9973
9974 let id = post_inc(&mut self.next_completion_id);
9975 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9976 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9977 CompletionsMenu::new_snippet_choices(
9978 id,
9979 true,
9980 choices,
9981 selection,
9982 buffer,
9983 snippet_sort_order,
9984 ),
9985 ));
9986 }
9987
9988 pub fn insert_snippet(
9989 &mut self,
9990 insertion_ranges: &[Range<MultiBufferOffset>],
9991 snippet: Snippet,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) -> Result<()> {
9995 struct Tabstop<T> {
9996 is_end_tabstop: bool,
9997 ranges: Vec<Range<T>>,
9998 choices: Option<Vec<String>>,
9999 }
10000
10001 let tabstops = self.buffer.update(cx, |buffer, cx| {
10002 let snippet_text: Arc<str> = snippet.text.clone().into();
10003 let edits = insertion_ranges
10004 .iter()
10005 .cloned()
10006 .map(|range| (range, snippet_text.clone()));
10007 let autoindent_mode = AutoindentMode::Block {
10008 original_indent_columns: Vec::new(),
10009 };
10010 buffer.edit(edits, Some(autoindent_mode), cx);
10011
10012 let snapshot = &*buffer.read(cx);
10013 let snippet = &snippet;
10014 snippet
10015 .tabstops
10016 .iter()
10017 .map(|tabstop| {
10018 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10019 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10020 });
10021 let mut tabstop_ranges = tabstop
10022 .ranges
10023 .iter()
10024 .flat_map(|tabstop_range| {
10025 let mut delta = 0_isize;
10026 insertion_ranges.iter().map(move |insertion_range| {
10027 let insertion_start = insertion_range.start + delta;
10028 delta += snippet.text.len() as isize
10029 - (insertion_range.end - insertion_range.start) as isize;
10030
10031 let start =
10032 (insertion_start + tabstop_range.start).min(snapshot.len());
10033 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10034 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10035 })
10036 })
10037 .collect::<Vec<_>>();
10038 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10039
10040 Tabstop {
10041 is_end_tabstop,
10042 ranges: tabstop_ranges,
10043 choices: tabstop.choices.clone(),
10044 }
10045 })
10046 .collect::<Vec<_>>()
10047 });
10048 if let Some(tabstop) = tabstops.first() {
10049 self.change_selections(Default::default(), window, cx, |s| {
10050 // Reverse order so that the first range is the newest created selection.
10051 // Completions will use it and autoscroll will prioritize it.
10052 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10053 });
10054
10055 if let Some(choices) = &tabstop.choices
10056 && let Some(selection) = tabstop.ranges.first()
10057 {
10058 self.show_snippet_choices(choices, selection.clone(), cx)
10059 }
10060
10061 // If we're already at the last tabstop and it's at the end of the snippet,
10062 // we're done, we don't need to keep the state around.
10063 if !tabstop.is_end_tabstop {
10064 let choices = tabstops
10065 .iter()
10066 .map(|tabstop| tabstop.choices.clone())
10067 .collect();
10068
10069 let ranges = tabstops
10070 .into_iter()
10071 .map(|tabstop| tabstop.ranges)
10072 .collect::<Vec<_>>();
10073
10074 self.snippet_stack.push(SnippetState {
10075 active_index: 0,
10076 ranges,
10077 choices,
10078 });
10079 }
10080
10081 // Check whether the just-entered snippet ends with an auto-closable bracket.
10082 if self.autoclose_regions.is_empty() {
10083 let snapshot = self.buffer.read(cx).snapshot(cx);
10084 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10085 let selection_head = selection.head();
10086 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10087 continue;
10088 };
10089
10090 let mut bracket_pair = None;
10091 let max_lookup_length = scope
10092 .brackets()
10093 .map(|(pair, _)| {
10094 pair.start
10095 .as_str()
10096 .chars()
10097 .count()
10098 .max(pair.end.as_str().chars().count())
10099 })
10100 .max();
10101 if let Some(max_lookup_length) = max_lookup_length {
10102 let next_text = snapshot
10103 .chars_at(selection_head)
10104 .take(max_lookup_length)
10105 .collect::<String>();
10106 let prev_text = snapshot
10107 .reversed_chars_at(selection_head)
10108 .take(max_lookup_length)
10109 .collect::<String>();
10110
10111 for (pair, enabled) in scope.brackets() {
10112 if enabled
10113 && pair.close
10114 && prev_text.starts_with(pair.start.as_str())
10115 && next_text.starts_with(pair.end.as_str())
10116 {
10117 bracket_pair = Some(pair.clone());
10118 break;
10119 }
10120 }
10121 }
10122
10123 if let Some(pair) = bracket_pair {
10124 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10125 let autoclose_enabled =
10126 self.use_autoclose && snapshot_settings.use_autoclose;
10127 if autoclose_enabled {
10128 let start = snapshot.anchor_after(selection_head);
10129 let end = snapshot.anchor_after(selection_head);
10130 self.autoclose_regions.push(AutocloseRegion {
10131 selection_id: selection.id,
10132 range: start..end,
10133 pair,
10134 });
10135 }
10136 }
10137 }
10138 }
10139 }
10140 Ok(())
10141 }
10142
10143 pub fn move_to_next_snippet_tabstop(
10144 &mut self,
10145 window: &mut Window,
10146 cx: &mut Context<Self>,
10147 ) -> bool {
10148 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10149 }
10150
10151 pub fn move_to_prev_snippet_tabstop(
10152 &mut self,
10153 window: &mut Window,
10154 cx: &mut Context<Self>,
10155 ) -> bool {
10156 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10157 }
10158
10159 pub fn move_to_snippet_tabstop(
10160 &mut self,
10161 bias: Bias,
10162 window: &mut Window,
10163 cx: &mut Context<Self>,
10164 ) -> bool {
10165 if let Some(mut snippet) = self.snippet_stack.pop() {
10166 match bias {
10167 Bias::Left => {
10168 if snippet.active_index > 0 {
10169 snippet.active_index -= 1;
10170 } else {
10171 self.snippet_stack.push(snippet);
10172 return false;
10173 }
10174 }
10175 Bias::Right => {
10176 if snippet.active_index + 1 < snippet.ranges.len() {
10177 snippet.active_index += 1;
10178 } else {
10179 self.snippet_stack.push(snippet);
10180 return false;
10181 }
10182 }
10183 }
10184 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10185 self.change_selections(Default::default(), window, cx, |s| {
10186 // Reverse order so that the first range is the newest created selection.
10187 // Completions will use it and autoscroll will prioritize it.
10188 s.select_ranges(current_ranges.iter().rev().cloned())
10189 });
10190
10191 if let Some(choices) = &snippet.choices[snippet.active_index]
10192 && let Some(selection) = current_ranges.first()
10193 {
10194 self.show_snippet_choices(choices, selection.clone(), cx);
10195 }
10196
10197 // If snippet state is not at the last tabstop, push it back on the stack
10198 if snippet.active_index + 1 < snippet.ranges.len() {
10199 self.snippet_stack.push(snippet);
10200 }
10201 return true;
10202 }
10203 }
10204
10205 false
10206 }
10207
10208 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10209 self.transact(window, cx, |this, window, cx| {
10210 this.select_all(&SelectAll, window, cx);
10211 this.insert("", window, cx);
10212 });
10213 }
10214
10215 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10216 if self.read_only(cx) {
10217 return;
10218 }
10219 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10220 self.transact(window, cx, |this, window, cx| {
10221 this.select_autoclose_pair(window, cx);
10222
10223 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10224
10225 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10226 if !this.linked_edit_ranges.is_empty() {
10227 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10228 let snapshot = this.buffer.read(cx).snapshot(cx);
10229
10230 for selection in selections.iter() {
10231 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10232 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10233 if selection_start.buffer_id != selection_end.buffer_id {
10234 continue;
10235 }
10236 if let Some(ranges) =
10237 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10238 {
10239 for (buffer, entries) in ranges {
10240 linked_ranges.entry(buffer).or_default().extend(entries);
10241 }
10242 }
10243 }
10244 }
10245
10246 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10247 for selection in &mut selections {
10248 if selection.is_empty() {
10249 let old_head = selection.head();
10250 let mut new_head =
10251 movement::left(&display_map, old_head.to_display_point(&display_map))
10252 .to_point(&display_map);
10253 if let Some((buffer, line_buffer_range)) = display_map
10254 .buffer_snapshot()
10255 .buffer_line_for_row(MultiBufferRow(old_head.row))
10256 {
10257 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10258 let indent_len = match indent_size.kind {
10259 IndentKind::Space => {
10260 buffer.settings_at(line_buffer_range.start, cx).tab_size
10261 }
10262 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10263 };
10264 if old_head.column <= indent_size.len && old_head.column > 0 {
10265 let indent_len = indent_len.get();
10266 new_head = cmp::min(
10267 new_head,
10268 MultiBufferPoint::new(
10269 old_head.row,
10270 ((old_head.column - 1) / indent_len) * indent_len,
10271 ),
10272 );
10273 }
10274 }
10275
10276 selection.set_head(new_head, SelectionGoal::None);
10277 }
10278 }
10279
10280 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10281 this.insert("", window, cx);
10282 let empty_str: Arc<str> = Arc::from("");
10283 for (buffer, edits) in linked_ranges {
10284 let snapshot = buffer.read(cx).snapshot();
10285 use text::ToPoint as TP;
10286
10287 let edits = edits
10288 .into_iter()
10289 .map(|range| {
10290 let end_point = TP::to_point(&range.end, &snapshot);
10291 let mut start_point = TP::to_point(&range.start, &snapshot);
10292
10293 if end_point == start_point {
10294 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10295 .saturating_sub(1);
10296 start_point =
10297 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10298 };
10299
10300 (start_point..end_point, empty_str.clone())
10301 })
10302 .sorted_by_key(|(range, _)| range.start)
10303 .collect::<Vec<_>>();
10304 buffer.update(cx, |this, cx| {
10305 this.edit(edits, None, cx);
10306 })
10307 }
10308 this.refresh_edit_prediction(true, false, window, cx);
10309 refresh_linked_ranges(this, window, cx);
10310 });
10311 }
10312
10313 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10314 if self.read_only(cx) {
10315 return;
10316 }
10317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10318 self.transact(window, cx, |this, window, cx| {
10319 this.change_selections(Default::default(), window, cx, |s| {
10320 s.move_with(|map, selection| {
10321 if selection.is_empty() {
10322 let cursor = movement::right(map, selection.head());
10323 selection.end = cursor;
10324 selection.reversed = true;
10325 selection.goal = SelectionGoal::None;
10326 }
10327 })
10328 });
10329 this.insert("", window, cx);
10330 this.refresh_edit_prediction(true, false, window, cx);
10331 });
10332 }
10333
10334 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10335 if self.mode.is_single_line() {
10336 cx.propagate();
10337 return;
10338 }
10339
10340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10341 if self.move_to_prev_snippet_tabstop(window, cx) {
10342 return;
10343 }
10344 self.outdent(&Outdent, window, cx);
10345 }
10346
10347 pub fn next_snippet_tabstop(
10348 &mut self,
10349 _: &NextSnippetTabstop,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10354 cx.propagate();
10355 return;
10356 }
10357
10358 if self.move_to_next_snippet_tabstop(window, cx) {
10359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10360 return;
10361 }
10362 cx.propagate();
10363 }
10364
10365 pub fn previous_snippet_tabstop(
10366 &mut self,
10367 _: &PreviousSnippetTabstop,
10368 window: &mut Window,
10369 cx: &mut Context<Self>,
10370 ) {
10371 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10372 cx.propagate();
10373 return;
10374 }
10375
10376 if self.move_to_prev_snippet_tabstop(window, cx) {
10377 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10378 return;
10379 }
10380 cx.propagate();
10381 }
10382
10383 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10384 if self.mode.is_single_line() {
10385 cx.propagate();
10386 return;
10387 }
10388
10389 if self.move_to_next_snippet_tabstop(window, cx) {
10390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10391 return;
10392 }
10393 if self.read_only(cx) {
10394 return;
10395 }
10396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10397 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10398 let buffer = self.buffer.read(cx);
10399 let snapshot = buffer.snapshot(cx);
10400 let rows_iter = selections.iter().map(|s| s.head().row);
10401 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10402
10403 let has_some_cursor_in_whitespace = selections
10404 .iter()
10405 .filter(|selection| selection.is_empty())
10406 .any(|selection| {
10407 let cursor = selection.head();
10408 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10409 cursor.column < current_indent.len
10410 });
10411
10412 let mut edits = Vec::new();
10413 let mut prev_edited_row = 0;
10414 let mut row_delta = 0;
10415 for selection in &mut selections {
10416 if selection.start.row != prev_edited_row {
10417 row_delta = 0;
10418 }
10419 prev_edited_row = selection.end.row;
10420
10421 // If the selection is non-empty, then increase the indentation of the selected lines.
10422 if !selection.is_empty() {
10423 row_delta =
10424 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10425 continue;
10426 }
10427
10428 let cursor = selection.head();
10429 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10430 if let Some(suggested_indent) =
10431 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10432 {
10433 // Don't do anything if already at suggested indent
10434 // and there is any other cursor which is not
10435 if has_some_cursor_in_whitespace
10436 && cursor.column == current_indent.len
10437 && current_indent.len == suggested_indent.len
10438 {
10439 continue;
10440 }
10441
10442 // Adjust line and move cursor to suggested indent
10443 // if cursor is not at suggested indent
10444 if cursor.column < suggested_indent.len
10445 && cursor.column <= current_indent.len
10446 && current_indent.len <= suggested_indent.len
10447 {
10448 selection.start = Point::new(cursor.row, suggested_indent.len);
10449 selection.end = selection.start;
10450 if row_delta == 0 {
10451 edits.extend(Buffer::edit_for_indent_size_adjustment(
10452 cursor.row,
10453 current_indent,
10454 suggested_indent,
10455 ));
10456 row_delta = suggested_indent.len - current_indent.len;
10457 }
10458 continue;
10459 }
10460
10461 // If current indent is more than suggested indent
10462 // only move cursor to current indent and skip indent
10463 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10464 selection.start = Point::new(cursor.row, current_indent.len);
10465 selection.end = selection.start;
10466 continue;
10467 }
10468 }
10469
10470 // Otherwise, insert a hard or soft tab.
10471 let settings = buffer.language_settings_at(cursor, cx);
10472 let tab_size = if settings.hard_tabs {
10473 IndentSize::tab()
10474 } else {
10475 let tab_size = settings.tab_size.get();
10476 let indent_remainder = snapshot
10477 .text_for_range(Point::new(cursor.row, 0)..cursor)
10478 .flat_map(str::chars)
10479 .fold(row_delta % tab_size, |counter: u32, c| {
10480 if c == '\t' {
10481 0
10482 } else {
10483 (counter + 1) % tab_size
10484 }
10485 });
10486
10487 let chars_to_next_tab_stop = tab_size - indent_remainder;
10488 IndentSize::spaces(chars_to_next_tab_stop)
10489 };
10490 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10491 selection.end = selection.start;
10492 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10493 row_delta += tab_size.len;
10494 }
10495
10496 self.transact(window, cx, |this, window, cx| {
10497 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10498 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10499 this.refresh_edit_prediction(true, false, window, cx);
10500 });
10501 }
10502
10503 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10504 if self.read_only(cx) {
10505 return;
10506 }
10507 if self.mode.is_single_line() {
10508 cx.propagate();
10509 return;
10510 }
10511
10512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10513 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10514 let mut prev_edited_row = 0;
10515 let mut row_delta = 0;
10516 let mut edits = Vec::new();
10517 let buffer = self.buffer.read(cx);
10518 let snapshot = buffer.snapshot(cx);
10519 for selection in &mut selections {
10520 if selection.start.row != prev_edited_row {
10521 row_delta = 0;
10522 }
10523 prev_edited_row = selection.end.row;
10524
10525 row_delta =
10526 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10527 }
10528
10529 self.transact(window, cx, |this, window, cx| {
10530 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10531 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10532 });
10533 }
10534
10535 fn indent_selection(
10536 buffer: &MultiBuffer,
10537 snapshot: &MultiBufferSnapshot,
10538 selection: &mut Selection<Point>,
10539 edits: &mut Vec<(Range<Point>, String)>,
10540 delta_for_start_row: u32,
10541 cx: &App,
10542 ) -> u32 {
10543 let settings = buffer.language_settings_at(selection.start, cx);
10544 let tab_size = settings.tab_size.get();
10545 let indent_kind = if settings.hard_tabs {
10546 IndentKind::Tab
10547 } else {
10548 IndentKind::Space
10549 };
10550 let mut start_row = selection.start.row;
10551 let mut end_row = selection.end.row + 1;
10552
10553 // If a selection ends at the beginning of a line, don't indent
10554 // that last line.
10555 if selection.end.column == 0 && selection.end.row > selection.start.row {
10556 end_row -= 1;
10557 }
10558
10559 // Avoid re-indenting a row that has already been indented by a
10560 // previous selection, but still update this selection's column
10561 // to reflect that indentation.
10562 if delta_for_start_row > 0 {
10563 start_row += 1;
10564 selection.start.column += delta_for_start_row;
10565 if selection.end.row == selection.start.row {
10566 selection.end.column += delta_for_start_row;
10567 }
10568 }
10569
10570 let mut delta_for_end_row = 0;
10571 let has_multiple_rows = start_row + 1 != end_row;
10572 for row in start_row..end_row {
10573 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10574 let indent_delta = match (current_indent.kind, indent_kind) {
10575 (IndentKind::Space, IndentKind::Space) => {
10576 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10577 IndentSize::spaces(columns_to_next_tab_stop)
10578 }
10579 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10580 (_, IndentKind::Tab) => IndentSize::tab(),
10581 };
10582
10583 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10584 0
10585 } else {
10586 selection.start.column
10587 };
10588 let row_start = Point::new(row, start);
10589 edits.push((
10590 row_start..row_start,
10591 indent_delta.chars().collect::<String>(),
10592 ));
10593
10594 // Update this selection's endpoints to reflect the indentation.
10595 if row == selection.start.row {
10596 selection.start.column += indent_delta.len;
10597 }
10598 if row == selection.end.row {
10599 selection.end.column += indent_delta.len;
10600 delta_for_end_row = indent_delta.len;
10601 }
10602 }
10603
10604 if selection.start.row == selection.end.row {
10605 delta_for_start_row + delta_for_end_row
10606 } else {
10607 delta_for_end_row
10608 }
10609 }
10610
10611 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10612 if self.read_only(cx) {
10613 return;
10614 }
10615 if self.mode.is_single_line() {
10616 cx.propagate();
10617 return;
10618 }
10619
10620 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10622 let selections = self.selections.all::<Point>(&display_map);
10623 let mut deletion_ranges = Vec::new();
10624 let mut last_outdent = None;
10625 {
10626 let buffer = self.buffer.read(cx);
10627 let snapshot = buffer.snapshot(cx);
10628 for selection in &selections {
10629 let settings = buffer.language_settings_at(selection.start, cx);
10630 let tab_size = settings.tab_size.get();
10631 let mut rows = selection.spanned_rows(false, &display_map);
10632
10633 // Avoid re-outdenting a row that has already been outdented by a
10634 // previous selection.
10635 if let Some(last_row) = last_outdent
10636 && last_row == rows.start
10637 {
10638 rows.start = rows.start.next_row();
10639 }
10640 let has_multiple_rows = rows.len() > 1;
10641 for row in rows.iter_rows() {
10642 let indent_size = snapshot.indent_size_for_line(row);
10643 if indent_size.len > 0 {
10644 let deletion_len = match indent_size.kind {
10645 IndentKind::Space => {
10646 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10647 if columns_to_prev_tab_stop == 0 {
10648 tab_size
10649 } else {
10650 columns_to_prev_tab_stop
10651 }
10652 }
10653 IndentKind::Tab => 1,
10654 };
10655 let start = if has_multiple_rows
10656 || deletion_len > selection.start.column
10657 || indent_size.len < selection.start.column
10658 {
10659 0
10660 } else {
10661 selection.start.column - deletion_len
10662 };
10663 deletion_ranges.push(
10664 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10665 );
10666 last_outdent = Some(row);
10667 }
10668 }
10669 }
10670 }
10671
10672 self.transact(window, cx, |this, window, cx| {
10673 this.buffer.update(cx, |buffer, cx| {
10674 let empty_str: Arc<str> = Arc::default();
10675 buffer.edit(
10676 deletion_ranges
10677 .into_iter()
10678 .map(|range| (range, empty_str.clone())),
10679 None,
10680 cx,
10681 );
10682 });
10683 let selections = this
10684 .selections
10685 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10686 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10687 });
10688 }
10689
10690 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10691 if self.read_only(cx) {
10692 return;
10693 }
10694 if self.mode.is_single_line() {
10695 cx.propagate();
10696 return;
10697 }
10698
10699 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10700 let selections = self
10701 .selections
10702 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10703 .into_iter()
10704 .map(|s| s.range());
10705
10706 self.transact(window, cx, |this, window, cx| {
10707 this.buffer.update(cx, |buffer, cx| {
10708 buffer.autoindent_ranges(selections, cx);
10709 });
10710 let selections = this
10711 .selections
10712 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10713 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10714 });
10715 }
10716
10717 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10718 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10719 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10720 let selections = self.selections.all::<Point>(&display_map);
10721
10722 let mut new_cursors = Vec::new();
10723 let mut edit_ranges = Vec::new();
10724 let mut selections = selections.iter().peekable();
10725 while let Some(selection) = selections.next() {
10726 let mut rows = selection.spanned_rows(false, &display_map);
10727
10728 // Accumulate contiguous regions of rows that we want to delete.
10729 while let Some(next_selection) = selections.peek() {
10730 let next_rows = next_selection.spanned_rows(false, &display_map);
10731 if next_rows.start <= rows.end {
10732 rows.end = next_rows.end;
10733 selections.next().unwrap();
10734 } else {
10735 break;
10736 }
10737 }
10738
10739 let buffer = display_map.buffer_snapshot();
10740 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10741 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10742 // If there's a line after the range, delete the \n from the end of the row range
10743 (
10744 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10745 rows.end,
10746 )
10747 } else {
10748 // If there isn't a line after the range, delete the \n from the line before the
10749 // start of the row range
10750 edit_start = edit_start.saturating_sub_usize(1);
10751 (buffer.len(), rows.start.previous_row())
10752 };
10753
10754 let text_layout_details = self.text_layout_details(window);
10755 let x = display_map.x_for_display_point(
10756 selection.head().to_display_point(&display_map),
10757 &text_layout_details,
10758 );
10759 let row = Point::new(target_row.0, 0)
10760 .to_display_point(&display_map)
10761 .row();
10762 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10763
10764 new_cursors.push((
10765 selection.id,
10766 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10767 SelectionGoal::None,
10768 ));
10769 edit_ranges.push(edit_start..edit_end);
10770 }
10771
10772 self.transact(window, cx, |this, window, cx| {
10773 let buffer = this.buffer.update(cx, |buffer, cx| {
10774 let empty_str: Arc<str> = Arc::default();
10775 buffer.edit(
10776 edit_ranges
10777 .into_iter()
10778 .map(|range| (range, empty_str.clone())),
10779 None,
10780 cx,
10781 );
10782 buffer.snapshot(cx)
10783 });
10784 let new_selections = new_cursors
10785 .into_iter()
10786 .map(|(id, cursor, goal)| {
10787 let cursor = cursor.to_point(&buffer);
10788 Selection {
10789 id,
10790 start: cursor,
10791 end: cursor,
10792 reversed: false,
10793 goal,
10794 }
10795 })
10796 .collect();
10797
10798 this.change_selections(Default::default(), window, cx, |s| {
10799 s.select(new_selections);
10800 });
10801 });
10802 }
10803
10804 pub fn join_lines_impl(
10805 &mut self,
10806 insert_whitespace: bool,
10807 window: &mut Window,
10808 cx: &mut Context<Self>,
10809 ) {
10810 if self.read_only(cx) {
10811 return;
10812 }
10813 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10814 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10815 let start = MultiBufferRow(selection.start.row);
10816 // Treat single line selections as if they include the next line. Otherwise this action
10817 // would do nothing for single line selections individual cursors.
10818 let end = if selection.start.row == selection.end.row {
10819 MultiBufferRow(selection.start.row + 1)
10820 } else {
10821 MultiBufferRow(selection.end.row)
10822 };
10823
10824 if let Some(last_row_range) = row_ranges.last_mut()
10825 && start <= last_row_range.end
10826 {
10827 last_row_range.end = end;
10828 continue;
10829 }
10830 row_ranges.push(start..end);
10831 }
10832
10833 let snapshot = self.buffer.read(cx).snapshot(cx);
10834 let mut cursor_positions = Vec::new();
10835 for row_range in &row_ranges {
10836 let anchor = snapshot.anchor_before(Point::new(
10837 row_range.end.previous_row().0,
10838 snapshot.line_len(row_range.end.previous_row()),
10839 ));
10840 cursor_positions.push(anchor..anchor);
10841 }
10842
10843 self.transact(window, cx, |this, window, cx| {
10844 for row_range in row_ranges.into_iter().rev() {
10845 for row in row_range.iter_rows().rev() {
10846 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10847 let next_line_row = row.next_row();
10848 let indent = snapshot.indent_size_for_line(next_line_row);
10849 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10850
10851 let replace =
10852 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10853 " "
10854 } else {
10855 ""
10856 };
10857
10858 this.buffer.update(cx, |buffer, cx| {
10859 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10860 });
10861 }
10862 }
10863
10864 this.change_selections(Default::default(), window, cx, |s| {
10865 s.select_anchor_ranges(cursor_positions)
10866 });
10867 });
10868 }
10869
10870 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10871 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10872 self.join_lines_impl(true, window, cx);
10873 }
10874
10875 pub fn sort_lines_case_sensitive(
10876 &mut self,
10877 _: &SortLinesCaseSensitive,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10882 }
10883
10884 pub fn sort_lines_by_length(
10885 &mut self,
10886 _: &SortLinesByLength,
10887 window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 self.manipulate_immutable_lines(window, cx, |lines| {
10891 lines.sort_by_key(|&line| line.chars().count())
10892 })
10893 }
10894
10895 pub fn sort_lines_case_insensitive(
10896 &mut self,
10897 _: &SortLinesCaseInsensitive,
10898 window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 self.manipulate_immutable_lines(window, cx, |lines| {
10902 lines.sort_by_key(|line| line.to_lowercase())
10903 })
10904 }
10905
10906 pub fn unique_lines_case_insensitive(
10907 &mut self,
10908 _: &UniqueLinesCaseInsensitive,
10909 window: &mut Window,
10910 cx: &mut Context<Self>,
10911 ) {
10912 self.manipulate_immutable_lines(window, cx, |lines| {
10913 let mut seen = HashSet::default();
10914 lines.retain(|line| seen.insert(line.to_lowercase()));
10915 })
10916 }
10917
10918 pub fn unique_lines_case_sensitive(
10919 &mut self,
10920 _: &UniqueLinesCaseSensitive,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 self.manipulate_immutable_lines(window, cx, |lines| {
10925 let mut seen = HashSet::default();
10926 lines.retain(|line| seen.insert(*line));
10927 })
10928 }
10929
10930 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10931 let snapshot = self.buffer.read(cx).snapshot(cx);
10932 for selection in self.selections.disjoint_anchors_arc().iter() {
10933 if snapshot
10934 .language_at(selection.start)
10935 .and_then(|lang| lang.config().wrap_characters.as_ref())
10936 .is_some()
10937 {
10938 return true;
10939 }
10940 }
10941 false
10942 }
10943
10944 fn wrap_selections_in_tag(
10945 &mut self,
10946 _: &WrapSelectionsInTag,
10947 window: &mut Window,
10948 cx: &mut Context<Self>,
10949 ) {
10950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10951
10952 let snapshot = self.buffer.read(cx).snapshot(cx);
10953
10954 let mut edits = Vec::new();
10955 let mut boundaries = Vec::new();
10956
10957 for selection in self
10958 .selections
10959 .all_adjusted(&self.display_snapshot(cx))
10960 .iter()
10961 {
10962 let Some(wrap_config) = snapshot
10963 .language_at(selection.start)
10964 .and_then(|lang| lang.config().wrap_characters.clone())
10965 else {
10966 continue;
10967 };
10968
10969 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10970 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10971
10972 let start_before = snapshot.anchor_before(selection.start);
10973 let end_after = snapshot.anchor_after(selection.end);
10974
10975 edits.push((start_before..start_before, open_tag));
10976 edits.push((end_after..end_after, close_tag));
10977
10978 boundaries.push((
10979 start_before,
10980 end_after,
10981 wrap_config.start_prefix.len(),
10982 wrap_config.end_suffix.len(),
10983 ));
10984 }
10985
10986 if edits.is_empty() {
10987 return;
10988 }
10989
10990 self.transact(window, cx, |this, window, cx| {
10991 let buffer = this.buffer.update(cx, |buffer, cx| {
10992 buffer.edit(edits, None, cx);
10993 buffer.snapshot(cx)
10994 });
10995
10996 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10997 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10998 boundaries.into_iter()
10999 {
11000 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11001 let close_offset = end_after
11002 .to_offset(&buffer)
11003 .saturating_sub_usize(end_suffix_len);
11004 new_selections.push(open_offset..open_offset);
11005 new_selections.push(close_offset..close_offset);
11006 }
11007
11008 this.change_selections(Default::default(), window, cx, |s| {
11009 s.select_ranges(new_selections);
11010 });
11011
11012 this.request_autoscroll(Autoscroll::fit(), cx);
11013 });
11014 }
11015
11016 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11017 let Some(project) = self.project.clone() else {
11018 return;
11019 };
11020 self.reload(project, window, cx)
11021 .detach_and_notify_err(window, cx);
11022 }
11023
11024 pub fn restore_file(
11025 &mut self,
11026 _: &::git::RestoreFile,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 ) {
11030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11031 let mut buffer_ids = HashSet::default();
11032 let snapshot = self.buffer().read(cx).snapshot(cx);
11033 for selection in self
11034 .selections
11035 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11036 {
11037 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11038 }
11039
11040 let buffer = self.buffer().read(cx);
11041 let ranges = buffer_ids
11042 .into_iter()
11043 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11044 .collect::<Vec<_>>();
11045
11046 self.restore_hunks_in_ranges(ranges, window, cx);
11047 }
11048
11049 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11050 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11051 let selections = self
11052 .selections
11053 .all(&self.display_snapshot(cx))
11054 .into_iter()
11055 .map(|s| s.range())
11056 .collect();
11057 self.restore_hunks_in_ranges(selections, window, cx);
11058 }
11059
11060 pub fn restore_hunks_in_ranges(
11061 &mut self,
11062 ranges: Vec<Range<Point>>,
11063 window: &mut Window,
11064 cx: &mut Context<Editor>,
11065 ) {
11066 let mut revert_changes = HashMap::default();
11067 let chunk_by = self
11068 .snapshot(window, cx)
11069 .hunks_for_ranges(ranges)
11070 .into_iter()
11071 .chunk_by(|hunk| hunk.buffer_id);
11072 for (buffer_id, hunks) in &chunk_by {
11073 let hunks = hunks.collect::<Vec<_>>();
11074 for hunk in &hunks {
11075 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11076 }
11077 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11078 }
11079 drop(chunk_by);
11080 if !revert_changes.is_empty() {
11081 self.transact(window, cx, |editor, window, cx| {
11082 editor.restore(revert_changes, window, cx);
11083 });
11084 }
11085 }
11086
11087 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11088 if let Some(status) = self
11089 .addons
11090 .iter()
11091 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11092 {
11093 return Some(status);
11094 }
11095 self.project
11096 .as_ref()?
11097 .read(cx)
11098 .status_for_buffer_id(buffer_id, cx)
11099 }
11100
11101 pub fn open_active_item_in_terminal(
11102 &mut self,
11103 _: &OpenInTerminal,
11104 window: &mut Window,
11105 cx: &mut Context<Self>,
11106 ) {
11107 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11108 let project_path = buffer.read(cx).project_path(cx)?;
11109 let project = self.project()?.read(cx);
11110 let entry = project.entry_for_path(&project_path, cx)?;
11111 let parent = match &entry.canonical_path {
11112 Some(canonical_path) => canonical_path.to_path_buf(),
11113 None => project.absolute_path(&project_path, cx)?,
11114 }
11115 .parent()?
11116 .to_path_buf();
11117 Some(parent)
11118 }) {
11119 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11120 }
11121 }
11122
11123 fn set_breakpoint_context_menu(
11124 &mut self,
11125 display_row: DisplayRow,
11126 position: Option<Anchor>,
11127 clicked_point: gpui::Point<Pixels>,
11128 window: &mut Window,
11129 cx: &mut Context<Self>,
11130 ) {
11131 let source = self
11132 .buffer
11133 .read(cx)
11134 .snapshot(cx)
11135 .anchor_before(Point::new(display_row.0, 0u32));
11136
11137 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11138
11139 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11140 self,
11141 source,
11142 clicked_point,
11143 context_menu,
11144 window,
11145 cx,
11146 );
11147 }
11148
11149 fn add_edit_breakpoint_block(
11150 &mut self,
11151 anchor: Anchor,
11152 breakpoint: &Breakpoint,
11153 edit_action: BreakpointPromptEditAction,
11154 window: &mut Window,
11155 cx: &mut Context<Self>,
11156 ) {
11157 let weak_editor = cx.weak_entity();
11158 let bp_prompt = cx.new(|cx| {
11159 BreakpointPromptEditor::new(
11160 weak_editor,
11161 anchor,
11162 breakpoint.clone(),
11163 edit_action,
11164 window,
11165 cx,
11166 )
11167 });
11168
11169 let height = bp_prompt.update(cx, |this, cx| {
11170 this.prompt
11171 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11172 });
11173 let cloned_prompt = bp_prompt.clone();
11174 let blocks = vec![BlockProperties {
11175 style: BlockStyle::Sticky,
11176 placement: BlockPlacement::Above(anchor),
11177 height: Some(height),
11178 render: Arc::new(move |cx| {
11179 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11180 cloned_prompt.clone().into_any_element()
11181 }),
11182 priority: 0,
11183 }];
11184
11185 let focus_handle = bp_prompt.focus_handle(cx);
11186 window.focus(&focus_handle);
11187
11188 let block_ids = self.insert_blocks(blocks, None, cx);
11189 bp_prompt.update(cx, |prompt, _| {
11190 prompt.add_block_ids(block_ids);
11191 });
11192 }
11193
11194 pub(crate) fn breakpoint_at_row(
11195 &self,
11196 row: u32,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) -> Option<(Anchor, Breakpoint)> {
11200 let snapshot = self.snapshot(window, cx);
11201 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11202
11203 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11204 }
11205
11206 pub(crate) fn breakpoint_at_anchor(
11207 &self,
11208 breakpoint_position: Anchor,
11209 snapshot: &EditorSnapshot,
11210 cx: &mut Context<Self>,
11211 ) -> Option<(Anchor, Breakpoint)> {
11212 let buffer = self
11213 .buffer
11214 .read(cx)
11215 .buffer_for_anchor(breakpoint_position, cx)?;
11216
11217 let enclosing_excerpt = breakpoint_position.excerpt_id;
11218 let buffer_snapshot = buffer.read(cx).snapshot();
11219
11220 let row = buffer_snapshot
11221 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11222 .row;
11223
11224 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11225 let anchor_end = snapshot
11226 .buffer_snapshot()
11227 .anchor_after(Point::new(row, line_len));
11228
11229 self.breakpoint_store
11230 .as_ref()?
11231 .read_with(cx, |breakpoint_store, cx| {
11232 breakpoint_store
11233 .breakpoints(
11234 &buffer,
11235 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11236 &buffer_snapshot,
11237 cx,
11238 )
11239 .next()
11240 .and_then(|(bp, _)| {
11241 let breakpoint_row = buffer_snapshot
11242 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11243 .row;
11244
11245 if breakpoint_row == row {
11246 snapshot
11247 .buffer_snapshot()
11248 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11249 .map(|position| (position, bp.bp.clone()))
11250 } else {
11251 None
11252 }
11253 })
11254 })
11255 }
11256
11257 pub fn edit_log_breakpoint(
11258 &mut self,
11259 _: &EditLogBreakpoint,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 if self.breakpoint_store.is_none() {
11264 return;
11265 }
11266
11267 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11268 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11269 message: None,
11270 state: BreakpointState::Enabled,
11271 condition: None,
11272 hit_condition: None,
11273 });
11274
11275 self.add_edit_breakpoint_block(
11276 anchor,
11277 &breakpoint,
11278 BreakpointPromptEditAction::Log,
11279 window,
11280 cx,
11281 );
11282 }
11283 }
11284
11285 fn breakpoints_at_cursors(
11286 &self,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11290 let snapshot = self.snapshot(window, cx);
11291 let cursors = self
11292 .selections
11293 .disjoint_anchors_arc()
11294 .iter()
11295 .map(|selection| {
11296 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11297
11298 let breakpoint_position = self
11299 .breakpoint_at_row(cursor_position.row, window, cx)
11300 .map(|bp| bp.0)
11301 .unwrap_or_else(|| {
11302 snapshot
11303 .display_snapshot
11304 .buffer_snapshot()
11305 .anchor_after(Point::new(cursor_position.row, 0))
11306 });
11307
11308 let breakpoint = self
11309 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11310 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11311
11312 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11313 })
11314 // 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.
11315 .collect::<HashMap<Anchor, _>>();
11316
11317 cursors.into_iter().collect()
11318 }
11319
11320 pub fn enable_breakpoint(
11321 &mut self,
11322 _: &crate::actions::EnableBreakpoint,
11323 window: &mut Window,
11324 cx: &mut Context<Self>,
11325 ) {
11326 if self.breakpoint_store.is_none() {
11327 return;
11328 }
11329
11330 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11331 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11332 continue;
11333 };
11334 self.edit_breakpoint_at_anchor(
11335 anchor,
11336 breakpoint,
11337 BreakpointEditAction::InvertState,
11338 cx,
11339 );
11340 }
11341 }
11342
11343 pub fn disable_breakpoint(
11344 &mut self,
11345 _: &crate::actions::DisableBreakpoint,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 if self.breakpoint_store.is_none() {
11350 return;
11351 }
11352
11353 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11354 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11355 continue;
11356 };
11357 self.edit_breakpoint_at_anchor(
11358 anchor,
11359 breakpoint,
11360 BreakpointEditAction::InvertState,
11361 cx,
11362 );
11363 }
11364 }
11365
11366 pub fn toggle_breakpoint(
11367 &mut self,
11368 _: &crate::actions::ToggleBreakpoint,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 ) {
11372 if self.breakpoint_store.is_none() {
11373 return;
11374 }
11375
11376 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11377 if let Some(breakpoint) = breakpoint {
11378 self.edit_breakpoint_at_anchor(
11379 anchor,
11380 breakpoint,
11381 BreakpointEditAction::Toggle,
11382 cx,
11383 );
11384 } else {
11385 self.edit_breakpoint_at_anchor(
11386 anchor,
11387 Breakpoint::new_standard(),
11388 BreakpointEditAction::Toggle,
11389 cx,
11390 );
11391 }
11392 }
11393 }
11394
11395 pub fn edit_breakpoint_at_anchor(
11396 &mut self,
11397 breakpoint_position: Anchor,
11398 breakpoint: Breakpoint,
11399 edit_action: BreakpointEditAction,
11400 cx: &mut Context<Self>,
11401 ) {
11402 let Some(breakpoint_store) = &self.breakpoint_store else {
11403 return;
11404 };
11405
11406 let Some(buffer) = self
11407 .buffer
11408 .read(cx)
11409 .buffer_for_anchor(breakpoint_position, cx)
11410 else {
11411 return;
11412 };
11413
11414 breakpoint_store.update(cx, |breakpoint_store, cx| {
11415 breakpoint_store.toggle_breakpoint(
11416 buffer,
11417 BreakpointWithPosition {
11418 position: breakpoint_position.text_anchor,
11419 bp: breakpoint,
11420 },
11421 edit_action,
11422 cx,
11423 );
11424 });
11425
11426 cx.notify();
11427 }
11428
11429 #[cfg(any(test, feature = "test-support"))]
11430 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11431 self.breakpoint_store.clone()
11432 }
11433
11434 pub fn prepare_restore_change(
11435 &self,
11436 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11437 hunk: &MultiBufferDiffHunk,
11438 cx: &mut App,
11439 ) -> Option<()> {
11440 if hunk.is_created_file() {
11441 return None;
11442 }
11443 let buffer = self.buffer.read(cx);
11444 let diff = buffer.diff_for(hunk.buffer_id)?;
11445 let buffer = buffer.buffer(hunk.buffer_id)?;
11446 let buffer = buffer.read(cx);
11447 let original_text = diff
11448 .read(cx)
11449 .base_text()
11450 .as_rope()
11451 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11452 let buffer_snapshot = buffer.snapshot();
11453 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11454 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11455 probe
11456 .0
11457 .start
11458 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11459 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11460 }) {
11461 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11462 Some(())
11463 } else {
11464 None
11465 }
11466 }
11467
11468 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11469 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11470 }
11471
11472 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11473 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11474 }
11475
11476 fn manipulate_lines<M>(
11477 &mut self,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 mut manipulate: M,
11481 ) where
11482 M: FnMut(&str) -> LineManipulationResult,
11483 {
11484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11485
11486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11487 let buffer = self.buffer.read(cx).snapshot(cx);
11488
11489 let mut edits = Vec::new();
11490
11491 let selections = self.selections.all::<Point>(&display_map);
11492 let mut selections = selections.iter().peekable();
11493 let mut contiguous_row_selections = Vec::new();
11494 let mut new_selections = Vec::new();
11495 let mut added_lines = 0;
11496 let mut removed_lines = 0;
11497
11498 while let Some(selection) = selections.next() {
11499 let (start_row, end_row) = consume_contiguous_rows(
11500 &mut contiguous_row_selections,
11501 selection,
11502 &display_map,
11503 &mut selections,
11504 );
11505
11506 let start_point = Point::new(start_row.0, 0);
11507 let end_point = Point::new(
11508 end_row.previous_row().0,
11509 buffer.line_len(end_row.previous_row()),
11510 );
11511 let text = buffer
11512 .text_for_range(start_point..end_point)
11513 .collect::<String>();
11514
11515 let LineManipulationResult {
11516 new_text,
11517 line_count_before,
11518 line_count_after,
11519 } = manipulate(&text);
11520
11521 edits.push((start_point..end_point, new_text));
11522
11523 // Selections must change based on added and removed line count
11524 let start_row =
11525 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11526 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11527 new_selections.push(Selection {
11528 id: selection.id,
11529 start: start_row,
11530 end: end_row,
11531 goal: SelectionGoal::None,
11532 reversed: selection.reversed,
11533 });
11534
11535 if line_count_after > line_count_before {
11536 added_lines += line_count_after - line_count_before;
11537 } else if line_count_before > line_count_after {
11538 removed_lines += line_count_before - line_count_after;
11539 }
11540 }
11541
11542 self.transact(window, cx, |this, window, cx| {
11543 let buffer = this.buffer.update(cx, |buffer, cx| {
11544 buffer.edit(edits, None, cx);
11545 buffer.snapshot(cx)
11546 });
11547
11548 // Recalculate offsets on newly edited buffer
11549 let new_selections = new_selections
11550 .iter()
11551 .map(|s| {
11552 let start_point = Point::new(s.start.0, 0);
11553 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11554 Selection {
11555 id: s.id,
11556 start: buffer.point_to_offset(start_point),
11557 end: buffer.point_to_offset(end_point),
11558 goal: s.goal,
11559 reversed: s.reversed,
11560 }
11561 })
11562 .collect();
11563
11564 this.change_selections(Default::default(), window, cx, |s| {
11565 s.select(new_selections);
11566 });
11567
11568 this.request_autoscroll(Autoscroll::fit(), cx);
11569 });
11570 }
11571
11572 fn manipulate_immutable_lines<Fn>(
11573 &mut self,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 mut callback: Fn,
11577 ) where
11578 Fn: FnMut(&mut Vec<&str>),
11579 {
11580 self.manipulate_lines(window, cx, |text| {
11581 let mut lines: Vec<&str> = text.split('\n').collect();
11582 let line_count_before = lines.len();
11583
11584 callback(&mut lines);
11585
11586 LineManipulationResult {
11587 new_text: lines.join("\n"),
11588 line_count_before,
11589 line_count_after: lines.len(),
11590 }
11591 });
11592 }
11593
11594 fn manipulate_mutable_lines<Fn>(
11595 &mut self,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 mut callback: Fn,
11599 ) where
11600 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11601 {
11602 self.manipulate_lines(window, cx, |text| {
11603 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11604 let line_count_before = lines.len();
11605
11606 callback(&mut lines);
11607
11608 LineManipulationResult {
11609 new_text: lines.join("\n"),
11610 line_count_before,
11611 line_count_after: lines.len(),
11612 }
11613 });
11614 }
11615
11616 pub fn convert_indentation_to_spaces(
11617 &mut self,
11618 _: &ConvertIndentationToSpaces,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 let settings = self.buffer.read(cx).language_settings(cx);
11623 let tab_size = settings.tab_size.get() as usize;
11624
11625 self.manipulate_mutable_lines(window, cx, |lines| {
11626 // Allocates a reasonably sized scratch buffer once for the whole loop
11627 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11628 // Avoids recomputing spaces that could be inserted many times
11629 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11630 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11631 .collect();
11632
11633 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11634 let mut chars = line.as_ref().chars();
11635 let mut col = 0;
11636 let mut changed = false;
11637
11638 for ch in chars.by_ref() {
11639 match ch {
11640 ' ' => {
11641 reindented_line.push(' ');
11642 col += 1;
11643 }
11644 '\t' => {
11645 // \t are converted to spaces depending on the current column
11646 let spaces_len = tab_size - (col % tab_size);
11647 reindented_line.extend(&space_cache[spaces_len - 1]);
11648 col += spaces_len;
11649 changed = true;
11650 }
11651 _ => {
11652 // If we dont append before break, the character is consumed
11653 reindented_line.push(ch);
11654 break;
11655 }
11656 }
11657 }
11658
11659 if !changed {
11660 reindented_line.clear();
11661 continue;
11662 }
11663 // Append the rest of the line and replace old reference with new one
11664 reindented_line.extend(chars);
11665 *line = Cow::Owned(reindented_line.clone());
11666 reindented_line.clear();
11667 }
11668 });
11669 }
11670
11671 pub fn convert_indentation_to_tabs(
11672 &mut self,
11673 _: &ConvertIndentationToTabs,
11674 window: &mut Window,
11675 cx: &mut Context<Self>,
11676 ) {
11677 let settings = self.buffer.read(cx).language_settings(cx);
11678 let tab_size = settings.tab_size.get() as usize;
11679
11680 self.manipulate_mutable_lines(window, cx, |lines| {
11681 // Allocates a reasonably sized buffer once for the whole loop
11682 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11683 // Avoids recomputing spaces that could be inserted many times
11684 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11685 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11686 .collect();
11687
11688 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11689 let mut chars = line.chars();
11690 let mut spaces_count = 0;
11691 let mut first_non_indent_char = None;
11692 let mut changed = false;
11693
11694 for ch in chars.by_ref() {
11695 match ch {
11696 ' ' => {
11697 // Keep track of spaces. Append \t when we reach tab_size
11698 spaces_count += 1;
11699 changed = true;
11700 if spaces_count == tab_size {
11701 reindented_line.push('\t');
11702 spaces_count = 0;
11703 }
11704 }
11705 '\t' => {
11706 reindented_line.push('\t');
11707 spaces_count = 0;
11708 }
11709 _ => {
11710 // Dont append it yet, we might have remaining spaces
11711 first_non_indent_char = Some(ch);
11712 break;
11713 }
11714 }
11715 }
11716
11717 if !changed {
11718 reindented_line.clear();
11719 continue;
11720 }
11721 // Remaining spaces that didn't make a full tab stop
11722 if spaces_count > 0 {
11723 reindented_line.extend(&space_cache[spaces_count - 1]);
11724 }
11725 // If we consume an extra character that was not indentation, add it back
11726 if let Some(extra_char) = first_non_indent_char {
11727 reindented_line.push(extra_char);
11728 }
11729 // Append the rest of the line and replace old reference with new one
11730 reindented_line.extend(chars);
11731 *line = Cow::Owned(reindented_line.clone());
11732 reindented_line.clear();
11733 }
11734 });
11735 }
11736
11737 pub fn convert_to_upper_case(
11738 &mut self,
11739 _: &ConvertToUpperCase,
11740 window: &mut Window,
11741 cx: &mut Context<Self>,
11742 ) {
11743 self.manipulate_text(window, cx, |text| text.to_uppercase())
11744 }
11745
11746 pub fn convert_to_lower_case(
11747 &mut self,
11748 _: &ConvertToLowerCase,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 self.manipulate_text(window, cx, |text| text.to_lowercase())
11753 }
11754
11755 pub fn convert_to_title_case(
11756 &mut self,
11757 _: &ConvertToTitleCase,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 self.manipulate_text(window, cx, |text| {
11762 text.split('\n')
11763 .map(|line| line.to_case(Case::Title))
11764 .join("\n")
11765 })
11766 }
11767
11768 pub fn convert_to_snake_case(
11769 &mut self,
11770 _: &ConvertToSnakeCase,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11775 }
11776
11777 pub fn convert_to_kebab_case(
11778 &mut self,
11779 _: &ConvertToKebabCase,
11780 window: &mut Window,
11781 cx: &mut Context<Self>,
11782 ) {
11783 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11784 }
11785
11786 pub fn convert_to_upper_camel_case(
11787 &mut self,
11788 _: &ConvertToUpperCamelCase,
11789 window: &mut Window,
11790 cx: &mut Context<Self>,
11791 ) {
11792 self.manipulate_text(window, cx, |text| {
11793 text.split('\n')
11794 .map(|line| line.to_case(Case::UpperCamel))
11795 .join("\n")
11796 })
11797 }
11798
11799 pub fn convert_to_lower_camel_case(
11800 &mut self,
11801 _: &ConvertToLowerCamelCase,
11802 window: &mut Window,
11803 cx: &mut Context<Self>,
11804 ) {
11805 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11806 }
11807
11808 pub fn convert_to_opposite_case(
11809 &mut self,
11810 _: &ConvertToOppositeCase,
11811 window: &mut Window,
11812 cx: &mut Context<Self>,
11813 ) {
11814 self.manipulate_text(window, cx, |text| {
11815 text.chars()
11816 .fold(String::with_capacity(text.len()), |mut t, c| {
11817 if c.is_uppercase() {
11818 t.extend(c.to_lowercase());
11819 } else {
11820 t.extend(c.to_uppercase());
11821 }
11822 t
11823 })
11824 })
11825 }
11826
11827 pub fn convert_to_sentence_case(
11828 &mut self,
11829 _: &ConvertToSentenceCase,
11830 window: &mut Window,
11831 cx: &mut Context<Self>,
11832 ) {
11833 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11834 }
11835
11836 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11837 self.manipulate_text(window, cx, |text| {
11838 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11839 if has_upper_case_characters {
11840 text.to_lowercase()
11841 } else {
11842 text.to_uppercase()
11843 }
11844 })
11845 }
11846
11847 pub fn convert_to_rot13(
11848 &mut self,
11849 _: &ConvertToRot13,
11850 window: &mut Window,
11851 cx: &mut Context<Self>,
11852 ) {
11853 self.manipulate_text(window, cx, |text| {
11854 text.chars()
11855 .map(|c| match c {
11856 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11857 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11858 _ => c,
11859 })
11860 .collect()
11861 })
11862 }
11863
11864 pub fn convert_to_rot47(
11865 &mut self,
11866 _: &ConvertToRot47,
11867 window: &mut Window,
11868 cx: &mut Context<Self>,
11869 ) {
11870 self.manipulate_text(window, cx, |text| {
11871 text.chars()
11872 .map(|c| {
11873 let code_point = c as u32;
11874 if code_point >= 33 && code_point <= 126 {
11875 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11876 }
11877 c
11878 })
11879 .collect()
11880 })
11881 }
11882
11883 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11884 where
11885 Fn: FnMut(&str) -> String,
11886 {
11887 let buffer = self.buffer.read(cx).snapshot(cx);
11888
11889 let mut new_selections = Vec::new();
11890 let mut edits = Vec::new();
11891 let mut selection_adjustment = 0isize;
11892
11893 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11894 let selection_is_empty = selection.is_empty();
11895
11896 let (start, end) = if selection_is_empty {
11897 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11898 (word_range.start, word_range.end)
11899 } else {
11900 (
11901 buffer.point_to_offset(selection.start),
11902 buffer.point_to_offset(selection.end),
11903 )
11904 };
11905
11906 let text = buffer.text_for_range(start..end).collect::<String>();
11907 let old_length = text.len() as isize;
11908 let text = callback(&text);
11909
11910 new_selections.push(Selection {
11911 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11912 end: MultiBufferOffset(
11913 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11914 ),
11915 goal: SelectionGoal::None,
11916 id: selection.id,
11917 reversed: selection.reversed,
11918 });
11919
11920 selection_adjustment += old_length - text.len() as isize;
11921
11922 edits.push((start..end, text));
11923 }
11924
11925 self.transact(window, cx, |this, window, cx| {
11926 this.buffer.update(cx, |buffer, cx| {
11927 buffer.edit(edits, None, cx);
11928 });
11929
11930 this.change_selections(Default::default(), window, cx, |s| {
11931 s.select(new_selections);
11932 });
11933
11934 this.request_autoscroll(Autoscroll::fit(), cx);
11935 });
11936 }
11937
11938 pub fn move_selection_on_drop(
11939 &mut self,
11940 selection: &Selection<Anchor>,
11941 target: DisplayPoint,
11942 is_cut: bool,
11943 window: &mut Window,
11944 cx: &mut Context<Self>,
11945 ) {
11946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11947 let buffer = display_map.buffer_snapshot();
11948 let mut edits = Vec::new();
11949 let insert_point = display_map
11950 .clip_point(target, Bias::Left)
11951 .to_point(&display_map);
11952 let text = buffer
11953 .text_for_range(selection.start..selection.end)
11954 .collect::<String>();
11955 if is_cut {
11956 edits.push(((selection.start..selection.end), String::new()));
11957 }
11958 let insert_anchor = buffer.anchor_before(insert_point);
11959 edits.push(((insert_anchor..insert_anchor), text));
11960 let last_edit_start = insert_anchor.bias_left(buffer);
11961 let last_edit_end = insert_anchor.bias_right(buffer);
11962 self.transact(window, cx, |this, window, cx| {
11963 this.buffer.update(cx, |buffer, cx| {
11964 buffer.edit(edits, None, cx);
11965 });
11966 this.change_selections(Default::default(), window, cx, |s| {
11967 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11968 });
11969 });
11970 }
11971
11972 pub fn clear_selection_drag_state(&mut self) {
11973 self.selection_drag_state = SelectionDragState::None;
11974 }
11975
11976 pub fn duplicate(
11977 &mut self,
11978 upwards: bool,
11979 whole_lines: bool,
11980 window: &mut Window,
11981 cx: &mut Context<Self>,
11982 ) {
11983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11984
11985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11986 let buffer = display_map.buffer_snapshot();
11987 let selections = self.selections.all::<Point>(&display_map);
11988
11989 let mut edits = Vec::new();
11990 let mut selections_iter = selections.iter().peekable();
11991 while let Some(selection) = selections_iter.next() {
11992 let mut rows = selection.spanned_rows(false, &display_map);
11993 // duplicate line-wise
11994 if whole_lines || selection.start == selection.end {
11995 // Avoid duplicating the same lines twice.
11996 while let Some(next_selection) = selections_iter.peek() {
11997 let next_rows = next_selection.spanned_rows(false, &display_map);
11998 if next_rows.start < rows.end {
11999 rows.end = next_rows.end;
12000 selections_iter.next().unwrap();
12001 } else {
12002 break;
12003 }
12004 }
12005
12006 // Copy the text from the selected row region and splice it either at the start
12007 // or end of the region.
12008 let start = Point::new(rows.start.0, 0);
12009 let end = Point::new(
12010 rows.end.previous_row().0,
12011 buffer.line_len(rows.end.previous_row()),
12012 );
12013
12014 let mut text = buffer.text_for_range(start..end).collect::<String>();
12015
12016 let insert_location = if upwards {
12017 // When duplicating upward, we need to insert before the current line.
12018 // If we're on the last line and it doesn't end with a newline,
12019 // we need to add a newline before the duplicated content.
12020 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12021 && buffer.max_point().column > 0
12022 && !text.ends_with('\n');
12023
12024 if needs_leading_newline {
12025 text.insert(0, '\n');
12026 end
12027 } else {
12028 text.push('\n');
12029 Point::new(rows.start.0, 0)
12030 }
12031 } else {
12032 text.push('\n');
12033 start
12034 };
12035 edits.push((insert_location..insert_location, text));
12036 } else {
12037 // duplicate character-wise
12038 let start = selection.start;
12039 let end = selection.end;
12040 let text = buffer.text_for_range(start..end).collect::<String>();
12041 edits.push((selection.end..selection.end, text));
12042 }
12043 }
12044
12045 self.transact(window, cx, |this, window, cx| {
12046 this.buffer.update(cx, |buffer, cx| {
12047 buffer.edit(edits, None, cx);
12048 });
12049
12050 // When duplicating upward with whole lines, move the cursor to the duplicated line
12051 if upwards && whole_lines {
12052 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12053
12054 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12055 let mut new_ranges = Vec::new();
12056 let selections = s.all::<Point>(&display_map);
12057 let mut selections_iter = selections.iter().peekable();
12058
12059 while let Some(first_selection) = selections_iter.next() {
12060 // Group contiguous selections together to find the total row span
12061 let mut group_selections = vec![first_selection];
12062 let mut rows = first_selection.spanned_rows(false, &display_map);
12063
12064 while let Some(next_selection) = selections_iter.peek() {
12065 let next_rows = next_selection.spanned_rows(false, &display_map);
12066 if next_rows.start < rows.end {
12067 rows.end = next_rows.end;
12068 group_selections.push(selections_iter.next().unwrap());
12069 } else {
12070 break;
12071 }
12072 }
12073
12074 let row_count = rows.end.0 - rows.start.0;
12075
12076 // Move all selections in this group up by the total number of duplicated rows
12077 for selection in group_selections {
12078 let new_start = Point::new(
12079 selection.start.row.saturating_sub(row_count),
12080 selection.start.column,
12081 );
12082
12083 let new_end = Point::new(
12084 selection.end.row.saturating_sub(row_count),
12085 selection.end.column,
12086 );
12087
12088 new_ranges.push(new_start..new_end);
12089 }
12090 }
12091
12092 s.select_ranges(new_ranges);
12093 });
12094 }
12095
12096 this.request_autoscroll(Autoscroll::fit(), cx);
12097 });
12098 }
12099
12100 pub fn duplicate_line_up(
12101 &mut self,
12102 _: &DuplicateLineUp,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 self.duplicate(true, true, window, cx);
12107 }
12108
12109 pub fn duplicate_line_down(
12110 &mut self,
12111 _: &DuplicateLineDown,
12112 window: &mut Window,
12113 cx: &mut Context<Self>,
12114 ) {
12115 self.duplicate(false, true, window, cx);
12116 }
12117
12118 pub fn duplicate_selection(
12119 &mut self,
12120 _: &DuplicateSelection,
12121 window: &mut Window,
12122 cx: &mut Context<Self>,
12123 ) {
12124 self.duplicate(false, false, window, cx);
12125 }
12126
12127 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12129 if self.mode.is_single_line() {
12130 cx.propagate();
12131 return;
12132 }
12133
12134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12135 let buffer = self.buffer.read(cx).snapshot(cx);
12136
12137 let mut edits = Vec::new();
12138 let mut unfold_ranges = Vec::new();
12139 let mut refold_creases = Vec::new();
12140
12141 let selections = self.selections.all::<Point>(&display_map);
12142 let mut selections = selections.iter().peekable();
12143 let mut contiguous_row_selections = Vec::new();
12144 let mut new_selections = Vec::new();
12145
12146 while let Some(selection) = selections.next() {
12147 // Find all the selections that span a contiguous row range
12148 let (start_row, end_row) = consume_contiguous_rows(
12149 &mut contiguous_row_selections,
12150 selection,
12151 &display_map,
12152 &mut selections,
12153 );
12154
12155 // Move the text spanned by the row range to be before the line preceding the row range
12156 if start_row.0 > 0 {
12157 let range_to_move = Point::new(
12158 start_row.previous_row().0,
12159 buffer.line_len(start_row.previous_row()),
12160 )
12161 ..Point::new(
12162 end_row.previous_row().0,
12163 buffer.line_len(end_row.previous_row()),
12164 );
12165 let insertion_point = display_map
12166 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12167 .0;
12168
12169 // Don't move lines across excerpts
12170 if buffer
12171 .excerpt_containing(insertion_point..range_to_move.end)
12172 .is_some()
12173 {
12174 let text = buffer
12175 .text_for_range(range_to_move.clone())
12176 .flat_map(|s| s.chars())
12177 .skip(1)
12178 .chain(['\n'])
12179 .collect::<String>();
12180
12181 edits.push((
12182 buffer.anchor_after(range_to_move.start)
12183 ..buffer.anchor_before(range_to_move.end),
12184 String::new(),
12185 ));
12186 let insertion_anchor = buffer.anchor_after(insertion_point);
12187 edits.push((insertion_anchor..insertion_anchor, text));
12188
12189 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12190
12191 // Move selections up
12192 new_selections.extend(contiguous_row_selections.drain(..).map(
12193 |mut selection| {
12194 selection.start.row -= row_delta;
12195 selection.end.row -= row_delta;
12196 selection
12197 },
12198 ));
12199
12200 // Move folds up
12201 unfold_ranges.push(range_to_move.clone());
12202 for fold in display_map.folds_in_range(
12203 buffer.anchor_before(range_to_move.start)
12204 ..buffer.anchor_after(range_to_move.end),
12205 ) {
12206 let mut start = fold.range.start.to_point(&buffer);
12207 let mut end = fold.range.end.to_point(&buffer);
12208 start.row -= row_delta;
12209 end.row -= row_delta;
12210 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12211 }
12212 }
12213 }
12214
12215 // If we didn't move line(s), preserve the existing selections
12216 new_selections.append(&mut contiguous_row_selections);
12217 }
12218
12219 self.transact(window, cx, |this, window, cx| {
12220 this.unfold_ranges(&unfold_ranges, true, true, cx);
12221 this.buffer.update(cx, |buffer, cx| {
12222 for (range, text) in edits {
12223 buffer.edit([(range, text)], None, cx);
12224 }
12225 });
12226 this.fold_creases(refold_creases, true, window, cx);
12227 this.change_selections(Default::default(), window, cx, |s| {
12228 s.select(new_selections);
12229 })
12230 });
12231 }
12232
12233 pub fn move_line_down(
12234 &mut self,
12235 _: &MoveLineDown,
12236 window: &mut Window,
12237 cx: &mut Context<Self>,
12238 ) {
12239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12240 if self.mode.is_single_line() {
12241 cx.propagate();
12242 return;
12243 }
12244
12245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12246 let buffer = self.buffer.read(cx).snapshot(cx);
12247
12248 let mut edits = Vec::new();
12249 let mut unfold_ranges = Vec::new();
12250 let mut refold_creases = Vec::new();
12251
12252 let selections = self.selections.all::<Point>(&display_map);
12253 let mut selections = selections.iter().peekable();
12254 let mut contiguous_row_selections = Vec::new();
12255 let mut new_selections = Vec::new();
12256
12257 while let Some(selection) = selections.next() {
12258 // Find all the selections that span a contiguous row range
12259 let (start_row, end_row) = consume_contiguous_rows(
12260 &mut contiguous_row_selections,
12261 selection,
12262 &display_map,
12263 &mut selections,
12264 );
12265
12266 // Move the text spanned by the row range to be after the last line of the row range
12267 if end_row.0 <= buffer.max_point().row {
12268 let range_to_move =
12269 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12270 let insertion_point = display_map
12271 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12272 .0;
12273
12274 // Don't move lines across excerpt boundaries
12275 if buffer
12276 .excerpt_containing(range_to_move.start..insertion_point)
12277 .is_some()
12278 {
12279 let mut text = String::from("\n");
12280 text.extend(buffer.text_for_range(range_to_move.clone()));
12281 text.pop(); // Drop trailing newline
12282 edits.push((
12283 buffer.anchor_after(range_to_move.start)
12284 ..buffer.anchor_before(range_to_move.end),
12285 String::new(),
12286 ));
12287 let insertion_anchor = buffer.anchor_after(insertion_point);
12288 edits.push((insertion_anchor..insertion_anchor, text));
12289
12290 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12291
12292 // Move selections down
12293 new_selections.extend(contiguous_row_selections.drain(..).map(
12294 |mut selection| {
12295 selection.start.row += row_delta;
12296 selection.end.row += row_delta;
12297 selection
12298 },
12299 ));
12300
12301 // Move folds down
12302 unfold_ranges.push(range_to_move.clone());
12303 for fold in display_map.folds_in_range(
12304 buffer.anchor_before(range_to_move.start)
12305 ..buffer.anchor_after(range_to_move.end),
12306 ) {
12307 let mut start = fold.range.start.to_point(&buffer);
12308 let mut end = fold.range.end.to_point(&buffer);
12309 start.row += row_delta;
12310 end.row += row_delta;
12311 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12312 }
12313 }
12314 }
12315
12316 // If we didn't move line(s), preserve the existing selections
12317 new_selections.append(&mut contiguous_row_selections);
12318 }
12319
12320 self.transact(window, cx, |this, window, cx| {
12321 this.unfold_ranges(&unfold_ranges, true, true, cx);
12322 this.buffer.update(cx, |buffer, cx| {
12323 for (range, text) in edits {
12324 buffer.edit([(range, text)], None, cx);
12325 }
12326 });
12327 this.fold_creases(refold_creases, true, window, cx);
12328 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12329 });
12330 }
12331
12332 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12334 let text_layout_details = &self.text_layout_details(window);
12335 self.transact(window, cx, |this, window, cx| {
12336 let edits = this.change_selections(Default::default(), window, cx, |s| {
12337 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12338 s.move_with(|display_map, selection| {
12339 if !selection.is_empty() {
12340 return;
12341 }
12342
12343 let mut head = selection.head();
12344 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12345 if head.column() == display_map.line_len(head.row()) {
12346 transpose_offset = display_map
12347 .buffer_snapshot()
12348 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12349 }
12350
12351 if transpose_offset == MultiBufferOffset(0) {
12352 return;
12353 }
12354
12355 *head.column_mut() += 1;
12356 head = display_map.clip_point(head, Bias::Right);
12357 let goal = SelectionGoal::HorizontalPosition(
12358 display_map
12359 .x_for_display_point(head, text_layout_details)
12360 .into(),
12361 );
12362 selection.collapse_to(head, goal);
12363
12364 let transpose_start = display_map
12365 .buffer_snapshot()
12366 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12367 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12368 let transpose_end = display_map
12369 .buffer_snapshot()
12370 .clip_offset(transpose_offset + 1usize, Bias::Right);
12371 if let Some(ch) = display_map
12372 .buffer_snapshot()
12373 .chars_at(transpose_start)
12374 .next()
12375 {
12376 edits.push((transpose_start..transpose_offset, String::new()));
12377 edits.push((transpose_end..transpose_end, ch.to_string()));
12378 }
12379 }
12380 });
12381 edits
12382 });
12383 this.buffer
12384 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12385 let selections = this
12386 .selections
12387 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12388 this.change_selections(Default::default(), window, cx, |s| {
12389 s.select(selections);
12390 });
12391 });
12392 }
12393
12394 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12396 if self.mode.is_single_line() {
12397 cx.propagate();
12398 return;
12399 }
12400
12401 self.rewrap_impl(RewrapOptions::default(), cx)
12402 }
12403
12404 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12405 let buffer = self.buffer.read(cx).snapshot(cx);
12406 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12407
12408 #[derive(Clone, Debug, PartialEq)]
12409 enum CommentFormat {
12410 /// single line comment, with prefix for line
12411 Line(String),
12412 /// single line within a block comment, with prefix for line
12413 BlockLine(String),
12414 /// a single line of a block comment that includes the initial delimiter
12415 BlockCommentWithStart(BlockCommentConfig),
12416 /// a single line of a block comment that includes the ending delimiter
12417 BlockCommentWithEnd(BlockCommentConfig),
12418 }
12419
12420 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12421 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12422 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12423 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12424 .peekable();
12425
12426 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12427 row
12428 } else {
12429 return Vec::new();
12430 };
12431
12432 let language_settings = buffer.language_settings_at(selection.head(), cx);
12433 let language_scope = buffer.language_scope_at(selection.head());
12434
12435 let indent_and_prefix_for_row =
12436 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12437 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12438 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12439 &language_scope
12440 {
12441 let indent_end = Point::new(row, indent.len);
12442 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12443 let line_text_after_indent = buffer
12444 .text_for_range(indent_end..line_end)
12445 .collect::<String>();
12446
12447 let is_within_comment_override = buffer
12448 .language_scope_at(indent_end)
12449 .is_some_and(|scope| scope.override_name() == Some("comment"));
12450 let comment_delimiters = if is_within_comment_override {
12451 // we are within a comment syntax node, but we don't
12452 // yet know what kind of comment: block, doc or line
12453 match (
12454 language_scope.documentation_comment(),
12455 language_scope.block_comment(),
12456 ) {
12457 (Some(config), _) | (_, Some(config))
12458 if buffer.contains_str_at(indent_end, &config.start) =>
12459 {
12460 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12461 }
12462 (Some(config), _) | (_, Some(config))
12463 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12464 {
12465 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12466 }
12467 (Some(config), _) | (_, Some(config))
12468 if buffer.contains_str_at(indent_end, &config.prefix) =>
12469 {
12470 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12471 }
12472 (_, _) => language_scope
12473 .line_comment_prefixes()
12474 .iter()
12475 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12476 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12477 }
12478 } else {
12479 // we not in an overridden comment node, but we may
12480 // be within a non-overridden line comment node
12481 language_scope
12482 .line_comment_prefixes()
12483 .iter()
12484 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12485 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12486 };
12487
12488 let rewrap_prefix = language_scope
12489 .rewrap_prefixes()
12490 .iter()
12491 .find_map(|prefix_regex| {
12492 prefix_regex.find(&line_text_after_indent).map(|mat| {
12493 if mat.start() == 0 {
12494 Some(mat.as_str().to_string())
12495 } else {
12496 None
12497 }
12498 })
12499 })
12500 .flatten();
12501 (comment_delimiters, rewrap_prefix)
12502 } else {
12503 (None, None)
12504 };
12505 (indent, comment_prefix, rewrap_prefix)
12506 };
12507
12508 let mut ranges = Vec::new();
12509 let from_empty_selection = selection.is_empty();
12510
12511 let mut current_range_start = first_row;
12512 let mut prev_row = first_row;
12513 let (
12514 mut current_range_indent,
12515 mut current_range_comment_delimiters,
12516 mut current_range_rewrap_prefix,
12517 ) = indent_and_prefix_for_row(first_row);
12518
12519 for row in non_blank_rows_iter.skip(1) {
12520 let has_paragraph_break = row > prev_row + 1;
12521
12522 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12523 indent_and_prefix_for_row(row);
12524
12525 let has_indent_change = row_indent != current_range_indent;
12526 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12527
12528 let has_boundary_change = has_comment_change
12529 || row_rewrap_prefix.is_some()
12530 || (has_indent_change && current_range_comment_delimiters.is_some());
12531
12532 if has_paragraph_break || has_boundary_change {
12533 ranges.push((
12534 language_settings.clone(),
12535 Point::new(current_range_start, 0)
12536 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12537 current_range_indent,
12538 current_range_comment_delimiters.clone(),
12539 current_range_rewrap_prefix.clone(),
12540 from_empty_selection,
12541 ));
12542 current_range_start = row;
12543 current_range_indent = row_indent;
12544 current_range_comment_delimiters = row_comment_delimiters;
12545 current_range_rewrap_prefix = row_rewrap_prefix;
12546 }
12547 prev_row = row;
12548 }
12549
12550 ranges.push((
12551 language_settings.clone(),
12552 Point::new(current_range_start, 0)
12553 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12554 current_range_indent,
12555 current_range_comment_delimiters,
12556 current_range_rewrap_prefix,
12557 from_empty_selection,
12558 ));
12559
12560 ranges
12561 });
12562
12563 let mut edits = Vec::new();
12564 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12565
12566 for (
12567 language_settings,
12568 wrap_range,
12569 mut indent_size,
12570 comment_prefix,
12571 rewrap_prefix,
12572 from_empty_selection,
12573 ) in wrap_ranges
12574 {
12575 let mut start_row = wrap_range.start.row;
12576 let mut end_row = wrap_range.end.row;
12577
12578 // Skip selections that overlap with a range that has already been rewrapped.
12579 let selection_range = start_row..end_row;
12580 if rewrapped_row_ranges
12581 .iter()
12582 .any(|range| range.overlaps(&selection_range))
12583 {
12584 continue;
12585 }
12586
12587 let tab_size = language_settings.tab_size;
12588
12589 let (line_prefix, inside_comment) = match &comment_prefix {
12590 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12591 (Some(prefix.as_str()), true)
12592 }
12593 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12594 (Some(prefix.as_ref()), true)
12595 }
12596 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12597 start: _,
12598 end: _,
12599 prefix,
12600 tab_size,
12601 })) => {
12602 indent_size.len += tab_size;
12603 (Some(prefix.as_ref()), true)
12604 }
12605 None => (None, false),
12606 };
12607 let indent_prefix = indent_size.chars().collect::<String>();
12608 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12609
12610 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12611 RewrapBehavior::InComments => inside_comment,
12612 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12613 RewrapBehavior::Anywhere => true,
12614 };
12615
12616 let should_rewrap = options.override_language_settings
12617 || allow_rewrap_based_on_language
12618 || self.hard_wrap.is_some();
12619 if !should_rewrap {
12620 continue;
12621 }
12622
12623 if from_empty_selection {
12624 'expand_upwards: while start_row > 0 {
12625 let prev_row = start_row - 1;
12626 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12627 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12628 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12629 {
12630 start_row = prev_row;
12631 } else {
12632 break 'expand_upwards;
12633 }
12634 }
12635
12636 'expand_downwards: while end_row < buffer.max_point().row {
12637 let next_row = end_row + 1;
12638 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12639 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12640 && !buffer.is_line_blank(MultiBufferRow(next_row))
12641 {
12642 end_row = next_row;
12643 } else {
12644 break 'expand_downwards;
12645 }
12646 }
12647 }
12648
12649 let start = Point::new(start_row, 0);
12650 let start_offset = ToOffset::to_offset(&start, &buffer);
12651 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12652 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12653 let mut first_line_delimiter = None;
12654 let mut last_line_delimiter = None;
12655 let Some(lines_without_prefixes) = selection_text
12656 .lines()
12657 .enumerate()
12658 .map(|(ix, line)| {
12659 let line_trimmed = line.trim_start();
12660 if rewrap_prefix.is_some() && ix > 0 {
12661 Ok(line_trimmed)
12662 } else if let Some(
12663 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12664 start,
12665 prefix,
12666 end,
12667 tab_size,
12668 })
12669 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12670 start,
12671 prefix,
12672 end,
12673 tab_size,
12674 }),
12675 ) = &comment_prefix
12676 {
12677 let line_trimmed = line_trimmed
12678 .strip_prefix(start.as_ref())
12679 .map(|s| {
12680 let mut indent_size = indent_size;
12681 indent_size.len -= tab_size;
12682 let indent_prefix: String = indent_size.chars().collect();
12683 first_line_delimiter = Some((indent_prefix, start));
12684 s.trim_start()
12685 })
12686 .unwrap_or(line_trimmed);
12687 let line_trimmed = line_trimmed
12688 .strip_suffix(end.as_ref())
12689 .map(|s| {
12690 last_line_delimiter = Some(end);
12691 s.trim_end()
12692 })
12693 .unwrap_or(line_trimmed);
12694 let line_trimmed = line_trimmed
12695 .strip_prefix(prefix.as_ref())
12696 .unwrap_or(line_trimmed);
12697 Ok(line_trimmed)
12698 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12699 line_trimmed.strip_prefix(prefix).with_context(|| {
12700 format!("line did not start with prefix {prefix:?}: {line:?}")
12701 })
12702 } else {
12703 line_trimmed
12704 .strip_prefix(&line_prefix.trim_start())
12705 .with_context(|| {
12706 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12707 })
12708 }
12709 })
12710 .collect::<Result<Vec<_>, _>>()
12711 .log_err()
12712 else {
12713 continue;
12714 };
12715
12716 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12717 buffer
12718 .language_settings_at(Point::new(start_row, 0), cx)
12719 .preferred_line_length as usize
12720 });
12721
12722 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12723 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12724 } else {
12725 line_prefix.clone()
12726 };
12727
12728 let wrapped_text = {
12729 let mut wrapped_text = wrap_with_prefix(
12730 line_prefix,
12731 subsequent_lines_prefix,
12732 lines_without_prefixes.join("\n"),
12733 wrap_column,
12734 tab_size,
12735 options.preserve_existing_whitespace,
12736 );
12737
12738 if let Some((indent, delimiter)) = first_line_delimiter {
12739 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12740 }
12741 if let Some(last_line) = last_line_delimiter {
12742 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12743 }
12744
12745 wrapped_text
12746 };
12747
12748 // TODO: should always use char-based diff while still supporting cursor behavior that
12749 // matches vim.
12750 let mut diff_options = DiffOptions::default();
12751 if options.override_language_settings {
12752 diff_options.max_word_diff_len = 0;
12753 diff_options.max_word_diff_line_count = 0;
12754 } else {
12755 diff_options.max_word_diff_len = usize::MAX;
12756 diff_options.max_word_diff_line_count = usize::MAX;
12757 }
12758
12759 for (old_range, new_text) in
12760 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12761 {
12762 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12763 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12764 edits.push((edit_start..edit_end, new_text));
12765 }
12766
12767 rewrapped_row_ranges.push(start_row..=end_row);
12768 }
12769
12770 self.buffer
12771 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12772 }
12773
12774 pub fn cut_common(
12775 &mut self,
12776 cut_no_selection_line: bool,
12777 window: &mut Window,
12778 cx: &mut Context<Self>,
12779 ) -> ClipboardItem {
12780 let mut text = String::new();
12781 let buffer = self.buffer.read(cx).snapshot(cx);
12782 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12783 let mut clipboard_selections = Vec::with_capacity(selections.len());
12784 {
12785 let max_point = buffer.max_point();
12786 let mut is_first = true;
12787 let mut prev_selection_was_entire_line = false;
12788 for selection in &mut selections {
12789 let is_entire_line =
12790 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12791 if is_entire_line {
12792 selection.start = Point::new(selection.start.row, 0);
12793 if !selection.is_empty() && selection.end.column == 0 {
12794 selection.end = cmp::min(max_point, selection.end);
12795 } else {
12796 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12797 }
12798 selection.goal = SelectionGoal::None;
12799 }
12800 if is_first {
12801 is_first = false;
12802 } else if !prev_selection_was_entire_line {
12803 text += "\n";
12804 }
12805 prev_selection_was_entire_line = is_entire_line;
12806 let mut len = 0;
12807 for chunk in buffer.text_for_range(selection.start..selection.end) {
12808 text.push_str(chunk);
12809 len += chunk.len();
12810 }
12811 clipboard_selections.push(ClipboardSelection {
12812 len,
12813 is_entire_line,
12814 first_line_indent: buffer
12815 .indent_size_for_line(MultiBufferRow(selection.start.row))
12816 .len,
12817 });
12818 }
12819 }
12820
12821 self.transact(window, cx, |this, window, cx| {
12822 this.change_selections(Default::default(), window, cx, |s| {
12823 s.select(selections);
12824 });
12825 this.insert("", window, cx);
12826 });
12827 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12828 }
12829
12830 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12832 let item = self.cut_common(true, window, cx);
12833 cx.write_to_clipboard(item);
12834 }
12835
12836 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12838 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12839 s.move_with(|snapshot, sel| {
12840 if sel.is_empty() {
12841 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12842 }
12843 if sel.is_empty() {
12844 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12845 }
12846 });
12847 });
12848 let item = self.cut_common(false, window, cx);
12849 cx.set_global(KillRing(item))
12850 }
12851
12852 pub fn kill_ring_yank(
12853 &mut self,
12854 _: &KillRingYank,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12859 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12860 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12861 (kill_ring.text().to_string(), kill_ring.metadata_json())
12862 } else {
12863 return;
12864 }
12865 } else {
12866 return;
12867 };
12868 self.do_paste(&text, metadata, false, window, cx);
12869 }
12870
12871 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12872 self.do_copy(true, cx);
12873 }
12874
12875 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12876 self.do_copy(false, cx);
12877 }
12878
12879 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12880 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12881 let buffer = self.buffer.read(cx).read(cx);
12882 let mut text = String::new();
12883
12884 let mut clipboard_selections = Vec::with_capacity(selections.len());
12885 {
12886 let max_point = buffer.max_point();
12887 let mut is_first = true;
12888 let mut prev_selection_was_entire_line = false;
12889 for selection in &selections {
12890 let mut start = selection.start;
12891 let mut end = selection.end;
12892 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12893 let mut add_trailing_newline = false;
12894 if is_entire_line {
12895 start = Point::new(start.row, 0);
12896 let next_line_start = Point::new(end.row + 1, 0);
12897 if next_line_start <= max_point {
12898 end = next_line_start;
12899 } else {
12900 // We're on the last line without a trailing newline.
12901 // Copy to the end of the line and add a newline afterwards.
12902 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12903 add_trailing_newline = true;
12904 }
12905 }
12906
12907 let mut trimmed_selections = Vec::new();
12908 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12909 let row = MultiBufferRow(start.row);
12910 let first_indent = buffer.indent_size_for_line(row);
12911 if first_indent.len == 0 || start.column > first_indent.len {
12912 trimmed_selections.push(start..end);
12913 } else {
12914 trimmed_selections.push(
12915 Point::new(row.0, first_indent.len)
12916 ..Point::new(row.0, buffer.line_len(row)),
12917 );
12918 for row in start.row + 1..=end.row {
12919 let mut line_len = buffer.line_len(MultiBufferRow(row));
12920 if row == end.row {
12921 line_len = end.column;
12922 }
12923 if line_len == 0 {
12924 trimmed_selections
12925 .push(Point::new(row, 0)..Point::new(row, line_len));
12926 continue;
12927 }
12928 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12929 if row_indent_size.len >= first_indent.len {
12930 trimmed_selections.push(
12931 Point::new(row, first_indent.len)..Point::new(row, line_len),
12932 );
12933 } else {
12934 trimmed_selections.clear();
12935 trimmed_selections.push(start..end);
12936 break;
12937 }
12938 }
12939 }
12940 } else {
12941 trimmed_selections.push(start..end);
12942 }
12943
12944 for trimmed_range in trimmed_selections {
12945 if is_first {
12946 is_first = false;
12947 } else if !prev_selection_was_entire_line {
12948 text += "\n";
12949 }
12950 prev_selection_was_entire_line = is_entire_line;
12951 let mut len = 0;
12952 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12953 text.push_str(chunk);
12954 len += chunk.len();
12955 }
12956 if add_trailing_newline {
12957 text.push('\n');
12958 len += 1;
12959 }
12960 clipboard_selections.push(ClipboardSelection {
12961 len,
12962 is_entire_line,
12963 first_line_indent: buffer
12964 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12965 .len,
12966 });
12967 }
12968 }
12969 }
12970
12971 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12972 text,
12973 clipboard_selections,
12974 ));
12975 }
12976
12977 pub fn do_paste(
12978 &mut self,
12979 text: &String,
12980 clipboard_selections: Option<Vec<ClipboardSelection>>,
12981 handle_entire_lines: bool,
12982 window: &mut Window,
12983 cx: &mut Context<Self>,
12984 ) {
12985 if self.read_only(cx) {
12986 return;
12987 }
12988
12989 let clipboard_text = Cow::Borrowed(text.as_str());
12990
12991 self.transact(window, cx, |this, window, cx| {
12992 let had_active_edit_prediction = this.has_active_edit_prediction();
12993 let display_map = this.display_snapshot(cx);
12994 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12995 let cursor_offset = this
12996 .selections
12997 .last::<MultiBufferOffset>(&display_map)
12998 .head();
12999
13000 if let Some(mut clipboard_selections) = clipboard_selections {
13001 let all_selections_were_entire_line =
13002 clipboard_selections.iter().all(|s| s.is_entire_line);
13003 let first_selection_indent_column =
13004 clipboard_selections.first().map(|s| s.first_line_indent);
13005 if clipboard_selections.len() != old_selections.len() {
13006 clipboard_selections.drain(..);
13007 }
13008 let mut auto_indent_on_paste = true;
13009
13010 this.buffer.update(cx, |buffer, cx| {
13011 let snapshot = buffer.read(cx);
13012 auto_indent_on_paste = snapshot
13013 .language_settings_at(cursor_offset, cx)
13014 .auto_indent_on_paste;
13015
13016 let mut start_offset = 0;
13017 let mut edits = Vec::new();
13018 let mut original_indent_columns = Vec::new();
13019 for (ix, selection) in old_selections.iter().enumerate() {
13020 let to_insert;
13021 let entire_line;
13022 let original_indent_column;
13023 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13024 let end_offset = start_offset + clipboard_selection.len;
13025 to_insert = &clipboard_text[start_offset..end_offset];
13026 entire_line = clipboard_selection.is_entire_line;
13027 start_offset = if entire_line {
13028 end_offset
13029 } else {
13030 end_offset + 1
13031 };
13032 original_indent_column = Some(clipboard_selection.first_line_indent);
13033 } else {
13034 to_insert = &*clipboard_text;
13035 entire_line = all_selections_were_entire_line;
13036 original_indent_column = first_selection_indent_column
13037 }
13038
13039 let (range, to_insert) =
13040 if selection.is_empty() && handle_entire_lines && entire_line {
13041 // If the corresponding selection was empty when this slice of the
13042 // clipboard text was written, then the entire line containing the
13043 // selection was copied. If this selection is also currently empty,
13044 // then paste the line before the current line of the buffer.
13045 let column = selection.start.to_point(&snapshot).column as usize;
13046 let line_start = selection.start - column;
13047 (line_start..line_start, Cow::Borrowed(to_insert))
13048 } else {
13049 let language = snapshot.language_at(selection.head());
13050 let range = selection.range();
13051 if let Some(language) = language
13052 && language.name() == "Markdown".into()
13053 {
13054 edit_for_markdown_paste(
13055 &snapshot,
13056 range,
13057 to_insert,
13058 url::Url::parse(to_insert).ok(),
13059 )
13060 } else {
13061 (range, Cow::Borrowed(to_insert))
13062 }
13063 };
13064
13065 edits.push((range, to_insert));
13066 original_indent_columns.push(original_indent_column);
13067 }
13068 drop(snapshot);
13069
13070 buffer.edit(
13071 edits,
13072 if auto_indent_on_paste {
13073 Some(AutoindentMode::Block {
13074 original_indent_columns,
13075 })
13076 } else {
13077 None
13078 },
13079 cx,
13080 );
13081 });
13082
13083 let selections = this
13084 .selections
13085 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13086 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13087 } else {
13088 let url = url::Url::parse(&clipboard_text).ok();
13089
13090 let auto_indent_mode = if !clipboard_text.is_empty() {
13091 Some(AutoindentMode::Block {
13092 original_indent_columns: Vec::new(),
13093 })
13094 } else {
13095 None
13096 };
13097
13098 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13099 let snapshot = buffer.snapshot(cx);
13100
13101 let anchors = old_selections
13102 .iter()
13103 .map(|s| {
13104 let anchor = snapshot.anchor_after(s.head());
13105 s.map(|_| anchor)
13106 })
13107 .collect::<Vec<_>>();
13108
13109 let mut edits = Vec::new();
13110
13111 for selection in old_selections.iter() {
13112 let language = snapshot.language_at(selection.head());
13113 let range = selection.range();
13114
13115 let (edit_range, edit_text) = if let Some(language) = language
13116 && language.name() == "Markdown".into()
13117 {
13118 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13119 } else {
13120 (range, clipboard_text.clone())
13121 };
13122
13123 edits.push((edit_range, edit_text));
13124 }
13125
13126 drop(snapshot);
13127 buffer.edit(edits, auto_indent_mode, cx);
13128
13129 anchors
13130 });
13131
13132 this.change_selections(Default::default(), window, cx, |s| {
13133 s.select_anchors(selection_anchors);
13134 });
13135 }
13136
13137 // 🤔 | .. | show_in_menu |
13138 // | .. | true true
13139 // | had_edit_prediction | false true
13140
13141 let trigger_in_words =
13142 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13143
13144 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13145 });
13146 }
13147
13148 pub fn diff_clipboard_with_selection(
13149 &mut self,
13150 _: &DiffClipboardWithSelection,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 let selections = self
13155 .selections
13156 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13157
13158 if selections.is_empty() {
13159 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13160 return;
13161 };
13162
13163 let clipboard_text = match cx.read_from_clipboard() {
13164 Some(item) => match item.entries().first() {
13165 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13166 _ => None,
13167 },
13168 None => None,
13169 };
13170
13171 let Some(clipboard_text) = clipboard_text else {
13172 log::warn!("Clipboard doesn't contain text.");
13173 return;
13174 };
13175
13176 window.dispatch_action(
13177 Box::new(DiffClipboardWithSelectionData {
13178 clipboard_text,
13179 editor: cx.entity(),
13180 }),
13181 cx,
13182 );
13183 }
13184
13185 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13187 if let Some(item) = cx.read_from_clipboard() {
13188 let entries = item.entries();
13189
13190 match entries.first() {
13191 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13192 // of all the pasted entries.
13193 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13194 .do_paste(
13195 clipboard_string.text(),
13196 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13197 true,
13198 window,
13199 cx,
13200 ),
13201 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13202 }
13203 }
13204 }
13205
13206 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13207 if self.read_only(cx) {
13208 return;
13209 }
13210
13211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13212
13213 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13214 if let Some((selections, _)) =
13215 self.selection_history.transaction(transaction_id).cloned()
13216 {
13217 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13218 s.select_anchors(selections.to_vec());
13219 });
13220 } else {
13221 log::error!(
13222 "No entry in selection_history found for undo. \
13223 This may correspond to a bug where undo does not update the selection. \
13224 If this is occurring, please add details to \
13225 https://github.com/zed-industries/zed/issues/22692"
13226 );
13227 }
13228 self.request_autoscroll(Autoscroll::fit(), cx);
13229 self.unmark_text(window, cx);
13230 self.refresh_edit_prediction(true, false, window, cx);
13231 cx.emit(EditorEvent::Edited { transaction_id });
13232 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13233 }
13234 }
13235
13236 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13237 if self.read_only(cx) {
13238 return;
13239 }
13240
13241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13242
13243 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13244 if let Some((_, Some(selections))) =
13245 self.selection_history.transaction(transaction_id).cloned()
13246 {
13247 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13248 s.select_anchors(selections.to_vec());
13249 });
13250 } else {
13251 log::error!(
13252 "No entry in selection_history found for redo. \
13253 This may correspond to a bug where undo does not update the selection. \
13254 If this is occurring, please add details to \
13255 https://github.com/zed-industries/zed/issues/22692"
13256 );
13257 }
13258 self.request_autoscroll(Autoscroll::fit(), cx);
13259 self.unmark_text(window, cx);
13260 self.refresh_edit_prediction(true, false, window, cx);
13261 cx.emit(EditorEvent::Edited { transaction_id });
13262 }
13263 }
13264
13265 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13266 self.buffer
13267 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13268 }
13269
13270 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13271 self.buffer
13272 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13273 }
13274
13275 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13277 self.change_selections(Default::default(), window, cx, |s| {
13278 s.move_with(|map, selection| {
13279 let cursor = if selection.is_empty() {
13280 movement::left(map, selection.start)
13281 } else {
13282 selection.start
13283 };
13284 selection.collapse_to(cursor, SelectionGoal::None);
13285 });
13286 })
13287 }
13288
13289 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13290 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13291 self.change_selections(Default::default(), window, cx, |s| {
13292 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13293 })
13294 }
13295
13296 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13298 self.change_selections(Default::default(), window, cx, |s| {
13299 s.move_with(|map, selection| {
13300 let cursor = if selection.is_empty() {
13301 movement::right(map, selection.end)
13302 } else {
13303 selection.end
13304 };
13305 selection.collapse_to(cursor, SelectionGoal::None)
13306 });
13307 })
13308 }
13309
13310 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13312 self.change_selections(Default::default(), window, cx, |s| {
13313 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13314 });
13315 }
13316
13317 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13318 if self.take_rename(true, window, cx).is_some() {
13319 return;
13320 }
13321
13322 if self.mode.is_single_line() {
13323 cx.propagate();
13324 return;
13325 }
13326
13327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13328
13329 let text_layout_details = &self.text_layout_details(window);
13330 let selection_count = self.selections.count();
13331 let first_selection = self.selections.first_anchor();
13332
13333 self.change_selections(Default::default(), window, cx, |s| {
13334 s.move_with(|map, selection| {
13335 if !selection.is_empty() {
13336 selection.goal = SelectionGoal::None;
13337 }
13338 let (cursor, goal) = movement::up(
13339 map,
13340 selection.start,
13341 selection.goal,
13342 false,
13343 text_layout_details,
13344 );
13345 selection.collapse_to(cursor, goal);
13346 });
13347 });
13348
13349 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13350 {
13351 cx.propagate();
13352 }
13353 }
13354
13355 pub fn move_up_by_lines(
13356 &mut self,
13357 action: &MoveUpByLines,
13358 window: &mut Window,
13359 cx: &mut Context<Self>,
13360 ) {
13361 if self.take_rename(true, window, cx).is_some() {
13362 return;
13363 }
13364
13365 if self.mode.is_single_line() {
13366 cx.propagate();
13367 return;
13368 }
13369
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13371
13372 let text_layout_details = &self.text_layout_details(window);
13373
13374 self.change_selections(Default::default(), window, cx, |s| {
13375 s.move_with(|map, selection| {
13376 if !selection.is_empty() {
13377 selection.goal = SelectionGoal::None;
13378 }
13379 let (cursor, goal) = movement::up_by_rows(
13380 map,
13381 selection.start,
13382 action.lines,
13383 selection.goal,
13384 false,
13385 text_layout_details,
13386 );
13387 selection.collapse_to(cursor, goal);
13388 });
13389 })
13390 }
13391
13392 pub fn move_down_by_lines(
13393 &mut self,
13394 action: &MoveDownByLines,
13395 window: &mut Window,
13396 cx: &mut Context<Self>,
13397 ) {
13398 if self.take_rename(true, window, cx).is_some() {
13399 return;
13400 }
13401
13402 if self.mode.is_single_line() {
13403 cx.propagate();
13404 return;
13405 }
13406
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13408
13409 let text_layout_details = &self.text_layout_details(window);
13410
13411 self.change_selections(Default::default(), window, cx, |s| {
13412 s.move_with(|map, selection| {
13413 if !selection.is_empty() {
13414 selection.goal = SelectionGoal::None;
13415 }
13416 let (cursor, goal) = movement::down_by_rows(
13417 map,
13418 selection.start,
13419 action.lines,
13420 selection.goal,
13421 false,
13422 text_layout_details,
13423 );
13424 selection.collapse_to(cursor, goal);
13425 });
13426 })
13427 }
13428
13429 pub fn select_down_by_lines(
13430 &mut self,
13431 action: &SelectDownByLines,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 let text_layout_details = &self.text_layout_details(window);
13437 self.change_selections(Default::default(), window, cx, |s| {
13438 s.move_heads_with(|map, head, goal| {
13439 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13440 })
13441 })
13442 }
13443
13444 pub fn select_up_by_lines(
13445 &mut self,
13446 action: &SelectUpByLines,
13447 window: &mut Window,
13448 cx: &mut Context<Self>,
13449 ) {
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13451 let text_layout_details = &self.text_layout_details(window);
13452 self.change_selections(Default::default(), window, cx, |s| {
13453 s.move_heads_with(|map, head, goal| {
13454 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13455 })
13456 })
13457 }
13458
13459 pub fn select_page_up(
13460 &mut self,
13461 _: &SelectPageUp,
13462 window: &mut Window,
13463 cx: &mut Context<Self>,
13464 ) {
13465 let Some(row_count) = self.visible_row_count() else {
13466 return;
13467 };
13468
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13470
13471 let text_layout_details = &self.text_layout_details(window);
13472
13473 self.change_selections(Default::default(), window, cx, |s| {
13474 s.move_heads_with(|map, head, goal| {
13475 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13476 })
13477 })
13478 }
13479
13480 pub fn move_page_up(
13481 &mut self,
13482 action: &MovePageUp,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 if self.take_rename(true, window, cx).is_some() {
13487 return;
13488 }
13489
13490 if self
13491 .context_menu
13492 .borrow_mut()
13493 .as_mut()
13494 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13495 .unwrap_or(false)
13496 {
13497 return;
13498 }
13499
13500 if matches!(self.mode, EditorMode::SingleLine) {
13501 cx.propagate();
13502 return;
13503 }
13504
13505 let Some(row_count) = self.visible_row_count() else {
13506 return;
13507 };
13508
13509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13510
13511 let effects = if action.center_cursor {
13512 SelectionEffects::scroll(Autoscroll::center())
13513 } else {
13514 SelectionEffects::default()
13515 };
13516
13517 let text_layout_details = &self.text_layout_details(window);
13518
13519 self.change_selections(effects, window, cx, |s| {
13520 s.move_with(|map, selection| {
13521 if !selection.is_empty() {
13522 selection.goal = SelectionGoal::None;
13523 }
13524 let (cursor, goal) = movement::up_by_rows(
13525 map,
13526 selection.end,
13527 row_count,
13528 selection.goal,
13529 false,
13530 text_layout_details,
13531 );
13532 selection.collapse_to(cursor, goal);
13533 });
13534 });
13535 }
13536
13537 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13539 let text_layout_details = &self.text_layout_details(window);
13540 self.change_selections(Default::default(), window, cx, |s| {
13541 s.move_heads_with(|map, head, goal| {
13542 movement::up(map, head, goal, false, text_layout_details)
13543 })
13544 })
13545 }
13546
13547 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13548 self.take_rename(true, window, cx);
13549
13550 if self.mode.is_single_line() {
13551 cx.propagate();
13552 return;
13553 }
13554
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13556
13557 let text_layout_details = &self.text_layout_details(window);
13558 let selection_count = self.selections.count();
13559 let first_selection = self.selections.first_anchor();
13560
13561 self.change_selections(Default::default(), window, cx, |s| {
13562 s.move_with(|map, selection| {
13563 if !selection.is_empty() {
13564 selection.goal = SelectionGoal::None;
13565 }
13566 let (cursor, goal) = movement::down(
13567 map,
13568 selection.end,
13569 selection.goal,
13570 false,
13571 text_layout_details,
13572 );
13573 selection.collapse_to(cursor, goal);
13574 });
13575 });
13576
13577 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13578 {
13579 cx.propagate();
13580 }
13581 }
13582
13583 pub fn select_page_down(
13584 &mut self,
13585 _: &SelectPageDown,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 let Some(row_count) = self.visible_row_count() else {
13590 return;
13591 };
13592
13593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13594
13595 let text_layout_details = &self.text_layout_details(window);
13596
13597 self.change_selections(Default::default(), window, cx, |s| {
13598 s.move_heads_with(|map, head, goal| {
13599 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13600 })
13601 })
13602 }
13603
13604 pub fn move_page_down(
13605 &mut self,
13606 action: &MovePageDown,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) {
13610 if self.take_rename(true, window, cx).is_some() {
13611 return;
13612 }
13613
13614 if self
13615 .context_menu
13616 .borrow_mut()
13617 .as_mut()
13618 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13619 .unwrap_or(false)
13620 {
13621 return;
13622 }
13623
13624 if matches!(self.mode, EditorMode::SingleLine) {
13625 cx.propagate();
13626 return;
13627 }
13628
13629 let Some(row_count) = self.visible_row_count() else {
13630 return;
13631 };
13632
13633 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13634
13635 let effects = if action.center_cursor {
13636 SelectionEffects::scroll(Autoscroll::center())
13637 } else {
13638 SelectionEffects::default()
13639 };
13640
13641 let text_layout_details = &self.text_layout_details(window);
13642 self.change_selections(effects, window, cx, |s| {
13643 s.move_with(|map, selection| {
13644 if !selection.is_empty() {
13645 selection.goal = SelectionGoal::None;
13646 }
13647 let (cursor, goal) = movement::down_by_rows(
13648 map,
13649 selection.end,
13650 row_count,
13651 selection.goal,
13652 false,
13653 text_layout_details,
13654 );
13655 selection.collapse_to(cursor, goal);
13656 });
13657 });
13658 }
13659
13660 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 let text_layout_details = &self.text_layout_details(window);
13663 self.change_selections(Default::default(), window, cx, |s| {
13664 s.move_heads_with(|map, head, goal| {
13665 movement::down(map, head, goal, false, text_layout_details)
13666 })
13667 });
13668 }
13669
13670 pub fn context_menu_first(
13671 &mut self,
13672 _: &ContextMenuFirst,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) {
13676 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13677 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13678 }
13679 }
13680
13681 pub fn context_menu_prev(
13682 &mut self,
13683 _: &ContextMenuPrevious,
13684 window: &mut Window,
13685 cx: &mut Context<Self>,
13686 ) {
13687 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13688 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13689 }
13690 }
13691
13692 pub fn context_menu_next(
13693 &mut self,
13694 _: &ContextMenuNext,
13695 window: &mut Window,
13696 cx: &mut Context<Self>,
13697 ) {
13698 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13699 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13700 }
13701 }
13702
13703 pub fn context_menu_last(
13704 &mut self,
13705 _: &ContextMenuLast,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13710 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13711 }
13712 }
13713
13714 pub fn signature_help_prev(
13715 &mut self,
13716 _: &SignatureHelpPrevious,
13717 _: &mut Window,
13718 cx: &mut Context<Self>,
13719 ) {
13720 if let Some(popover) = self.signature_help_state.popover_mut() {
13721 if popover.current_signature == 0 {
13722 popover.current_signature = popover.signatures.len() - 1;
13723 } else {
13724 popover.current_signature -= 1;
13725 }
13726 cx.notify();
13727 }
13728 }
13729
13730 pub fn signature_help_next(
13731 &mut self,
13732 _: &SignatureHelpNext,
13733 _: &mut Window,
13734 cx: &mut Context<Self>,
13735 ) {
13736 if let Some(popover) = self.signature_help_state.popover_mut() {
13737 if popover.current_signature + 1 == popover.signatures.len() {
13738 popover.current_signature = 0;
13739 } else {
13740 popover.current_signature += 1;
13741 }
13742 cx.notify();
13743 }
13744 }
13745
13746 pub fn move_to_previous_word_start(
13747 &mut self,
13748 _: &MoveToPreviousWordStart,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.move_cursors_with(|map, head, _| {
13755 (
13756 movement::previous_word_start(map, head),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn move_to_previous_subword_start(
13764 &mut self,
13765 _: &MoveToPreviousSubwordStart,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13770 self.change_selections(Default::default(), window, cx, |s| {
13771 s.move_cursors_with(|map, head, _| {
13772 (
13773 movement::previous_subword_start(map, head),
13774 SelectionGoal::None,
13775 )
13776 });
13777 })
13778 }
13779
13780 pub fn select_to_previous_word_start(
13781 &mut self,
13782 _: &SelectToPreviousWordStart,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13787 self.change_selections(Default::default(), window, cx, |s| {
13788 s.move_heads_with(|map, head, _| {
13789 (
13790 movement::previous_word_start(map, head),
13791 SelectionGoal::None,
13792 )
13793 });
13794 })
13795 }
13796
13797 pub fn select_to_previous_subword_start(
13798 &mut self,
13799 _: &SelectToPreviousSubwordStart,
13800 window: &mut Window,
13801 cx: &mut Context<Self>,
13802 ) {
13803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13804 self.change_selections(Default::default(), window, cx, |s| {
13805 s.move_heads_with(|map, head, _| {
13806 (
13807 movement::previous_subword_start(map, head),
13808 SelectionGoal::None,
13809 )
13810 });
13811 })
13812 }
13813
13814 pub fn delete_to_previous_word_start(
13815 &mut self,
13816 action: &DeleteToPreviousWordStart,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) {
13820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13821 self.transact(window, cx, |this, window, cx| {
13822 this.select_autoclose_pair(window, cx);
13823 this.change_selections(Default::default(), window, cx, |s| {
13824 s.move_with(|map, selection| {
13825 if selection.is_empty() {
13826 let mut cursor = if action.ignore_newlines {
13827 movement::previous_word_start(map, selection.head())
13828 } else {
13829 movement::previous_word_start_or_newline(map, selection.head())
13830 };
13831 cursor = movement::adjust_greedy_deletion(
13832 map,
13833 selection.head(),
13834 cursor,
13835 action.ignore_brackets,
13836 );
13837 selection.set_head(cursor, SelectionGoal::None);
13838 }
13839 });
13840 });
13841 this.insert("", window, cx);
13842 });
13843 }
13844
13845 pub fn delete_to_previous_subword_start(
13846 &mut self,
13847 _: &DeleteToPreviousSubwordStart,
13848 window: &mut Window,
13849 cx: &mut Context<Self>,
13850 ) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13852 self.transact(window, cx, |this, window, cx| {
13853 this.select_autoclose_pair(window, cx);
13854 this.change_selections(Default::default(), window, cx, |s| {
13855 s.move_with(|map, selection| {
13856 if selection.is_empty() {
13857 let mut cursor = movement::previous_subword_start(map, selection.head());
13858 cursor =
13859 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13860 selection.set_head(cursor, SelectionGoal::None);
13861 }
13862 });
13863 });
13864 this.insert("", window, cx);
13865 });
13866 }
13867
13868 pub fn move_to_next_word_end(
13869 &mut self,
13870 _: &MoveToNextWordEnd,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) {
13874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13875 self.change_selections(Default::default(), window, cx, |s| {
13876 s.move_cursors_with(|map, head, _| {
13877 (movement::next_word_end(map, head), SelectionGoal::None)
13878 });
13879 })
13880 }
13881
13882 pub fn move_to_next_subword_end(
13883 &mut self,
13884 _: &MoveToNextSubwordEnd,
13885 window: &mut Window,
13886 cx: &mut Context<Self>,
13887 ) {
13888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13889 self.change_selections(Default::default(), window, cx, |s| {
13890 s.move_cursors_with(|map, head, _| {
13891 (movement::next_subword_end(map, head), SelectionGoal::None)
13892 });
13893 })
13894 }
13895
13896 pub fn select_to_next_word_end(
13897 &mut self,
13898 _: &SelectToNextWordEnd,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13903 self.change_selections(Default::default(), window, cx, |s| {
13904 s.move_heads_with(|map, head, _| {
13905 (movement::next_word_end(map, head), SelectionGoal::None)
13906 });
13907 })
13908 }
13909
13910 pub fn select_to_next_subword_end(
13911 &mut self,
13912 _: &SelectToNextSubwordEnd,
13913 window: &mut Window,
13914 cx: &mut Context<Self>,
13915 ) {
13916 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13917 self.change_selections(Default::default(), window, cx, |s| {
13918 s.move_heads_with(|map, head, _| {
13919 (movement::next_subword_end(map, head), SelectionGoal::None)
13920 });
13921 })
13922 }
13923
13924 pub fn delete_to_next_word_end(
13925 &mut self,
13926 action: &DeleteToNextWordEnd,
13927 window: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) {
13930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13931 self.transact(window, cx, |this, window, cx| {
13932 this.change_selections(Default::default(), window, cx, |s| {
13933 s.move_with(|map, selection| {
13934 if selection.is_empty() {
13935 let mut cursor = if action.ignore_newlines {
13936 movement::next_word_end(map, selection.head())
13937 } else {
13938 movement::next_word_end_or_newline(map, selection.head())
13939 };
13940 cursor = movement::adjust_greedy_deletion(
13941 map,
13942 selection.head(),
13943 cursor,
13944 action.ignore_brackets,
13945 );
13946 selection.set_head(cursor, SelectionGoal::None);
13947 }
13948 });
13949 });
13950 this.insert("", window, cx);
13951 });
13952 }
13953
13954 pub fn delete_to_next_subword_end(
13955 &mut self,
13956 _: &DeleteToNextSubwordEnd,
13957 window: &mut Window,
13958 cx: &mut Context<Self>,
13959 ) {
13960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13961 self.transact(window, cx, |this, window, cx| {
13962 this.change_selections(Default::default(), window, cx, |s| {
13963 s.move_with(|map, selection| {
13964 if selection.is_empty() {
13965 let mut cursor = movement::next_subword_end(map, selection.head());
13966 cursor =
13967 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13968 selection.set_head(cursor, SelectionGoal::None);
13969 }
13970 });
13971 });
13972 this.insert("", window, cx);
13973 });
13974 }
13975
13976 pub fn move_to_beginning_of_line(
13977 &mut self,
13978 action: &MoveToBeginningOfLine,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13983 self.change_selections(Default::default(), window, cx, |s| {
13984 s.move_cursors_with(|map, head, _| {
13985 (
13986 movement::indented_line_beginning(
13987 map,
13988 head,
13989 action.stop_at_soft_wraps,
13990 action.stop_at_indent,
13991 ),
13992 SelectionGoal::None,
13993 )
13994 });
13995 })
13996 }
13997
13998 pub fn select_to_beginning_of_line(
13999 &mut self,
14000 action: &SelectToBeginningOfLine,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14005 self.change_selections(Default::default(), window, cx, |s| {
14006 s.move_heads_with(|map, head, _| {
14007 (
14008 movement::indented_line_beginning(
14009 map,
14010 head,
14011 action.stop_at_soft_wraps,
14012 action.stop_at_indent,
14013 ),
14014 SelectionGoal::None,
14015 )
14016 });
14017 });
14018 }
14019
14020 pub fn delete_to_beginning_of_line(
14021 &mut self,
14022 action: &DeleteToBeginningOfLine,
14023 window: &mut Window,
14024 cx: &mut Context<Self>,
14025 ) {
14026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14027 self.transact(window, cx, |this, window, cx| {
14028 this.change_selections(Default::default(), window, cx, |s| {
14029 s.move_with(|_, selection| {
14030 selection.reversed = true;
14031 });
14032 });
14033
14034 this.select_to_beginning_of_line(
14035 &SelectToBeginningOfLine {
14036 stop_at_soft_wraps: false,
14037 stop_at_indent: action.stop_at_indent,
14038 },
14039 window,
14040 cx,
14041 );
14042 this.backspace(&Backspace, window, cx);
14043 });
14044 }
14045
14046 pub fn move_to_end_of_line(
14047 &mut self,
14048 action: &MoveToEndOfLine,
14049 window: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14053 self.change_selections(Default::default(), window, cx, |s| {
14054 s.move_cursors_with(|map, head, _| {
14055 (
14056 movement::line_end(map, head, action.stop_at_soft_wraps),
14057 SelectionGoal::None,
14058 )
14059 });
14060 })
14061 }
14062
14063 pub fn select_to_end_of_line(
14064 &mut self,
14065 action: &SelectToEndOfLine,
14066 window: &mut Window,
14067 cx: &mut Context<Self>,
14068 ) {
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.move_heads_with(|map, head, _| {
14072 (
14073 movement::line_end(map, head, action.stop_at_soft_wraps),
14074 SelectionGoal::None,
14075 )
14076 });
14077 })
14078 }
14079
14080 pub fn delete_to_end_of_line(
14081 &mut self,
14082 _: &DeleteToEndOfLine,
14083 window: &mut Window,
14084 cx: &mut Context<Self>,
14085 ) {
14086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14087 self.transact(window, cx, |this, window, cx| {
14088 this.select_to_end_of_line(
14089 &SelectToEndOfLine {
14090 stop_at_soft_wraps: false,
14091 },
14092 window,
14093 cx,
14094 );
14095 this.delete(&Delete, window, cx);
14096 });
14097 }
14098
14099 pub fn cut_to_end_of_line(
14100 &mut self,
14101 action: &CutToEndOfLine,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14106 self.transact(window, cx, |this, window, cx| {
14107 this.select_to_end_of_line(
14108 &SelectToEndOfLine {
14109 stop_at_soft_wraps: false,
14110 },
14111 window,
14112 cx,
14113 );
14114 if !action.stop_at_newlines {
14115 this.change_selections(Default::default(), window, cx, |s| {
14116 s.move_with(|_, sel| {
14117 if sel.is_empty() {
14118 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14119 }
14120 });
14121 });
14122 }
14123 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14124 let item = this.cut_common(false, window, cx);
14125 cx.write_to_clipboard(item);
14126 });
14127 }
14128
14129 pub fn move_to_start_of_paragraph(
14130 &mut self,
14131 _: &MoveToStartOfParagraph,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 if matches!(self.mode, EditorMode::SingleLine) {
14136 cx.propagate();
14137 return;
14138 }
14139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14140 self.change_selections(Default::default(), window, cx, |s| {
14141 s.move_with(|map, selection| {
14142 selection.collapse_to(
14143 movement::start_of_paragraph(map, selection.head(), 1),
14144 SelectionGoal::None,
14145 )
14146 });
14147 })
14148 }
14149
14150 pub fn move_to_end_of_paragraph(
14151 &mut self,
14152 _: &MoveToEndOfParagraph,
14153 window: &mut Window,
14154 cx: &mut Context<Self>,
14155 ) {
14156 if matches!(self.mode, EditorMode::SingleLine) {
14157 cx.propagate();
14158 return;
14159 }
14160 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14161 self.change_selections(Default::default(), window, cx, |s| {
14162 s.move_with(|map, selection| {
14163 selection.collapse_to(
14164 movement::end_of_paragraph(map, selection.head(), 1),
14165 SelectionGoal::None,
14166 )
14167 });
14168 })
14169 }
14170
14171 pub fn select_to_start_of_paragraph(
14172 &mut self,
14173 _: &SelectToStartOfParagraph,
14174 window: &mut Window,
14175 cx: &mut Context<Self>,
14176 ) {
14177 if matches!(self.mode, EditorMode::SingleLine) {
14178 cx.propagate();
14179 return;
14180 }
14181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14182 self.change_selections(Default::default(), window, cx, |s| {
14183 s.move_heads_with(|map, head, _| {
14184 (
14185 movement::start_of_paragraph(map, head, 1),
14186 SelectionGoal::None,
14187 )
14188 });
14189 })
14190 }
14191
14192 pub fn select_to_end_of_paragraph(
14193 &mut self,
14194 _: &SelectToEndOfParagraph,
14195 window: &mut Window,
14196 cx: &mut Context<Self>,
14197 ) {
14198 if matches!(self.mode, EditorMode::SingleLine) {
14199 cx.propagate();
14200 return;
14201 }
14202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14203 self.change_selections(Default::default(), window, cx, |s| {
14204 s.move_heads_with(|map, head, _| {
14205 (
14206 movement::end_of_paragraph(map, head, 1),
14207 SelectionGoal::None,
14208 )
14209 });
14210 })
14211 }
14212
14213 pub fn move_to_start_of_excerpt(
14214 &mut self,
14215 _: &MoveToStartOfExcerpt,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) {
14219 if matches!(self.mode, EditorMode::SingleLine) {
14220 cx.propagate();
14221 return;
14222 }
14223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14224 self.change_selections(Default::default(), window, cx, |s| {
14225 s.move_with(|map, selection| {
14226 selection.collapse_to(
14227 movement::start_of_excerpt(
14228 map,
14229 selection.head(),
14230 workspace::searchable::Direction::Prev,
14231 ),
14232 SelectionGoal::None,
14233 )
14234 });
14235 })
14236 }
14237
14238 pub fn move_to_start_of_next_excerpt(
14239 &mut self,
14240 _: &MoveToStartOfNextExcerpt,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) {
14244 if matches!(self.mode, EditorMode::SingleLine) {
14245 cx.propagate();
14246 return;
14247 }
14248
14249 self.change_selections(Default::default(), window, cx, |s| {
14250 s.move_with(|map, selection| {
14251 selection.collapse_to(
14252 movement::start_of_excerpt(
14253 map,
14254 selection.head(),
14255 workspace::searchable::Direction::Next,
14256 ),
14257 SelectionGoal::None,
14258 )
14259 });
14260 })
14261 }
14262
14263 pub fn move_to_end_of_excerpt(
14264 &mut self,
14265 _: &MoveToEndOfExcerpt,
14266 window: &mut Window,
14267 cx: &mut Context<Self>,
14268 ) {
14269 if matches!(self.mode, EditorMode::SingleLine) {
14270 cx.propagate();
14271 return;
14272 }
14273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14274 self.change_selections(Default::default(), window, cx, |s| {
14275 s.move_with(|map, selection| {
14276 selection.collapse_to(
14277 movement::end_of_excerpt(
14278 map,
14279 selection.head(),
14280 workspace::searchable::Direction::Next,
14281 ),
14282 SelectionGoal::None,
14283 )
14284 });
14285 })
14286 }
14287
14288 pub fn move_to_end_of_previous_excerpt(
14289 &mut self,
14290 _: &MoveToEndOfPreviousExcerpt,
14291 window: &mut Window,
14292 cx: &mut Context<Self>,
14293 ) {
14294 if matches!(self.mode, EditorMode::SingleLine) {
14295 cx.propagate();
14296 return;
14297 }
14298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14299 self.change_selections(Default::default(), window, cx, |s| {
14300 s.move_with(|map, selection| {
14301 selection.collapse_to(
14302 movement::end_of_excerpt(
14303 map,
14304 selection.head(),
14305 workspace::searchable::Direction::Prev,
14306 ),
14307 SelectionGoal::None,
14308 )
14309 });
14310 })
14311 }
14312
14313 pub fn select_to_start_of_excerpt(
14314 &mut self,
14315 _: &SelectToStartOfExcerpt,
14316 window: &mut Window,
14317 cx: &mut Context<Self>,
14318 ) {
14319 if matches!(self.mode, EditorMode::SingleLine) {
14320 cx.propagate();
14321 return;
14322 }
14323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14324 self.change_selections(Default::default(), window, cx, |s| {
14325 s.move_heads_with(|map, head, _| {
14326 (
14327 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14328 SelectionGoal::None,
14329 )
14330 });
14331 })
14332 }
14333
14334 pub fn select_to_start_of_next_excerpt(
14335 &mut self,
14336 _: &SelectToStartOfNextExcerpt,
14337 window: &mut Window,
14338 cx: &mut Context<Self>,
14339 ) {
14340 if matches!(self.mode, EditorMode::SingleLine) {
14341 cx.propagate();
14342 return;
14343 }
14344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14345 self.change_selections(Default::default(), window, cx, |s| {
14346 s.move_heads_with(|map, head, _| {
14347 (
14348 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14349 SelectionGoal::None,
14350 )
14351 });
14352 })
14353 }
14354
14355 pub fn select_to_end_of_excerpt(
14356 &mut self,
14357 _: &SelectToEndOfExcerpt,
14358 window: &mut Window,
14359 cx: &mut Context<Self>,
14360 ) {
14361 if matches!(self.mode, EditorMode::SingleLine) {
14362 cx.propagate();
14363 return;
14364 }
14365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14366 self.change_selections(Default::default(), window, cx, |s| {
14367 s.move_heads_with(|map, head, _| {
14368 (
14369 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14370 SelectionGoal::None,
14371 )
14372 });
14373 })
14374 }
14375
14376 pub fn select_to_end_of_previous_excerpt(
14377 &mut self,
14378 _: &SelectToEndOfPreviousExcerpt,
14379 window: &mut Window,
14380 cx: &mut Context<Self>,
14381 ) {
14382 if matches!(self.mode, EditorMode::SingleLine) {
14383 cx.propagate();
14384 return;
14385 }
14386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14387 self.change_selections(Default::default(), window, cx, |s| {
14388 s.move_heads_with(|map, head, _| {
14389 (
14390 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14391 SelectionGoal::None,
14392 )
14393 });
14394 })
14395 }
14396
14397 pub fn move_to_beginning(
14398 &mut self,
14399 _: &MoveToBeginning,
14400 window: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) {
14403 if matches!(self.mode, EditorMode::SingleLine) {
14404 cx.propagate();
14405 return;
14406 }
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 self.change_selections(Default::default(), window, cx, |s| {
14409 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14410 });
14411 }
14412
14413 pub fn select_to_beginning(
14414 &mut self,
14415 _: &SelectToBeginning,
14416 window: &mut Window,
14417 cx: &mut Context<Self>,
14418 ) {
14419 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14420 selection.set_head(Point::zero(), SelectionGoal::None);
14421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14422 self.change_selections(Default::default(), window, cx, |s| {
14423 s.select(vec![selection]);
14424 });
14425 }
14426
14427 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14428 if matches!(self.mode, EditorMode::SingleLine) {
14429 cx.propagate();
14430 return;
14431 }
14432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14433 let cursor = self.buffer.read(cx).read(cx).len();
14434 self.change_selections(Default::default(), window, cx, |s| {
14435 s.select_ranges(vec![cursor..cursor])
14436 });
14437 }
14438
14439 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14440 self.nav_history = nav_history;
14441 }
14442
14443 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14444 self.nav_history.as_ref()
14445 }
14446
14447 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14448 self.push_to_nav_history(
14449 self.selections.newest_anchor().head(),
14450 None,
14451 false,
14452 true,
14453 cx,
14454 );
14455 }
14456
14457 fn push_to_nav_history(
14458 &mut self,
14459 cursor_anchor: Anchor,
14460 new_position: Option<Point>,
14461 is_deactivate: bool,
14462 always: bool,
14463 cx: &mut Context<Self>,
14464 ) {
14465 if let Some(nav_history) = self.nav_history.as_mut() {
14466 let buffer = self.buffer.read(cx).read(cx);
14467 let cursor_position = cursor_anchor.to_point(&buffer);
14468 let scroll_state = self.scroll_manager.anchor();
14469 let scroll_top_row = scroll_state.top_row(&buffer);
14470 drop(buffer);
14471
14472 if let Some(new_position) = new_position {
14473 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14474 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14475 return;
14476 }
14477 }
14478
14479 nav_history.push(
14480 Some(NavigationData {
14481 cursor_anchor,
14482 cursor_position,
14483 scroll_anchor: scroll_state,
14484 scroll_top_row,
14485 }),
14486 cx,
14487 );
14488 cx.emit(EditorEvent::PushedToNavHistory {
14489 anchor: cursor_anchor,
14490 is_deactivate,
14491 })
14492 }
14493 }
14494
14495 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14497 let buffer = self.buffer.read(cx).snapshot(cx);
14498 let mut selection = self
14499 .selections
14500 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14501 selection.set_head(buffer.len(), SelectionGoal::None);
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.select(vec![selection]);
14504 });
14505 }
14506
14507 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14509 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14510 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14511 });
14512 }
14513
14514 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14517 let mut selections = self.selections.all::<Point>(&display_map);
14518 let max_point = display_map.buffer_snapshot().max_point();
14519 for selection in &mut selections {
14520 let rows = selection.spanned_rows(true, &display_map);
14521 selection.start = Point::new(rows.start.0, 0);
14522 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14523 selection.reversed = false;
14524 }
14525 self.change_selections(Default::default(), window, cx, |s| {
14526 s.select(selections);
14527 });
14528 }
14529
14530 pub fn split_selection_into_lines(
14531 &mut self,
14532 action: &SplitSelectionIntoLines,
14533 window: &mut Window,
14534 cx: &mut Context<Self>,
14535 ) {
14536 let selections = self
14537 .selections
14538 .all::<Point>(&self.display_snapshot(cx))
14539 .into_iter()
14540 .map(|selection| selection.start..selection.end)
14541 .collect::<Vec<_>>();
14542 self.unfold_ranges(&selections, true, true, cx);
14543
14544 let mut new_selection_ranges = Vec::new();
14545 {
14546 let buffer = self.buffer.read(cx).read(cx);
14547 for selection in selections {
14548 for row in selection.start.row..selection.end.row {
14549 let line_start = Point::new(row, 0);
14550 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14551
14552 if action.keep_selections {
14553 // Keep the selection range for each line
14554 let selection_start = if row == selection.start.row {
14555 selection.start
14556 } else {
14557 line_start
14558 };
14559 new_selection_ranges.push(selection_start..line_end);
14560 } else {
14561 // Collapse to cursor at end of line
14562 new_selection_ranges.push(line_end..line_end);
14563 }
14564 }
14565
14566 let is_multiline_selection = selection.start.row != selection.end.row;
14567 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14568 // so this action feels more ergonomic when paired with other selection operations
14569 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14570 if !should_skip_last {
14571 if action.keep_selections {
14572 if is_multiline_selection {
14573 let line_start = Point::new(selection.end.row, 0);
14574 new_selection_ranges.push(line_start..selection.end);
14575 } else {
14576 new_selection_ranges.push(selection.start..selection.end);
14577 }
14578 } else {
14579 new_selection_ranges.push(selection.end..selection.end);
14580 }
14581 }
14582 }
14583 }
14584 self.change_selections(Default::default(), window, cx, |s| {
14585 s.select_ranges(new_selection_ranges);
14586 });
14587 }
14588
14589 pub fn add_selection_above(
14590 &mut self,
14591 action: &AddSelectionAbove,
14592 window: &mut Window,
14593 cx: &mut Context<Self>,
14594 ) {
14595 self.add_selection(true, action.skip_soft_wrap, window, cx);
14596 }
14597
14598 pub fn add_selection_below(
14599 &mut self,
14600 action: &AddSelectionBelow,
14601 window: &mut Window,
14602 cx: &mut Context<Self>,
14603 ) {
14604 self.add_selection(false, action.skip_soft_wrap, window, cx);
14605 }
14606
14607 fn add_selection(
14608 &mut self,
14609 above: bool,
14610 skip_soft_wrap: bool,
14611 window: &mut Window,
14612 cx: &mut Context<Self>,
14613 ) {
14614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14615
14616 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14617 let all_selections = self.selections.all::<Point>(&display_map);
14618 let text_layout_details = self.text_layout_details(window);
14619
14620 let (mut columnar_selections, new_selections_to_columnarize) = {
14621 if let Some(state) = self.add_selections_state.as_ref() {
14622 let columnar_selection_ids: HashSet<_> = state
14623 .groups
14624 .iter()
14625 .flat_map(|group| group.stack.iter())
14626 .copied()
14627 .collect();
14628
14629 all_selections
14630 .into_iter()
14631 .partition(|s| columnar_selection_ids.contains(&s.id))
14632 } else {
14633 (Vec::new(), all_selections)
14634 }
14635 };
14636
14637 let mut state = self
14638 .add_selections_state
14639 .take()
14640 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14641
14642 for selection in new_selections_to_columnarize {
14643 let range = selection.display_range(&display_map).sorted();
14644 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14645 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14646 let positions = start_x.min(end_x)..start_x.max(end_x);
14647 let mut stack = Vec::new();
14648 for row in range.start.row().0..=range.end.row().0 {
14649 if let Some(selection) = self.selections.build_columnar_selection(
14650 &display_map,
14651 DisplayRow(row),
14652 &positions,
14653 selection.reversed,
14654 &text_layout_details,
14655 ) {
14656 stack.push(selection.id);
14657 columnar_selections.push(selection);
14658 }
14659 }
14660 if !stack.is_empty() {
14661 if above {
14662 stack.reverse();
14663 }
14664 state.groups.push(AddSelectionsGroup { above, stack });
14665 }
14666 }
14667
14668 let mut final_selections = Vec::new();
14669 let end_row = if above {
14670 DisplayRow(0)
14671 } else {
14672 display_map.max_point().row()
14673 };
14674
14675 let mut last_added_item_per_group = HashMap::default();
14676 for group in state.groups.iter_mut() {
14677 if let Some(last_id) = group.stack.last() {
14678 last_added_item_per_group.insert(*last_id, group);
14679 }
14680 }
14681
14682 for selection in columnar_selections {
14683 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14684 if above == group.above {
14685 let range = selection.display_range(&display_map).sorted();
14686 debug_assert_eq!(range.start.row(), range.end.row());
14687 let mut row = range.start.row();
14688 let positions =
14689 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14690 Pixels::from(start)..Pixels::from(end)
14691 } else {
14692 let start_x =
14693 display_map.x_for_display_point(range.start, &text_layout_details);
14694 let end_x =
14695 display_map.x_for_display_point(range.end, &text_layout_details);
14696 start_x.min(end_x)..start_x.max(end_x)
14697 };
14698
14699 let mut maybe_new_selection = None;
14700 let direction = if above { -1 } else { 1 };
14701
14702 while row != end_row {
14703 if skip_soft_wrap {
14704 row = display_map
14705 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14706 .row();
14707 } else if above {
14708 row.0 -= 1;
14709 } else {
14710 row.0 += 1;
14711 }
14712
14713 if let Some(new_selection) = self.selections.build_columnar_selection(
14714 &display_map,
14715 row,
14716 &positions,
14717 selection.reversed,
14718 &text_layout_details,
14719 ) {
14720 maybe_new_selection = Some(new_selection);
14721 break;
14722 }
14723 }
14724
14725 if let Some(new_selection) = maybe_new_selection {
14726 group.stack.push(new_selection.id);
14727 if above {
14728 final_selections.push(new_selection);
14729 final_selections.push(selection);
14730 } else {
14731 final_selections.push(selection);
14732 final_selections.push(new_selection);
14733 }
14734 } else {
14735 final_selections.push(selection);
14736 }
14737 } else {
14738 group.stack.pop();
14739 }
14740 } else {
14741 final_selections.push(selection);
14742 }
14743 }
14744
14745 self.change_selections(Default::default(), window, cx, |s| {
14746 s.select(final_selections);
14747 });
14748
14749 let final_selection_ids: HashSet<_> = self
14750 .selections
14751 .all::<Point>(&display_map)
14752 .iter()
14753 .map(|s| s.id)
14754 .collect();
14755 state.groups.retain_mut(|group| {
14756 // selections might get merged above so we remove invalid items from stacks
14757 group.stack.retain(|id| final_selection_ids.contains(id));
14758
14759 // single selection in stack can be treated as initial state
14760 group.stack.len() > 1
14761 });
14762
14763 if !state.groups.is_empty() {
14764 self.add_selections_state = Some(state);
14765 }
14766 }
14767
14768 fn select_match_ranges(
14769 &mut self,
14770 range: Range<MultiBufferOffset>,
14771 reversed: bool,
14772 replace_newest: bool,
14773 auto_scroll: Option<Autoscroll>,
14774 window: &mut Window,
14775 cx: &mut Context<Editor>,
14776 ) {
14777 self.unfold_ranges(
14778 std::slice::from_ref(&range),
14779 false,
14780 auto_scroll.is_some(),
14781 cx,
14782 );
14783 let effects = if let Some(scroll) = auto_scroll {
14784 SelectionEffects::scroll(scroll)
14785 } else {
14786 SelectionEffects::no_scroll()
14787 };
14788 self.change_selections(effects, window, cx, |s| {
14789 if replace_newest {
14790 s.delete(s.newest_anchor().id);
14791 }
14792 if reversed {
14793 s.insert_range(range.end..range.start);
14794 } else {
14795 s.insert_range(range);
14796 }
14797 });
14798 }
14799
14800 pub fn select_next_match_internal(
14801 &mut self,
14802 display_map: &DisplaySnapshot,
14803 replace_newest: bool,
14804 autoscroll: Option<Autoscroll>,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) -> Result<()> {
14808 let buffer = display_map.buffer_snapshot();
14809 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14810 if let Some(mut select_next_state) = self.select_next_state.take() {
14811 let query = &select_next_state.query;
14812 if !select_next_state.done {
14813 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14814 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14815 let mut next_selected_range = None;
14816
14817 let bytes_after_last_selection =
14818 buffer.bytes_in_range(last_selection.end..buffer.len());
14819 let bytes_before_first_selection =
14820 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14821 let query_matches = query
14822 .stream_find_iter(bytes_after_last_selection)
14823 .map(|result| (last_selection.end, result))
14824 .chain(
14825 query
14826 .stream_find_iter(bytes_before_first_selection)
14827 .map(|result| (MultiBufferOffset(0), result)),
14828 );
14829
14830 for (start_offset, query_match) in query_matches {
14831 let query_match = query_match.unwrap(); // can only fail due to I/O
14832 let offset_range =
14833 start_offset + query_match.start()..start_offset + query_match.end();
14834
14835 if !select_next_state.wordwise
14836 || (!buffer.is_inside_word(offset_range.start, None)
14837 && !buffer.is_inside_word(offset_range.end, None))
14838 {
14839 let idx = selections
14840 .partition_point(|selection| selection.end <= offset_range.start);
14841 let overlaps = selections
14842 .get(idx)
14843 .map_or(false, |selection| selection.start < offset_range.end);
14844
14845 if !overlaps {
14846 next_selected_range = Some(offset_range);
14847 break;
14848 }
14849 }
14850 }
14851
14852 if let Some(next_selected_range) = next_selected_range {
14853 self.select_match_ranges(
14854 next_selected_range,
14855 last_selection.reversed,
14856 replace_newest,
14857 autoscroll,
14858 window,
14859 cx,
14860 );
14861 } else {
14862 select_next_state.done = true;
14863 }
14864 }
14865
14866 self.select_next_state = Some(select_next_state);
14867 } else {
14868 let mut only_carets = true;
14869 let mut same_text_selected = true;
14870 let mut selected_text = None;
14871
14872 let mut selections_iter = selections.iter().peekable();
14873 while let Some(selection) = selections_iter.next() {
14874 if selection.start != selection.end {
14875 only_carets = false;
14876 }
14877
14878 if same_text_selected {
14879 if selected_text.is_none() {
14880 selected_text =
14881 Some(buffer.text_for_range(selection.range()).collect::<String>());
14882 }
14883
14884 if let Some(next_selection) = selections_iter.peek() {
14885 if next_selection.len() == selection.len() {
14886 let next_selected_text = buffer
14887 .text_for_range(next_selection.range())
14888 .collect::<String>();
14889 if Some(next_selected_text) != selected_text {
14890 same_text_selected = false;
14891 selected_text = None;
14892 }
14893 } else {
14894 same_text_selected = false;
14895 selected_text = None;
14896 }
14897 }
14898 }
14899 }
14900
14901 if only_carets {
14902 for selection in &mut selections {
14903 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14904 selection.start = word_range.start;
14905 selection.end = word_range.end;
14906 selection.goal = SelectionGoal::None;
14907 selection.reversed = false;
14908 self.select_match_ranges(
14909 selection.start..selection.end,
14910 selection.reversed,
14911 replace_newest,
14912 autoscroll,
14913 window,
14914 cx,
14915 );
14916 }
14917
14918 if selections.len() == 1 {
14919 let selection = selections
14920 .last()
14921 .expect("ensured that there's only one selection");
14922 let query = buffer
14923 .text_for_range(selection.start..selection.end)
14924 .collect::<String>();
14925 let is_empty = query.is_empty();
14926 let select_state = SelectNextState {
14927 query: self.build_query(&[query], cx)?,
14928 wordwise: true,
14929 done: is_empty,
14930 };
14931 self.select_next_state = Some(select_state);
14932 } else {
14933 self.select_next_state = None;
14934 }
14935 } else if let Some(selected_text) = selected_text {
14936 self.select_next_state = Some(SelectNextState {
14937 query: self.build_query(&[selected_text], cx)?,
14938 wordwise: false,
14939 done: false,
14940 });
14941 self.select_next_match_internal(
14942 display_map,
14943 replace_newest,
14944 autoscroll,
14945 window,
14946 cx,
14947 )?;
14948 }
14949 }
14950 Ok(())
14951 }
14952
14953 pub fn select_all_matches(
14954 &mut self,
14955 _action: &SelectAllMatches,
14956 window: &mut Window,
14957 cx: &mut Context<Self>,
14958 ) -> Result<()> {
14959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14960
14961 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14962
14963 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14964 let Some(select_next_state) = self.select_next_state.as_mut() else {
14965 return Ok(());
14966 };
14967 if select_next_state.done {
14968 return Ok(());
14969 }
14970
14971 let mut new_selections = Vec::new();
14972
14973 let reversed = self
14974 .selections
14975 .oldest::<MultiBufferOffset>(&display_map)
14976 .reversed;
14977 let buffer = display_map.buffer_snapshot();
14978 let query_matches = select_next_state
14979 .query
14980 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14981
14982 for query_match in query_matches.into_iter() {
14983 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14984 let offset_range = if reversed {
14985 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14986 } else {
14987 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14988 };
14989
14990 if !select_next_state.wordwise
14991 || (!buffer.is_inside_word(offset_range.start, None)
14992 && !buffer.is_inside_word(offset_range.end, None))
14993 {
14994 new_selections.push(offset_range.start..offset_range.end);
14995 }
14996 }
14997
14998 select_next_state.done = true;
14999
15000 if new_selections.is_empty() {
15001 log::error!("bug: new_selections is empty in select_all_matches");
15002 return Ok(());
15003 }
15004
15005 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15006 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15007 selections.select_ranges(new_selections)
15008 });
15009
15010 Ok(())
15011 }
15012
15013 pub fn select_next(
15014 &mut self,
15015 action: &SelectNext,
15016 window: &mut Window,
15017 cx: &mut Context<Self>,
15018 ) -> Result<()> {
15019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15020 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15021 self.select_next_match_internal(
15022 &display_map,
15023 action.replace_newest,
15024 Some(Autoscroll::newest()),
15025 window,
15026 cx,
15027 )?;
15028 Ok(())
15029 }
15030
15031 pub fn select_previous(
15032 &mut self,
15033 action: &SelectPrevious,
15034 window: &mut Window,
15035 cx: &mut Context<Self>,
15036 ) -> Result<()> {
15037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15038 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15039 let buffer = display_map.buffer_snapshot();
15040 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15041 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15042 let query = &select_prev_state.query;
15043 if !select_prev_state.done {
15044 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15045 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15046 let mut next_selected_range = None;
15047 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15048 let bytes_before_last_selection =
15049 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15050 let bytes_after_first_selection =
15051 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15052 let query_matches = query
15053 .stream_find_iter(bytes_before_last_selection)
15054 .map(|result| (last_selection.start, result))
15055 .chain(
15056 query
15057 .stream_find_iter(bytes_after_first_selection)
15058 .map(|result| (buffer.len(), result)),
15059 );
15060 for (end_offset, query_match) in query_matches {
15061 let query_match = query_match.unwrap(); // can only fail due to I/O
15062 let offset_range =
15063 end_offset - query_match.end()..end_offset - query_match.start();
15064
15065 if !select_prev_state.wordwise
15066 || (!buffer.is_inside_word(offset_range.start, None)
15067 && !buffer.is_inside_word(offset_range.end, None))
15068 {
15069 next_selected_range = Some(offset_range);
15070 break;
15071 }
15072 }
15073
15074 if let Some(next_selected_range) = next_selected_range {
15075 self.select_match_ranges(
15076 next_selected_range,
15077 last_selection.reversed,
15078 action.replace_newest,
15079 Some(Autoscroll::newest()),
15080 window,
15081 cx,
15082 );
15083 } else {
15084 select_prev_state.done = true;
15085 }
15086 }
15087
15088 self.select_prev_state = Some(select_prev_state);
15089 } else {
15090 let mut only_carets = true;
15091 let mut same_text_selected = true;
15092 let mut selected_text = None;
15093
15094 let mut selections_iter = selections.iter().peekable();
15095 while let Some(selection) = selections_iter.next() {
15096 if selection.start != selection.end {
15097 only_carets = false;
15098 }
15099
15100 if same_text_selected {
15101 if selected_text.is_none() {
15102 selected_text =
15103 Some(buffer.text_for_range(selection.range()).collect::<String>());
15104 }
15105
15106 if let Some(next_selection) = selections_iter.peek() {
15107 if next_selection.len() == selection.len() {
15108 let next_selected_text = buffer
15109 .text_for_range(next_selection.range())
15110 .collect::<String>();
15111 if Some(next_selected_text) != selected_text {
15112 same_text_selected = false;
15113 selected_text = None;
15114 }
15115 } else {
15116 same_text_selected = false;
15117 selected_text = None;
15118 }
15119 }
15120 }
15121 }
15122
15123 if only_carets {
15124 for selection in &mut selections {
15125 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15126 selection.start = word_range.start;
15127 selection.end = word_range.end;
15128 selection.goal = SelectionGoal::None;
15129 selection.reversed = false;
15130 self.select_match_ranges(
15131 selection.start..selection.end,
15132 selection.reversed,
15133 action.replace_newest,
15134 Some(Autoscroll::newest()),
15135 window,
15136 cx,
15137 );
15138 }
15139 if selections.len() == 1 {
15140 let selection = selections
15141 .last()
15142 .expect("ensured that there's only one selection");
15143 let query = buffer
15144 .text_for_range(selection.start..selection.end)
15145 .collect::<String>();
15146 let is_empty = query.is_empty();
15147 let select_state = SelectNextState {
15148 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15149 wordwise: true,
15150 done: is_empty,
15151 };
15152 self.select_prev_state = Some(select_state);
15153 } else {
15154 self.select_prev_state = None;
15155 }
15156 } else if let Some(selected_text) = selected_text {
15157 self.select_prev_state = Some(SelectNextState {
15158 query: self
15159 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15160 wordwise: false,
15161 done: false,
15162 });
15163 self.select_previous(action, window, cx)?;
15164 }
15165 }
15166 Ok(())
15167 }
15168
15169 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15170 /// setting the case sensitivity based on the global
15171 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15172 /// editor's settings.
15173 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15174 where
15175 I: IntoIterator<Item = P>,
15176 P: AsRef<[u8]>,
15177 {
15178 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15179 || EditorSettings::get_global(cx).search.case_sensitive,
15180 |value| value,
15181 );
15182
15183 let mut builder = AhoCorasickBuilder::new();
15184 builder.ascii_case_insensitive(!case_sensitive);
15185 builder.build(patterns)
15186 }
15187
15188 pub fn find_next_match(
15189 &mut self,
15190 _: &FindNextMatch,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) -> Result<()> {
15194 let selections = self.selections.disjoint_anchors_arc();
15195 match selections.first() {
15196 Some(first) if selections.len() >= 2 => {
15197 self.change_selections(Default::default(), window, cx, |s| {
15198 s.select_ranges([first.range()]);
15199 });
15200 }
15201 _ => self.select_next(
15202 &SelectNext {
15203 replace_newest: true,
15204 },
15205 window,
15206 cx,
15207 )?,
15208 }
15209 Ok(())
15210 }
15211
15212 pub fn find_previous_match(
15213 &mut self,
15214 _: &FindPreviousMatch,
15215 window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) -> Result<()> {
15218 let selections = self.selections.disjoint_anchors_arc();
15219 match selections.last() {
15220 Some(last) if selections.len() >= 2 => {
15221 self.change_selections(Default::default(), window, cx, |s| {
15222 s.select_ranges([last.range()]);
15223 });
15224 }
15225 _ => self.select_previous(
15226 &SelectPrevious {
15227 replace_newest: true,
15228 },
15229 window,
15230 cx,
15231 )?,
15232 }
15233 Ok(())
15234 }
15235
15236 pub fn toggle_comments(
15237 &mut self,
15238 action: &ToggleComments,
15239 window: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) {
15242 if self.read_only(cx) {
15243 return;
15244 }
15245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15246 let text_layout_details = &self.text_layout_details(window);
15247 self.transact(window, cx, |this, window, cx| {
15248 let mut selections = this
15249 .selections
15250 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15251 let mut edits = Vec::new();
15252 let mut selection_edit_ranges = Vec::new();
15253 let mut last_toggled_row = None;
15254 let snapshot = this.buffer.read(cx).read(cx);
15255 let empty_str: Arc<str> = Arc::default();
15256 let mut suffixes_inserted = Vec::new();
15257 let ignore_indent = action.ignore_indent;
15258
15259 fn comment_prefix_range(
15260 snapshot: &MultiBufferSnapshot,
15261 row: MultiBufferRow,
15262 comment_prefix: &str,
15263 comment_prefix_whitespace: &str,
15264 ignore_indent: bool,
15265 ) -> Range<Point> {
15266 let indent_size = if ignore_indent {
15267 0
15268 } else {
15269 snapshot.indent_size_for_line(row).len
15270 };
15271
15272 let start = Point::new(row.0, indent_size);
15273
15274 let mut line_bytes = snapshot
15275 .bytes_in_range(start..snapshot.max_point())
15276 .flatten()
15277 .copied();
15278
15279 // If this line currently begins with the line comment prefix, then record
15280 // the range containing the prefix.
15281 if line_bytes
15282 .by_ref()
15283 .take(comment_prefix.len())
15284 .eq(comment_prefix.bytes())
15285 {
15286 // Include any whitespace that matches the comment prefix.
15287 let matching_whitespace_len = line_bytes
15288 .zip(comment_prefix_whitespace.bytes())
15289 .take_while(|(a, b)| a == b)
15290 .count() as u32;
15291 let end = Point::new(
15292 start.row,
15293 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15294 );
15295 start..end
15296 } else {
15297 start..start
15298 }
15299 }
15300
15301 fn comment_suffix_range(
15302 snapshot: &MultiBufferSnapshot,
15303 row: MultiBufferRow,
15304 comment_suffix: &str,
15305 comment_suffix_has_leading_space: bool,
15306 ) -> Range<Point> {
15307 let end = Point::new(row.0, snapshot.line_len(row));
15308 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15309
15310 let mut line_end_bytes = snapshot
15311 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15312 .flatten()
15313 .copied();
15314
15315 let leading_space_len = if suffix_start_column > 0
15316 && line_end_bytes.next() == Some(b' ')
15317 && comment_suffix_has_leading_space
15318 {
15319 1
15320 } else {
15321 0
15322 };
15323
15324 // If this line currently begins with the line comment prefix, then record
15325 // the range containing the prefix.
15326 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15327 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15328 start..end
15329 } else {
15330 end..end
15331 }
15332 }
15333
15334 // TODO: Handle selections that cross excerpts
15335 for selection in &mut selections {
15336 let start_column = snapshot
15337 .indent_size_for_line(MultiBufferRow(selection.start.row))
15338 .len;
15339 let language = if let Some(language) =
15340 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15341 {
15342 language
15343 } else {
15344 continue;
15345 };
15346
15347 selection_edit_ranges.clear();
15348
15349 // If multiple selections contain a given row, avoid processing that
15350 // row more than once.
15351 let mut start_row = MultiBufferRow(selection.start.row);
15352 if last_toggled_row == Some(start_row) {
15353 start_row = start_row.next_row();
15354 }
15355 let end_row =
15356 if selection.end.row > selection.start.row && selection.end.column == 0 {
15357 MultiBufferRow(selection.end.row - 1)
15358 } else {
15359 MultiBufferRow(selection.end.row)
15360 };
15361 last_toggled_row = Some(end_row);
15362
15363 if start_row > end_row {
15364 continue;
15365 }
15366
15367 // If the language has line comments, toggle those.
15368 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15369
15370 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15371 if ignore_indent {
15372 full_comment_prefixes = full_comment_prefixes
15373 .into_iter()
15374 .map(|s| Arc::from(s.trim_end()))
15375 .collect();
15376 }
15377
15378 if !full_comment_prefixes.is_empty() {
15379 let first_prefix = full_comment_prefixes
15380 .first()
15381 .expect("prefixes is non-empty");
15382 let prefix_trimmed_lengths = full_comment_prefixes
15383 .iter()
15384 .map(|p| p.trim_end_matches(' ').len())
15385 .collect::<SmallVec<[usize; 4]>>();
15386
15387 let mut all_selection_lines_are_comments = true;
15388
15389 for row in start_row.0..=end_row.0 {
15390 let row = MultiBufferRow(row);
15391 if start_row < end_row && snapshot.is_line_blank(row) {
15392 continue;
15393 }
15394
15395 let prefix_range = full_comment_prefixes
15396 .iter()
15397 .zip(prefix_trimmed_lengths.iter().copied())
15398 .map(|(prefix, trimmed_prefix_len)| {
15399 comment_prefix_range(
15400 snapshot.deref(),
15401 row,
15402 &prefix[..trimmed_prefix_len],
15403 &prefix[trimmed_prefix_len..],
15404 ignore_indent,
15405 )
15406 })
15407 .max_by_key(|range| range.end.column - range.start.column)
15408 .expect("prefixes is non-empty");
15409
15410 if prefix_range.is_empty() {
15411 all_selection_lines_are_comments = false;
15412 }
15413
15414 selection_edit_ranges.push(prefix_range);
15415 }
15416
15417 if all_selection_lines_are_comments {
15418 edits.extend(
15419 selection_edit_ranges
15420 .iter()
15421 .cloned()
15422 .map(|range| (range, empty_str.clone())),
15423 );
15424 } else {
15425 let min_column = selection_edit_ranges
15426 .iter()
15427 .map(|range| range.start.column)
15428 .min()
15429 .unwrap_or(0);
15430 edits.extend(selection_edit_ranges.iter().map(|range| {
15431 let position = Point::new(range.start.row, min_column);
15432 (position..position, first_prefix.clone())
15433 }));
15434 }
15435 } else if let Some(BlockCommentConfig {
15436 start: full_comment_prefix,
15437 end: comment_suffix,
15438 ..
15439 }) = language.block_comment()
15440 {
15441 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15442 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15443 let prefix_range = comment_prefix_range(
15444 snapshot.deref(),
15445 start_row,
15446 comment_prefix,
15447 comment_prefix_whitespace,
15448 ignore_indent,
15449 );
15450 let suffix_range = comment_suffix_range(
15451 snapshot.deref(),
15452 end_row,
15453 comment_suffix.trim_start_matches(' '),
15454 comment_suffix.starts_with(' '),
15455 );
15456
15457 if prefix_range.is_empty() || suffix_range.is_empty() {
15458 edits.push((
15459 prefix_range.start..prefix_range.start,
15460 full_comment_prefix.clone(),
15461 ));
15462 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15463 suffixes_inserted.push((end_row, comment_suffix.len()));
15464 } else {
15465 edits.push((prefix_range, empty_str.clone()));
15466 edits.push((suffix_range, empty_str.clone()));
15467 }
15468 } else {
15469 continue;
15470 }
15471 }
15472
15473 drop(snapshot);
15474 this.buffer.update(cx, |buffer, cx| {
15475 buffer.edit(edits, None, cx);
15476 });
15477
15478 // Adjust selections so that they end before any comment suffixes that
15479 // were inserted.
15480 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15481 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15482 let snapshot = this.buffer.read(cx).read(cx);
15483 for selection in &mut selections {
15484 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15485 match row.cmp(&MultiBufferRow(selection.end.row)) {
15486 Ordering::Less => {
15487 suffixes_inserted.next();
15488 continue;
15489 }
15490 Ordering::Greater => break,
15491 Ordering::Equal => {
15492 if selection.end.column == snapshot.line_len(row) {
15493 if selection.is_empty() {
15494 selection.start.column -= suffix_len as u32;
15495 }
15496 selection.end.column -= suffix_len as u32;
15497 }
15498 break;
15499 }
15500 }
15501 }
15502 }
15503
15504 drop(snapshot);
15505 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15506
15507 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15508 let selections_on_single_row = selections.windows(2).all(|selections| {
15509 selections[0].start.row == selections[1].start.row
15510 && selections[0].end.row == selections[1].end.row
15511 && selections[0].start.row == selections[0].end.row
15512 });
15513 let selections_selecting = selections
15514 .iter()
15515 .any(|selection| selection.start != selection.end);
15516 let advance_downwards = action.advance_downwards
15517 && selections_on_single_row
15518 && !selections_selecting
15519 && !matches!(this.mode, EditorMode::SingleLine);
15520
15521 if advance_downwards {
15522 let snapshot = this.buffer.read(cx).snapshot(cx);
15523
15524 this.change_selections(Default::default(), window, cx, |s| {
15525 s.move_cursors_with(|display_snapshot, display_point, _| {
15526 let mut point = display_point.to_point(display_snapshot);
15527 point.row += 1;
15528 point = snapshot.clip_point(point, Bias::Left);
15529 let display_point = point.to_display_point(display_snapshot);
15530 let goal = SelectionGoal::HorizontalPosition(
15531 display_snapshot
15532 .x_for_display_point(display_point, text_layout_details)
15533 .into(),
15534 );
15535 (display_point, goal)
15536 })
15537 });
15538 }
15539 });
15540 }
15541
15542 pub fn select_enclosing_symbol(
15543 &mut self,
15544 _: &SelectEnclosingSymbol,
15545 window: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) {
15548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15549
15550 let buffer = self.buffer.read(cx).snapshot(cx);
15551 let old_selections = self
15552 .selections
15553 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15554 .into_boxed_slice();
15555
15556 fn update_selection(
15557 selection: &Selection<MultiBufferOffset>,
15558 buffer_snap: &MultiBufferSnapshot,
15559 ) -> Option<Selection<MultiBufferOffset>> {
15560 let cursor = selection.head();
15561 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15562 for symbol in symbols.iter().rev() {
15563 let start = symbol.range.start.to_offset(buffer_snap);
15564 let end = symbol.range.end.to_offset(buffer_snap);
15565 let new_range = start..end;
15566 if start < selection.start || end > selection.end {
15567 return Some(Selection {
15568 id: selection.id,
15569 start: new_range.start,
15570 end: new_range.end,
15571 goal: SelectionGoal::None,
15572 reversed: selection.reversed,
15573 });
15574 }
15575 }
15576 None
15577 }
15578
15579 let mut selected_larger_symbol = false;
15580 let new_selections = old_selections
15581 .iter()
15582 .map(|selection| match update_selection(selection, &buffer) {
15583 Some(new_selection) => {
15584 if new_selection.range() != selection.range() {
15585 selected_larger_symbol = true;
15586 }
15587 new_selection
15588 }
15589 None => selection.clone(),
15590 })
15591 .collect::<Vec<_>>();
15592
15593 if selected_larger_symbol {
15594 self.change_selections(Default::default(), window, cx, |s| {
15595 s.select(new_selections);
15596 });
15597 }
15598 }
15599
15600 pub fn select_larger_syntax_node(
15601 &mut self,
15602 _: &SelectLargerSyntaxNode,
15603 window: &mut Window,
15604 cx: &mut Context<Self>,
15605 ) {
15606 let Some(visible_row_count) = self.visible_row_count() else {
15607 return;
15608 };
15609 let old_selections: Box<[_]> = self
15610 .selections
15611 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15612 .into();
15613 if old_selections.is_empty() {
15614 return;
15615 }
15616
15617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15618
15619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15620 let buffer = self.buffer.read(cx).snapshot(cx);
15621
15622 let mut selected_larger_node = false;
15623 let mut new_selections = old_selections
15624 .iter()
15625 .map(|selection| {
15626 let old_range = selection.start..selection.end;
15627
15628 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15629 // manually select word at selection
15630 if ["string_content", "inline"].contains(&node.kind()) {
15631 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15632 // ignore if word is already selected
15633 if !word_range.is_empty() && old_range != word_range {
15634 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15635 // only select word if start and end point belongs to same word
15636 if word_range == last_word_range {
15637 selected_larger_node = true;
15638 return Selection {
15639 id: selection.id,
15640 start: word_range.start,
15641 end: word_range.end,
15642 goal: SelectionGoal::None,
15643 reversed: selection.reversed,
15644 };
15645 }
15646 }
15647 }
15648 }
15649
15650 let mut new_range = old_range.clone();
15651 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15652 new_range = range;
15653 if !node.is_named() {
15654 continue;
15655 }
15656 if !display_map.intersects_fold(new_range.start)
15657 && !display_map.intersects_fold(new_range.end)
15658 {
15659 break;
15660 }
15661 }
15662
15663 selected_larger_node |= new_range != old_range;
15664 Selection {
15665 id: selection.id,
15666 start: new_range.start,
15667 end: new_range.end,
15668 goal: SelectionGoal::None,
15669 reversed: selection.reversed,
15670 }
15671 })
15672 .collect::<Vec<_>>();
15673
15674 if !selected_larger_node {
15675 return; // don't put this call in the history
15676 }
15677
15678 // scroll based on transformation done to the last selection created by the user
15679 let (last_old, last_new) = old_selections
15680 .last()
15681 .zip(new_selections.last().cloned())
15682 .expect("old_selections isn't empty");
15683
15684 // revert selection
15685 let is_selection_reversed = {
15686 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15687 new_selections.last_mut().expect("checked above").reversed =
15688 should_newest_selection_be_reversed;
15689 should_newest_selection_be_reversed
15690 };
15691
15692 if selected_larger_node {
15693 self.select_syntax_node_history.disable_clearing = true;
15694 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15695 s.select(new_selections.clone());
15696 });
15697 self.select_syntax_node_history.disable_clearing = false;
15698 }
15699
15700 let start_row = last_new.start.to_display_point(&display_map).row().0;
15701 let end_row = last_new.end.to_display_point(&display_map).row().0;
15702 let selection_height = end_row - start_row + 1;
15703 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15704
15705 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15706 let scroll_behavior = if fits_on_the_screen {
15707 self.request_autoscroll(Autoscroll::fit(), cx);
15708 SelectSyntaxNodeScrollBehavior::FitSelection
15709 } else if is_selection_reversed {
15710 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15711 SelectSyntaxNodeScrollBehavior::CursorTop
15712 } else {
15713 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15714 SelectSyntaxNodeScrollBehavior::CursorBottom
15715 };
15716
15717 self.select_syntax_node_history.push((
15718 old_selections,
15719 scroll_behavior,
15720 is_selection_reversed,
15721 ));
15722 }
15723
15724 pub fn select_smaller_syntax_node(
15725 &mut self,
15726 _: &SelectSmallerSyntaxNode,
15727 window: &mut Window,
15728 cx: &mut Context<Self>,
15729 ) {
15730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15731
15732 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15733 self.select_syntax_node_history.pop()
15734 {
15735 if let Some(selection) = selections.last_mut() {
15736 selection.reversed = is_selection_reversed;
15737 }
15738
15739 self.select_syntax_node_history.disable_clearing = true;
15740 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15741 s.select(selections.to_vec());
15742 });
15743 self.select_syntax_node_history.disable_clearing = false;
15744
15745 match scroll_behavior {
15746 SelectSyntaxNodeScrollBehavior::CursorTop => {
15747 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15748 }
15749 SelectSyntaxNodeScrollBehavior::FitSelection => {
15750 self.request_autoscroll(Autoscroll::fit(), cx);
15751 }
15752 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15753 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15754 }
15755 }
15756 }
15757 }
15758
15759 pub fn unwrap_syntax_node(
15760 &mut self,
15761 _: &UnwrapSyntaxNode,
15762 window: &mut Window,
15763 cx: &mut Context<Self>,
15764 ) {
15765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15766
15767 let buffer = self.buffer.read(cx).snapshot(cx);
15768 let selections = self
15769 .selections
15770 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15771 .into_iter()
15772 // subtracting the offset requires sorting
15773 .sorted_by_key(|i| i.start);
15774
15775 let full_edits = selections
15776 .into_iter()
15777 .filter_map(|selection| {
15778 let child = if selection.is_empty()
15779 && let Some((_, ancestor_range)) =
15780 buffer.syntax_ancestor(selection.start..selection.end)
15781 {
15782 ancestor_range
15783 } else {
15784 selection.range()
15785 };
15786
15787 let mut parent = child.clone();
15788 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15789 parent = ancestor_range;
15790 if parent.start < child.start || parent.end > child.end {
15791 break;
15792 }
15793 }
15794
15795 if parent == child {
15796 return None;
15797 }
15798 let text = buffer.text_for_range(child).collect::<String>();
15799 Some((selection.id, parent, text))
15800 })
15801 .collect::<Vec<_>>();
15802 if full_edits.is_empty() {
15803 return;
15804 }
15805
15806 self.transact(window, cx, |this, window, cx| {
15807 this.buffer.update(cx, |buffer, cx| {
15808 buffer.edit(
15809 full_edits
15810 .iter()
15811 .map(|(_, p, t)| (p.clone(), t.clone()))
15812 .collect::<Vec<_>>(),
15813 None,
15814 cx,
15815 );
15816 });
15817 this.change_selections(Default::default(), window, cx, |s| {
15818 let mut offset = 0;
15819 let mut selections = vec![];
15820 for (id, parent, text) in full_edits {
15821 let start = parent.start - offset;
15822 offset += (parent.end - parent.start) - text.len();
15823 selections.push(Selection {
15824 id,
15825 start,
15826 end: start + text.len(),
15827 reversed: false,
15828 goal: Default::default(),
15829 });
15830 }
15831 s.select(selections);
15832 });
15833 });
15834 }
15835
15836 pub fn select_next_syntax_node(
15837 &mut self,
15838 _: &SelectNextSyntaxNode,
15839 window: &mut Window,
15840 cx: &mut Context<Self>,
15841 ) {
15842 let old_selections: Box<[_]> = self
15843 .selections
15844 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15845 .into();
15846 if old_selections.is_empty() {
15847 return;
15848 }
15849
15850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15851
15852 let buffer = self.buffer.read(cx).snapshot(cx);
15853 let mut selected_sibling = false;
15854
15855 let new_selections = old_selections
15856 .iter()
15857 .map(|selection| {
15858 let old_range = selection.start..selection.end;
15859
15860 let old_range =
15861 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15862 let excerpt = buffer.excerpt_containing(old_range.clone());
15863
15864 if let Some(mut excerpt) = excerpt
15865 && let Some(node) = excerpt
15866 .buffer()
15867 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15868 {
15869 let new_range = excerpt.map_range_from_buffer(
15870 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15871 );
15872 selected_sibling = true;
15873 Selection {
15874 id: selection.id,
15875 start: new_range.start,
15876 end: new_range.end,
15877 goal: SelectionGoal::None,
15878 reversed: selection.reversed,
15879 }
15880 } else {
15881 selection.clone()
15882 }
15883 })
15884 .collect::<Vec<_>>();
15885
15886 if selected_sibling {
15887 self.change_selections(
15888 SelectionEffects::scroll(Autoscroll::fit()),
15889 window,
15890 cx,
15891 |s| {
15892 s.select(new_selections);
15893 },
15894 );
15895 }
15896 }
15897
15898 pub fn select_prev_syntax_node(
15899 &mut self,
15900 _: &SelectPreviousSyntaxNode,
15901 window: &mut Window,
15902 cx: &mut Context<Self>,
15903 ) {
15904 let old_selections: Box<[_]> = self
15905 .selections
15906 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15907 .into();
15908 if old_selections.is_empty() {
15909 return;
15910 }
15911
15912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15913
15914 let buffer = self.buffer.read(cx).snapshot(cx);
15915 let mut selected_sibling = false;
15916
15917 let new_selections = old_selections
15918 .iter()
15919 .map(|selection| {
15920 let old_range = selection.start..selection.end;
15921 let old_range =
15922 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15923 let excerpt = buffer.excerpt_containing(old_range.clone());
15924
15925 if let Some(mut excerpt) = excerpt
15926 && let Some(node) = excerpt
15927 .buffer()
15928 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15929 {
15930 let new_range = excerpt.map_range_from_buffer(
15931 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15932 );
15933 selected_sibling = true;
15934 Selection {
15935 id: selection.id,
15936 start: new_range.start,
15937 end: new_range.end,
15938 goal: SelectionGoal::None,
15939 reversed: selection.reversed,
15940 }
15941 } else {
15942 selection.clone()
15943 }
15944 })
15945 .collect::<Vec<_>>();
15946
15947 if selected_sibling {
15948 self.change_selections(
15949 SelectionEffects::scroll(Autoscroll::fit()),
15950 window,
15951 cx,
15952 |s| {
15953 s.select(new_selections);
15954 },
15955 );
15956 }
15957 }
15958
15959 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15960 if !EditorSettings::get_global(cx).gutter.runnables {
15961 self.clear_tasks();
15962 return Task::ready(());
15963 }
15964 let project = self.project().map(Entity::downgrade);
15965 let task_sources = self.lsp_task_sources(cx);
15966 let multi_buffer = self.buffer.downgrade();
15967 cx.spawn_in(window, async move |editor, cx| {
15968 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15969 let Some(project) = project.and_then(|p| p.upgrade()) else {
15970 return;
15971 };
15972 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15973 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15974 }) else {
15975 return;
15976 };
15977
15978 let hide_runnables = project
15979 .update(cx, |project, _| project.is_via_collab())
15980 .unwrap_or(true);
15981 if hide_runnables {
15982 return;
15983 }
15984 let new_rows =
15985 cx.background_spawn({
15986 let snapshot = display_snapshot.clone();
15987 async move {
15988 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15989 }
15990 })
15991 .await;
15992 let Ok(lsp_tasks) =
15993 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15994 else {
15995 return;
15996 };
15997 let lsp_tasks = lsp_tasks.await;
15998
15999 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16000 lsp_tasks
16001 .into_iter()
16002 .flat_map(|(kind, tasks)| {
16003 tasks.into_iter().filter_map(move |(location, task)| {
16004 Some((kind.clone(), location?, task))
16005 })
16006 })
16007 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16008 let buffer = location.target.buffer;
16009 let buffer_snapshot = buffer.read(cx).snapshot();
16010 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16011 |(excerpt_id, snapshot, _)| {
16012 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16013 display_snapshot
16014 .buffer_snapshot()
16015 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16016 } else {
16017 None
16018 }
16019 },
16020 );
16021 if let Some(offset) = offset {
16022 let task_buffer_range =
16023 location.target.range.to_point(&buffer_snapshot);
16024 let context_buffer_range =
16025 task_buffer_range.to_offset(&buffer_snapshot);
16026 let context_range = BufferOffset(context_buffer_range.start)
16027 ..BufferOffset(context_buffer_range.end);
16028
16029 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16030 .or_insert_with(|| RunnableTasks {
16031 templates: Vec::new(),
16032 offset,
16033 column: task_buffer_range.start.column,
16034 extra_variables: HashMap::default(),
16035 context_range,
16036 })
16037 .templates
16038 .push((kind, task.original_task().clone()));
16039 }
16040
16041 acc
16042 })
16043 }) else {
16044 return;
16045 };
16046
16047 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16048 buffer.language_settings(cx).tasks.prefer_lsp
16049 }) else {
16050 return;
16051 };
16052
16053 let rows = Self::runnable_rows(
16054 project,
16055 display_snapshot,
16056 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16057 new_rows,
16058 cx.clone(),
16059 )
16060 .await;
16061 editor
16062 .update(cx, |editor, _| {
16063 editor.clear_tasks();
16064 for (key, mut value) in rows {
16065 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16066 value.templates.extend(lsp_tasks.templates);
16067 }
16068
16069 editor.insert_tasks(key, value);
16070 }
16071 for (key, value) in lsp_tasks_by_rows {
16072 editor.insert_tasks(key, value);
16073 }
16074 })
16075 .ok();
16076 })
16077 }
16078 fn fetch_runnable_ranges(
16079 snapshot: &DisplaySnapshot,
16080 range: Range<Anchor>,
16081 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16082 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16083 }
16084
16085 fn runnable_rows(
16086 project: Entity<Project>,
16087 snapshot: DisplaySnapshot,
16088 prefer_lsp: bool,
16089 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16090 cx: AsyncWindowContext,
16091 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16092 cx.spawn(async move |cx| {
16093 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16094 for (run_range, mut runnable) in runnable_ranges {
16095 let Some(tasks) = cx
16096 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16097 .ok()
16098 else {
16099 continue;
16100 };
16101 let mut tasks = tasks.await;
16102
16103 if prefer_lsp {
16104 tasks.retain(|(task_kind, _)| {
16105 !matches!(task_kind, TaskSourceKind::Language { .. })
16106 });
16107 }
16108 if tasks.is_empty() {
16109 continue;
16110 }
16111
16112 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16113 let Some(row) = snapshot
16114 .buffer_snapshot()
16115 .buffer_line_for_row(MultiBufferRow(point.row))
16116 .map(|(_, range)| range.start.row)
16117 else {
16118 continue;
16119 };
16120
16121 let context_range =
16122 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16123 runnable_rows.push((
16124 (runnable.buffer_id, row),
16125 RunnableTasks {
16126 templates: tasks,
16127 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16128 context_range,
16129 column: point.column,
16130 extra_variables: runnable.extra_captures,
16131 },
16132 ));
16133 }
16134 runnable_rows
16135 })
16136 }
16137
16138 fn templates_with_tags(
16139 project: &Entity<Project>,
16140 runnable: &mut Runnable,
16141 cx: &mut App,
16142 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16143 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16144 let (worktree_id, file) = project
16145 .buffer_for_id(runnable.buffer, cx)
16146 .and_then(|buffer| buffer.read(cx).file())
16147 .map(|file| (file.worktree_id(cx), file.clone()))
16148 .unzip();
16149
16150 (
16151 project.task_store().read(cx).task_inventory().cloned(),
16152 worktree_id,
16153 file,
16154 )
16155 });
16156
16157 let tags = mem::take(&mut runnable.tags);
16158 let language = runnable.language.clone();
16159 cx.spawn(async move |cx| {
16160 let mut templates_with_tags = Vec::new();
16161 if let Some(inventory) = inventory {
16162 for RunnableTag(tag) in tags {
16163 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16164 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16165 }) else {
16166 return templates_with_tags;
16167 };
16168 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16169 move |(_, template)| {
16170 template.tags.iter().any(|source_tag| source_tag == &tag)
16171 },
16172 ));
16173 }
16174 }
16175 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16176
16177 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16178 // Strongest source wins; if we have worktree tag binding, prefer that to
16179 // global and language bindings;
16180 // if we have a global binding, prefer that to language binding.
16181 let first_mismatch = templates_with_tags
16182 .iter()
16183 .position(|(tag_source, _)| tag_source != leading_tag_source);
16184 if let Some(index) = first_mismatch {
16185 templates_with_tags.truncate(index);
16186 }
16187 }
16188
16189 templates_with_tags
16190 })
16191 }
16192
16193 pub fn move_to_enclosing_bracket(
16194 &mut self,
16195 _: &MoveToEnclosingBracket,
16196 window: &mut Window,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16200 self.change_selections(Default::default(), window, cx, |s| {
16201 s.move_offsets_with(|snapshot, selection| {
16202 let Some(enclosing_bracket_ranges) =
16203 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16204 else {
16205 return;
16206 };
16207
16208 let mut best_length = usize::MAX;
16209 let mut best_inside = false;
16210 let mut best_in_bracket_range = false;
16211 let mut best_destination = None;
16212 for (open, close) in enclosing_bracket_ranges {
16213 let close = close.to_inclusive();
16214 let length = *close.end() - open.start;
16215 let inside = selection.start >= open.end && selection.end <= *close.start();
16216 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16217 || close.contains(&selection.head());
16218
16219 // If best is next to a bracket and current isn't, skip
16220 if !in_bracket_range && best_in_bracket_range {
16221 continue;
16222 }
16223
16224 // Prefer smaller lengths unless best is inside and current isn't
16225 if length > best_length && (best_inside || !inside) {
16226 continue;
16227 }
16228
16229 best_length = length;
16230 best_inside = inside;
16231 best_in_bracket_range = in_bracket_range;
16232 best_destination = Some(
16233 if close.contains(&selection.start) && close.contains(&selection.end) {
16234 if inside { open.end } else { open.start }
16235 } else if inside {
16236 *close.start()
16237 } else {
16238 *close.end()
16239 },
16240 );
16241 }
16242
16243 if let Some(destination) = best_destination {
16244 selection.collapse_to(destination, SelectionGoal::None);
16245 }
16246 })
16247 });
16248 }
16249
16250 pub fn undo_selection(
16251 &mut self,
16252 _: &UndoSelection,
16253 window: &mut Window,
16254 cx: &mut Context<Self>,
16255 ) {
16256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16257 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16258 self.selection_history.mode = SelectionHistoryMode::Undoing;
16259 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16260 this.end_selection(window, cx);
16261 this.change_selections(
16262 SelectionEffects::scroll(Autoscroll::newest()),
16263 window,
16264 cx,
16265 |s| s.select_anchors(entry.selections.to_vec()),
16266 );
16267 });
16268 self.selection_history.mode = SelectionHistoryMode::Normal;
16269
16270 self.select_next_state = entry.select_next_state;
16271 self.select_prev_state = entry.select_prev_state;
16272 self.add_selections_state = entry.add_selections_state;
16273 }
16274 }
16275
16276 pub fn redo_selection(
16277 &mut self,
16278 _: &RedoSelection,
16279 window: &mut Window,
16280 cx: &mut Context<Self>,
16281 ) {
16282 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16283 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16284 self.selection_history.mode = SelectionHistoryMode::Redoing;
16285 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16286 this.end_selection(window, cx);
16287 this.change_selections(
16288 SelectionEffects::scroll(Autoscroll::newest()),
16289 window,
16290 cx,
16291 |s| s.select_anchors(entry.selections.to_vec()),
16292 );
16293 });
16294 self.selection_history.mode = SelectionHistoryMode::Normal;
16295
16296 self.select_next_state = entry.select_next_state;
16297 self.select_prev_state = entry.select_prev_state;
16298 self.add_selections_state = entry.add_selections_state;
16299 }
16300 }
16301
16302 pub fn expand_excerpts(
16303 &mut self,
16304 action: &ExpandExcerpts,
16305 _: &mut Window,
16306 cx: &mut Context<Self>,
16307 ) {
16308 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16309 }
16310
16311 pub fn expand_excerpts_down(
16312 &mut self,
16313 action: &ExpandExcerptsDown,
16314 _: &mut Window,
16315 cx: &mut Context<Self>,
16316 ) {
16317 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16318 }
16319
16320 pub fn expand_excerpts_up(
16321 &mut self,
16322 action: &ExpandExcerptsUp,
16323 _: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) {
16326 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16327 }
16328
16329 pub fn expand_excerpts_for_direction(
16330 &mut self,
16331 lines: u32,
16332 direction: ExpandExcerptDirection,
16333
16334 cx: &mut Context<Self>,
16335 ) {
16336 let selections = self.selections.disjoint_anchors_arc();
16337
16338 let lines = if lines == 0 {
16339 EditorSettings::get_global(cx).expand_excerpt_lines
16340 } else {
16341 lines
16342 };
16343
16344 self.buffer.update(cx, |buffer, cx| {
16345 let snapshot = buffer.snapshot(cx);
16346 let mut excerpt_ids = selections
16347 .iter()
16348 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16349 .collect::<Vec<_>>();
16350 excerpt_ids.sort();
16351 excerpt_ids.dedup();
16352 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16353 })
16354 }
16355
16356 pub fn expand_excerpt(
16357 &mut self,
16358 excerpt: ExcerptId,
16359 direction: ExpandExcerptDirection,
16360 window: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) {
16363 let current_scroll_position = self.scroll_position(cx);
16364 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16365 let mut scroll = None;
16366
16367 if direction == ExpandExcerptDirection::Down {
16368 let multi_buffer = self.buffer.read(cx);
16369 let snapshot = multi_buffer.snapshot(cx);
16370 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16371 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16372 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16373 {
16374 let buffer_snapshot = buffer.read(cx).snapshot();
16375 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16376 let last_row = buffer_snapshot.max_point().row;
16377 let lines_below = last_row.saturating_sub(excerpt_end_row);
16378 if lines_below >= lines_to_expand {
16379 scroll = Some(
16380 current_scroll_position
16381 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16382 );
16383 }
16384 }
16385 }
16386 if direction == ExpandExcerptDirection::Up
16387 && self
16388 .buffer
16389 .read(cx)
16390 .snapshot(cx)
16391 .excerpt_before(excerpt)
16392 .is_none()
16393 {
16394 scroll = Some(current_scroll_position);
16395 }
16396
16397 self.buffer.update(cx, |buffer, cx| {
16398 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16399 });
16400
16401 if let Some(new_scroll_position) = scroll {
16402 self.set_scroll_position(new_scroll_position, window, cx);
16403 }
16404 }
16405
16406 pub fn go_to_singleton_buffer_point(
16407 &mut self,
16408 point: Point,
16409 window: &mut Window,
16410 cx: &mut Context<Self>,
16411 ) {
16412 self.go_to_singleton_buffer_range(point..point, window, cx);
16413 }
16414
16415 pub fn go_to_singleton_buffer_range(
16416 &mut self,
16417 range: Range<Point>,
16418 window: &mut Window,
16419 cx: &mut Context<Self>,
16420 ) {
16421 let multibuffer = self.buffer().read(cx);
16422 let Some(buffer) = multibuffer.as_singleton() else {
16423 return;
16424 };
16425 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16426 return;
16427 };
16428 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16429 return;
16430 };
16431 self.change_selections(
16432 SelectionEffects::default().nav_history(true),
16433 window,
16434 cx,
16435 |s| s.select_anchor_ranges([start..end]),
16436 );
16437 }
16438
16439 pub fn go_to_diagnostic(
16440 &mut self,
16441 action: &GoToDiagnostic,
16442 window: &mut Window,
16443 cx: &mut Context<Self>,
16444 ) {
16445 if !self.diagnostics_enabled() {
16446 return;
16447 }
16448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16449 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16450 }
16451
16452 pub fn go_to_prev_diagnostic(
16453 &mut self,
16454 action: &GoToPreviousDiagnostic,
16455 window: &mut Window,
16456 cx: &mut Context<Self>,
16457 ) {
16458 if !self.diagnostics_enabled() {
16459 return;
16460 }
16461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16462 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16463 }
16464
16465 pub fn go_to_diagnostic_impl(
16466 &mut self,
16467 direction: Direction,
16468 severity: GoToDiagnosticSeverityFilter,
16469 window: &mut Window,
16470 cx: &mut Context<Self>,
16471 ) {
16472 let buffer = self.buffer.read(cx).snapshot(cx);
16473 let selection = self
16474 .selections
16475 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16476
16477 let mut active_group_id = None;
16478 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16479 && active_group.active_range.start.to_offset(&buffer) == selection.start
16480 {
16481 active_group_id = Some(active_group.group_id);
16482 }
16483
16484 fn filtered<'a>(
16485 severity: GoToDiagnosticSeverityFilter,
16486 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16487 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16488 diagnostics
16489 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16490 .filter(|entry| entry.range.start != entry.range.end)
16491 .filter(|entry| !entry.diagnostic.is_unnecessary)
16492 }
16493
16494 let before = filtered(
16495 severity,
16496 buffer
16497 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16498 .filter(|entry| entry.range.start <= selection.start),
16499 );
16500 let after = filtered(
16501 severity,
16502 buffer
16503 .diagnostics_in_range(selection.start..buffer.len())
16504 .filter(|entry| entry.range.start >= selection.start),
16505 );
16506
16507 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16508 if direction == Direction::Prev {
16509 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16510 {
16511 for diagnostic in prev_diagnostics.into_iter().rev() {
16512 if diagnostic.range.start != selection.start
16513 || active_group_id
16514 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16515 {
16516 found = Some(diagnostic);
16517 break 'outer;
16518 }
16519 }
16520 }
16521 } else {
16522 for diagnostic in after.chain(before) {
16523 if diagnostic.range.start != selection.start
16524 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16525 {
16526 found = Some(diagnostic);
16527 break;
16528 }
16529 }
16530 }
16531 let Some(next_diagnostic) = found else {
16532 return;
16533 };
16534
16535 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16536 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16537 return;
16538 };
16539 let snapshot = self.snapshot(window, cx);
16540 if snapshot.intersects_fold(next_diagnostic.range.start) {
16541 self.unfold_ranges(
16542 std::slice::from_ref(&next_diagnostic.range),
16543 true,
16544 false,
16545 cx,
16546 );
16547 }
16548 self.change_selections(Default::default(), window, cx, |s| {
16549 s.select_ranges(vec![
16550 next_diagnostic.range.start..next_diagnostic.range.start,
16551 ])
16552 });
16553 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16554 self.refresh_edit_prediction(false, true, window, cx);
16555 }
16556
16557 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16559 let snapshot = self.snapshot(window, cx);
16560 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16561 self.go_to_hunk_before_or_after_position(
16562 &snapshot,
16563 selection.head(),
16564 Direction::Next,
16565 window,
16566 cx,
16567 );
16568 }
16569
16570 pub fn go_to_hunk_before_or_after_position(
16571 &mut self,
16572 snapshot: &EditorSnapshot,
16573 position: Point,
16574 direction: Direction,
16575 window: &mut Window,
16576 cx: &mut Context<Editor>,
16577 ) {
16578 let row = if direction == Direction::Next {
16579 self.hunk_after_position(snapshot, position)
16580 .map(|hunk| hunk.row_range.start)
16581 } else {
16582 self.hunk_before_position(snapshot, position)
16583 };
16584
16585 if let Some(row) = row {
16586 let destination = Point::new(row.0, 0);
16587 let autoscroll = Autoscroll::center();
16588
16589 self.unfold_ranges(&[destination..destination], false, false, cx);
16590 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16591 s.select_ranges([destination..destination]);
16592 });
16593 }
16594 }
16595
16596 fn hunk_after_position(
16597 &mut self,
16598 snapshot: &EditorSnapshot,
16599 position: Point,
16600 ) -> Option<MultiBufferDiffHunk> {
16601 snapshot
16602 .buffer_snapshot()
16603 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16604 .find(|hunk| hunk.row_range.start.0 > position.row)
16605 .or_else(|| {
16606 snapshot
16607 .buffer_snapshot()
16608 .diff_hunks_in_range(Point::zero()..position)
16609 .find(|hunk| hunk.row_range.end.0 < position.row)
16610 })
16611 }
16612
16613 fn go_to_prev_hunk(
16614 &mut self,
16615 _: &GoToPreviousHunk,
16616 window: &mut Window,
16617 cx: &mut Context<Self>,
16618 ) {
16619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16620 let snapshot = self.snapshot(window, cx);
16621 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16622 self.go_to_hunk_before_or_after_position(
16623 &snapshot,
16624 selection.head(),
16625 Direction::Prev,
16626 window,
16627 cx,
16628 );
16629 }
16630
16631 fn hunk_before_position(
16632 &mut self,
16633 snapshot: &EditorSnapshot,
16634 position: Point,
16635 ) -> Option<MultiBufferRow> {
16636 snapshot
16637 .buffer_snapshot()
16638 .diff_hunk_before(position)
16639 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16640 }
16641
16642 fn go_to_next_change(
16643 &mut self,
16644 _: &GoToNextChange,
16645 window: &mut Window,
16646 cx: &mut Context<Self>,
16647 ) {
16648 if let Some(selections) = self
16649 .change_list
16650 .next_change(1, Direction::Next)
16651 .map(|s| s.to_vec())
16652 {
16653 self.change_selections(Default::default(), window, cx, |s| {
16654 let map = s.display_snapshot();
16655 s.select_display_ranges(selections.iter().map(|a| {
16656 let point = a.to_display_point(&map);
16657 point..point
16658 }))
16659 })
16660 }
16661 }
16662
16663 fn go_to_previous_change(
16664 &mut self,
16665 _: &GoToPreviousChange,
16666 window: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) {
16669 if let Some(selections) = self
16670 .change_list
16671 .next_change(1, Direction::Prev)
16672 .map(|s| s.to_vec())
16673 {
16674 self.change_selections(Default::default(), window, cx, |s| {
16675 let map = s.display_snapshot();
16676 s.select_display_ranges(selections.iter().map(|a| {
16677 let point = a.to_display_point(&map);
16678 point..point
16679 }))
16680 })
16681 }
16682 }
16683
16684 pub fn go_to_next_document_highlight(
16685 &mut self,
16686 _: &GoToNextDocumentHighlight,
16687 window: &mut Window,
16688 cx: &mut Context<Self>,
16689 ) {
16690 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16691 }
16692
16693 pub fn go_to_prev_document_highlight(
16694 &mut self,
16695 _: &GoToPreviousDocumentHighlight,
16696 window: &mut Window,
16697 cx: &mut Context<Self>,
16698 ) {
16699 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16700 }
16701
16702 pub fn go_to_document_highlight_before_or_after_position(
16703 &mut self,
16704 direction: Direction,
16705 window: &mut Window,
16706 cx: &mut Context<Editor>,
16707 ) {
16708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16709 let snapshot = self.snapshot(window, cx);
16710 let buffer = &snapshot.buffer_snapshot();
16711 let position = self
16712 .selections
16713 .newest::<Point>(&snapshot.display_snapshot)
16714 .head();
16715 let anchor_position = buffer.anchor_after(position);
16716
16717 // Get all document highlights (both read and write)
16718 let mut all_highlights = Vec::new();
16719
16720 if let Some((_, read_highlights)) = self
16721 .background_highlights
16722 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16723 {
16724 all_highlights.extend(read_highlights.iter());
16725 }
16726
16727 if let Some((_, write_highlights)) = self
16728 .background_highlights
16729 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16730 {
16731 all_highlights.extend(write_highlights.iter());
16732 }
16733
16734 if all_highlights.is_empty() {
16735 return;
16736 }
16737
16738 // Sort highlights by position
16739 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16740
16741 let target_highlight = match direction {
16742 Direction::Next => {
16743 // Find the first highlight after the current position
16744 all_highlights
16745 .iter()
16746 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16747 }
16748 Direction::Prev => {
16749 // Find the last highlight before the current position
16750 all_highlights
16751 .iter()
16752 .rev()
16753 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16754 }
16755 };
16756
16757 if let Some(highlight) = target_highlight {
16758 let destination = highlight.start.to_point(buffer);
16759 let autoscroll = Autoscroll::center();
16760
16761 self.unfold_ranges(&[destination..destination], false, false, cx);
16762 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16763 s.select_ranges([destination..destination]);
16764 });
16765 }
16766 }
16767
16768 fn go_to_line<T: 'static>(
16769 &mut self,
16770 position: Anchor,
16771 highlight_color: Option<Hsla>,
16772 window: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) {
16775 let snapshot = self.snapshot(window, cx).display_snapshot;
16776 let position = position.to_point(&snapshot.buffer_snapshot());
16777 let start = snapshot
16778 .buffer_snapshot()
16779 .clip_point(Point::new(position.row, 0), Bias::Left);
16780 let end = start + Point::new(1, 0);
16781 let start = snapshot.buffer_snapshot().anchor_before(start);
16782 let end = snapshot.buffer_snapshot().anchor_before(end);
16783
16784 self.highlight_rows::<T>(
16785 start..end,
16786 highlight_color
16787 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16788 Default::default(),
16789 cx,
16790 );
16791
16792 if self.buffer.read(cx).is_singleton() {
16793 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16794 }
16795 }
16796
16797 pub fn go_to_definition(
16798 &mut self,
16799 _: &GoToDefinition,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) -> Task<Result<Navigated>> {
16803 let definition =
16804 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16805 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16806 cx.spawn_in(window, async move |editor, cx| {
16807 if definition.await? == Navigated::Yes {
16808 return Ok(Navigated::Yes);
16809 }
16810 match fallback_strategy {
16811 GoToDefinitionFallback::None => Ok(Navigated::No),
16812 GoToDefinitionFallback::FindAllReferences => {
16813 match editor.update_in(cx, |editor, window, cx| {
16814 editor.find_all_references(&FindAllReferences, window, cx)
16815 })? {
16816 Some(references) => references.await,
16817 None => Ok(Navigated::No),
16818 }
16819 }
16820 }
16821 })
16822 }
16823
16824 pub fn go_to_declaration(
16825 &mut self,
16826 _: &GoToDeclaration,
16827 window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) -> Task<Result<Navigated>> {
16830 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16831 }
16832
16833 pub fn go_to_declaration_split(
16834 &mut self,
16835 _: &GoToDeclaration,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) -> Task<Result<Navigated>> {
16839 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16840 }
16841
16842 pub fn go_to_implementation(
16843 &mut self,
16844 _: &GoToImplementation,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) -> Task<Result<Navigated>> {
16848 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16849 }
16850
16851 pub fn go_to_implementation_split(
16852 &mut self,
16853 _: &GoToImplementationSplit,
16854 window: &mut Window,
16855 cx: &mut Context<Self>,
16856 ) -> Task<Result<Navigated>> {
16857 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16858 }
16859
16860 pub fn go_to_type_definition(
16861 &mut self,
16862 _: &GoToTypeDefinition,
16863 window: &mut Window,
16864 cx: &mut Context<Self>,
16865 ) -> Task<Result<Navigated>> {
16866 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16867 }
16868
16869 pub fn go_to_definition_split(
16870 &mut self,
16871 _: &GoToDefinitionSplit,
16872 window: &mut Window,
16873 cx: &mut Context<Self>,
16874 ) -> Task<Result<Navigated>> {
16875 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16876 }
16877
16878 pub fn go_to_type_definition_split(
16879 &mut self,
16880 _: &GoToTypeDefinitionSplit,
16881 window: &mut Window,
16882 cx: &mut Context<Self>,
16883 ) -> Task<Result<Navigated>> {
16884 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16885 }
16886
16887 fn go_to_definition_of_kind(
16888 &mut self,
16889 kind: GotoDefinitionKind,
16890 split: bool,
16891 window: &mut Window,
16892 cx: &mut Context<Self>,
16893 ) -> Task<Result<Navigated>> {
16894 let Some(provider) = self.semantics_provider.clone() else {
16895 return Task::ready(Ok(Navigated::No));
16896 };
16897 let head = self
16898 .selections
16899 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16900 .head();
16901 let buffer = self.buffer.read(cx);
16902 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16903 return Task::ready(Ok(Navigated::No));
16904 };
16905 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16906 return Task::ready(Ok(Navigated::No));
16907 };
16908
16909 cx.spawn_in(window, async move |editor, cx| {
16910 let Some(definitions) = definitions.await? else {
16911 return Ok(Navigated::No);
16912 };
16913 let navigated = editor
16914 .update_in(cx, |editor, window, cx| {
16915 editor.navigate_to_hover_links(
16916 Some(kind),
16917 definitions
16918 .into_iter()
16919 .filter(|location| {
16920 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16921 })
16922 .map(HoverLink::Text)
16923 .collect::<Vec<_>>(),
16924 split,
16925 window,
16926 cx,
16927 )
16928 })?
16929 .await?;
16930 anyhow::Ok(navigated)
16931 })
16932 }
16933
16934 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16935 let selection = self.selections.newest_anchor();
16936 let head = selection.head();
16937 let tail = selection.tail();
16938
16939 let Some((buffer, start_position)) =
16940 self.buffer.read(cx).text_anchor_for_position(head, cx)
16941 else {
16942 return;
16943 };
16944
16945 let end_position = if head != tail {
16946 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16947 return;
16948 };
16949 Some(pos)
16950 } else {
16951 None
16952 };
16953
16954 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16955 let url = if let Some(end_pos) = end_position {
16956 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16957 } else {
16958 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16959 };
16960
16961 if let Some(url) = url {
16962 cx.update(|window, cx| {
16963 if parse_zed_link(&url, cx).is_some() {
16964 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16965 } else {
16966 cx.open_url(&url);
16967 }
16968 })?;
16969 }
16970
16971 anyhow::Ok(())
16972 });
16973
16974 url_finder.detach();
16975 }
16976
16977 pub fn open_selected_filename(
16978 &mut self,
16979 _: &OpenSelectedFilename,
16980 window: &mut Window,
16981 cx: &mut Context<Self>,
16982 ) {
16983 let Some(workspace) = self.workspace() else {
16984 return;
16985 };
16986
16987 let position = self.selections.newest_anchor().head();
16988
16989 let Some((buffer, buffer_position)) =
16990 self.buffer.read(cx).text_anchor_for_position(position, cx)
16991 else {
16992 return;
16993 };
16994
16995 let project = self.project.clone();
16996
16997 cx.spawn_in(window, async move |_, cx| {
16998 let result = find_file(&buffer, project, buffer_position, cx).await;
16999
17000 if let Some((_, path)) = result {
17001 workspace
17002 .update_in(cx, |workspace, window, cx| {
17003 workspace.open_resolved_path(path, window, cx)
17004 })?
17005 .await?;
17006 }
17007 anyhow::Ok(())
17008 })
17009 .detach();
17010 }
17011
17012 pub(crate) fn navigate_to_hover_links(
17013 &mut self,
17014 kind: Option<GotoDefinitionKind>,
17015 definitions: Vec<HoverLink>,
17016 split: bool,
17017 window: &mut Window,
17018 cx: &mut Context<Editor>,
17019 ) -> Task<Result<Navigated>> {
17020 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17021 let mut first_url_or_file = None;
17022 let definitions: Vec<_> = definitions
17023 .into_iter()
17024 .filter_map(|def| match def {
17025 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17026 HoverLink::InlayHint(lsp_location, server_id) => {
17027 let computation =
17028 self.compute_target_location(lsp_location, server_id, window, cx);
17029 Some(cx.background_spawn(computation))
17030 }
17031 HoverLink::Url(url) => {
17032 first_url_or_file = Some(Either::Left(url));
17033 None
17034 }
17035 HoverLink::File(path) => {
17036 first_url_or_file = Some(Either::Right(path));
17037 None
17038 }
17039 })
17040 .collect();
17041
17042 let Some(workspace) = self.workspace() else {
17043 return Task::ready(Ok(Navigated::No));
17044 };
17045
17046 cx.spawn_in(window, async move |editor, cx| {
17047 let locations: Vec<Location> = future::join_all(definitions)
17048 .await
17049 .into_iter()
17050 .filter_map(|location| location.transpose())
17051 .collect::<Result<_>>()
17052 .context("location tasks")?;
17053 let mut locations = cx.update(|_, cx| {
17054 locations
17055 .into_iter()
17056 .map(|location| {
17057 let buffer = location.buffer.read(cx);
17058 (location.buffer, location.range.to_point(buffer))
17059 })
17060 .into_group_map()
17061 })?;
17062 let mut num_locations = 0;
17063 for ranges in locations.values_mut() {
17064 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17065 ranges.dedup();
17066 num_locations += ranges.len();
17067 }
17068
17069 if num_locations > 1 {
17070 let tab_kind = match kind {
17071 Some(GotoDefinitionKind::Implementation) => "Implementations",
17072 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17073 Some(GotoDefinitionKind::Declaration) => "Declarations",
17074 Some(GotoDefinitionKind::Type) => "Types",
17075 };
17076 let title = editor
17077 .update_in(cx, |_, _, cx| {
17078 let target = locations
17079 .iter()
17080 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17081 .map(|(buffer, location)| {
17082 buffer
17083 .read(cx)
17084 .text_for_range(location.clone())
17085 .collect::<String>()
17086 })
17087 .filter(|text| !text.contains('\n'))
17088 .unique()
17089 .take(3)
17090 .join(", ");
17091 if target.is_empty() {
17092 tab_kind.to_owned()
17093 } else {
17094 format!("{tab_kind} for {target}")
17095 }
17096 })
17097 .context("buffer title")?;
17098
17099 let opened = workspace
17100 .update_in(cx, |workspace, window, cx| {
17101 let allow_preview = PreviewTabsSettings::get_global(cx)
17102 .enable_preview_multibuffer_from_code_navigation;
17103 Self::open_locations_in_multibuffer(
17104 workspace,
17105 locations,
17106 title,
17107 split,
17108 allow_preview,
17109 MultibufferSelectionMode::First,
17110 window,
17111 cx,
17112 )
17113 })
17114 .is_ok();
17115
17116 anyhow::Ok(Navigated::from_bool(opened))
17117 } else if num_locations == 0 {
17118 // If there is one url or file, open it directly
17119 match first_url_or_file {
17120 Some(Either::Left(url)) => {
17121 cx.update(|_, cx| cx.open_url(&url))?;
17122 Ok(Navigated::Yes)
17123 }
17124 Some(Either::Right(path)) => {
17125 // TODO(andrew): respect preview tab settings
17126 // `enable_keep_preview_on_code_navigation` and
17127 // `enable_preview_file_from_code_navigation`
17128 workspace
17129 .update_in(cx, |workspace, window, cx| {
17130 workspace.open_resolved_path(path, window, cx)
17131 })?
17132 .await?;
17133 Ok(Navigated::Yes)
17134 }
17135 None => Ok(Navigated::No),
17136 }
17137 } else {
17138 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17139 let target_range = target_ranges.first().unwrap().clone();
17140
17141 editor.update_in(cx, |editor, window, cx| {
17142 let range = target_range.to_point(target_buffer.read(cx));
17143 let range = editor.range_for_match(&range);
17144 let range = collapse_multiline_range(range);
17145
17146 if !split
17147 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17148 {
17149 editor.go_to_singleton_buffer_range(range, window, cx);
17150 } else {
17151 let pane = workspace.read(cx).active_pane().clone();
17152 window.defer(cx, move |window, cx| {
17153 let target_editor: Entity<Self> =
17154 workspace.update(cx, |workspace, cx| {
17155 let pane = if split {
17156 workspace.adjacent_pane(window, cx)
17157 } else {
17158 workspace.active_pane().clone()
17159 };
17160
17161 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17162 let keep_old_preview = preview_tabs_settings
17163 .enable_keep_preview_on_code_navigation;
17164 let allow_new_preview = preview_tabs_settings
17165 .enable_preview_file_from_code_navigation;
17166
17167 workspace.open_project_item(
17168 pane,
17169 target_buffer.clone(),
17170 true,
17171 true,
17172 keep_old_preview,
17173 allow_new_preview,
17174 window,
17175 cx,
17176 )
17177 });
17178 target_editor.update(cx, |target_editor, cx| {
17179 // When selecting a definition in a different buffer, disable the nav history
17180 // to avoid creating a history entry at the previous cursor location.
17181 pane.update(cx, |pane, _| pane.disable_history());
17182 target_editor.go_to_singleton_buffer_range(range, window, cx);
17183 pane.update(cx, |pane, _| pane.enable_history());
17184 });
17185 });
17186 }
17187 Navigated::Yes
17188 })
17189 }
17190 })
17191 }
17192
17193 fn compute_target_location(
17194 &self,
17195 lsp_location: lsp::Location,
17196 server_id: LanguageServerId,
17197 window: &mut Window,
17198 cx: &mut Context<Self>,
17199 ) -> Task<anyhow::Result<Option<Location>>> {
17200 let Some(project) = self.project.clone() else {
17201 return Task::ready(Ok(None));
17202 };
17203
17204 cx.spawn_in(window, async move |editor, cx| {
17205 let location_task = editor.update(cx, |_, cx| {
17206 project.update(cx, |project, cx| {
17207 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17208 })
17209 })?;
17210 let location = Some({
17211 let target_buffer_handle = location_task.await.context("open local buffer")?;
17212 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17213 let target_start = target_buffer
17214 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17215 let target_end = target_buffer
17216 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17217 target_buffer.anchor_after(target_start)
17218 ..target_buffer.anchor_before(target_end)
17219 })?;
17220 Location {
17221 buffer: target_buffer_handle,
17222 range,
17223 }
17224 });
17225 Ok(location)
17226 })
17227 }
17228
17229 fn go_to_next_reference(
17230 &mut self,
17231 _: &GoToNextReference,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17236 if let Some(task) = task {
17237 task.detach();
17238 };
17239 }
17240
17241 fn go_to_prev_reference(
17242 &mut self,
17243 _: &GoToPreviousReference,
17244 window: &mut Window,
17245 cx: &mut Context<Self>,
17246 ) {
17247 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17248 if let Some(task) = task {
17249 task.detach();
17250 };
17251 }
17252
17253 pub fn go_to_reference_before_or_after_position(
17254 &mut self,
17255 direction: Direction,
17256 count: usize,
17257 window: &mut Window,
17258 cx: &mut Context<Self>,
17259 ) -> Option<Task<Result<()>>> {
17260 let selection = self.selections.newest_anchor();
17261 let head = selection.head();
17262
17263 let multi_buffer = self.buffer.read(cx);
17264
17265 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17266 let workspace = self.workspace()?;
17267 let project = workspace.read(cx).project().clone();
17268 let references =
17269 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17270 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17271 let Some(locations) = references.await? else {
17272 return Ok(());
17273 };
17274
17275 if locations.is_empty() {
17276 // totally normal - the cursor may be on something which is not
17277 // a symbol (e.g. a keyword)
17278 log::info!("no references found under cursor");
17279 return Ok(());
17280 }
17281
17282 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17283
17284 let (locations, current_location_index) =
17285 multi_buffer.update(cx, |multi_buffer, cx| {
17286 let mut locations = locations
17287 .into_iter()
17288 .filter_map(|loc| {
17289 let start = multi_buffer.buffer_anchor_to_anchor(
17290 &loc.buffer,
17291 loc.range.start,
17292 cx,
17293 )?;
17294 let end = multi_buffer.buffer_anchor_to_anchor(
17295 &loc.buffer,
17296 loc.range.end,
17297 cx,
17298 )?;
17299 Some(start..end)
17300 })
17301 .collect::<Vec<_>>();
17302
17303 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17304 // There is an O(n) implementation, but given this list will be
17305 // small (usually <100 items), the extra O(log(n)) factor isn't
17306 // worth the (surprisingly large amount of) extra complexity.
17307 locations
17308 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17309
17310 let head_offset = head.to_offset(&multi_buffer_snapshot);
17311
17312 let current_location_index = locations.iter().position(|loc| {
17313 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17314 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17315 });
17316
17317 (locations, current_location_index)
17318 })?;
17319
17320 let Some(current_location_index) = current_location_index else {
17321 // This indicates something has gone wrong, because we already
17322 // handle the "no references" case above
17323 log::error!(
17324 "failed to find current reference under cursor. Total references: {}",
17325 locations.len()
17326 );
17327 return Ok(());
17328 };
17329
17330 let destination_location_index = match direction {
17331 Direction::Next => (current_location_index + count) % locations.len(),
17332 Direction::Prev => {
17333 (current_location_index + locations.len() - count % locations.len())
17334 % locations.len()
17335 }
17336 };
17337
17338 // TODO(cameron): is this needed?
17339 // the thinking is to avoid "jumping to the current location" (avoid
17340 // polluting "jumplist" in vim terms)
17341 if current_location_index == destination_location_index {
17342 return Ok(());
17343 }
17344
17345 let Range { start, end } = locations[destination_location_index];
17346
17347 editor.update_in(cx, |editor, window, cx| {
17348 let effects = SelectionEffects::default();
17349
17350 editor.unfold_ranges(&[start..end], false, false, cx);
17351 editor.change_selections(effects, window, cx, |s| {
17352 s.select_ranges([start..start]);
17353 });
17354 })?;
17355
17356 Ok(())
17357 }))
17358 }
17359
17360 pub fn find_all_references(
17361 &mut self,
17362 _: &FindAllReferences,
17363 window: &mut Window,
17364 cx: &mut Context<Self>,
17365 ) -> Option<Task<Result<Navigated>>> {
17366 let selection = self
17367 .selections
17368 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17369 let multi_buffer = self.buffer.read(cx);
17370 let head = selection.head();
17371
17372 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17373 let head_anchor = multi_buffer_snapshot.anchor_at(
17374 head,
17375 if head < selection.tail() {
17376 Bias::Right
17377 } else {
17378 Bias::Left
17379 },
17380 );
17381
17382 match self
17383 .find_all_references_task_sources
17384 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17385 {
17386 Ok(_) => {
17387 log::info!(
17388 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17389 );
17390 return None;
17391 }
17392 Err(i) => {
17393 self.find_all_references_task_sources.insert(i, head_anchor);
17394 }
17395 }
17396
17397 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17398 let workspace = self.workspace()?;
17399 let project = workspace.read(cx).project().clone();
17400 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17401 Some(cx.spawn_in(window, async move |editor, cx| {
17402 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17403 if let Ok(i) = editor
17404 .find_all_references_task_sources
17405 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17406 {
17407 editor.find_all_references_task_sources.remove(i);
17408 }
17409 });
17410
17411 let Some(locations) = references.await? else {
17412 return anyhow::Ok(Navigated::No);
17413 };
17414 let mut locations = cx.update(|_, cx| {
17415 locations
17416 .into_iter()
17417 .map(|location| {
17418 let buffer = location.buffer.read(cx);
17419 (location.buffer, location.range.to_point(buffer))
17420 })
17421 .into_group_map()
17422 })?;
17423 if locations.is_empty() {
17424 return anyhow::Ok(Navigated::No);
17425 }
17426 for ranges in locations.values_mut() {
17427 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17428 ranges.dedup();
17429 }
17430
17431 workspace.update_in(cx, |workspace, window, cx| {
17432 let target = locations
17433 .iter()
17434 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17435 .map(|(buffer, location)| {
17436 buffer
17437 .read(cx)
17438 .text_for_range(location.clone())
17439 .collect::<String>()
17440 })
17441 .filter(|text| !text.contains('\n'))
17442 .unique()
17443 .take(3)
17444 .join(", ");
17445 let title = if target.is_empty() {
17446 "References".to_owned()
17447 } else {
17448 format!("References to {target}")
17449 };
17450 let allow_preview = PreviewTabsSettings::get_global(cx)
17451 .enable_preview_multibuffer_from_code_navigation;
17452 Self::open_locations_in_multibuffer(
17453 workspace,
17454 locations,
17455 title,
17456 false,
17457 allow_preview,
17458 MultibufferSelectionMode::First,
17459 window,
17460 cx,
17461 );
17462 Navigated::Yes
17463 })
17464 }))
17465 }
17466
17467 /// Opens a multibuffer with the given project locations in it
17468 pub fn open_locations_in_multibuffer(
17469 workspace: &mut Workspace,
17470 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17471 title: String,
17472 split: bool,
17473 allow_preview: bool,
17474 multibuffer_selection_mode: MultibufferSelectionMode,
17475 window: &mut Window,
17476 cx: &mut Context<Workspace>,
17477 ) {
17478 if locations.is_empty() {
17479 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17480 return;
17481 }
17482
17483 let capability = workspace.project().read(cx).capability();
17484 let mut ranges = <Vec<Range<Anchor>>>::new();
17485
17486 // a key to find existing multibuffer editors with the same set of locations
17487 // to prevent us from opening more and more multibuffer tabs for searches and the like
17488 let mut key = (title.clone(), vec![]);
17489 let excerpt_buffer = cx.new(|cx| {
17490 let key = &mut key.1;
17491 let mut multibuffer = MultiBuffer::new(capability);
17492 for (buffer, mut ranges_for_buffer) in locations {
17493 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17494 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17495 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17496 PathKey::for_buffer(&buffer, cx),
17497 buffer.clone(),
17498 ranges_for_buffer,
17499 multibuffer_context_lines(cx),
17500 cx,
17501 );
17502 ranges.extend(new_ranges)
17503 }
17504
17505 multibuffer.with_title(title)
17506 });
17507 let existing = workspace.active_pane().update(cx, |pane, cx| {
17508 pane.items()
17509 .filter_map(|item| item.downcast::<Editor>())
17510 .find(|editor| {
17511 editor
17512 .read(cx)
17513 .lookup_key
17514 .as_ref()
17515 .and_then(|it| {
17516 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17517 })
17518 .is_some_and(|it| *it == key)
17519 })
17520 });
17521 let was_existing = existing.is_some();
17522 let editor = existing.unwrap_or_else(|| {
17523 cx.new(|cx| {
17524 let mut editor = Editor::for_multibuffer(
17525 excerpt_buffer,
17526 Some(workspace.project().clone()),
17527 window,
17528 cx,
17529 );
17530 editor.lookup_key = Some(Box::new(key));
17531 editor
17532 })
17533 });
17534 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17535 MultibufferSelectionMode::First => {
17536 if let Some(first_range) = ranges.first() {
17537 editor.change_selections(
17538 SelectionEffects::no_scroll(),
17539 window,
17540 cx,
17541 |selections| {
17542 selections.clear_disjoint();
17543 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17544 },
17545 );
17546 }
17547 editor.highlight_background::<Self>(
17548 &ranges,
17549 |_, theme| theme.colors().editor_highlighted_line_background,
17550 cx,
17551 );
17552 }
17553 MultibufferSelectionMode::All => {
17554 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17555 selections.clear_disjoint();
17556 selections.select_anchor_ranges(ranges);
17557 });
17558 }
17559 });
17560
17561 let item = Box::new(editor);
17562
17563 let pane = if split {
17564 workspace.adjacent_pane(window, cx)
17565 } else {
17566 workspace.active_pane().clone()
17567 };
17568 let activate_pane = split;
17569
17570 let mut destination_index = None;
17571 pane.update(cx, |pane, cx| {
17572 if allow_preview && !was_existing {
17573 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17574 }
17575 if was_existing && !allow_preview {
17576 pane.unpreview_item_if_preview(item.item_id());
17577 }
17578 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17579 });
17580 }
17581
17582 pub fn rename(
17583 &mut self,
17584 _: &Rename,
17585 window: &mut Window,
17586 cx: &mut Context<Self>,
17587 ) -> Option<Task<Result<()>>> {
17588 use language::ToOffset as _;
17589
17590 let provider = self.semantics_provider.clone()?;
17591 let selection = self.selections.newest_anchor().clone();
17592 let (cursor_buffer, cursor_buffer_position) = self
17593 .buffer
17594 .read(cx)
17595 .text_anchor_for_position(selection.head(), cx)?;
17596 let (tail_buffer, cursor_buffer_position_end) = self
17597 .buffer
17598 .read(cx)
17599 .text_anchor_for_position(selection.tail(), cx)?;
17600 if tail_buffer != cursor_buffer {
17601 return None;
17602 }
17603
17604 let snapshot = cursor_buffer.read(cx).snapshot();
17605 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17606 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17607 let prepare_rename = provider
17608 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17609 .unwrap_or_else(|| Task::ready(Ok(None)));
17610 drop(snapshot);
17611
17612 Some(cx.spawn_in(window, async move |this, cx| {
17613 let rename_range = if let Some(range) = prepare_rename.await? {
17614 Some(range)
17615 } else {
17616 this.update(cx, |this, cx| {
17617 let buffer = this.buffer.read(cx).snapshot(cx);
17618 let mut buffer_highlights = this
17619 .document_highlights_for_position(selection.head(), &buffer)
17620 .filter(|highlight| {
17621 highlight.start.excerpt_id == selection.head().excerpt_id
17622 && highlight.end.excerpt_id == selection.head().excerpt_id
17623 });
17624 buffer_highlights
17625 .next()
17626 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17627 })?
17628 };
17629 if let Some(rename_range) = rename_range {
17630 this.update_in(cx, |this, window, cx| {
17631 let snapshot = cursor_buffer.read(cx).snapshot();
17632 let rename_buffer_range = rename_range.to_offset(&snapshot);
17633 let cursor_offset_in_rename_range =
17634 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17635 let cursor_offset_in_rename_range_end =
17636 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17637
17638 this.take_rename(false, window, cx);
17639 let buffer = this.buffer.read(cx).read(cx);
17640 let cursor_offset = selection.head().to_offset(&buffer);
17641 let rename_start =
17642 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17643 let rename_end = rename_start + rename_buffer_range.len();
17644 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17645 let mut old_highlight_id = None;
17646 let old_name: Arc<str> = buffer
17647 .chunks(rename_start..rename_end, true)
17648 .map(|chunk| {
17649 if old_highlight_id.is_none() {
17650 old_highlight_id = chunk.syntax_highlight_id;
17651 }
17652 chunk.text
17653 })
17654 .collect::<String>()
17655 .into();
17656
17657 drop(buffer);
17658
17659 // Position the selection in the rename editor so that it matches the current selection.
17660 this.show_local_selections = false;
17661 let rename_editor = cx.new(|cx| {
17662 let mut editor = Editor::single_line(window, cx);
17663 editor.buffer.update(cx, |buffer, cx| {
17664 buffer.edit(
17665 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17666 None,
17667 cx,
17668 )
17669 });
17670 let cursor_offset_in_rename_range =
17671 MultiBufferOffset(cursor_offset_in_rename_range);
17672 let cursor_offset_in_rename_range_end =
17673 MultiBufferOffset(cursor_offset_in_rename_range_end);
17674 let rename_selection_range = match cursor_offset_in_rename_range
17675 .cmp(&cursor_offset_in_rename_range_end)
17676 {
17677 Ordering::Equal => {
17678 editor.select_all(&SelectAll, window, cx);
17679 return editor;
17680 }
17681 Ordering::Less => {
17682 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17683 }
17684 Ordering::Greater => {
17685 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17686 }
17687 };
17688 if rename_selection_range.end.0 > old_name.len() {
17689 editor.select_all(&SelectAll, window, cx);
17690 } else {
17691 editor.change_selections(Default::default(), window, cx, |s| {
17692 s.select_ranges([rename_selection_range]);
17693 });
17694 }
17695 editor
17696 });
17697 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17698 if e == &EditorEvent::Focused {
17699 cx.emit(EditorEvent::FocusedIn)
17700 }
17701 })
17702 .detach();
17703
17704 let write_highlights =
17705 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17706 let read_highlights =
17707 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17708 let ranges = write_highlights
17709 .iter()
17710 .flat_map(|(_, ranges)| ranges.iter())
17711 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17712 .cloned()
17713 .collect();
17714
17715 this.highlight_text::<Rename>(
17716 ranges,
17717 HighlightStyle {
17718 fade_out: Some(0.6),
17719 ..Default::default()
17720 },
17721 cx,
17722 );
17723 let rename_focus_handle = rename_editor.focus_handle(cx);
17724 window.focus(&rename_focus_handle);
17725 let block_id = this.insert_blocks(
17726 [BlockProperties {
17727 style: BlockStyle::Flex,
17728 placement: BlockPlacement::Below(range.start),
17729 height: Some(1),
17730 render: Arc::new({
17731 let rename_editor = rename_editor.clone();
17732 move |cx: &mut BlockContext| {
17733 let mut text_style = cx.editor_style.text.clone();
17734 if let Some(highlight_style) = old_highlight_id
17735 .and_then(|h| h.style(&cx.editor_style.syntax))
17736 {
17737 text_style = text_style.highlight(highlight_style);
17738 }
17739 div()
17740 .block_mouse_except_scroll()
17741 .pl(cx.anchor_x)
17742 .child(EditorElement::new(
17743 &rename_editor,
17744 EditorStyle {
17745 background: cx.theme().system().transparent,
17746 local_player: cx.editor_style.local_player,
17747 text: text_style,
17748 scrollbar_width: cx.editor_style.scrollbar_width,
17749 syntax: cx.editor_style.syntax.clone(),
17750 status: cx.editor_style.status.clone(),
17751 inlay_hints_style: HighlightStyle {
17752 font_weight: Some(FontWeight::BOLD),
17753 ..make_inlay_hints_style(cx.app)
17754 },
17755 edit_prediction_styles: make_suggestion_styles(
17756 cx.app,
17757 ),
17758 ..EditorStyle::default()
17759 },
17760 ))
17761 .into_any_element()
17762 }
17763 }),
17764 priority: 0,
17765 }],
17766 Some(Autoscroll::fit()),
17767 cx,
17768 )[0];
17769 this.pending_rename = Some(RenameState {
17770 range,
17771 old_name,
17772 editor: rename_editor,
17773 block_id,
17774 });
17775 })?;
17776 }
17777
17778 Ok(())
17779 }))
17780 }
17781
17782 pub fn confirm_rename(
17783 &mut self,
17784 _: &ConfirmRename,
17785 window: &mut Window,
17786 cx: &mut Context<Self>,
17787 ) -> Option<Task<Result<()>>> {
17788 let rename = self.take_rename(false, window, cx)?;
17789 let workspace = self.workspace()?.downgrade();
17790 let (buffer, start) = self
17791 .buffer
17792 .read(cx)
17793 .text_anchor_for_position(rename.range.start, cx)?;
17794 let (end_buffer, _) = self
17795 .buffer
17796 .read(cx)
17797 .text_anchor_for_position(rename.range.end, cx)?;
17798 if buffer != end_buffer {
17799 return None;
17800 }
17801
17802 let old_name = rename.old_name;
17803 let new_name = rename.editor.read(cx).text(cx);
17804
17805 let rename = self.semantics_provider.as_ref()?.perform_rename(
17806 &buffer,
17807 start,
17808 new_name.clone(),
17809 cx,
17810 )?;
17811
17812 Some(cx.spawn_in(window, async move |editor, cx| {
17813 let project_transaction = rename.await?;
17814 Self::open_project_transaction(
17815 &editor,
17816 workspace,
17817 project_transaction,
17818 format!("Rename: {} → {}", old_name, new_name),
17819 cx,
17820 )
17821 .await?;
17822
17823 editor.update(cx, |editor, cx| {
17824 editor.refresh_document_highlights(cx);
17825 })?;
17826 Ok(())
17827 }))
17828 }
17829
17830 fn take_rename(
17831 &mut self,
17832 moving_cursor: bool,
17833 window: &mut Window,
17834 cx: &mut Context<Self>,
17835 ) -> Option<RenameState> {
17836 let rename = self.pending_rename.take()?;
17837 if rename.editor.focus_handle(cx).is_focused(window) {
17838 window.focus(&self.focus_handle);
17839 }
17840
17841 self.remove_blocks(
17842 [rename.block_id].into_iter().collect(),
17843 Some(Autoscroll::fit()),
17844 cx,
17845 );
17846 self.clear_highlights::<Rename>(cx);
17847 self.show_local_selections = true;
17848
17849 if moving_cursor {
17850 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17851 editor
17852 .selections
17853 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17854 .head()
17855 });
17856
17857 // Update the selection to match the position of the selection inside
17858 // the rename editor.
17859 let snapshot = self.buffer.read(cx).read(cx);
17860 let rename_range = rename.range.to_offset(&snapshot);
17861 let cursor_in_editor = snapshot
17862 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17863 .min(rename_range.end);
17864 drop(snapshot);
17865
17866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17867 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17868 });
17869 } else {
17870 self.refresh_document_highlights(cx);
17871 }
17872
17873 Some(rename)
17874 }
17875
17876 pub fn pending_rename(&self) -> Option<&RenameState> {
17877 self.pending_rename.as_ref()
17878 }
17879
17880 fn format(
17881 &mut self,
17882 _: &Format,
17883 window: &mut Window,
17884 cx: &mut Context<Self>,
17885 ) -> Option<Task<Result<()>>> {
17886 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17887
17888 let project = match &self.project {
17889 Some(project) => project.clone(),
17890 None => return None,
17891 };
17892
17893 Some(self.perform_format(
17894 project,
17895 FormatTrigger::Manual,
17896 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17897 window,
17898 cx,
17899 ))
17900 }
17901
17902 fn format_selections(
17903 &mut self,
17904 _: &FormatSelections,
17905 window: &mut Window,
17906 cx: &mut Context<Self>,
17907 ) -> Option<Task<Result<()>>> {
17908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17909
17910 let project = match &self.project {
17911 Some(project) => project.clone(),
17912 None => return None,
17913 };
17914
17915 let ranges = self
17916 .selections
17917 .all_adjusted(&self.display_snapshot(cx))
17918 .into_iter()
17919 .map(|selection| selection.range())
17920 .collect_vec();
17921
17922 Some(self.perform_format(
17923 project,
17924 FormatTrigger::Manual,
17925 FormatTarget::Ranges(ranges),
17926 window,
17927 cx,
17928 ))
17929 }
17930
17931 fn perform_format(
17932 &mut self,
17933 project: Entity<Project>,
17934 trigger: FormatTrigger,
17935 target: FormatTarget,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) -> Task<Result<()>> {
17939 let buffer = self.buffer.clone();
17940 let (buffers, target) = match target {
17941 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17942 FormatTarget::Ranges(selection_ranges) => {
17943 let multi_buffer = buffer.read(cx);
17944 let snapshot = multi_buffer.read(cx);
17945 let mut buffers = HashSet::default();
17946 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17947 BTreeMap::new();
17948 for selection_range in selection_ranges {
17949 for (buffer, buffer_range, _) in
17950 snapshot.range_to_buffer_ranges(selection_range)
17951 {
17952 let buffer_id = buffer.remote_id();
17953 let start = buffer.anchor_before(buffer_range.start);
17954 let end = buffer.anchor_after(buffer_range.end);
17955 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17956 buffer_id_to_ranges
17957 .entry(buffer_id)
17958 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17959 .or_insert_with(|| vec![start..end]);
17960 }
17961 }
17962 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17963 }
17964 };
17965
17966 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17967 let selections_prev = transaction_id_prev
17968 .and_then(|transaction_id_prev| {
17969 // default to selections as they were after the last edit, if we have them,
17970 // instead of how they are now.
17971 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17972 // will take you back to where you made the last edit, instead of staying where you scrolled
17973 self.selection_history
17974 .transaction(transaction_id_prev)
17975 .map(|t| t.0.clone())
17976 })
17977 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17978
17979 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17980 let format = project.update(cx, |project, cx| {
17981 project.format(buffers, target, true, trigger, cx)
17982 });
17983
17984 cx.spawn_in(window, async move |editor, cx| {
17985 let transaction = futures::select_biased! {
17986 transaction = format.log_err().fuse() => transaction,
17987 () = timeout => {
17988 log::warn!("timed out waiting for formatting");
17989 None
17990 }
17991 };
17992
17993 buffer
17994 .update(cx, |buffer, cx| {
17995 if let Some(transaction) = transaction
17996 && !buffer.is_singleton()
17997 {
17998 buffer.push_transaction(&transaction.0, cx);
17999 }
18000 cx.notify();
18001 })
18002 .ok();
18003
18004 if let Some(transaction_id_now) =
18005 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18006 {
18007 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18008 if has_new_transaction {
18009 _ = editor.update(cx, |editor, _| {
18010 editor
18011 .selection_history
18012 .insert_transaction(transaction_id_now, selections_prev);
18013 });
18014 }
18015 }
18016
18017 Ok(())
18018 })
18019 }
18020
18021 fn organize_imports(
18022 &mut self,
18023 _: &OrganizeImports,
18024 window: &mut Window,
18025 cx: &mut Context<Self>,
18026 ) -> Option<Task<Result<()>>> {
18027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18028 let project = match &self.project {
18029 Some(project) => project.clone(),
18030 None => return None,
18031 };
18032 Some(self.perform_code_action_kind(
18033 project,
18034 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18035 window,
18036 cx,
18037 ))
18038 }
18039
18040 fn perform_code_action_kind(
18041 &mut self,
18042 project: Entity<Project>,
18043 kind: CodeActionKind,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) -> Task<Result<()>> {
18047 let buffer = self.buffer.clone();
18048 let buffers = buffer.read(cx).all_buffers();
18049 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18050 let apply_action = project.update(cx, |project, cx| {
18051 project.apply_code_action_kind(buffers, kind, true, cx)
18052 });
18053 cx.spawn_in(window, async move |_, cx| {
18054 let transaction = futures::select_biased! {
18055 () = timeout => {
18056 log::warn!("timed out waiting for executing code action");
18057 None
18058 }
18059 transaction = apply_action.log_err().fuse() => transaction,
18060 };
18061 buffer
18062 .update(cx, |buffer, cx| {
18063 // check if we need this
18064 if let Some(transaction) = transaction
18065 && !buffer.is_singleton()
18066 {
18067 buffer.push_transaction(&transaction.0, cx);
18068 }
18069 cx.notify();
18070 })
18071 .ok();
18072 Ok(())
18073 })
18074 }
18075
18076 pub fn restart_language_server(
18077 &mut self,
18078 _: &RestartLanguageServer,
18079 _: &mut Window,
18080 cx: &mut Context<Self>,
18081 ) {
18082 if let Some(project) = self.project.clone() {
18083 self.buffer.update(cx, |multi_buffer, cx| {
18084 project.update(cx, |project, cx| {
18085 project.restart_language_servers_for_buffers(
18086 multi_buffer.all_buffers().into_iter().collect(),
18087 HashSet::default(),
18088 cx,
18089 );
18090 });
18091 })
18092 }
18093 }
18094
18095 pub fn stop_language_server(
18096 &mut self,
18097 _: &StopLanguageServer,
18098 _: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 if let Some(project) = self.project.clone() {
18102 self.buffer.update(cx, |multi_buffer, cx| {
18103 project.update(cx, |project, cx| {
18104 project.stop_language_servers_for_buffers(
18105 multi_buffer.all_buffers().into_iter().collect(),
18106 HashSet::default(),
18107 cx,
18108 );
18109 });
18110 });
18111 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18112 }
18113 }
18114
18115 fn cancel_language_server_work(
18116 workspace: &mut Workspace,
18117 _: &actions::CancelLanguageServerWork,
18118 _: &mut Window,
18119 cx: &mut Context<Workspace>,
18120 ) {
18121 let project = workspace.project();
18122 let buffers = workspace
18123 .active_item(cx)
18124 .and_then(|item| item.act_as::<Editor>(cx))
18125 .map_or(HashSet::default(), |editor| {
18126 editor.read(cx).buffer.read(cx).all_buffers()
18127 });
18128 project.update(cx, |project, cx| {
18129 project.cancel_language_server_work_for_buffers(buffers, cx);
18130 });
18131 }
18132
18133 fn show_character_palette(
18134 &mut self,
18135 _: &ShowCharacterPalette,
18136 window: &mut Window,
18137 _: &mut Context<Self>,
18138 ) {
18139 window.show_character_palette();
18140 }
18141
18142 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18143 if !self.diagnostics_enabled() {
18144 return;
18145 }
18146
18147 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18148 let buffer = self.buffer.read(cx).snapshot(cx);
18149 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18150 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18151 let is_valid = buffer
18152 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18153 .any(|entry| {
18154 entry.diagnostic.is_primary
18155 && !entry.range.is_empty()
18156 && entry.range.start == primary_range_start
18157 && entry.diagnostic.message == active_diagnostics.active_message
18158 });
18159
18160 if !is_valid {
18161 self.dismiss_diagnostics(cx);
18162 }
18163 }
18164 }
18165
18166 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18167 match &self.active_diagnostics {
18168 ActiveDiagnostic::Group(group) => Some(group),
18169 _ => None,
18170 }
18171 }
18172
18173 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18174 if !self.diagnostics_enabled() {
18175 return;
18176 }
18177 self.dismiss_diagnostics(cx);
18178 self.active_diagnostics = ActiveDiagnostic::All;
18179 }
18180
18181 fn activate_diagnostics(
18182 &mut self,
18183 buffer_id: BufferId,
18184 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18185 window: &mut Window,
18186 cx: &mut Context<Self>,
18187 ) {
18188 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18189 return;
18190 }
18191 self.dismiss_diagnostics(cx);
18192 let snapshot = self.snapshot(window, cx);
18193 let buffer = self.buffer.read(cx).snapshot(cx);
18194 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18195 return;
18196 };
18197
18198 let diagnostic_group = buffer
18199 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18200 .collect::<Vec<_>>();
18201
18202 let language_registry = self
18203 .project()
18204 .map(|project| project.read(cx).languages().clone());
18205
18206 let blocks = renderer.render_group(
18207 diagnostic_group,
18208 buffer_id,
18209 snapshot,
18210 cx.weak_entity(),
18211 language_registry,
18212 cx,
18213 );
18214
18215 let blocks = self.display_map.update(cx, |display_map, cx| {
18216 display_map.insert_blocks(blocks, cx).into_iter().collect()
18217 });
18218 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18219 active_range: buffer.anchor_before(diagnostic.range.start)
18220 ..buffer.anchor_after(diagnostic.range.end),
18221 active_message: diagnostic.diagnostic.message.clone(),
18222 group_id: diagnostic.diagnostic.group_id,
18223 blocks,
18224 });
18225 cx.notify();
18226 }
18227
18228 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18229 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18230 return;
18231 };
18232
18233 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18234 if let ActiveDiagnostic::Group(group) = prev {
18235 self.display_map.update(cx, |display_map, cx| {
18236 display_map.remove_blocks(group.blocks, cx);
18237 });
18238 cx.notify();
18239 }
18240 }
18241
18242 /// Disable inline diagnostics rendering for this editor.
18243 pub fn disable_inline_diagnostics(&mut self) {
18244 self.inline_diagnostics_enabled = false;
18245 self.inline_diagnostics_update = Task::ready(());
18246 self.inline_diagnostics.clear();
18247 }
18248
18249 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18250 self.diagnostics_enabled = false;
18251 self.dismiss_diagnostics(cx);
18252 self.inline_diagnostics_update = Task::ready(());
18253 self.inline_diagnostics.clear();
18254 }
18255
18256 pub fn disable_word_completions(&mut self) {
18257 self.word_completions_enabled = false;
18258 }
18259
18260 pub fn diagnostics_enabled(&self) -> bool {
18261 self.diagnostics_enabled && self.mode.is_full()
18262 }
18263
18264 pub fn inline_diagnostics_enabled(&self) -> bool {
18265 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18266 }
18267
18268 pub fn show_inline_diagnostics(&self) -> bool {
18269 self.show_inline_diagnostics
18270 }
18271
18272 pub fn toggle_inline_diagnostics(
18273 &mut self,
18274 _: &ToggleInlineDiagnostics,
18275 window: &mut Window,
18276 cx: &mut Context<Editor>,
18277 ) {
18278 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18279 self.refresh_inline_diagnostics(false, window, cx);
18280 }
18281
18282 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18283 self.diagnostics_max_severity = severity;
18284 self.display_map.update(cx, |display_map, _| {
18285 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18286 });
18287 }
18288
18289 pub fn toggle_diagnostics(
18290 &mut self,
18291 _: &ToggleDiagnostics,
18292 window: &mut Window,
18293 cx: &mut Context<Editor>,
18294 ) {
18295 if !self.diagnostics_enabled() {
18296 return;
18297 }
18298
18299 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18300 EditorSettings::get_global(cx)
18301 .diagnostics_max_severity
18302 .filter(|severity| severity != &DiagnosticSeverity::Off)
18303 .unwrap_or(DiagnosticSeverity::Hint)
18304 } else {
18305 DiagnosticSeverity::Off
18306 };
18307 self.set_max_diagnostics_severity(new_severity, cx);
18308 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18309 self.active_diagnostics = ActiveDiagnostic::None;
18310 self.inline_diagnostics_update = Task::ready(());
18311 self.inline_diagnostics.clear();
18312 } else {
18313 self.refresh_inline_diagnostics(false, window, cx);
18314 }
18315
18316 cx.notify();
18317 }
18318
18319 pub fn toggle_minimap(
18320 &mut self,
18321 _: &ToggleMinimap,
18322 window: &mut Window,
18323 cx: &mut Context<Editor>,
18324 ) {
18325 if self.supports_minimap(cx) {
18326 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18327 }
18328 }
18329
18330 fn refresh_inline_diagnostics(
18331 &mut self,
18332 debounce: bool,
18333 window: &mut Window,
18334 cx: &mut Context<Self>,
18335 ) {
18336 let max_severity = ProjectSettings::get_global(cx)
18337 .diagnostics
18338 .inline
18339 .max_severity
18340 .unwrap_or(self.diagnostics_max_severity);
18341
18342 if !self.inline_diagnostics_enabled()
18343 || !self.diagnostics_enabled()
18344 || !self.show_inline_diagnostics
18345 || max_severity == DiagnosticSeverity::Off
18346 {
18347 self.inline_diagnostics_update = Task::ready(());
18348 self.inline_diagnostics.clear();
18349 return;
18350 }
18351
18352 let debounce_ms = ProjectSettings::get_global(cx)
18353 .diagnostics
18354 .inline
18355 .update_debounce_ms;
18356 let debounce = if debounce && debounce_ms > 0 {
18357 Some(Duration::from_millis(debounce_ms))
18358 } else {
18359 None
18360 };
18361 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18362 if let Some(debounce) = debounce {
18363 cx.background_executor().timer(debounce).await;
18364 }
18365 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18366 editor
18367 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18368 .ok()
18369 }) else {
18370 return;
18371 };
18372
18373 let new_inline_diagnostics = cx
18374 .background_spawn(async move {
18375 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18376 for diagnostic_entry in
18377 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18378 {
18379 let message = diagnostic_entry
18380 .diagnostic
18381 .message
18382 .split_once('\n')
18383 .map(|(line, _)| line)
18384 .map(SharedString::new)
18385 .unwrap_or_else(|| {
18386 SharedString::new(&*diagnostic_entry.diagnostic.message)
18387 });
18388 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18389 let (Ok(i) | Err(i)) = inline_diagnostics
18390 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18391 inline_diagnostics.insert(
18392 i,
18393 (
18394 start_anchor,
18395 InlineDiagnostic {
18396 message,
18397 group_id: diagnostic_entry.diagnostic.group_id,
18398 start: diagnostic_entry.range.start.to_point(&snapshot),
18399 is_primary: diagnostic_entry.diagnostic.is_primary,
18400 severity: diagnostic_entry.diagnostic.severity,
18401 },
18402 ),
18403 );
18404 }
18405 inline_diagnostics
18406 })
18407 .await;
18408
18409 editor
18410 .update(cx, |editor, cx| {
18411 editor.inline_diagnostics = new_inline_diagnostics;
18412 cx.notify();
18413 })
18414 .ok();
18415 });
18416 }
18417
18418 fn pull_diagnostics(
18419 &mut self,
18420 buffer_id: Option<BufferId>,
18421 window: &Window,
18422 cx: &mut Context<Self>,
18423 ) -> Option<()> {
18424 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18425 return None;
18426 }
18427 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18428 .diagnostics
18429 .lsp_pull_diagnostics;
18430 if !pull_diagnostics_settings.enabled {
18431 return None;
18432 }
18433 let project = self.project()?.downgrade();
18434
18435 let mut edited_buffer_ids = HashSet::default();
18436 let mut edited_worktree_ids = HashSet::default();
18437 let edited_buffers = match buffer_id {
18438 Some(buffer_id) => {
18439 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18440 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18441 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18442 edited_worktree_ids.insert(worktree_id);
18443 vec![buffer]
18444 }
18445 None => self
18446 .buffer()
18447 .read(cx)
18448 .all_buffers()
18449 .into_iter()
18450 .filter(|buffer| {
18451 let buffer = buffer.read(cx);
18452 match buffer.file().map(|f| f.worktree_id(cx)) {
18453 Some(worktree_id) => {
18454 edited_buffer_ids.insert(buffer.remote_id());
18455 edited_worktree_ids.insert(worktree_id);
18456 true
18457 }
18458 None => false,
18459 }
18460 })
18461 .collect::<Vec<_>>(),
18462 };
18463
18464 if edited_buffers.is_empty() {
18465 self.pull_diagnostics_task = Task::ready(());
18466 self.pull_diagnostics_background_task = Task::ready(());
18467 return None;
18468 }
18469
18470 let mut already_used_buffers = HashSet::default();
18471 let related_open_buffers = self
18472 .workspace
18473 .as_ref()
18474 .and_then(|(workspace, _)| workspace.upgrade())
18475 .into_iter()
18476 .flat_map(|workspace| workspace.read(cx).panes())
18477 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18478 .filter(|editor| editor != &cx.entity())
18479 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18480 .filter(|buffer| {
18481 let buffer = buffer.read(cx);
18482 let buffer_id = buffer.remote_id();
18483 if already_used_buffers.insert(buffer_id) {
18484 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18485 return !edited_buffer_ids.contains(&buffer_id)
18486 && !edited_worktree_ids.contains(&worktree_id);
18487 }
18488 }
18489 false
18490 })
18491 .collect::<Vec<_>>();
18492
18493 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18494 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18495 if buffers.is_empty() {
18496 return Task::ready(());
18497 }
18498 let project_weak = project.clone();
18499 cx.spawn_in(window, async move |_, cx| {
18500 cx.background_executor().timer(delay).await;
18501
18502 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18503 buffers
18504 .into_iter()
18505 .filter_map(|buffer| {
18506 project_weak
18507 .update(cx, |project, cx| {
18508 project.lsp_store().update(cx, |lsp_store, cx| {
18509 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18510 })
18511 })
18512 .ok()
18513 })
18514 .collect::<FuturesUnordered<_>>()
18515 }) else {
18516 return;
18517 };
18518
18519 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18520 if let Err(e) = pull_task {
18521 log::error!("Failed to update project diagnostics: {e:#}");
18522 }
18523 }
18524 })
18525 };
18526
18527 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18528 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18529
18530 Some(())
18531 }
18532
18533 pub fn set_selections_from_remote(
18534 &mut self,
18535 selections: Vec<Selection<Anchor>>,
18536 pending_selection: Option<Selection<Anchor>>,
18537 window: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 let old_cursor_position = self.selections.newest_anchor().head();
18541 self.selections
18542 .change_with(&self.display_snapshot(cx), |s| {
18543 s.select_anchors(selections);
18544 if let Some(pending_selection) = pending_selection {
18545 s.set_pending(pending_selection, SelectMode::Character);
18546 } else {
18547 s.clear_pending();
18548 }
18549 });
18550 self.selections_did_change(
18551 false,
18552 &old_cursor_position,
18553 SelectionEffects::default(),
18554 window,
18555 cx,
18556 );
18557 }
18558
18559 pub fn transact(
18560 &mut self,
18561 window: &mut Window,
18562 cx: &mut Context<Self>,
18563 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18564 ) -> Option<TransactionId> {
18565 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18566 this.start_transaction_at(Instant::now(), window, cx);
18567 update(this, window, cx);
18568 this.end_transaction_at(Instant::now(), cx)
18569 })
18570 }
18571
18572 pub fn start_transaction_at(
18573 &mut self,
18574 now: Instant,
18575 window: &mut Window,
18576 cx: &mut Context<Self>,
18577 ) -> Option<TransactionId> {
18578 self.end_selection(window, cx);
18579 if let Some(tx_id) = self
18580 .buffer
18581 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18582 {
18583 self.selection_history
18584 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18585 cx.emit(EditorEvent::TransactionBegun {
18586 transaction_id: tx_id,
18587 });
18588 Some(tx_id)
18589 } else {
18590 None
18591 }
18592 }
18593
18594 pub fn end_transaction_at(
18595 &mut self,
18596 now: Instant,
18597 cx: &mut Context<Self>,
18598 ) -> Option<TransactionId> {
18599 if let Some(transaction_id) = self
18600 .buffer
18601 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18602 {
18603 if let Some((_, end_selections)) =
18604 self.selection_history.transaction_mut(transaction_id)
18605 {
18606 *end_selections = Some(self.selections.disjoint_anchors_arc());
18607 } else {
18608 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18609 }
18610
18611 cx.emit(EditorEvent::Edited { transaction_id });
18612 Some(transaction_id)
18613 } else {
18614 None
18615 }
18616 }
18617
18618 pub fn modify_transaction_selection_history(
18619 &mut self,
18620 transaction_id: TransactionId,
18621 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18622 ) -> bool {
18623 self.selection_history
18624 .transaction_mut(transaction_id)
18625 .map(modify)
18626 .is_some()
18627 }
18628
18629 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18630 if self.selection_mark_mode {
18631 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18632 s.move_with(|_, sel| {
18633 sel.collapse_to(sel.head(), SelectionGoal::None);
18634 });
18635 })
18636 }
18637 self.selection_mark_mode = true;
18638 cx.notify();
18639 }
18640
18641 pub fn swap_selection_ends(
18642 &mut self,
18643 _: &actions::SwapSelectionEnds,
18644 window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18648 s.move_with(|_, sel| {
18649 if sel.start != sel.end {
18650 sel.reversed = !sel.reversed
18651 }
18652 });
18653 });
18654 self.request_autoscroll(Autoscroll::newest(), cx);
18655 cx.notify();
18656 }
18657
18658 pub fn toggle_focus(
18659 workspace: &mut Workspace,
18660 _: &actions::ToggleFocus,
18661 window: &mut Window,
18662 cx: &mut Context<Workspace>,
18663 ) {
18664 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18665 return;
18666 };
18667 workspace.activate_item(&item, true, true, window, cx);
18668 }
18669
18670 pub fn toggle_fold(
18671 &mut self,
18672 _: &actions::ToggleFold,
18673 window: &mut Window,
18674 cx: &mut Context<Self>,
18675 ) {
18676 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18678 let selection = self.selections.newest::<Point>(&display_map);
18679
18680 let range = if selection.is_empty() {
18681 let point = selection.head().to_display_point(&display_map);
18682 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18683 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18684 .to_point(&display_map);
18685 start..end
18686 } else {
18687 selection.range()
18688 };
18689 if display_map.folds_in_range(range).next().is_some() {
18690 self.unfold_lines(&Default::default(), window, cx)
18691 } else {
18692 self.fold(&Default::default(), window, cx)
18693 }
18694 } else {
18695 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18696 let buffer_ids: HashSet<_> = self
18697 .selections
18698 .disjoint_anchor_ranges()
18699 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18700 .collect();
18701
18702 let should_unfold = buffer_ids
18703 .iter()
18704 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18705
18706 for buffer_id in buffer_ids {
18707 if should_unfold {
18708 self.unfold_buffer(buffer_id, cx);
18709 } else {
18710 self.fold_buffer(buffer_id, cx);
18711 }
18712 }
18713 }
18714 }
18715
18716 pub fn toggle_fold_recursive(
18717 &mut self,
18718 _: &actions::ToggleFoldRecursive,
18719 window: &mut Window,
18720 cx: &mut Context<Self>,
18721 ) {
18722 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18723
18724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18725 let range = if selection.is_empty() {
18726 let point = selection.head().to_display_point(&display_map);
18727 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18728 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18729 .to_point(&display_map);
18730 start..end
18731 } else {
18732 selection.range()
18733 };
18734 if display_map.folds_in_range(range).next().is_some() {
18735 self.unfold_recursive(&Default::default(), window, cx)
18736 } else {
18737 self.fold_recursive(&Default::default(), window, cx)
18738 }
18739 }
18740
18741 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18742 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18743 let mut to_fold = Vec::new();
18744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18745 let selections = self.selections.all_adjusted(&display_map);
18746
18747 for selection in selections {
18748 let range = selection.range().sorted();
18749 let buffer_start_row = range.start.row;
18750
18751 if range.start.row != range.end.row {
18752 let mut found = false;
18753 let mut row = range.start.row;
18754 while row <= range.end.row {
18755 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18756 {
18757 found = true;
18758 row = crease.range().end.row + 1;
18759 to_fold.push(crease);
18760 } else {
18761 row += 1
18762 }
18763 }
18764 if found {
18765 continue;
18766 }
18767 }
18768
18769 for row in (0..=range.start.row).rev() {
18770 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18771 && crease.range().end.row >= buffer_start_row
18772 {
18773 to_fold.push(crease);
18774 if row <= range.start.row {
18775 break;
18776 }
18777 }
18778 }
18779 }
18780
18781 self.fold_creases(to_fold, true, window, cx);
18782 } else {
18783 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18784 let buffer_ids = self
18785 .selections
18786 .disjoint_anchor_ranges()
18787 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18788 .collect::<HashSet<_>>();
18789 for buffer_id in buffer_ids {
18790 self.fold_buffer(buffer_id, cx);
18791 }
18792 }
18793 }
18794
18795 pub fn toggle_fold_all(
18796 &mut self,
18797 _: &actions::ToggleFoldAll,
18798 window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 if self.buffer.read(cx).is_singleton() {
18802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18803 let has_folds = display_map
18804 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18805 .next()
18806 .is_some();
18807
18808 if has_folds {
18809 self.unfold_all(&actions::UnfoldAll, window, cx);
18810 } else {
18811 self.fold_all(&actions::FoldAll, window, cx);
18812 }
18813 } else {
18814 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18815 let should_unfold = buffer_ids
18816 .iter()
18817 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18818
18819 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18820 editor
18821 .update_in(cx, |editor, _, cx| {
18822 for buffer_id in buffer_ids {
18823 if should_unfold {
18824 editor.unfold_buffer(buffer_id, cx);
18825 } else {
18826 editor.fold_buffer(buffer_id, cx);
18827 }
18828 }
18829 })
18830 .ok();
18831 });
18832 }
18833 }
18834
18835 fn fold_at_level(
18836 &mut self,
18837 fold_at: &FoldAtLevel,
18838 window: &mut Window,
18839 cx: &mut Context<Self>,
18840 ) {
18841 if !self.buffer.read(cx).is_singleton() {
18842 return;
18843 }
18844
18845 let fold_at_level = fold_at.0;
18846 let snapshot = self.buffer.read(cx).snapshot(cx);
18847 let mut to_fold = Vec::new();
18848 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18849
18850 let row_ranges_to_keep: Vec<Range<u32>> = self
18851 .selections
18852 .all::<Point>(&self.display_snapshot(cx))
18853 .into_iter()
18854 .map(|sel| sel.start.row..sel.end.row)
18855 .collect();
18856
18857 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18858 while start_row < end_row {
18859 match self
18860 .snapshot(window, cx)
18861 .crease_for_buffer_row(MultiBufferRow(start_row))
18862 {
18863 Some(crease) => {
18864 let nested_start_row = crease.range().start.row + 1;
18865 let nested_end_row = crease.range().end.row;
18866
18867 if current_level < fold_at_level {
18868 stack.push((nested_start_row, nested_end_row, current_level + 1));
18869 } else if current_level == fold_at_level {
18870 // Fold iff there is no selection completely contained within the fold region
18871 if !row_ranges_to_keep.iter().any(|selection| {
18872 selection.end >= nested_start_row
18873 && selection.start <= nested_end_row
18874 }) {
18875 to_fold.push(crease);
18876 }
18877 }
18878
18879 start_row = nested_end_row + 1;
18880 }
18881 None => start_row += 1,
18882 }
18883 }
18884 }
18885
18886 self.fold_creases(to_fold, true, window, cx);
18887 }
18888
18889 pub fn fold_at_level_1(
18890 &mut self,
18891 _: &actions::FoldAtLevel1,
18892 window: &mut Window,
18893 cx: &mut Context<Self>,
18894 ) {
18895 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18896 }
18897
18898 pub fn fold_at_level_2(
18899 &mut self,
18900 _: &actions::FoldAtLevel2,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18905 }
18906
18907 pub fn fold_at_level_3(
18908 &mut self,
18909 _: &actions::FoldAtLevel3,
18910 window: &mut Window,
18911 cx: &mut Context<Self>,
18912 ) {
18913 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18914 }
18915
18916 pub fn fold_at_level_4(
18917 &mut self,
18918 _: &actions::FoldAtLevel4,
18919 window: &mut Window,
18920 cx: &mut Context<Self>,
18921 ) {
18922 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18923 }
18924
18925 pub fn fold_at_level_5(
18926 &mut self,
18927 _: &actions::FoldAtLevel5,
18928 window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) {
18931 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18932 }
18933
18934 pub fn fold_at_level_6(
18935 &mut self,
18936 _: &actions::FoldAtLevel6,
18937 window: &mut Window,
18938 cx: &mut Context<Self>,
18939 ) {
18940 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18941 }
18942
18943 pub fn fold_at_level_7(
18944 &mut self,
18945 _: &actions::FoldAtLevel7,
18946 window: &mut Window,
18947 cx: &mut Context<Self>,
18948 ) {
18949 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18950 }
18951
18952 pub fn fold_at_level_8(
18953 &mut self,
18954 _: &actions::FoldAtLevel8,
18955 window: &mut Window,
18956 cx: &mut Context<Self>,
18957 ) {
18958 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18959 }
18960
18961 pub fn fold_at_level_9(
18962 &mut self,
18963 _: &actions::FoldAtLevel9,
18964 window: &mut Window,
18965 cx: &mut Context<Self>,
18966 ) {
18967 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18968 }
18969
18970 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18971 if self.buffer.read(cx).is_singleton() {
18972 let mut fold_ranges = Vec::new();
18973 let snapshot = self.buffer.read(cx).snapshot(cx);
18974
18975 for row in 0..snapshot.max_row().0 {
18976 if let Some(foldable_range) = self
18977 .snapshot(window, cx)
18978 .crease_for_buffer_row(MultiBufferRow(row))
18979 {
18980 fold_ranges.push(foldable_range);
18981 }
18982 }
18983
18984 self.fold_creases(fold_ranges, true, window, cx);
18985 } else {
18986 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18987 editor
18988 .update_in(cx, |editor, _, cx| {
18989 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18990 editor.fold_buffer(buffer_id, cx);
18991 }
18992 })
18993 .ok();
18994 });
18995 }
18996 }
18997
18998 pub fn fold_function_bodies(
18999 &mut self,
19000 _: &actions::FoldFunctionBodies,
19001 window: &mut Window,
19002 cx: &mut Context<Self>,
19003 ) {
19004 let snapshot = self.buffer.read(cx).snapshot(cx);
19005
19006 let ranges = snapshot
19007 .text_object_ranges(
19008 MultiBufferOffset(0)..snapshot.len(),
19009 TreeSitterOptions::default(),
19010 )
19011 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19012 .collect::<Vec<_>>();
19013
19014 let creases = ranges
19015 .into_iter()
19016 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19017 .collect();
19018
19019 self.fold_creases(creases, true, window, cx);
19020 }
19021
19022 pub fn fold_recursive(
19023 &mut self,
19024 _: &actions::FoldRecursive,
19025 window: &mut Window,
19026 cx: &mut Context<Self>,
19027 ) {
19028 let mut to_fold = Vec::new();
19029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19030 let selections = self.selections.all_adjusted(&display_map);
19031
19032 for selection in selections {
19033 let range = selection.range().sorted();
19034 let buffer_start_row = range.start.row;
19035
19036 if range.start.row != range.end.row {
19037 let mut found = false;
19038 for row in range.start.row..=range.end.row {
19039 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19040 found = true;
19041 to_fold.push(crease);
19042 }
19043 }
19044 if found {
19045 continue;
19046 }
19047 }
19048
19049 for row in (0..=range.start.row).rev() {
19050 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19051 if crease.range().end.row >= buffer_start_row {
19052 to_fold.push(crease);
19053 } else {
19054 break;
19055 }
19056 }
19057 }
19058 }
19059
19060 self.fold_creases(to_fold, true, window, cx);
19061 }
19062
19063 pub fn fold_at(
19064 &mut self,
19065 buffer_row: MultiBufferRow,
19066 window: &mut Window,
19067 cx: &mut Context<Self>,
19068 ) {
19069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19070
19071 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19072 let autoscroll = self
19073 .selections
19074 .all::<Point>(&display_map)
19075 .iter()
19076 .any(|selection| crease.range().overlaps(&selection.range()));
19077
19078 self.fold_creases(vec![crease], autoscroll, window, cx);
19079 }
19080 }
19081
19082 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19083 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19085 let buffer = display_map.buffer_snapshot();
19086 let selections = self.selections.all::<Point>(&display_map);
19087 let ranges = selections
19088 .iter()
19089 .map(|s| {
19090 let range = s.display_range(&display_map).sorted();
19091 let mut start = range.start.to_point(&display_map);
19092 let mut end = range.end.to_point(&display_map);
19093 start.column = 0;
19094 end.column = buffer.line_len(MultiBufferRow(end.row));
19095 start..end
19096 })
19097 .collect::<Vec<_>>();
19098
19099 self.unfold_ranges(&ranges, true, true, cx);
19100 } else {
19101 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19102 let buffer_ids = self
19103 .selections
19104 .disjoint_anchor_ranges()
19105 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19106 .collect::<HashSet<_>>();
19107 for buffer_id in buffer_ids {
19108 self.unfold_buffer(buffer_id, cx);
19109 }
19110 }
19111 }
19112
19113 pub fn unfold_recursive(
19114 &mut self,
19115 _: &UnfoldRecursive,
19116 _window: &mut Window,
19117 cx: &mut Context<Self>,
19118 ) {
19119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19120 let selections = self.selections.all::<Point>(&display_map);
19121 let ranges = selections
19122 .iter()
19123 .map(|s| {
19124 let mut range = s.display_range(&display_map).sorted();
19125 *range.start.column_mut() = 0;
19126 *range.end.column_mut() = display_map.line_len(range.end.row());
19127 let start = range.start.to_point(&display_map);
19128 let end = range.end.to_point(&display_map);
19129 start..end
19130 })
19131 .collect::<Vec<_>>();
19132
19133 self.unfold_ranges(&ranges, true, true, cx);
19134 }
19135
19136 pub fn unfold_at(
19137 &mut self,
19138 buffer_row: MultiBufferRow,
19139 _window: &mut Window,
19140 cx: &mut Context<Self>,
19141 ) {
19142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19143
19144 let intersection_range = Point::new(buffer_row.0, 0)
19145 ..Point::new(
19146 buffer_row.0,
19147 display_map.buffer_snapshot().line_len(buffer_row),
19148 );
19149
19150 let autoscroll = self
19151 .selections
19152 .all::<Point>(&display_map)
19153 .iter()
19154 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19155
19156 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19157 }
19158
19159 pub fn unfold_all(
19160 &mut self,
19161 _: &actions::UnfoldAll,
19162 _window: &mut Window,
19163 cx: &mut Context<Self>,
19164 ) {
19165 if self.buffer.read(cx).is_singleton() {
19166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19167 self.unfold_ranges(
19168 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19169 true,
19170 true,
19171 cx,
19172 );
19173 } else {
19174 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19175 editor
19176 .update(cx, |editor, cx| {
19177 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19178 editor.unfold_buffer(buffer_id, cx);
19179 }
19180 })
19181 .ok();
19182 });
19183 }
19184 }
19185
19186 pub fn fold_selected_ranges(
19187 &mut self,
19188 _: &FoldSelectedRanges,
19189 window: &mut Window,
19190 cx: &mut Context<Self>,
19191 ) {
19192 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19193 let selections = self.selections.all_adjusted(&display_map);
19194 let ranges = selections
19195 .into_iter()
19196 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19197 .collect::<Vec<_>>();
19198 self.fold_creases(ranges, true, window, cx);
19199 }
19200
19201 pub fn fold_ranges<T: ToOffset + Clone>(
19202 &mut self,
19203 ranges: Vec<Range<T>>,
19204 auto_scroll: bool,
19205 window: &mut Window,
19206 cx: &mut Context<Self>,
19207 ) {
19208 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19209 let ranges = ranges
19210 .into_iter()
19211 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19212 .collect::<Vec<_>>();
19213 self.fold_creases(ranges, auto_scroll, window, cx);
19214 }
19215
19216 pub fn fold_creases<T: ToOffset + Clone>(
19217 &mut self,
19218 creases: Vec<Crease<T>>,
19219 auto_scroll: bool,
19220 _window: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) {
19223 if creases.is_empty() {
19224 return;
19225 }
19226
19227 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19228
19229 if auto_scroll {
19230 self.request_autoscroll(Autoscroll::fit(), cx);
19231 }
19232
19233 cx.notify();
19234
19235 self.scrollbar_marker_state.dirty = true;
19236 self.folds_did_change(cx);
19237 }
19238
19239 /// Removes any folds whose ranges intersect any of the given ranges.
19240 pub fn unfold_ranges<T: ToOffset + Clone>(
19241 &mut self,
19242 ranges: &[Range<T>],
19243 inclusive: bool,
19244 auto_scroll: bool,
19245 cx: &mut Context<Self>,
19246 ) {
19247 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19248 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19249 });
19250 self.folds_did_change(cx);
19251 }
19252
19253 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19254 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19255 return;
19256 }
19257
19258 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19259 self.display_map.update(cx, |display_map, cx| {
19260 display_map.fold_buffers([buffer_id], cx)
19261 });
19262
19263 let snapshot = self.display_snapshot(cx);
19264 self.selections.change_with(&snapshot, |selections| {
19265 selections.remove_selections_from_buffer(buffer_id);
19266 });
19267
19268 cx.emit(EditorEvent::BufferFoldToggled {
19269 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19270 folded: true,
19271 });
19272 cx.notify();
19273 }
19274
19275 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19276 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19277 return;
19278 }
19279 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19280 self.display_map.update(cx, |display_map, cx| {
19281 display_map.unfold_buffers([buffer_id], cx);
19282 });
19283 cx.emit(EditorEvent::BufferFoldToggled {
19284 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19285 folded: false,
19286 });
19287 cx.notify();
19288 }
19289
19290 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19291 self.display_map.read(cx).is_buffer_folded(buffer)
19292 }
19293
19294 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19295 self.display_map.read(cx).folded_buffers()
19296 }
19297
19298 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19299 self.display_map.update(cx, |display_map, cx| {
19300 display_map.disable_header_for_buffer(buffer_id, cx);
19301 });
19302 cx.notify();
19303 }
19304
19305 /// Removes any folds with the given ranges.
19306 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19307 &mut self,
19308 ranges: &[Range<T>],
19309 type_id: TypeId,
19310 auto_scroll: bool,
19311 cx: &mut Context<Self>,
19312 ) {
19313 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19314 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19315 });
19316 self.folds_did_change(cx);
19317 }
19318
19319 fn remove_folds_with<T: ToOffset + Clone>(
19320 &mut self,
19321 ranges: &[Range<T>],
19322 auto_scroll: bool,
19323 cx: &mut Context<Self>,
19324 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19325 ) {
19326 if ranges.is_empty() {
19327 return;
19328 }
19329
19330 let mut buffers_affected = HashSet::default();
19331 let multi_buffer = self.buffer().read(cx);
19332 for range in ranges {
19333 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19334 buffers_affected.insert(buffer.read(cx).remote_id());
19335 };
19336 }
19337
19338 self.display_map.update(cx, update);
19339
19340 if auto_scroll {
19341 self.request_autoscroll(Autoscroll::fit(), cx);
19342 }
19343
19344 cx.notify();
19345 self.scrollbar_marker_state.dirty = true;
19346 self.active_indent_guides_state.dirty = true;
19347 }
19348
19349 pub fn update_renderer_widths(
19350 &mut self,
19351 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19352 cx: &mut Context<Self>,
19353 ) -> bool {
19354 self.display_map
19355 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19356 }
19357
19358 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19359 self.display_map.read(cx).fold_placeholder.clone()
19360 }
19361
19362 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19363 self.use_base_text_line_numbers = show;
19364 }
19365
19366 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19367 self.buffer.update(cx, |buffer, cx| {
19368 buffer.set_all_diff_hunks_expanded(cx);
19369 });
19370 }
19371
19372 pub fn expand_all_diff_hunks(
19373 &mut self,
19374 _: &ExpandAllDiffHunks,
19375 _window: &mut Window,
19376 cx: &mut Context<Self>,
19377 ) {
19378 self.buffer.update(cx, |buffer, cx| {
19379 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19380 });
19381 }
19382
19383 pub fn collapse_all_diff_hunks(
19384 &mut self,
19385 _: &CollapseAllDiffHunks,
19386 _window: &mut Window,
19387 cx: &mut Context<Self>,
19388 ) {
19389 self.buffer.update(cx, |buffer, cx| {
19390 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19391 });
19392 }
19393
19394 pub fn toggle_selected_diff_hunks(
19395 &mut self,
19396 _: &ToggleSelectedDiffHunks,
19397 _window: &mut Window,
19398 cx: &mut Context<Self>,
19399 ) {
19400 let ranges: Vec<_> = self
19401 .selections
19402 .disjoint_anchors()
19403 .iter()
19404 .map(|s| s.range())
19405 .collect();
19406 self.toggle_diff_hunks_in_ranges(ranges, cx);
19407 }
19408
19409 pub fn diff_hunks_in_ranges<'a>(
19410 &'a self,
19411 ranges: &'a [Range<Anchor>],
19412 buffer: &'a MultiBufferSnapshot,
19413 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19414 ranges.iter().flat_map(move |range| {
19415 let end_excerpt_id = range.end.excerpt_id;
19416 let range = range.to_point(buffer);
19417 let mut peek_end = range.end;
19418 if range.end.row < buffer.max_row().0 {
19419 peek_end = Point::new(range.end.row + 1, 0);
19420 }
19421 buffer
19422 .diff_hunks_in_range(range.start..peek_end)
19423 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19424 })
19425 }
19426
19427 pub fn has_stageable_diff_hunks_in_ranges(
19428 &self,
19429 ranges: &[Range<Anchor>],
19430 snapshot: &MultiBufferSnapshot,
19431 ) -> bool {
19432 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19433 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19434 }
19435
19436 pub fn toggle_staged_selected_diff_hunks(
19437 &mut self,
19438 _: &::git::ToggleStaged,
19439 _: &mut Window,
19440 cx: &mut Context<Self>,
19441 ) {
19442 let snapshot = self.buffer.read(cx).snapshot(cx);
19443 let ranges: Vec<_> = self
19444 .selections
19445 .disjoint_anchors()
19446 .iter()
19447 .map(|s| s.range())
19448 .collect();
19449 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19450 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19451 }
19452
19453 pub fn set_render_diff_hunk_controls(
19454 &mut self,
19455 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19456 cx: &mut Context<Self>,
19457 ) {
19458 self.render_diff_hunk_controls = render_diff_hunk_controls;
19459 cx.notify();
19460 }
19461
19462 pub fn stage_and_next(
19463 &mut self,
19464 _: &::git::StageAndNext,
19465 window: &mut Window,
19466 cx: &mut Context<Self>,
19467 ) {
19468 self.do_stage_or_unstage_and_next(true, window, cx);
19469 }
19470
19471 pub fn unstage_and_next(
19472 &mut self,
19473 _: &::git::UnstageAndNext,
19474 window: &mut Window,
19475 cx: &mut Context<Self>,
19476 ) {
19477 self.do_stage_or_unstage_and_next(false, window, cx);
19478 }
19479
19480 pub fn stage_or_unstage_diff_hunks(
19481 &mut self,
19482 stage: bool,
19483 ranges: Vec<Range<Anchor>>,
19484 cx: &mut Context<Self>,
19485 ) {
19486 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19487 cx.spawn(async move |this, cx| {
19488 task.await?;
19489 this.update(cx, |this, cx| {
19490 let snapshot = this.buffer.read(cx).snapshot(cx);
19491 let chunk_by = this
19492 .diff_hunks_in_ranges(&ranges, &snapshot)
19493 .chunk_by(|hunk| hunk.buffer_id);
19494 for (buffer_id, hunks) in &chunk_by {
19495 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19496 }
19497 })
19498 })
19499 .detach_and_log_err(cx);
19500 }
19501
19502 fn save_buffers_for_ranges_if_needed(
19503 &mut self,
19504 ranges: &[Range<Anchor>],
19505 cx: &mut Context<Editor>,
19506 ) -> Task<Result<()>> {
19507 let multibuffer = self.buffer.read(cx);
19508 let snapshot = multibuffer.read(cx);
19509 let buffer_ids: HashSet<_> = ranges
19510 .iter()
19511 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19512 .collect();
19513 drop(snapshot);
19514
19515 let mut buffers = HashSet::default();
19516 for buffer_id in buffer_ids {
19517 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19518 let buffer = buffer_entity.read(cx);
19519 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19520 {
19521 buffers.insert(buffer_entity);
19522 }
19523 }
19524 }
19525
19526 if let Some(project) = &self.project {
19527 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19528 } else {
19529 Task::ready(Ok(()))
19530 }
19531 }
19532
19533 fn do_stage_or_unstage_and_next(
19534 &mut self,
19535 stage: bool,
19536 window: &mut Window,
19537 cx: &mut Context<Self>,
19538 ) {
19539 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19540
19541 if ranges.iter().any(|range| range.start != range.end) {
19542 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19543 return;
19544 }
19545
19546 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19547 let snapshot = self.snapshot(window, cx);
19548 let position = self
19549 .selections
19550 .newest::<Point>(&snapshot.display_snapshot)
19551 .head();
19552 let mut row = snapshot
19553 .buffer_snapshot()
19554 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19555 .find(|hunk| hunk.row_range.start.0 > position.row)
19556 .map(|hunk| hunk.row_range.start);
19557
19558 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19559 // Outside of the project diff editor, wrap around to the beginning.
19560 if !all_diff_hunks_expanded {
19561 row = row.or_else(|| {
19562 snapshot
19563 .buffer_snapshot()
19564 .diff_hunks_in_range(Point::zero()..position)
19565 .find(|hunk| hunk.row_range.end.0 < position.row)
19566 .map(|hunk| hunk.row_range.start)
19567 });
19568 }
19569
19570 if let Some(row) = row {
19571 let destination = Point::new(row.0, 0);
19572 let autoscroll = Autoscroll::center();
19573
19574 self.unfold_ranges(&[destination..destination], false, false, cx);
19575 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19576 s.select_ranges([destination..destination]);
19577 });
19578 }
19579 }
19580
19581 fn do_stage_or_unstage(
19582 &self,
19583 stage: bool,
19584 buffer_id: BufferId,
19585 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19586 cx: &mut App,
19587 ) -> Option<()> {
19588 let project = self.project()?;
19589 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19590 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19591 let buffer_snapshot = buffer.read(cx).snapshot();
19592 let file_exists = buffer_snapshot
19593 .file()
19594 .is_some_and(|file| file.disk_state().exists());
19595 diff.update(cx, |diff, cx| {
19596 diff.stage_or_unstage_hunks(
19597 stage,
19598 &hunks
19599 .map(|hunk| buffer_diff::DiffHunk {
19600 buffer_range: hunk.buffer_range,
19601 // We don't need to pass in word diffs here because they're only used for rendering and
19602 // this function changes internal state
19603 base_word_diffs: Vec::default(),
19604 buffer_word_diffs: Vec::default(),
19605 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19606 ..hunk.diff_base_byte_range.end.0,
19607 secondary_status: hunk.secondary_status,
19608 range: Point::zero()..Point::zero(), // unused
19609 })
19610 .collect::<Vec<_>>(),
19611 &buffer_snapshot,
19612 file_exists,
19613 cx,
19614 )
19615 });
19616 None
19617 }
19618
19619 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19620 let ranges: Vec<_> = self
19621 .selections
19622 .disjoint_anchors()
19623 .iter()
19624 .map(|s| s.range())
19625 .collect();
19626 self.buffer
19627 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19628 }
19629
19630 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19631 self.buffer.update(cx, |buffer, cx| {
19632 let ranges = vec![Anchor::min()..Anchor::max()];
19633 if !buffer.all_diff_hunks_expanded()
19634 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19635 {
19636 buffer.collapse_diff_hunks(ranges, cx);
19637 true
19638 } else {
19639 false
19640 }
19641 })
19642 }
19643
19644 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19645 if self.buffer.read(cx).all_diff_hunks_expanded() {
19646 return true;
19647 }
19648 let ranges = vec![Anchor::min()..Anchor::max()];
19649 self.buffer
19650 .read(cx)
19651 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19652 }
19653
19654 fn toggle_diff_hunks_in_ranges(
19655 &mut self,
19656 ranges: Vec<Range<Anchor>>,
19657 cx: &mut Context<Editor>,
19658 ) {
19659 self.buffer.update(cx, |buffer, cx| {
19660 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19661 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19662 })
19663 }
19664
19665 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19666 self.buffer.update(cx, |buffer, cx| {
19667 let snapshot = buffer.snapshot(cx);
19668 let excerpt_id = range.end.excerpt_id;
19669 let point_range = range.to_point(&snapshot);
19670 let expand = !buffer.single_hunk_is_expanded(range, cx);
19671 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19672 })
19673 }
19674
19675 pub(crate) fn apply_all_diff_hunks(
19676 &mut self,
19677 _: &ApplyAllDiffHunks,
19678 window: &mut Window,
19679 cx: &mut Context<Self>,
19680 ) {
19681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19682
19683 let buffers = self.buffer.read(cx).all_buffers();
19684 for branch_buffer in buffers {
19685 branch_buffer.update(cx, |branch_buffer, cx| {
19686 branch_buffer.merge_into_base(Vec::new(), cx);
19687 });
19688 }
19689
19690 if let Some(project) = self.project.clone() {
19691 self.save(
19692 SaveOptions {
19693 format: true,
19694 autosave: false,
19695 },
19696 project,
19697 window,
19698 cx,
19699 )
19700 .detach_and_log_err(cx);
19701 }
19702 }
19703
19704 pub(crate) fn apply_selected_diff_hunks(
19705 &mut self,
19706 _: &ApplyDiffHunk,
19707 window: &mut Window,
19708 cx: &mut Context<Self>,
19709 ) {
19710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19711 let snapshot = self.snapshot(window, cx);
19712 let hunks = snapshot.hunks_for_ranges(
19713 self.selections
19714 .all(&snapshot.display_snapshot)
19715 .into_iter()
19716 .map(|selection| selection.range()),
19717 );
19718 let mut ranges_by_buffer = HashMap::default();
19719 self.transact(window, cx, |editor, _window, cx| {
19720 for hunk in hunks {
19721 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19722 ranges_by_buffer
19723 .entry(buffer.clone())
19724 .or_insert_with(Vec::new)
19725 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19726 }
19727 }
19728
19729 for (buffer, ranges) in ranges_by_buffer {
19730 buffer.update(cx, |buffer, cx| {
19731 buffer.merge_into_base(ranges, cx);
19732 });
19733 }
19734 });
19735
19736 if let Some(project) = self.project.clone() {
19737 self.save(
19738 SaveOptions {
19739 format: true,
19740 autosave: false,
19741 },
19742 project,
19743 window,
19744 cx,
19745 )
19746 .detach_and_log_err(cx);
19747 }
19748 }
19749
19750 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19751 if hovered != self.gutter_hovered {
19752 self.gutter_hovered = hovered;
19753 cx.notify();
19754 }
19755 }
19756
19757 pub fn insert_blocks(
19758 &mut self,
19759 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19760 autoscroll: Option<Autoscroll>,
19761 cx: &mut Context<Self>,
19762 ) -> Vec<CustomBlockId> {
19763 let blocks = self
19764 .display_map
19765 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19766 if let Some(autoscroll) = autoscroll {
19767 self.request_autoscroll(autoscroll, cx);
19768 }
19769 cx.notify();
19770 blocks
19771 }
19772
19773 pub fn resize_blocks(
19774 &mut self,
19775 heights: HashMap<CustomBlockId, u32>,
19776 autoscroll: Option<Autoscroll>,
19777 cx: &mut Context<Self>,
19778 ) {
19779 self.display_map
19780 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19781 if let Some(autoscroll) = autoscroll {
19782 self.request_autoscroll(autoscroll, cx);
19783 }
19784 cx.notify();
19785 }
19786
19787 pub fn replace_blocks(
19788 &mut self,
19789 renderers: HashMap<CustomBlockId, RenderBlock>,
19790 autoscroll: Option<Autoscroll>,
19791 cx: &mut Context<Self>,
19792 ) {
19793 self.display_map
19794 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19795 if let Some(autoscroll) = autoscroll {
19796 self.request_autoscroll(autoscroll, cx);
19797 }
19798 cx.notify();
19799 }
19800
19801 pub fn remove_blocks(
19802 &mut self,
19803 block_ids: HashSet<CustomBlockId>,
19804 autoscroll: Option<Autoscroll>,
19805 cx: &mut Context<Self>,
19806 ) {
19807 self.display_map.update(cx, |display_map, cx| {
19808 display_map.remove_blocks(block_ids, cx)
19809 });
19810 if let Some(autoscroll) = autoscroll {
19811 self.request_autoscroll(autoscroll, cx);
19812 }
19813 cx.notify();
19814 }
19815
19816 pub fn row_for_block(
19817 &self,
19818 block_id: CustomBlockId,
19819 cx: &mut Context<Self>,
19820 ) -> Option<DisplayRow> {
19821 self.display_map
19822 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19823 }
19824
19825 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19826 self.focused_block = Some(focused_block);
19827 }
19828
19829 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19830 self.focused_block.take()
19831 }
19832
19833 pub fn insert_creases(
19834 &mut self,
19835 creases: impl IntoIterator<Item = Crease<Anchor>>,
19836 cx: &mut Context<Self>,
19837 ) -> Vec<CreaseId> {
19838 self.display_map
19839 .update(cx, |map, cx| map.insert_creases(creases, cx))
19840 }
19841
19842 pub fn remove_creases(
19843 &mut self,
19844 ids: impl IntoIterator<Item = CreaseId>,
19845 cx: &mut Context<Self>,
19846 ) -> Vec<(CreaseId, Range<Anchor>)> {
19847 self.display_map
19848 .update(cx, |map, cx| map.remove_creases(ids, cx))
19849 }
19850
19851 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19852 self.display_map
19853 .update(cx, |map, cx| map.snapshot(cx))
19854 .longest_row()
19855 }
19856
19857 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19858 self.display_map
19859 .update(cx, |map, cx| map.snapshot(cx))
19860 .max_point()
19861 }
19862
19863 pub fn text(&self, cx: &App) -> String {
19864 self.buffer.read(cx).read(cx).text()
19865 }
19866
19867 pub fn is_empty(&self, cx: &App) -> bool {
19868 self.buffer.read(cx).read(cx).is_empty()
19869 }
19870
19871 pub fn text_option(&self, cx: &App) -> Option<String> {
19872 let text = self.text(cx);
19873 let text = text.trim();
19874
19875 if text.is_empty() {
19876 return None;
19877 }
19878
19879 Some(text.to_string())
19880 }
19881
19882 pub fn set_text(
19883 &mut self,
19884 text: impl Into<Arc<str>>,
19885 window: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) {
19888 self.transact(window, cx, |this, _, cx| {
19889 this.buffer
19890 .read(cx)
19891 .as_singleton()
19892 .expect("you can only call set_text on editors for singleton buffers")
19893 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19894 });
19895 }
19896
19897 pub fn display_text(&self, cx: &mut App) -> String {
19898 self.display_map
19899 .update(cx, |map, cx| map.snapshot(cx))
19900 .text()
19901 }
19902
19903 fn create_minimap(
19904 &self,
19905 minimap_settings: MinimapSettings,
19906 window: &mut Window,
19907 cx: &mut Context<Self>,
19908 ) -> Option<Entity<Self>> {
19909 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19910 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19911 }
19912
19913 fn initialize_new_minimap(
19914 &self,
19915 minimap_settings: MinimapSettings,
19916 window: &mut Window,
19917 cx: &mut Context<Self>,
19918 ) -> Entity<Self> {
19919 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19920
19921 let mut minimap = Editor::new_internal(
19922 EditorMode::Minimap {
19923 parent: cx.weak_entity(),
19924 },
19925 self.buffer.clone(),
19926 None,
19927 Some(self.display_map.clone()),
19928 window,
19929 cx,
19930 );
19931 minimap.scroll_manager.clone_state(&self.scroll_manager);
19932 minimap.set_text_style_refinement(TextStyleRefinement {
19933 font_size: Some(MINIMAP_FONT_SIZE),
19934 font_weight: Some(MINIMAP_FONT_WEIGHT),
19935 ..Default::default()
19936 });
19937 minimap.update_minimap_configuration(minimap_settings, cx);
19938 cx.new(|_| minimap)
19939 }
19940
19941 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19942 let current_line_highlight = minimap_settings
19943 .current_line_highlight
19944 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19945 self.set_current_line_highlight(Some(current_line_highlight));
19946 }
19947
19948 pub fn minimap(&self) -> Option<&Entity<Self>> {
19949 self.minimap
19950 .as_ref()
19951 .filter(|_| self.minimap_visibility.visible())
19952 }
19953
19954 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19955 let mut wrap_guides = smallvec![];
19956
19957 if self.show_wrap_guides == Some(false) {
19958 return wrap_guides;
19959 }
19960
19961 let settings = self.buffer.read(cx).language_settings(cx);
19962 if settings.show_wrap_guides {
19963 match self.soft_wrap_mode(cx) {
19964 SoftWrap::Column(soft_wrap) => {
19965 wrap_guides.push((soft_wrap as usize, true));
19966 }
19967 SoftWrap::Bounded(soft_wrap) => {
19968 wrap_guides.push((soft_wrap as usize, true));
19969 }
19970 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19971 }
19972 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19973 }
19974
19975 wrap_guides
19976 }
19977
19978 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19979 let settings = self.buffer.read(cx).language_settings(cx);
19980 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19981 match mode {
19982 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19983 SoftWrap::None
19984 }
19985 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19986 language_settings::SoftWrap::PreferredLineLength => {
19987 SoftWrap::Column(settings.preferred_line_length)
19988 }
19989 language_settings::SoftWrap::Bounded => {
19990 SoftWrap::Bounded(settings.preferred_line_length)
19991 }
19992 }
19993 }
19994
19995 pub fn set_soft_wrap_mode(
19996 &mut self,
19997 mode: language_settings::SoftWrap,
19998
19999 cx: &mut Context<Self>,
20000 ) {
20001 self.soft_wrap_mode_override = Some(mode);
20002 cx.notify();
20003 }
20004
20005 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20006 self.hard_wrap = hard_wrap;
20007 cx.notify();
20008 }
20009
20010 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20011 self.text_style_refinement = Some(style);
20012 }
20013
20014 /// called by the Element so we know what style we were most recently rendered with.
20015 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20016 // We intentionally do not inform the display map about the minimap style
20017 // so that wrapping is not recalculated and stays consistent for the editor
20018 // and its linked minimap.
20019 if !self.mode.is_minimap() {
20020 let font = style.text.font();
20021 let font_size = style.text.font_size.to_pixels(window.rem_size());
20022 let display_map = self
20023 .placeholder_display_map
20024 .as_ref()
20025 .filter(|_| self.is_empty(cx))
20026 .unwrap_or(&self.display_map);
20027
20028 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20029 }
20030 self.style = Some(style);
20031 }
20032
20033 pub fn style(&self) -> Option<&EditorStyle> {
20034 self.style.as_ref()
20035 }
20036
20037 // Called by the element. This method is not designed to be called outside of the editor
20038 // element's layout code because it does not notify when rewrapping is computed synchronously.
20039 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20040 if self.is_empty(cx) {
20041 self.placeholder_display_map
20042 .as_ref()
20043 .map_or(false, |display_map| {
20044 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20045 })
20046 } else {
20047 self.display_map
20048 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20049 }
20050 }
20051
20052 pub fn set_soft_wrap(&mut self) {
20053 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20054 }
20055
20056 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20057 if self.soft_wrap_mode_override.is_some() {
20058 self.soft_wrap_mode_override.take();
20059 } else {
20060 let soft_wrap = match self.soft_wrap_mode(cx) {
20061 SoftWrap::GitDiff => return,
20062 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20063 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20064 language_settings::SoftWrap::None
20065 }
20066 };
20067 self.soft_wrap_mode_override = Some(soft_wrap);
20068 }
20069 cx.notify();
20070 }
20071
20072 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20073 let Some(workspace) = self.workspace() else {
20074 return;
20075 };
20076 let fs = workspace.read(cx).app_state().fs.clone();
20077 let current_show = TabBarSettings::get_global(cx).show;
20078 update_settings_file(fs, cx, move |setting, _| {
20079 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20080 });
20081 }
20082
20083 pub fn toggle_indent_guides(
20084 &mut self,
20085 _: &ToggleIndentGuides,
20086 _: &mut Window,
20087 cx: &mut Context<Self>,
20088 ) {
20089 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20090 self.buffer
20091 .read(cx)
20092 .language_settings(cx)
20093 .indent_guides
20094 .enabled
20095 });
20096 self.show_indent_guides = Some(!currently_enabled);
20097 cx.notify();
20098 }
20099
20100 fn should_show_indent_guides(&self) -> Option<bool> {
20101 self.show_indent_guides
20102 }
20103
20104 pub fn disable_indent_guides_for_buffer(
20105 &mut self,
20106 buffer_id: BufferId,
20107 cx: &mut Context<Self>,
20108 ) {
20109 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20110 cx.notify();
20111 }
20112
20113 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20114 self.buffers_with_disabled_indent_guides
20115 .contains(&buffer_id)
20116 }
20117
20118 pub fn toggle_line_numbers(
20119 &mut self,
20120 _: &ToggleLineNumbers,
20121 _: &mut Window,
20122 cx: &mut Context<Self>,
20123 ) {
20124 let mut editor_settings = EditorSettings::get_global(cx).clone();
20125 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20126 EditorSettings::override_global(editor_settings, cx);
20127 }
20128
20129 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20130 if let Some(show_line_numbers) = self.show_line_numbers {
20131 return show_line_numbers;
20132 }
20133 EditorSettings::get_global(cx).gutter.line_numbers
20134 }
20135
20136 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20137 match (
20138 self.use_relative_line_numbers,
20139 EditorSettings::get_global(cx).relative_line_numbers,
20140 ) {
20141 (None, setting) => setting,
20142 (Some(false), _) => RelativeLineNumbers::Disabled,
20143 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20144 (Some(true), _) => RelativeLineNumbers::Enabled,
20145 }
20146 }
20147
20148 pub fn toggle_relative_line_numbers(
20149 &mut self,
20150 _: &ToggleRelativeLineNumbers,
20151 _: &mut Window,
20152 cx: &mut Context<Self>,
20153 ) {
20154 let is_relative = self.relative_line_numbers(cx);
20155 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20156 }
20157
20158 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20159 self.use_relative_line_numbers = is_relative;
20160 cx.notify();
20161 }
20162
20163 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20164 self.show_gutter = show_gutter;
20165 cx.notify();
20166 }
20167
20168 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20169 self.show_scrollbars = ScrollbarAxes {
20170 horizontal: show,
20171 vertical: show,
20172 };
20173 cx.notify();
20174 }
20175
20176 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20177 self.show_scrollbars.vertical = show;
20178 cx.notify();
20179 }
20180
20181 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20182 self.show_scrollbars.horizontal = show;
20183 cx.notify();
20184 }
20185
20186 pub fn set_minimap_visibility(
20187 &mut self,
20188 minimap_visibility: MinimapVisibility,
20189 window: &mut Window,
20190 cx: &mut Context<Self>,
20191 ) {
20192 if self.minimap_visibility != minimap_visibility {
20193 if minimap_visibility.visible() && self.minimap.is_none() {
20194 let minimap_settings = EditorSettings::get_global(cx).minimap;
20195 self.minimap =
20196 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20197 }
20198 self.minimap_visibility = minimap_visibility;
20199 cx.notify();
20200 }
20201 }
20202
20203 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20204 self.set_show_scrollbars(false, cx);
20205 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20206 }
20207
20208 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20209 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20210 }
20211
20212 /// Normally the text in full mode and auto height editors is padded on the
20213 /// left side by roughly half a character width for improved hit testing.
20214 ///
20215 /// Use this method to disable this for cases where this is not wanted (e.g.
20216 /// if you want to align the editor text with some other text above or below)
20217 /// or if you want to add this padding to single-line editors.
20218 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20219 self.offset_content = offset_content;
20220 cx.notify();
20221 }
20222
20223 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20224 self.show_line_numbers = Some(show_line_numbers);
20225 cx.notify();
20226 }
20227
20228 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20229 self.disable_expand_excerpt_buttons = true;
20230 cx.notify();
20231 }
20232
20233 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20234 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20235 cx.notify();
20236 }
20237
20238 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20239 self.show_code_actions = Some(show_code_actions);
20240 cx.notify();
20241 }
20242
20243 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20244 self.show_runnables = Some(show_runnables);
20245 cx.notify();
20246 }
20247
20248 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20249 self.show_breakpoints = Some(show_breakpoints);
20250 cx.notify();
20251 }
20252
20253 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20254 if self.display_map.read(cx).masked != masked {
20255 self.display_map.update(cx, |map, _| map.masked = masked);
20256 }
20257 cx.notify()
20258 }
20259
20260 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20261 self.show_wrap_guides = Some(show_wrap_guides);
20262 cx.notify();
20263 }
20264
20265 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20266 self.show_indent_guides = Some(show_indent_guides);
20267 cx.notify();
20268 }
20269
20270 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20271 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20272 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20273 && let Some(dir) = file.abs_path(cx).parent()
20274 {
20275 return Some(dir.to_owned());
20276 }
20277 }
20278
20279 None
20280 }
20281
20282 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20283 self.active_excerpt(cx)?
20284 .1
20285 .read(cx)
20286 .file()
20287 .and_then(|f| f.as_local())
20288 }
20289
20290 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20291 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20292 let buffer = buffer.read(cx);
20293 if let Some(project_path) = buffer.project_path(cx) {
20294 let project = self.project()?.read(cx);
20295 project.absolute_path(&project_path, cx)
20296 } else {
20297 buffer
20298 .file()
20299 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20300 }
20301 })
20302 }
20303
20304 pub fn reveal_in_finder(
20305 &mut self,
20306 _: &RevealInFileManager,
20307 _window: &mut Window,
20308 cx: &mut Context<Self>,
20309 ) {
20310 if let Some(target) = self.target_file(cx) {
20311 cx.reveal_path(&target.abs_path(cx));
20312 }
20313 }
20314
20315 pub fn copy_path(
20316 &mut self,
20317 _: &zed_actions::workspace::CopyPath,
20318 _window: &mut Window,
20319 cx: &mut Context<Self>,
20320 ) {
20321 if let Some(path) = self.target_file_abs_path(cx)
20322 && let Some(path) = path.to_str()
20323 {
20324 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20325 } else {
20326 cx.propagate();
20327 }
20328 }
20329
20330 pub fn copy_relative_path(
20331 &mut self,
20332 _: &zed_actions::workspace::CopyRelativePath,
20333 _window: &mut Window,
20334 cx: &mut Context<Self>,
20335 ) {
20336 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20337 let project = self.project()?.read(cx);
20338 let path = buffer.read(cx).file()?.path();
20339 let path = path.display(project.path_style(cx));
20340 Some(path)
20341 }) {
20342 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20343 } else {
20344 cx.propagate();
20345 }
20346 }
20347
20348 /// Returns the project path for the editor's buffer, if any buffer is
20349 /// opened in the editor.
20350 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20351 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20352 buffer.read(cx).project_path(cx)
20353 } else {
20354 None
20355 }
20356 }
20357
20358 // Returns true if the editor handled a go-to-line request
20359 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20360 maybe!({
20361 let breakpoint_store = self.breakpoint_store.as_ref()?;
20362
20363 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20364 else {
20365 self.clear_row_highlights::<ActiveDebugLine>();
20366 return None;
20367 };
20368
20369 let position = active_stack_frame.position;
20370 let buffer_id = position.buffer_id?;
20371 let snapshot = self
20372 .project
20373 .as_ref()?
20374 .read(cx)
20375 .buffer_for_id(buffer_id, cx)?
20376 .read(cx)
20377 .snapshot();
20378
20379 let mut handled = false;
20380 for (id, ExcerptRange { context, .. }) in
20381 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20382 {
20383 if context.start.cmp(&position, &snapshot).is_ge()
20384 || context.end.cmp(&position, &snapshot).is_lt()
20385 {
20386 continue;
20387 }
20388 let snapshot = self.buffer.read(cx).snapshot(cx);
20389 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20390
20391 handled = true;
20392 self.clear_row_highlights::<ActiveDebugLine>();
20393
20394 self.go_to_line::<ActiveDebugLine>(
20395 multibuffer_anchor,
20396 Some(cx.theme().colors().editor_debugger_active_line_background),
20397 window,
20398 cx,
20399 );
20400
20401 cx.notify();
20402 }
20403
20404 handled.then_some(())
20405 })
20406 .is_some()
20407 }
20408
20409 pub fn copy_file_name_without_extension(
20410 &mut self,
20411 _: &CopyFileNameWithoutExtension,
20412 _: &mut Window,
20413 cx: &mut Context<Self>,
20414 ) {
20415 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20416 let file = buffer.read(cx).file()?;
20417 file.path().file_stem()
20418 }) {
20419 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20420 }
20421 }
20422
20423 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20424 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20425 let file = buffer.read(cx).file()?;
20426 Some(file.file_name(cx))
20427 }) {
20428 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20429 }
20430 }
20431
20432 pub fn toggle_git_blame(
20433 &mut self,
20434 _: &::git::Blame,
20435 window: &mut Window,
20436 cx: &mut Context<Self>,
20437 ) {
20438 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20439
20440 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20441 self.start_git_blame(true, window, cx);
20442 }
20443
20444 cx.notify();
20445 }
20446
20447 pub fn toggle_git_blame_inline(
20448 &mut self,
20449 _: &ToggleGitBlameInline,
20450 window: &mut Window,
20451 cx: &mut Context<Self>,
20452 ) {
20453 self.toggle_git_blame_inline_internal(true, window, cx);
20454 cx.notify();
20455 }
20456
20457 pub fn open_git_blame_commit(
20458 &mut self,
20459 _: &OpenGitBlameCommit,
20460 window: &mut Window,
20461 cx: &mut Context<Self>,
20462 ) {
20463 self.open_git_blame_commit_internal(window, cx);
20464 }
20465
20466 fn open_git_blame_commit_internal(
20467 &mut self,
20468 window: &mut Window,
20469 cx: &mut Context<Self>,
20470 ) -> Option<()> {
20471 let blame = self.blame.as_ref()?;
20472 let snapshot = self.snapshot(window, cx);
20473 let cursor = self
20474 .selections
20475 .newest::<Point>(&snapshot.display_snapshot)
20476 .head();
20477 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20478 let (_, blame_entry) = blame
20479 .update(cx, |blame, cx| {
20480 blame
20481 .blame_for_rows(
20482 &[RowInfo {
20483 buffer_id: Some(buffer.remote_id()),
20484 buffer_row: Some(point.row),
20485 ..Default::default()
20486 }],
20487 cx,
20488 )
20489 .next()
20490 })
20491 .flatten()?;
20492 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20493 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20494 let workspace = self.workspace()?.downgrade();
20495 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20496 None
20497 }
20498
20499 pub fn git_blame_inline_enabled(&self) -> bool {
20500 self.git_blame_inline_enabled
20501 }
20502
20503 pub fn toggle_selection_menu(
20504 &mut self,
20505 _: &ToggleSelectionMenu,
20506 _: &mut Window,
20507 cx: &mut Context<Self>,
20508 ) {
20509 self.show_selection_menu = self
20510 .show_selection_menu
20511 .map(|show_selections_menu| !show_selections_menu)
20512 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20513
20514 cx.notify();
20515 }
20516
20517 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20518 self.show_selection_menu
20519 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20520 }
20521
20522 fn start_git_blame(
20523 &mut self,
20524 user_triggered: bool,
20525 window: &mut Window,
20526 cx: &mut Context<Self>,
20527 ) {
20528 if let Some(project) = self.project() {
20529 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20530 && buffer.read(cx).file().is_none()
20531 {
20532 return;
20533 }
20534
20535 let focused = self.focus_handle(cx).contains_focused(window, cx);
20536
20537 let project = project.clone();
20538 let blame = cx
20539 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20540 self.blame_subscription =
20541 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20542 self.blame = Some(blame);
20543 }
20544 }
20545
20546 fn toggle_git_blame_inline_internal(
20547 &mut self,
20548 user_triggered: bool,
20549 window: &mut Window,
20550 cx: &mut Context<Self>,
20551 ) {
20552 if self.git_blame_inline_enabled {
20553 self.git_blame_inline_enabled = false;
20554 self.show_git_blame_inline = false;
20555 self.show_git_blame_inline_delay_task.take();
20556 } else {
20557 self.git_blame_inline_enabled = true;
20558 self.start_git_blame_inline(user_triggered, window, cx);
20559 }
20560
20561 cx.notify();
20562 }
20563
20564 fn start_git_blame_inline(
20565 &mut self,
20566 user_triggered: bool,
20567 window: &mut Window,
20568 cx: &mut Context<Self>,
20569 ) {
20570 self.start_git_blame(user_triggered, window, cx);
20571
20572 if ProjectSettings::get_global(cx)
20573 .git
20574 .inline_blame_delay()
20575 .is_some()
20576 {
20577 self.start_inline_blame_timer(window, cx);
20578 } else {
20579 self.show_git_blame_inline = true
20580 }
20581 }
20582
20583 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20584 self.blame.as_ref()
20585 }
20586
20587 pub fn show_git_blame_gutter(&self) -> bool {
20588 self.show_git_blame_gutter
20589 }
20590
20591 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20592 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20593 }
20594
20595 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20596 self.show_git_blame_inline
20597 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20598 && !self.newest_selection_head_on_empty_line(cx)
20599 && self.has_blame_entries(cx)
20600 }
20601
20602 fn has_blame_entries(&self, cx: &App) -> bool {
20603 self.blame()
20604 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20605 }
20606
20607 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20608 let cursor_anchor = self.selections.newest_anchor().head();
20609
20610 let snapshot = self.buffer.read(cx).snapshot(cx);
20611 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20612
20613 snapshot.line_len(buffer_row) == 0
20614 }
20615
20616 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20617 let buffer_and_selection = maybe!({
20618 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20619 let selection_range = selection.range();
20620
20621 let multi_buffer = self.buffer().read(cx);
20622 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20623 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20624
20625 let (buffer, range, _) = if selection.reversed {
20626 buffer_ranges.first()
20627 } else {
20628 buffer_ranges.last()
20629 }?;
20630
20631 let selection = text::ToPoint::to_point(&range.start, buffer).row
20632 ..text::ToPoint::to_point(&range.end, buffer).row;
20633 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20634 });
20635
20636 let Some((buffer, selection)) = buffer_and_selection else {
20637 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20638 };
20639
20640 let Some(project) = self.project() else {
20641 return Task::ready(Err(anyhow!("editor does not have project")));
20642 };
20643
20644 project.update(cx, |project, cx| {
20645 project.get_permalink_to_line(&buffer, selection, cx)
20646 })
20647 }
20648
20649 pub fn copy_permalink_to_line(
20650 &mut self,
20651 _: &CopyPermalinkToLine,
20652 window: &mut Window,
20653 cx: &mut Context<Self>,
20654 ) {
20655 let permalink_task = self.get_permalink_to_line(cx);
20656 let workspace = self.workspace();
20657
20658 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20659 Ok(permalink) => {
20660 cx.update(|_, cx| {
20661 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20662 })
20663 .ok();
20664 }
20665 Err(err) => {
20666 let message = format!("Failed to copy permalink: {err}");
20667
20668 anyhow::Result::<()>::Err(err).log_err();
20669
20670 if let Some(workspace) = workspace {
20671 workspace
20672 .update_in(cx, |workspace, _, cx| {
20673 struct CopyPermalinkToLine;
20674
20675 workspace.show_toast(
20676 Toast::new(
20677 NotificationId::unique::<CopyPermalinkToLine>(),
20678 message,
20679 ),
20680 cx,
20681 )
20682 })
20683 .ok();
20684 }
20685 }
20686 })
20687 .detach();
20688 }
20689
20690 pub fn copy_file_location(
20691 &mut self,
20692 _: &CopyFileLocation,
20693 _: &mut Window,
20694 cx: &mut Context<Self>,
20695 ) {
20696 let selection = self
20697 .selections
20698 .newest::<Point>(&self.display_snapshot(cx))
20699 .start
20700 .row
20701 + 1;
20702 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20703 let project = self.project()?.read(cx);
20704 let file = buffer.read(cx).file()?;
20705 let path = file.path().display(project.path_style(cx));
20706
20707 Some(format!("{path}:{selection}"))
20708 }) {
20709 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20710 }
20711 }
20712
20713 pub fn open_permalink_to_line(
20714 &mut self,
20715 _: &OpenPermalinkToLine,
20716 window: &mut Window,
20717 cx: &mut Context<Self>,
20718 ) {
20719 let permalink_task = self.get_permalink_to_line(cx);
20720 let workspace = self.workspace();
20721
20722 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20723 Ok(permalink) => {
20724 cx.update(|_, cx| {
20725 cx.open_url(permalink.as_ref());
20726 })
20727 .ok();
20728 }
20729 Err(err) => {
20730 let message = format!("Failed to open permalink: {err}");
20731
20732 anyhow::Result::<()>::Err(err).log_err();
20733
20734 if let Some(workspace) = workspace {
20735 workspace
20736 .update(cx, |workspace, cx| {
20737 struct OpenPermalinkToLine;
20738
20739 workspace.show_toast(
20740 Toast::new(
20741 NotificationId::unique::<OpenPermalinkToLine>(),
20742 message,
20743 ),
20744 cx,
20745 )
20746 })
20747 .ok();
20748 }
20749 }
20750 })
20751 .detach();
20752 }
20753
20754 pub fn insert_uuid_v4(
20755 &mut self,
20756 _: &InsertUuidV4,
20757 window: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) {
20760 self.insert_uuid(UuidVersion::V4, window, cx);
20761 }
20762
20763 pub fn insert_uuid_v7(
20764 &mut self,
20765 _: &InsertUuidV7,
20766 window: &mut Window,
20767 cx: &mut Context<Self>,
20768 ) {
20769 self.insert_uuid(UuidVersion::V7, window, cx);
20770 }
20771
20772 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20773 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20774 self.transact(window, cx, |this, window, cx| {
20775 let edits = this
20776 .selections
20777 .all::<Point>(&this.display_snapshot(cx))
20778 .into_iter()
20779 .map(|selection| {
20780 let uuid = match version {
20781 UuidVersion::V4 => uuid::Uuid::new_v4(),
20782 UuidVersion::V7 => uuid::Uuid::now_v7(),
20783 };
20784
20785 (selection.range(), uuid.to_string())
20786 });
20787 this.edit(edits, cx);
20788 this.refresh_edit_prediction(true, false, window, cx);
20789 });
20790 }
20791
20792 pub fn open_selections_in_multibuffer(
20793 &mut self,
20794 _: &OpenSelectionsInMultibuffer,
20795 window: &mut Window,
20796 cx: &mut Context<Self>,
20797 ) {
20798 let multibuffer = self.buffer.read(cx);
20799
20800 let Some(buffer) = multibuffer.as_singleton() else {
20801 return;
20802 };
20803
20804 let Some(workspace) = self.workspace() else {
20805 return;
20806 };
20807
20808 let title = multibuffer.title(cx).to_string();
20809
20810 let locations = self
20811 .selections
20812 .all_anchors(&self.display_snapshot(cx))
20813 .iter()
20814 .map(|selection| {
20815 (
20816 buffer.clone(),
20817 (selection.start.text_anchor..selection.end.text_anchor)
20818 .to_point(buffer.read(cx)),
20819 )
20820 })
20821 .into_group_map();
20822
20823 cx.spawn_in(window, async move |_, cx| {
20824 workspace.update_in(cx, |workspace, window, cx| {
20825 Self::open_locations_in_multibuffer(
20826 workspace,
20827 locations,
20828 format!("Selections for '{title}'"),
20829 false,
20830 false,
20831 MultibufferSelectionMode::All,
20832 window,
20833 cx,
20834 );
20835 })
20836 })
20837 .detach();
20838 }
20839
20840 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20841 /// last highlight added will be used.
20842 ///
20843 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20844 pub fn highlight_rows<T: 'static>(
20845 &mut self,
20846 range: Range<Anchor>,
20847 color: Hsla,
20848 options: RowHighlightOptions,
20849 cx: &mut Context<Self>,
20850 ) {
20851 let snapshot = self.buffer().read(cx).snapshot(cx);
20852 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20853 let ix = row_highlights.binary_search_by(|highlight| {
20854 Ordering::Equal
20855 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20856 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20857 });
20858
20859 if let Err(mut ix) = ix {
20860 let index = post_inc(&mut self.highlight_order);
20861
20862 // If this range intersects with the preceding highlight, then merge it with
20863 // the preceding highlight. Otherwise insert a new highlight.
20864 let mut merged = false;
20865 if ix > 0 {
20866 let prev_highlight = &mut row_highlights[ix - 1];
20867 if prev_highlight
20868 .range
20869 .end
20870 .cmp(&range.start, &snapshot)
20871 .is_ge()
20872 {
20873 ix -= 1;
20874 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20875 prev_highlight.range.end = range.end;
20876 }
20877 merged = true;
20878 prev_highlight.index = index;
20879 prev_highlight.color = color;
20880 prev_highlight.options = options;
20881 }
20882 }
20883
20884 if !merged {
20885 row_highlights.insert(
20886 ix,
20887 RowHighlight {
20888 range,
20889 index,
20890 color,
20891 options,
20892 type_id: TypeId::of::<T>(),
20893 },
20894 );
20895 }
20896
20897 // If any of the following highlights intersect with this one, merge them.
20898 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20899 let highlight = &row_highlights[ix];
20900 if next_highlight
20901 .range
20902 .start
20903 .cmp(&highlight.range.end, &snapshot)
20904 .is_le()
20905 {
20906 if next_highlight
20907 .range
20908 .end
20909 .cmp(&highlight.range.end, &snapshot)
20910 .is_gt()
20911 {
20912 row_highlights[ix].range.end = next_highlight.range.end;
20913 }
20914 row_highlights.remove(ix + 1);
20915 } else {
20916 break;
20917 }
20918 }
20919 }
20920 }
20921
20922 /// Remove any highlighted row ranges of the given type that intersect the
20923 /// given ranges.
20924 pub fn remove_highlighted_rows<T: 'static>(
20925 &mut self,
20926 ranges_to_remove: Vec<Range<Anchor>>,
20927 cx: &mut Context<Self>,
20928 ) {
20929 let snapshot = self.buffer().read(cx).snapshot(cx);
20930 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20931 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20932 row_highlights.retain(|highlight| {
20933 while let Some(range_to_remove) = ranges_to_remove.peek() {
20934 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20935 Ordering::Less | Ordering::Equal => {
20936 ranges_to_remove.next();
20937 }
20938 Ordering::Greater => {
20939 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20940 Ordering::Less | Ordering::Equal => {
20941 return false;
20942 }
20943 Ordering::Greater => break,
20944 }
20945 }
20946 }
20947 }
20948
20949 true
20950 })
20951 }
20952
20953 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20954 pub fn clear_row_highlights<T: 'static>(&mut self) {
20955 self.highlighted_rows.remove(&TypeId::of::<T>());
20956 }
20957
20958 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20959 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20960 self.highlighted_rows
20961 .get(&TypeId::of::<T>())
20962 .map_or(&[] as &[_], |vec| vec.as_slice())
20963 .iter()
20964 .map(|highlight| (highlight.range.clone(), highlight.color))
20965 }
20966
20967 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20968 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20969 /// Allows to ignore certain kinds of highlights.
20970 pub fn highlighted_display_rows(
20971 &self,
20972 window: &mut Window,
20973 cx: &mut App,
20974 ) -> BTreeMap<DisplayRow, LineHighlight> {
20975 let snapshot = self.snapshot(window, cx);
20976 let mut used_highlight_orders = HashMap::default();
20977 self.highlighted_rows
20978 .iter()
20979 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20980 .fold(
20981 BTreeMap::<DisplayRow, LineHighlight>::new(),
20982 |mut unique_rows, highlight| {
20983 let start = highlight.range.start.to_display_point(&snapshot);
20984 let end = highlight.range.end.to_display_point(&snapshot);
20985 let start_row = start.row().0;
20986 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20987 {
20988 end.row().0.saturating_sub(1)
20989 } else {
20990 end.row().0
20991 };
20992 for row in start_row..=end_row {
20993 let used_index =
20994 used_highlight_orders.entry(row).or_insert(highlight.index);
20995 if highlight.index >= *used_index {
20996 *used_index = highlight.index;
20997 unique_rows.insert(
20998 DisplayRow(row),
20999 LineHighlight {
21000 include_gutter: highlight.options.include_gutter,
21001 border: None,
21002 background: highlight.color.into(),
21003 type_id: Some(highlight.type_id),
21004 },
21005 );
21006 }
21007 }
21008 unique_rows
21009 },
21010 )
21011 }
21012
21013 pub fn highlighted_display_row_for_autoscroll(
21014 &self,
21015 snapshot: &DisplaySnapshot,
21016 ) -> Option<DisplayRow> {
21017 self.highlighted_rows
21018 .values()
21019 .flat_map(|highlighted_rows| highlighted_rows.iter())
21020 .filter_map(|highlight| {
21021 if highlight.options.autoscroll {
21022 Some(highlight.range.start.to_display_point(snapshot).row())
21023 } else {
21024 None
21025 }
21026 })
21027 .min()
21028 }
21029
21030 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21031 self.highlight_background::<SearchWithinRange>(
21032 ranges,
21033 |_, colors| colors.colors().editor_document_highlight_read_background,
21034 cx,
21035 )
21036 }
21037
21038 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21039 self.breadcrumb_header = Some(new_header);
21040 }
21041
21042 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21043 self.clear_background_highlights::<SearchWithinRange>(cx);
21044 }
21045
21046 pub fn highlight_background<T: 'static>(
21047 &mut self,
21048 ranges: &[Range<Anchor>],
21049 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21050 cx: &mut Context<Self>,
21051 ) {
21052 self.background_highlights.insert(
21053 HighlightKey::Type(TypeId::of::<T>()),
21054 (Arc::new(color_fetcher), Arc::from(ranges)),
21055 );
21056 self.scrollbar_marker_state.dirty = true;
21057 cx.notify();
21058 }
21059
21060 pub fn highlight_background_key<T: 'static>(
21061 &mut self,
21062 key: usize,
21063 ranges: &[Range<Anchor>],
21064 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21065 cx: &mut Context<Self>,
21066 ) {
21067 self.background_highlights.insert(
21068 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21069 (Arc::new(color_fetcher), Arc::from(ranges)),
21070 );
21071 self.scrollbar_marker_state.dirty = true;
21072 cx.notify();
21073 }
21074
21075 pub fn clear_background_highlights<T: 'static>(
21076 &mut self,
21077 cx: &mut Context<Self>,
21078 ) -> Option<BackgroundHighlight> {
21079 let text_highlights = self
21080 .background_highlights
21081 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21082 if !text_highlights.1.is_empty() {
21083 self.scrollbar_marker_state.dirty = true;
21084 cx.notify();
21085 }
21086 Some(text_highlights)
21087 }
21088
21089 pub fn highlight_gutter<T: 'static>(
21090 &mut self,
21091 ranges: impl Into<Vec<Range<Anchor>>>,
21092 color_fetcher: fn(&App) -> Hsla,
21093 cx: &mut Context<Self>,
21094 ) {
21095 self.gutter_highlights
21096 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21097 cx.notify();
21098 }
21099
21100 pub fn clear_gutter_highlights<T: 'static>(
21101 &mut self,
21102 cx: &mut Context<Self>,
21103 ) -> Option<GutterHighlight> {
21104 cx.notify();
21105 self.gutter_highlights.remove(&TypeId::of::<T>())
21106 }
21107
21108 pub fn insert_gutter_highlight<T: 'static>(
21109 &mut self,
21110 range: Range<Anchor>,
21111 color_fetcher: fn(&App) -> Hsla,
21112 cx: &mut Context<Self>,
21113 ) {
21114 let snapshot = self.buffer().read(cx).snapshot(cx);
21115 let mut highlights = self
21116 .gutter_highlights
21117 .remove(&TypeId::of::<T>())
21118 .map(|(_, highlights)| highlights)
21119 .unwrap_or_default();
21120 let ix = highlights.binary_search_by(|highlight| {
21121 Ordering::Equal
21122 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21123 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21124 });
21125 if let Err(ix) = ix {
21126 highlights.insert(ix, range);
21127 }
21128 self.gutter_highlights
21129 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21130 }
21131
21132 pub fn remove_gutter_highlights<T: 'static>(
21133 &mut self,
21134 ranges_to_remove: Vec<Range<Anchor>>,
21135 cx: &mut Context<Self>,
21136 ) {
21137 let snapshot = self.buffer().read(cx).snapshot(cx);
21138 let Some((color_fetcher, mut gutter_highlights)) =
21139 self.gutter_highlights.remove(&TypeId::of::<T>())
21140 else {
21141 return;
21142 };
21143 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21144 gutter_highlights.retain(|highlight| {
21145 while let Some(range_to_remove) = ranges_to_remove.peek() {
21146 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21147 Ordering::Less | Ordering::Equal => {
21148 ranges_to_remove.next();
21149 }
21150 Ordering::Greater => {
21151 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21152 Ordering::Less | Ordering::Equal => {
21153 return false;
21154 }
21155 Ordering::Greater => break,
21156 }
21157 }
21158 }
21159 }
21160
21161 true
21162 });
21163 self.gutter_highlights
21164 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21165 }
21166
21167 #[cfg(feature = "test-support")]
21168 pub fn all_text_highlights(
21169 &self,
21170 window: &mut Window,
21171 cx: &mut Context<Self>,
21172 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21173 let snapshot = self.snapshot(window, cx);
21174 self.display_map.update(cx, |display_map, _| {
21175 display_map
21176 .all_text_highlights()
21177 .map(|highlight| {
21178 let (style, ranges) = highlight.as_ref();
21179 (
21180 *style,
21181 ranges
21182 .iter()
21183 .map(|range| range.clone().to_display_points(&snapshot))
21184 .collect(),
21185 )
21186 })
21187 .collect()
21188 })
21189 }
21190
21191 #[cfg(feature = "test-support")]
21192 pub fn all_text_background_highlights(
21193 &self,
21194 window: &mut Window,
21195 cx: &mut Context<Self>,
21196 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21197 let snapshot = self.snapshot(window, cx);
21198 let buffer = &snapshot.buffer_snapshot();
21199 let start = buffer.anchor_before(MultiBufferOffset(0));
21200 let end = buffer.anchor_after(buffer.len());
21201 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21202 }
21203
21204 #[cfg(any(test, feature = "test-support"))]
21205 pub fn sorted_background_highlights_in_range(
21206 &self,
21207 search_range: Range<Anchor>,
21208 display_snapshot: &DisplaySnapshot,
21209 theme: &Theme,
21210 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21211 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21212 res.sort_by(|a, b| {
21213 a.0.start
21214 .cmp(&b.0.start)
21215 .then_with(|| a.0.end.cmp(&b.0.end))
21216 .then_with(|| a.1.cmp(&b.1))
21217 });
21218 res
21219 }
21220
21221 #[cfg(feature = "test-support")]
21222 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21223 let snapshot = self.buffer().read(cx).snapshot(cx);
21224
21225 let highlights = self
21226 .background_highlights
21227 .get(&HighlightKey::Type(TypeId::of::<
21228 items::BufferSearchHighlights,
21229 >()));
21230
21231 if let Some((_color, ranges)) = highlights {
21232 ranges
21233 .iter()
21234 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21235 .collect_vec()
21236 } else {
21237 vec![]
21238 }
21239 }
21240
21241 fn document_highlights_for_position<'a>(
21242 &'a self,
21243 position: Anchor,
21244 buffer: &'a MultiBufferSnapshot,
21245 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21246 let read_highlights = self
21247 .background_highlights
21248 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21249 .map(|h| &h.1);
21250 let write_highlights = self
21251 .background_highlights
21252 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21253 .map(|h| &h.1);
21254 let left_position = position.bias_left(buffer);
21255 let right_position = position.bias_right(buffer);
21256 read_highlights
21257 .into_iter()
21258 .chain(write_highlights)
21259 .flat_map(move |ranges| {
21260 let start_ix = match ranges.binary_search_by(|probe| {
21261 let cmp = probe.end.cmp(&left_position, buffer);
21262 if cmp.is_ge() {
21263 Ordering::Greater
21264 } else {
21265 Ordering::Less
21266 }
21267 }) {
21268 Ok(i) | Err(i) => i,
21269 };
21270
21271 ranges[start_ix..]
21272 .iter()
21273 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21274 })
21275 }
21276
21277 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21278 self.background_highlights
21279 .get(&HighlightKey::Type(TypeId::of::<T>()))
21280 .is_some_and(|(_, highlights)| !highlights.is_empty())
21281 }
21282
21283 /// Returns all background highlights for a given range.
21284 ///
21285 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21286 pub fn background_highlights_in_range(
21287 &self,
21288 search_range: Range<Anchor>,
21289 display_snapshot: &DisplaySnapshot,
21290 theme: &Theme,
21291 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21292 let mut results = Vec::new();
21293 for (color_fetcher, ranges) in self.background_highlights.values() {
21294 let start_ix = match ranges.binary_search_by(|probe| {
21295 let cmp = probe
21296 .end
21297 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21298 if cmp.is_gt() {
21299 Ordering::Greater
21300 } else {
21301 Ordering::Less
21302 }
21303 }) {
21304 Ok(i) | Err(i) => i,
21305 };
21306 for (index, range) in ranges[start_ix..].iter().enumerate() {
21307 if range
21308 .start
21309 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21310 .is_ge()
21311 {
21312 break;
21313 }
21314
21315 let color = color_fetcher(&(start_ix + index), theme);
21316 let start = range.start.to_display_point(display_snapshot);
21317 let end = range.end.to_display_point(display_snapshot);
21318 results.push((start..end, color))
21319 }
21320 }
21321 results
21322 }
21323
21324 pub fn gutter_highlights_in_range(
21325 &self,
21326 search_range: Range<Anchor>,
21327 display_snapshot: &DisplaySnapshot,
21328 cx: &App,
21329 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21330 let mut results = Vec::new();
21331 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21332 let color = color_fetcher(cx);
21333 let start_ix = match ranges.binary_search_by(|probe| {
21334 let cmp = probe
21335 .end
21336 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21337 if cmp.is_gt() {
21338 Ordering::Greater
21339 } else {
21340 Ordering::Less
21341 }
21342 }) {
21343 Ok(i) | Err(i) => i,
21344 };
21345 for range in &ranges[start_ix..] {
21346 if range
21347 .start
21348 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21349 .is_ge()
21350 {
21351 break;
21352 }
21353
21354 let start = range.start.to_display_point(display_snapshot);
21355 let end = range.end.to_display_point(display_snapshot);
21356 results.push((start..end, color))
21357 }
21358 }
21359 results
21360 }
21361
21362 /// Get the text ranges corresponding to the redaction query
21363 pub fn redacted_ranges(
21364 &self,
21365 search_range: Range<Anchor>,
21366 display_snapshot: &DisplaySnapshot,
21367 cx: &App,
21368 ) -> Vec<Range<DisplayPoint>> {
21369 display_snapshot
21370 .buffer_snapshot()
21371 .redacted_ranges(search_range, |file| {
21372 if let Some(file) = file {
21373 file.is_private()
21374 && EditorSettings::get(
21375 Some(SettingsLocation {
21376 worktree_id: file.worktree_id(cx),
21377 path: file.path().as_ref(),
21378 }),
21379 cx,
21380 )
21381 .redact_private_values
21382 } else {
21383 false
21384 }
21385 })
21386 .map(|range| {
21387 range.start.to_display_point(display_snapshot)
21388 ..range.end.to_display_point(display_snapshot)
21389 })
21390 .collect()
21391 }
21392
21393 pub fn highlight_text_key<T: 'static>(
21394 &mut self,
21395 key: usize,
21396 ranges: Vec<Range<Anchor>>,
21397 style: HighlightStyle,
21398 merge: bool,
21399 cx: &mut Context<Self>,
21400 ) {
21401 self.display_map.update(cx, |map, cx| {
21402 map.highlight_text(
21403 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21404 ranges,
21405 style,
21406 merge,
21407 cx,
21408 );
21409 });
21410 cx.notify();
21411 }
21412
21413 pub fn highlight_text<T: 'static>(
21414 &mut self,
21415 ranges: Vec<Range<Anchor>>,
21416 style: HighlightStyle,
21417 cx: &mut Context<Self>,
21418 ) {
21419 self.display_map.update(cx, |map, cx| {
21420 map.highlight_text(
21421 HighlightKey::Type(TypeId::of::<T>()),
21422 ranges,
21423 style,
21424 false,
21425 cx,
21426 )
21427 });
21428 cx.notify();
21429 }
21430
21431 pub fn text_highlights<'a, T: 'static>(
21432 &'a self,
21433 cx: &'a App,
21434 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21435 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21436 }
21437
21438 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21439 let cleared = self
21440 .display_map
21441 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21442 if cleared {
21443 cx.notify();
21444 }
21445 }
21446
21447 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21448 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21449 && self.focus_handle.is_focused(window)
21450 }
21451
21452 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21453 self.show_cursor_when_unfocused = is_enabled;
21454 cx.notify();
21455 }
21456
21457 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21458 cx.notify();
21459 }
21460
21461 fn on_debug_session_event(
21462 &mut self,
21463 _session: Entity<Session>,
21464 event: &SessionEvent,
21465 cx: &mut Context<Self>,
21466 ) {
21467 if let SessionEvent::InvalidateInlineValue = event {
21468 self.refresh_inline_values(cx);
21469 }
21470 }
21471
21472 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21473 let Some(project) = self.project.clone() else {
21474 return;
21475 };
21476
21477 if !self.inline_value_cache.enabled {
21478 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21479 self.splice_inlays(&inlays, Vec::new(), cx);
21480 return;
21481 }
21482
21483 let current_execution_position = self
21484 .highlighted_rows
21485 .get(&TypeId::of::<ActiveDebugLine>())
21486 .and_then(|lines| lines.last().map(|line| line.range.end));
21487
21488 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21489 let inline_values = editor
21490 .update(cx, |editor, cx| {
21491 let Some(current_execution_position) = current_execution_position else {
21492 return Some(Task::ready(Ok(Vec::new())));
21493 };
21494
21495 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21496 let snapshot = buffer.snapshot(cx);
21497
21498 let excerpt = snapshot.excerpt_containing(
21499 current_execution_position..current_execution_position,
21500 )?;
21501
21502 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21503 })?;
21504
21505 let range =
21506 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21507
21508 project.inline_values(buffer, range, cx)
21509 })
21510 .ok()
21511 .flatten()?
21512 .await
21513 .context("refreshing debugger inlays")
21514 .log_err()?;
21515
21516 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21517
21518 for (buffer_id, inline_value) in inline_values
21519 .into_iter()
21520 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21521 {
21522 buffer_inline_values
21523 .entry(buffer_id)
21524 .or_default()
21525 .push(inline_value);
21526 }
21527
21528 editor
21529 .update(cx, |editor, cx| {
21530 let snapshot = editor.buffer.read(cx).snapshot(cx);
21531 let mut new_inlays = Vec::default();
21532
21533 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21534 let buffer_id = buffer_snapshot.remote_id();
21535 buffer_inline_values
21536 .get(&buffer_id)
21537 .into_iter()
21538 .flatten()
21539 .for_each(|hint| {
21540 let inlay = Inlay::debugger(
21541 post_inc(&mut editor.next_inlay_id),
21542 Anchor::in_buffer(excerpt_id, hint.position),
21543 hint.text(),
21544 );
21545 if !inlay.text().chars().contains(&'\n') {
21546 new_inlays.push(inlay);
21547 }
21548 });
21549 }
21550
21551 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21552 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21553
21554 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21555 })
21556 .ok()?;
21557 Some(())
21558 });
21559 }
21560
21561 fn on_buffer_event(
21562 &mut self,
21563 multibuffer: &Entity<MultiBuffer>,
21564 event: &multi_buffer::Event,
21565 window: &mut Window,
21566 cx: &mut Context<Self>,
21567 ) {
21568 match event {
21569 multi_buffer::Event::Edited { edited_buffer } => {
21570 self.scrollbar_marker_state.dirty = true;
21571 self.active_indent_guides_state.dirty = true;
21572 self.refresh_active_diagnostics(cx);
21573 self.refresh_code_actions(window, cx);
21574 self.refresh_single_line_folds(window, cx);
21575 self.refresh_matching_bracket_highlights(window, cx);
21576 if self.has_active_edit_prediction() {
21577 self.update_visible_edit_prediction(window, cx);
21578 }
21579
21580 if let Some(buffer) = edited_buffer {
21581 if buffer.read(cx).file().is_none() {
21582 cx.emit(EditorEvent::TitleChanged);
21583 }
21584
21585 if self.project.is_some() {
21586 let buffer_id = buffer.read(cx).remote_id();
21587 self.register_buffer(buffer_id, cx);
21588 self.update_lsp_data(Some(buffer_id), window, cx);
21589 self.refresh_inlay_hints(
21590 InlayHintRefreshReason::BufferEdited(buffer_id),
21591 cx,
21592 );
21593 }
21594 }
21595
21596 cx.emit(EditorEvent::BufferEdited);
21597 cx.emit(SearchEvent::MatchesInvalidated);
21598
21599 let Some(project) = &self.project else { return };
21600 let (telemetry, is_via_ssh) = {
21601 let project = project.read(cx);
21602 let telemetry = project.client().telemetry().clone();
21603 let is_via_ssh = project.is_via_remote_server();
21604 (telemetry, is_via_ssh)
21605 };
21606 telemetry.log_edit_event("editor", is_via_ssh);
21607 }
21608 multi_buffer::Event::ExcerptsAdded {
21609 buffer,
21610 predecessor,
21611 excerpts,
21612 } => {
21613 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21614 let buffer_id = buffer.read(cx).remote_id();
21615 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21616 && let Some(project) = &self.project
21617 {
21618 update_uncommitted_diff_for_buffer(
21619 cx.entity(),
21620 project,
21621 [buffer.clone()],
21622 self.buffer.clone(),
21623 cx,
21624 )
21625 .detach();
21626 }
21627 self.update_lsp_data(Some(buffer_id), window, cx);
21628 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21629 self.colorize_brackets(false, cx);
21630 cx.emit(EditorEvent::ExcerptsAdded {
21631 buffer: buffer.clone(),
21632 predecessor: *predecessor,
21633 excerpts: excerpts.clone(),
21634 });
21635 }
21636 multi_buffer::Event::ExcerptsRemoved {
21637 ids,
21638 removed_buffer_ids,
21639 } => {
21640 if let Some(inlay_hints) = &mut self.inlay_hints {
21641 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21642 }
21643 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21644 for buffer_id in removed_buffer_ids {
21645 self.registered_buffers.remove(buffer_id);
21646 }
21647 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21648 cx.emit(EditorEvent::ExcerptsRemoved {
21649 ids: ids.clone(),
21650 removed_buffer_ids: removed_buffer_ids.clone(),
21651 });
21652 }
21653 multi_buffer::Event::ExcerptsEdited {
21654 excerpt_ids,
21655 buffer_ids,
21656 } => {
21657 self.display_map.update(cx, |map, cx| {
21658 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21659 });
21660 cx.emit(EditorEvent::ExcerptsEdited {
21661 ids: excerpt_ids.clone(),
21662 });
21663 }
21664 multi_buffer::Event::ExcerptsExpanded { ids } => {
21665 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21666 self.refresh_document_highlights(cx);
21667 for id in ids {
21668 self.fetched_tree_sitter_chunks.remove(id);
21669 }
21670 self.colorize_brackets(false, cx);
21671 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21672 }
21673 multi_buffer::Event::Reparsed(buffer_id) => {
21674 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21675 self.refresh_selected_text_highlights(true, window, cx);
21676 self.colorize_brackets(true, cx);
21677 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21678
21679 cx.emit(EditorEvent::Reparsed(*buffer_id));
21680 }
21681 multi_buffer::Event::DiffHunksToggled => {
21682 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21683 }
21684 multi_buffer::Event::LanguageChanged(buffer_id) => {
21685 self.registered_buffers.remove(&buffer_id);
21686 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21687 cx.emit(EditorEvent::Reparsed(*buffer_id));
21688 cx.notify();
21689 }
21690 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21691 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21692 multi_buffer::Event::FileHandleChanged
21693 | multi_buffer::Event::Reloaded
21694 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21695 multi_buffer::Event::DiagnosticsUpdated => {
21696 self.update_diagnostics_state(window, cx);
21697 }
21698 _ => {}
21699 };
21700 }
21701
21702 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21703 if !self.diagnostics_enabled() {
21704 return;
21705 }
21706 self.refresh_active_diagnostics(cx);
21707 self.refresh_inline_diagnostics(true, window, cx);
21708 self.scrollbar_marker_state.dirty = true;
21709 cx.notify();
21710 }
21711
21712 pub fn start_temporary_diff_override(&mut self) {
21713 self.load_diff_task.take();
21714 self.temporary_diff_override = true;
21715 }
21716
21717 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21718 self.temporary_diff_override = false;
21719 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21720 self.buffer.update(cx, |buffer, cx| {
21721 buffer.set_all_diff_hunks_collapsed(cx);
21722 });
21723
21724 if let Some(project) = self.project.clone() {
21725 self.load_diff_task = Some(
21726 update_uncommitted_diff_for_buffer(
21727 cx.entity(),
21728 &project,
21729 self.buffer.read(cx).all_buffers(),
21730 self.buffer.clone(),
21731 cx,
21732 )
21733 .shared(),
21734 );
21735 }
21736 }
21737
21738 fn on_display_map_changed(
21739 &mut self,
21740 _: Entity<DisplayMap>,
21741 _: &mut Window,
21742 cx: &mut Context<Self>,
21743 ) {
21744 cx.notify();
21745 }
21746
21747 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
21748 if !self.mode.is_full() {
21749 return None;
21750 }
21751
21752 let theme_settings = theme::ThemeSettings::get_global(cx);
21753 let theme = cx.theme();
21754 let accent_colors = theme.accents().clone();
21755
21756 let accent_overrides = theme_settings
21757 .theme_overrides
21758 .get(theme.name.as_ref())
21759 .map(|theme_style| &theme_style.accents)
21760 .into_iter()
21761 .flatten()
21762 .chain(
21763 theme_settings
21764 .experimental_theme_overrides
21765 .as_ref()
21766 .map(|overrides| &overrides.accents)
21767 .into_iter()
21768 .flatten(),
21769 )
21770 .flat_map(|accent| accent.0.clone())
21771 .collect();
21772
21773 Some(AccentData {
21774 colors: accent_colors,
21775 overrides: accent_overrides,
21776 })
21777 }
21778
21779 fn fetch_applicable_language_settings(
21780 &self,
21781 cx: &App,
21782 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21783 if !self.mode.is_full() {
21784 return HashMap::default();
21785 }
21786
21787 self.buffer().read(cx).all_buffers().into_iter().fold(
21788 HashMap::default(),
21789 |mut acc, buffer| {
21790 let buffer = buffer.read(cx);
21791 let language = buffer.language().map(|language| language.name());
21792 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21793 let file = buffer.file();
21794 v.insert(language_settings(language, file, cx).into_owned());
21795 }
21796 acc
21797 },
21798 )
21799 }
21800
21801 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21802 let new_language_settings = self.fetch_applicable_language_settings(cx);
21803 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21804 self.applicable_language_settings = new_language_settings;
21805
21806 let new_accents = self.fetch_accent_data(cx);
21807 let accents_changed = new_accents != self.accent_data;
21808 self.accent_data = new_accents;
21809
21810 if self.diagnostics_enabled() {
21811 let new_severity = EditorSettings::get_global(cx)
21812 .diagnostics_max_severity
21813 .unwrap_or(DiagnosticSeverity::Hint);
21814 self.set_max_diagnostics_severity(new_severity, cx);
21815 }
21816 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21817 self.update_edit_prediction_settings(cx);
21818 self.refresh_edit_prediction(true, false, window, cx);
21819 self.refresh_inline_values(cx);
21820 self.refresh_inlay_hints(
21821 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21822 self.selections.newest_anchor().head(),
21823 &self.buffer.read(cx).snapshot(cx),
21824 cx,
21825 )),
21826 cx,
21827 );
21828
21829 let old_cursor_shape = self.cursor_shape;
21830 let old_show_breadcrumbs = self.show_breadcrumbs;
21831
21832 {
21833 let editor_settings = EditorSettings::get_global(cx);
21834 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21835 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21836 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21837 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21838 }
21839
21840 if old_cursor_shape != self.cursor_shape {
21841 cx.emit(EditorEvent::CursorShapeChanged);
21842 }
21843
21844 if old_show_breadcrumbs != self.show_breadcrumbs {
21845 cx.emit(EditorEvent::BreadcrumbsChanged);
21846 }
21847
21848 let project_settings = ProjectSettings::get_global(cx);
21849 self.buffer_serialization = self
21850 .should_serialize_buffer()
21851 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21852
21853 if self.mode.is_full() {
21854 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21855 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21856 if self.show_inline_diagnostics != show_inline_diagnostics {
21857 self.show_inline_diagnostics = show_inline_diagnostics;
21858 self.refresh_inline_diagnostics(false, window, cx);
21859 }
21860
21861 if self.git_blame_inline_enabled != inline_blame_enabled {
21862 self.toggle_git_blame_inline_internal(false, window, cx);
21863 }
21864
21865 let minimap_settings = EditorSettings::get_global(cx).minimap;
21866 if self.minimap_visibility != MinimapVisibility::Disabled {
21867 if self.minimap_visibility.settings_visibility()
21868 != minimap_settings.minimap_enabled()
21869 {
21870 self.set_minimap_visibility(
21871 MinimapVisibility::for_mode(self.mode(), cx),
21872 window,
21873 cx,
21874 );
21875 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21876 minimap_entity.update(cx, |minimap_editor, cx| {
21877 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21878 })
21879 }
21880 }
21881
21882 if language_settings_changed || accents_changed {
21883 self.colorize_brackets(true, cx);
21884 }
21885
21886 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21887 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21888 }) {
21889 if !inlay_splice.is_empty() {
21890 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21891 }
21892 self.refresh_colors_for_visible_range(None, window, cx);
21893 }
21894 }
21895
21896 cx.notify();
21897 }
21898
21899 pub fn set_searchable(&mut self, searchable: bool) {
21900 self.searchable = searchable;
21901 }
21902
21903 pub fn searchable(&self) -> bool {
21904 self.searchable
21905 }
21906
21907 pub fn open_excerpts_in_split(
21908 &mut self,
21909 _: &OpenExcerptsSplit,
21910 window: &mut Window,
21911 cx: &mut Context<Self>,
21912 ) {
21913 self.open_excerpts_common(None, true, window, cx)
21914 }
21915
21916 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21917 self.open_excerpts_common(None, false, window, cx)
21918 }
21919
21920 fn open_excerpts_common(
21921 &mut self,
21922 jump_data: Option<JumpData>,
21923 split: bool,
21924 window: &mut Window,
21925 cx: &mut Context<Self>,
21926 ) {
21927 let Some(workspace) = self.workspace() else {
21928 cx.propagate();
21929 return;
21930 };
21931
21932 if self.buffer.read(cx).is_singleton() {
21933 cx.propagate();
21934 return;
21935 }
21936
21937 let mut new_selections_by_buffer = HashMap::default();
21938 match &jump_data {
21939 Some(JumpData::MultiBufferPoint {
21940 excerpt_id,
21941 position,
21942 anchor,
21943 line_offset_from_top,
21944 }) => {
21945 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21946 if let Some(buffer) = multi_buffer_snapshot
21947 .buffer_id_for_excerpt(*excerpt_id)
21948 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21949 {
21950 let buffer_snapshot = buffer.read(cx).snapshot();
21951 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21952 language::ToPoint::to_point(anchor, &buffer_snapshot)
21953 } else {
21954 buffer_snapshot.clip_point(*position, Bias::Left)
21955 };
21956 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21957 new_selections_by_buffer.insert(
21958 buffer,
21959 (
21960 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21961 Some(*line_offset_from_top),
21962 ),
21963 );
21964 }
21965 }
21966 Some(JumpData::MultiBufferRow {
21967 row,
21968 line_offset_from_top,
21969 }) => {
21970 let point = MultiBufferPoint::new(row.0, 0);
21971 if let Some((buffer, buffer_point, _)) =
21972 self.buffer.read(cx).point_to_buffer_point(point, cx)
21973 {
21974 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21975 new_selections_by_buffer
21976 .entry(buffer)
21977 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21978 .0
21979 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21980 }
21981 }
21982 None => {
21983 let selections = self
21984 .selections
21985 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21986 let multi_buffer = self.buffer.read(cx);
21987 for selection in selections {
21988 for (snapshot, range, _, anchor) in multi_buffer
21989 .snapshot(cx)
21990 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21991 {
21992 if let Some(anchor) = anchor {
21993 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21994 else {
21995 continue;
21996 };
21997 let offset = text::ToOffset::to_offset(
21998 &anchor.text_anchor,
21999 &buffer_handle.read(cx).snapshot(),
22000 );
22001 let range = BufferOffset(offset)..BufferOffset(offset);
22002 new_selections_by_buffer
22003 .entry(buffer_handle)
22004 .or_insert((Vec::new(), None))
22005 .0
22006 .push(range)
22007 } else {
22008 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22009 else {
22010 continue;
22011 };
22012 new_selections_by_buffer
22013 .entry(buffer_handle)
22014 .or_insert((Vec::new(), None))
22015 .0
22016 .push(range)
22017 }
22018 }
22019 }
22020 }
22021 }
22022
22023 new_selections_by_buffer
22024 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22025
22026 if new_selections_by_buffer.is_empty() {
22027 return;
22028 }
22029
22030 // We defer the pane interaction because we ourselves are a workspace item
22031 // and activating a new item causes the pane to call a method on us reentrantly,
22032 // which panics if we're on the stack.
22033 window.defer(cx, move |window, cx| {
22034 workspace.update(cx, |workspace, cx| {
22035 let pane = if split {
22036 workspace.adjacent_pane(window, cx)
22037 } else {
22038 workspace.active_pane().clone()
22039 };
22040
22041 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22042 let buffer_read = buffer.read(cx);
22043 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22044 (true, project::File::from_dyn(Some(file)).is_some())
22045 } else {
22046 (false, false)
22047 };
22048
22049 // If project file is none workspace.open_project_item will fail to open the excerpt
22050 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22051 // so we check if there's a tab match in that case first
22052 let editor = (!has_file || !is_project_file)
22053 .then(|| {
22054 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22055 // so `workspace.open_project_item` will never find them, always opening a new editor.
22056 // Instead, we try to activate the existing editor in the pane first.
22057 let (editor, pane_item_index, pane_item_id) =
22058 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22059 let editor = item.downcast::<Editor>()?;
22060 let singleton_buffer =
22061 editor.read(cx).buffer().read(cx).as_singleton()?;
22062 if singleton_buffer == buffer {
22063 Some((editor, i, item.item_id()))
22064 } else {
22065 None
22066 }
22067 })?;
22068 pane.update(cx, |pane, cx| {
22069 pane.activate_item(pane_item_index, true, true, window, cx);
22070 if !PreviewTabsSettings::get_global(cx)
22071 .enable_preview_from_multibuffer
22072 {
22073 pane.unpreview_item_if_preview(pane_item_id);
22074 }
22075 });
22076 Some(editor)
22077 })
22078 .flatten()
22079 .unwrap_or_else(|| {
22080 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22081 .enable_keep_preview_on_code_navigation;
22082 let allow_new_preview =
22083 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22084 workspace.open_project_item::<Self>(
22085 pane.clone(),
22086 buffer,
22087 true,
22088 true,
22089 keep_old_preview,
22090 allow_new_preview,
22091 window,
22092 cx,
22093 )
22094 });
22095
22096 editor.update(cx, |editor, cx| {
22097 if has_file && !is_project_file {
22098 editor.set_read_only(true);
22099 }
22100 let autoscroll = match scroll_offset {
22101 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22102 None => Autoscroll::newest(),
22103 };
22104 let nav_history = editor.nav_history.take();
22105 editor.change_selections(
22106 SelectionEffects::scroll(autoscroll),
22107 window,
22108 cx,
22109 |s| {
22110 s.select_ranges(ranges.into_iter().map(|range| {
22111 // we checked that the editor is a singleton editor so the offsets are valid
22112 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
22113 }));
22114 },
22115 );
22116 editor.nav_history = nav_history;
22117 });
22118 }
22119 })
22120 });
22121 }
22122
22123 // Allow opening excerpts for buffers that either belong to the current project
22124 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22125 // are also supported so tests and other in-memory views keep working.
22126 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22127 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22128 }
22129
22130 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22131 let snapshot = self.buffer.read(cx).read(cx);
22132 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22133 Some(
22134 ranges
22135 .iter()
22136 .map(move |range| {
22137 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22138 })
22139 .collect(),
22140 )
22141 }
22142
22143 fn selection_replacement_ranges(
22144 &self,
22145 range: Range<MultiBufferOffsetUtf16>,
22146 cx: &mut App,
22147 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22148 let selections = self
22149 .selections
22150 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22151 let newest_selection = selections
22152 .iter()
22153 .max_by_key(|selection| selection.id)
22154 .unwrap();
22155 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22156 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22157 let snapshot = self.buffer.read(cx).read(cx);
22158 selections
22159 .into_iter()
22160 .map(|mut selection| {
22161 selection.start.0.0 =
22162 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22163 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22164 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22165 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22166 })
22167 .collect()
22168 }
22169
22170 fn report_editor_event(
22171 &self,
22172 reported_event: ReportEditorEvent,
22173 file_extension: Option<String>,
22174 cx: &App,
22175 ) {
22176 if cfg!(any(test, feature = "test-support")) {
22177 return;
22178 }
22179
22180 let Some(project) = &self.project else { return };
22181
22182 // If None, we are in a file without an extension
22183 let file = self
22184 .buffer
22185 .read(cx)
22186 .as_singleton()
22187 .and_then(|b| b.read(cx).file());
22188 let file_extension = file_extension.or(file
22189 .as_ref()
22190 .and_then(|file| Path::new(file.file_name(cx)).extension())
22191 .and_then(|e| e.to_str())
22192 .map(|a| a.to_string()));
22193
22194 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22195 .map(|vim_mode| vim_mode.0)
22196 .unwrap_or(false);
22197
22198 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22199 let copilot_enabled = edit_predictions_provider
22200 == language::language_settings::EditPredictionProvider::Copilot;
22201 let copilot_enabled_for_language = self
22202 .buffer
22203 .read(cx)
22204 .language_settings(cx)
22205 .show_edit_predictions;
22206
22207 let project = project.read(cx);
22208 let event_type = reported_event.event_type();
22209
22210 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22211 telemetry::event!(
22212 event_type,
22213 type = if auto_saved {"autosave"} else {"manual"},
22214 file_extension,
22215 vim_mode,
22216 copilot_enabled,
22217 copilot_enabled_for_language,
22218 edit_predictions_provider,
22219 is_via_ssh = project.is_via_remote_server(),
22220 );
22221 } else {
22222 telemetry::event!(
22223 event_type,
22224 file_extension,
22225 vim_mode,
22226 copilot_enabled,
22227 copilot_enabled_for_language,
22228 edit_predictions_provider,
22229 is_via_ssh = project.is_via_remote_server(),
22230 );
22231 };
22232 }
22233
22234 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22235 /// with each line being an array of {text, highlight} objects.
22236 fn copy_highlight_json(
22237 &mut self,
22238 _: &CopyHighlightJson,
22239 window: &mut Window,
22240 cx: &mut Context<Self>,
22241 ) {
22242 #[derive(Serialize)]
22243 struct Chunk<'a> {
22244 text: String,
22245 highlight: Option<&'a str>,
22246 }
22247
22248 let snapshot = self.buffer.read(cx).snapshot(cx);
22249 let range = self
22250 .selected_text_range(false, window, cx)
22251 .and_then(|selection| {
22252 if selection.range.is_empty() {
22253 None
22254 } else {
22255 Some(
22256 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22257 selection.range.start,
22258 )))
22259 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22260 selection.range.end,
22261 ))),
22262 )
22263 }
22264 })
22265 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22266
22267 let chunks = snapshot.chunks(range, true);
22268 let mut lines = Vec::new();
22269 let mut line: VecDeque<Chunk> = VecDeque::new();
22270
22271 let Some(style) = self.style.as_ref() else {
22272 return;
22273 };
22274
22275 for chunk in chunks {
22276 let highlight = chunk
22277 .syntax_highlight_id
22278 .and_then(|id| id.name(&style.syntax));
22279 let mut chunk_lines = chunk.text.split('\n').peekable();
22280 while let Some(text) = chunk_lines.next() {
22281 let mut merged_with_last_token = false;
22282 if let Some(last_token) = line.back_mut()
22283 && last_token.highlight == highlight
22284 {
22285 last_token.text.push_str(text);
22286 merged_with_last_token = true;
22287 }
22288
22289 if !merged_with_last_token {
22290 line.push_back(Chunk {
22291 text: text.into(),
22292 highlight,
22293 });
22294 }
22295
22296 if chunk_lines.peek().is_some() {
22297 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22298 line.pop_front();
22299 }
22300 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22301 line.pop_back();
22302 }
22303
22304 lines.push(mem::take(&mut line));
22305 }
22306 }
22307 }
22308
22309 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22310 return;
22311 };
22312 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22313 }
22314
22315 pub fn open_context_menu(
22316 &mut self,
22317 _: &OpenContextMenu,
22318 window: &mut Window,
22319 cx: &mut Context<Self>,
22320 ) {
22321 self.request_autoscroll(Autoscroll::newest(), cx);
22322 let position = self
22323 .selections
22324 .newest_display(&self.display_snapshot(cx))
22325 .start;
22326 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22327 }
22328
22329 pub fn replay_insert_event(
22330 &mut self,
22331 text: &str,
22332 relative_utf16_range: Option<Range<isize>>,
22333 window: &mut Window,
22334 cx: &mut Context<Self>,
22335 ) {
22336 if !self.input_enabled {
22337 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22338 return;
22339 }
22340 if let Some(relative_utf16_range) = relative_utf16_range {
22341 let selections = self
22342 .selections
22343 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22344 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22345 let new_ranges = selections.into_iter().map(|range| {
22346 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22347 range
22348 .head()
22349 .0
22350 .0
22351 .saturating_add_signed(relative_utf16_range.start),
22352 ));
22353 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22354 range
22355 .head()
22356 .0
22357 .0
22358 .saturating_add_signed(relative_utf16_range.end),
22359 ));
22360 start..end
22361 });
22362 s.select_ranges(new_ranges);
22363 });
22364 }
22365
22366 self.handle_input(text, window, cx);
22367 }
22368
22369 pub fn is_focused(&self, window: &Window) -> bool {
22370 self.focus_handle.is_focused(window)
22371 }
22372
22373 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22374 cx.emit(EditorEvent::Focused);
22375
22376 if let Some(descendant) = self
22377 .last_focused_descendant
22378 .take()
22379 .and_then(|descendant| descendant.upgrade())
22380 {
22381 window.focus(&descendant);
22382 } else {
22383 if let Some(blame) = self.blame.as_ref() {
22384 blame.update(cx, GitBlame::focus)
22385 }
22386
22387 self.blink_manager.update(cx, BlinkManager::enable);
22388 self.show_cursor_names(window, cx);
22389 self.buffer.update(cx, |buffer, cx| {
22390 buffer.finalize_last_transaction(cx);
22391 if self.leader_id.is_none() {
22392 buffer.set_active_selections(
22393 &self.selections.disjoint_anchors_arc(),
22394 self.selections.line_mode(),
22395 self.cursor_shape,
22396 cx,
22397 );
22398 }
22399 });
22400
22401 if let Some(position_map) = self.last_position_map.clone() {
22402 EditorElement::mouse_moved(
22403 self,
22404 &MouseMoveEvent {
22405 position: window.mouse_position(),
22406 pressed_button: None,
22407 modifiers: window.modifiers(),
22408 },
22409 &position_map,
22410 window,
22411 cx,
22412 );
22413 }
22414 }
22415 }
22416
22417 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22418 cx.emit(EditorEvent::FocusedIn)
22419 }
22420
22421 fn handle_focus_out(
22422 &mut self,
22423 event: FocusOutEvent,
22424 _window: &mut Window,
22425 cx: &mut Context<Self>,
22426 ) {
22427 if event.blurred != self.focus_handle {
22428 self.last_focused_descendant = Some(event.blurred);
22429 }
22430 self.selection_drag_state = SelectionDragState::None;
22431 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22432 }
22433
22434 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22435 self.blink_manager.update(cx, BlinkManager::disable);
22436 self.buffer
22437 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22438
22439 if let Some(blame) = self.blame.as_ref() {
22440 blame.update(cx, GitBlame::blur)
22441 }
22442 if !self.hover_state.focused(window, cx) {
22443 hide_hover(self, cx);
22444 }
22445 if !self
22446 .context_menu
22447 .borrow()
22448 .as_ref()
22449 .is_some_and(|context_menu| context_menu.focused(window, cx))
22450 {
22451 self.hide_context_menu(window, cx);
22452 }
22453 self.take_active_edit_prediction(cx);
22454 cx.emit(EditorEvent::Blurred);
22455 cx.notify();
22456 }
22457
22458 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22459 let mut pending: String = window
22460 .pending_input_keystrokes()
22461 .into_iter()
22462 .flatten()
22463 .filter_map(|keystroke| keystroke.key_char.clone())
22464 .collect();
22465
22466 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22467 pending = "".to_string();
22468 }
22469
22470 let existing_pending = self
22471 .text_highlights::<PendingInput>(cx)
22472 .map(|(_, ranges)| ranges.to_vec());
22473 if existing_pending.is_none() && pending.is_empty() {
22474 return;
22475 }
22476 let transaction =
22477 self.transact(window, cx, |this, window, cx| {
22478 let selections = this
22479 .selections
22480 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22481 let edits = selections
22482 .iter()
22483 .map(|selection| (selection.end..selection.end, pending.clone()));
22484 this.edit(edits, cx);
22485 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22486 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22487 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22488 }));
22489 });
22490 if let Some(existing_ranges) = existing_pending {
22491 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22492 this.edit(edits, cx);
22493 }
22494 });
22495
22496 let snapshot = self.snapshot(window, cx);
22497 let ranges = self
22498 .selections
22499 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22500 .into_iter()
22501 .map(|selection| {
22502 snapshot.buffer_snapshot().anchor_after(selection.end)
22503 ..snapshot
22504 .buffer_snapshot()
22505 .anchor_before(selection.end + pending.len())
22506 })
22507 .collect();
22508
22509 if pending.is_empty() {
22510 self.clear_highlights::<PendingInput>(cx);
22511 } else {
22512 self.highlight_text::<PendingInput>(
22513 ranges,
22514 HighlightStyle {
22515 underline: Some(UnderlineStyle {
22516 thickness: px(1.),
22517 color: None,
22518 wavy: false,
22519 }),
22520 ..Default::default()
22521 },
22522 cx,
22523 );
22524 }
22525
22526 self.ime_transaction = self.ime_transaction.or(transaction);
22527 if let Some(transaction) = self.ime_transaction {
22528 self.buffer.update(cx, |buffer, cx| {
22529 buffer.group_until_transaction(transaction, cx);
22530 });
22531 }
22532
22533 if self.text_highlights::<PendingInput>(cx).is_none() {
22534 self.ime_transaction.take();
22535 }
22536 }
22537
22538 pub fn register_action_renderer(
22539 &mut self,
22540 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22541 ) -> Subscription {
22542 let id = self.next_editor_action_id.post_inc();
22543 self.editor_actions
22544 .borrow_mut()
22545 .insert(id, Box::new(listener));
22546
22547 let editor_actions = self.editor_actions.clone();
22548 Subscription::new(move || {
22549 editor_actions.borrow_mut().remove(&id);
22550 })
22551 }
22552
22553 pub fn register_action<A: Action>(
22554 &mut self,
22555 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22556 ) -> Subscription {
22557 let id = self.next_editor_action_id.post_inc();
22558 let listener = Arc::new(listener);
22559 self.editor_actions.borrow_mut().insert(
22560 id,
22561 Box::new(move |_, window, _| {
22562 let listener = listener.clone();
22563 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22564 let action = action.downcast_ref().unwrap();
22565 if phase == DispatchPhase::Bubble {
22566 listener(action, window, cx)
22567 }
22568 })
22569 }),
22570 );
22571
22572 let editor_actions = self.editor_actions.clone();
22573 Subscription::new(move || {
22574 editor_actions.borrow_mut().remove(&id);
22575 })
22576 }
22577
22578 pub fn file_header_size(&self) -> u32 {
22579 FILE_HEADER_HEIGHT
22580 }
22581
22582 pub fn restore(
22583 &mut self,
22584 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22585 window: &mut Window,
22586 cx: &mut Context<Self>,
22587 ) {
22588 let workspace = self.workspace();
22589 let project = self.project();
22590 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22591 let mut tasks = Vec::new();
22592 for (buffer_id, changes) in revert_changes {
22593 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22594 buffer.update(cx, |buffer, cx| {
22595 buffer.edit(
22596 changes
22597 .into_iter()
22598 .map(|(range, text)| (range, text.to_string())),
22599 None,
22600 cx,
22601 );
22602 });
22603
22604 if let Some(project) =
22605 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22606 {
22607 project.update(cx, |project, cx| {
22608 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22609 })
22610 }
22611 }
22612 }
22613 tasks
22614 });
22615 cx.spawn_in(window, async move |_, cx| {
22616 for (buffer, task) in save_tasks {
22617 let result = task.await;
22618 if result.is_err() {
22619 let Some(path) = buffer
22620 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22621 .ok()
22622 else {
22623 continue;
22624 };
22625 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22626 let Some(task) = cx
22627 .update_window_entity(workspace, |workspace, window, cx| {
22628 workspace
22629 .open_path_preview(path, None, false, false, false, window, cx)
22630 })
22631 .ok()
22632 else {
22633 continue;
22634 };
22635 task.await.log_err();
22636 }
22637 }
22638 }
22639 })
22640 .detach();
22641 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22642 selections.refresh()
22643 });
22644 }
22645
22646 pub fn to_pixel_point(
22647 &self,
22648 source: multi_buffer::Anchor,
22649 editor_snapshot: &EditorSnapshot,
22650 window: &mut Window,
22651 ) -> Option<gpui::Point<Pixels>> {
22652 let source_point = source.to_display_point(editor_snapshot);
22653 self.display_to_pixel_point(source_point, editor_snapshot, window)
22654 }
22655
22656 pub fn display_to_pixel_point(
22657 &self,
22658 source: DisplayPoint,
22659 editor_snapshot: &EditorSnapshot,
22660 window: &mut Window,
22661 ) -> Option<gpui::Point<Pixels>> {
22662 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22663 let text_layout_details = self.text_layout_details(window);
22664 let scroll_top = text_layout_details
22665 .scroll_anchor
22666 .scroll_position(editor_snapshot)
22667 .y;
22668
22669 if source.row().as_f64() < scroll_top.floor() {
22670 return None;
22671 }
22672 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22673 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22674 Some(gpui::Point::new(source_x, source_y))
22675 }
22676
22677 pub fn has_visible_completions_menu(&self) -> bool {
22678 !self.edit_prediction_preview_is_active()
22679 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22680 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22681 })
22682 }
22683
22684 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22685 if self.mode.is_minimap() {
22686 return;
22687 }
22688 self.addons
22689 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22690 }
22691
22692 pub fn unregister_addon<T: Addon>(&mut self) {
22693 self.addons.remove(&std::any::TypeId::of::<T>());
22694 }
22695
22696 pub fn addon<T: Addon>(&self) -> Option<&T> {
22697 let type_id = std::any::TypeId::of::<T>();
22698 self.addons
22699 .get(&type_id)
22700 .and_then(|item| item.to_any().downcast_ref::<T>())
22701 }
22702
22703 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22704 let type_id = std::any::TypeId::of::<T>();
22705 self.addons
22706 .get_mut(&type_id)
22707 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22708 }
22709
22710 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22711 let text_layout_details = self.text_layout_details(window);
22712 let style = &text_layout_details.editor_style;
22713 let font_id = window.text_system().resolve_font(&style.text.font());
22714 let font_size = style.text.font_size.to_pixels(window.rem_size());
22715 let line_height = style.text.line_height_in_pixels(window.rem_size());
22716 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22717 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22718
22719 CharacterDimensions {
22720 em_width,
22721 em_advance,
22722 line_height,
22723 }
22724 }
22725
22726 pub fn last_gutter_dimensions(&self) -> &GutterDimensions {
22727 &self.gutter_dimensions
22728 }
22729
22730 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22731 self.load_diff_task.clone()
22732 }
22733
22734 fn read_metadata_from_db(
22735 &mut self,
22736 item_id: u64,
22737 workspace_id: WorkspaceId,
22738 window: &mut Window,
22739 cx: &mut Context<Editor>,
22740 ) {
22741 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22742 && !self.mode.is_minimap()
22743 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22744 {
22745 let buffer_snapshot = OnceCell::new();
22746
22747 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22748 && !folds.is_empty()
22749 {
22750 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22751 self.fold_ranges(
22752 folds
22753 .into_iter()
22754 .map(|(start, end)| {
22755 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22756 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22757 })
22758 .collect(),
22759 false,
22760 window,
22761 cx,
22762 );
22763 }
22764
22765 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22766 && !selections.is_empty()
22767 {
22768 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22769 // skip adding the initial selection to selection history
22770 self.selection_history.mode = SelectionHistoryMode::Skipping;
22771 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22772 s.select_ranges(selections.into_iter().map(|(start, end)| {
22773 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22774 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22775 }));
22776 });
22777 self.selection_history.mode = SelectionHistoryMode::Normal;
22778 };
22779 }
22780
22781 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22782 }
22783
22784 fn update_lsp_data(
22785 &mut self,
22786 for_buffer: Option<BufferId>,
22787 window: &mut Window,
22788 cx: &mut Context<'_, Self>,
22789 ) {
22790 self.pull_diagnostics(for_buffer, window, cx);
22791 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22792 }
22793
22794 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22795 if self.ignore_lsp_data() {
22796 return;
22797 }
22798 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22799 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22800 }
22801 }
22802
22803 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22804 if self.ignore_lsp_data() {
22805 return;
22806 }
22807
22808 if !self.registered_buffers.contains_key(&buffer_id)
22809 && let Some(project) = self.project.as_ref()
22810 {
22811 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22812 project.update(cx, |project, cx| {
22813 self.registered_buffers.insert(
22814 buffer_id,
22815 project.register_buffer_with_language_servers(&buffer, cx),
22816 );
22817 });
22818 } else {
22819 self.registered_buffers.remove(&buffer_id);
22820 }
22821 }
22822 }
22823
22824 fn ignore_lsp_data(&self) -> bool {
22825 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22826 // skip any LSP updates for it.
22827 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22828 }
22829}
22830
22831fn edit_for_markdown_paste<'a>(
22832 buffer: &MultiBufferSnapshot,
22833 range: Range<MultiBufferOffset>,
22834 to_insert: &'a str,
22835 url: Option<url::Url>,
22836) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22837 if url.is_none() {
22838 return (range, Cow::Borrowed(to_insert));
22839 };
22840
22841 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22842
22843 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22844 Cow::Borrowed(to_insert)
22845 } else {
22846 Cow::Owned(format!("[{old_text}]({to_insert})"))
22847 };
22848 (range, new_text)
22849}
22850
22851fn process_completion_for_edit(
22852 completion: &Completion,
22853 intent: CompletionIntent,
22854 buffer: &Entity<Buffer>,
22855 cursor_position: &text::Anchor,
22856 cx: &mut Context<Editor>,
22857) -> CompletionEdit {
22858 let buffer = buffer.read(cx);
22859 let buffer_snapshot = buffer.snapshot();
22860 let (snippet, new_text) = if completion.is_snippet() {
22861 let mut snippet_source = completion.new_text.clone();
22862 // Workaround for typescript language server issues so that methods don't expand within
22863 // strings and functions with type expressions. The previous point is used because the query
22864 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22865 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22866 let previous_point = if previous_point.column > 0 {
22867 cursor_position.to_previous_offset(&buffer_snapshot)
22868 } else {
22869 cursor_position.to_offset(&buffer_snapshot)
22870 };
22871 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22872 && scope.prefers_label_for_snippet_in_completion()
22873 && let Some(label) = completion.label()
22874 && matches!(
22875 completion.kind(),
22876 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22877 )
22878 {
22879 snippet_source = label;
22880 }
22881 match Snippet::parse(&snippet_source).log_err() {
22882 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22883 None => (None, completion.new_text.clone()),
22884 }
22885 } else {
22886 (None, completion.new_text.clone())
22887 };
22888
22889 let mut range_to_replace = {
22890 let replace_range = &completion.replace_range;
22891 if let CompletionSource::Lsp {
22892 insert_range: Some(insert_range),
22893 ..
22894 } = &completion.source
22895 {
22896 debug_assert_eq!(
22897 insert_range.start, replace_range.start,
22898 "insert_range and replace_range should start at the same position"
22899 );
22900 debug_assert!(
22901 insert_range
22902 .start
22903 .cmp(cursor_position, &buffer_snapshot)
22904 .is_le(),
22905 "insert_range should start before or at cursor position"
22906 );
22907 debug_assert!(
22908 replace_range
22909 .start
22910 .cmp(cursor_position, &buffer_snapshot)
22911 .is_le(),
22912 "replace_range should start before or at cursor position"
22913 );
22914
22915 let should_replace = match intent {
22916 CompletionIntent::CompleteWithInsert => false,
22917 CompletionIntent::CompleteWithReplace => true,
22918 CompletionIntent::Complete | CompletionIntent::Compose => {
22919 let insert_mode =
22920 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22921 .completions
22922 .lsp_insert_mode;
22923 match insert_mode {
22924 LspInsertMode::Insert => false,
22925 LspInsertMode::Replace => true,
22926 LspInsertMode::ReplaceSubsequence => {
22927 let mut text_to_replace = buffer.chars_for_range(
22928 buffer.anchor_before(replace_range.start)
22929 ..buffer.anchor_after(replace_range.end),
22930 );
22931 let mut current_needle = text_to_replace.next();
22932 for haystack_ch in completion.label.text.chars() {
22933 if let Some(needle_ch) = current_needle
22934 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22935 {
22936 current_needle = text_to_replace.next();
22937 }
22938 }
22939 current_needle.is_none()
22940 }
22941 LspInsertMode::ReplaceSuffix => {
22942 if replace_range
22943 .end
22944 .cmp(cursor_position, &buffer_snapshot)
22945 .is_gt()
22946 {
22947 let range_after_cursor = *cursor_position..replace_range.end;
22948 let text_after_cursor = buffer
22949 .text_for_range(
22950 buffer.anchor_before(range_after_cursor.start)
22951 ..buffer.anchor_after(range_after_cursor.end),
22952 )
22953 .collect::<String>()
22954 .to_ascii_lowercase();
22955 completion
22956 .label
22957 .text
22958 .to_ascii_lowercase()
22959 .ends_with(&text_after_cursor)
22960 } else {
22961 true
22962 }
22963 }
22964 }
22965 }
22966 };
22967
22968 if should_replace {
22969 replace_range.clone()
22970 } else {
22971 insert_range.clone()
22972 }
22973 } else {
22974 replace_range.clone()
22975 }
22976 };
22977
22978 if range_to_replace
22979 .end
22980 .cmp(cursor_position, &buffer_snapshot)
22981 .is_lt()
22982 {
22983 range_to_replace.end = *cursor_position;
22984 }
22985
22986 let replace_range = range_to_replace.to_offset(buffer);
22987 CompletionEdit {
22988 new_text,
22989 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22990 snippet,
22991 }
22992}
22993
22994struct CompletionEdit {
22995 new_text: String,
22996 replace_range: Range<BufferOffset>,
22997 snippet: Option<Snippet>,
22998}
22999
23000fn insert_extra_newline_brackets(
23001 buffer: &MultiBufferSnapshot,
23002 range: Range<MultiBufferOffset>,
23003 language: &language::LanguageScope,
23004) -> bool {
23005 let leading_whitespace_len = buffer
23006 .reversed_chars_at(range.start)
23007 .take_while(|c| c.is_whitespace() && *c != '\n')
23008 .map(|c| c.len_utf8())
23009 .sum::<usize>();
23010 let trailing_whitespace_len = buffer
23011 .chars_at(range.end)
23012 .take_while(|c| c.is_whitespace() && *c != '\n')
23013 .map(|c| c.len_utf8())
23014 .sum::<usize>();
23015 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23016
23017 language.brackets().any(|(pair, enabled)| {
23018 let pair_start = pair.start.trim_end();
23019 let pair_end = pair.end.trim_start();
23020
23021 enabled
23022 && pair.newline
23023 && buffer.contains_str_at(range.end, pair_end)
23024 && buffer.contains_str_at(
23025 range.start.saturating_sub_usize(pair_start.len()),
23026 pair_start,
23027 )
23028 })
23029}
23030
23031fn insert_extra_newline_tree_sitter(
23032 buffer: &MultiBufferSnapshot,
23033 range: Range<MultiBufferOffset>,
23034) -> bool {
23035 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23036 [(buffer, range, _)] => (*buffer, range.clone()),
23037 _ => return false,
23038 };
23039 let pair = {
23040 let mut result: Option<BracketMatch<usize>> = None;
23041
23042 for pair in buffer
23043 .all_bracket_ranges(range.start.0..range.end.0)
23044 .filter(move |pair| {
23045 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23046 })
23047 {
23048 let len = pair.close_range.end - pair.open_range.start;
23049
23050 if let Some(existing) = &result {
23051 let existing_len = existing.close_range.end - existing.open_range.start;
23052 if len > existing_len {
23053 continue;
23054 }
23055 }
23056
23057 result = Some(pair);
23058 }
23059
23060 result
23061 };
23062 let Some(pair) = pair else {
23063 return false;
23064 };
23065 pair.newline_only
23066 && buffer
23067 .chars_for_range(pair.open_range.end..range.start.0)
23068 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23069 .all(|c| c.is_whitespace() && c != '\n')
23070}
23071
23072fn update_uncommitted_diff_for_buffer(
23073 editor: Entity<Editor>,
23074 project: &Entity<Project>,
23075 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23076 buffer: Entity<MultiBuffer>,
23077 cx: &mut App,
23078) -> Task<()> {
23079 let mut tasks = Vec::new();
23080 project.update(cx, |project, cx| {
23081 for buffer in buffers {
23082 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23083 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23084 }
23085 }
23086 });
23087 cx.spawn(async move |cx| {
23088 let diffs = future::join_all(tasks).await;
23089 if editor
23090 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23091 .unwrap_or(false)
23092 {
23093 return;
23094 }
23095
23096 buffer
23097 .update(cx, |buffer, cx| {
23098 for diff in diffs.into_iter().flatten() {
23099 buffer.add_diff(diff, cx);
23100 }
23101 })
23102 .ok();
23103 })
23104}
23105
23106fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23107 let tab_size = tab_size.get() as usize;
23108 let mut width = offset;
23109
23110 for ch in text.chars() {
23111 width += if ch == '\t' {
23112 tab_size - (width % tab_size)
23113 } else {
23114 1
23115 };
23116 }
23117
23118 width - offset
23119}
23120
23121#[cfg(test)]
23122mod tests {
23123 use super::*;
23124
23125 #[test]
23126 fn test_string_size_with_expanded_tabs() {
23127 let nz = |val| NonZeroU32::new(val).unwrap();
23128 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23129 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23130 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23131 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23132 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23133 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23134 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23135 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23136 }
23137}
23138
23139/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23140struct WordBreakingTokenizer<'a> {
23141 input: &'a str,
23142}
23143
23144impl<'a> WordBreakingTokenizer<'a> {
23145 fn new(input: &'a str) -> Self {
23146 Self { input }
23147 }
23148}
23149
23150fn is_char_ideographic(ch: char) -> bool {
23151 use unicode_script::Script::*;
23152 use unicode_script::UnicodeScript;
23153 matches!(ch.script(), Han | Tangut | Yi)
23154}
23155
23156fn is_grapheme_ideographic(text: &str) -> bool {
23157 text.chars().any(is_char_ideographic)
23158}
23159
23160fn is_grapheme_whitespace(text: &str) -> bool {
23161 text.chars().any(|x| x.is_whitespace())
23162}
23163
23164fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23165 text.chars()
23166 .next()
23167 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23168}
23169
23170#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23171enum WordBreakToken<'a> {
23172 Word { token: &'a str, grapheme_len: usize },
23173 InlineWhitespace { token: &'a str, grapheme_len: usize },
23174 Newline,
23175}
23176
23177impl<'a> Iterator for WordBreakingTokenizer<'a> {
23178 /// Yields a span, the count of graphemes in the token, and whether it was
23179 /// whitespace. Note that it also breaks at word boundaries.
23180 type Item = WordBreakToken<'a>;
23181
23182 fn next(&mut self) -> Option<Self::Item> {
23183 use unicode_segmentation::UnicodeSegmentation;
23184 if self.input.is_empty() {
23185 return None;
23186 }
23187
23188 let mut iter = self.input.graphemes(true).peekable();
23189 let mut offset = 0;
23190 let mut grapheme_len = 0;
23191 if let Some(first_grapheme) = iter.next() {
23192 let is_newline = first_grapheme == "\n";
23193 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23194 offset += first_grapheme.len();
23195 grapheme_len += 1;
23196 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23197 if let Some(grapheme) = iter.peek().copied()
23198 && should_stay_with_preceding_ideograph(grapheme)
23199 {
23200 offset += grapheme.len();
23201 grapheme_len += 1;
23202 }
23203 } else {
23204 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23205 let mut next_word_bound = words.peek().copied();
23206 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23207 next_word_bound = words.next();
23208 }
23209 while let Some(grapheme) = iter.peek().copied() {
23210 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23211 break;
23212 };
23213 if is_grapheme_whitespace(grapheme) != is_whitespace
23214 || (grapheme == "\n") != is_newline
23215 {
23216 break;
23217 };
23218 offset += grapheme.len();
23219 grapheme_len += 1;
23220 iter.next();
23221 }
23222 }
23223 let token = &self.input[..offset];
23224 self.input = &self.input[offset..];
23225 if token == "\n" {
23226 Some(WordBreakToken::Newline)
23227 } else if is_whitespace {
23228 Some(WordBreakToken::InlineWhitespace {
23229 token,
23230 grapheme_len,
23231 })
23232 } else {
23233 Some(WordBreakToken::Word {
23234 token,
23235 grapheme_len,
23236 })
23237 }
23238 } else {
23239 None
23240 }
23241 }
23242}
23243
23244#[test]
23245fn test_word_breaking_tokenizer() {
23246 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23247 ("", &[]),
23248 (" ", &[whitespace(" ", 2)]),
23249 ("Ʒ", &[word("Ʒ", 1)]),
23250 ("Ǽ", &[word("Ǽ", 1)]),
23251 ("⋑", &[word("⋑", 1)]),
23252 ("⋑⋑", &[word("⋑⋑", 2)]),
23253 (
23254 "原理,进而",
23255 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23256 ),
23257 (
23258 "hello world",
23259 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23260 ),
23261 (
23262 "hello, world",
23263 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23264 ),
23265 (
23266 " hello world",
23267 &[
23268 whitespace(" ", 2),
23269 word("hello", 5),
23270 whitespace(" ", 1),
23271 word("world", 5),
23272 ],
23273 ),
23274 (
23275 "这是什么 \n 钢笔",
23276 &[
23277 word("这", 1),
23278 word("是", 1),
23279 word("什", 1),
23280 word("么", 1),
23281 whitespace(" ", 1),
23282 newline(),
23283 whitespace(" ", 1),
23284 word("钢", 1),
23285 word("笔", 1),
23286 ],
23287 ),
23288 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23289 ];
23290
23291 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23292 WordBreakToken::Word {
23293 token,
23294 grapheme_len,
23295 }
23296 }
23297
23298 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23299 WordBreakToken::InlineWhitespace {
23300 token,
23301 grapheme_len,
23302 }
23303 }
23304
23305 fn newline() -> WordBreakToken<'static> {
23306 WordBreakToken::Newline
23307 }
23308
23309 for (input, result) in tests {
23310 assert_eq!(
23311 WordBreakingTokenizer::new(input)
23312 .collect::<Vec<_>>()
23313 .as_slice(),
23314 *result,
23315 );
23316 }
23317}
23318
23319fn wrap_with_prefix(
23320 first_line_prefix: String,
23321 subsequent_lines_prefix: String,
23322 unwrapped_text: String,
23323 wrap_column: usize,
23324 tab_size: NonZeroU32,
23325 preserve_existing_whitespace: bool,
23326) -> String {
23327 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23328 let subsequent_lines_prefix_len =
23329 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23330 let mut wrapped_text = String::new();
23331 let mut current_line = first_line_prefix;
23332 let mut is_first_line = true;
23333
23334 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23335 let mut current_line_len = first_line_prefix_len;
23336 let mut in_whitespace = false;
23337 for token in tokenizer {
23338 let have_preceding_whitespace = in_whitespace;
23339 match token {
23340 WordBreakToken::Word {
23341 token,
23342 grapheme_len,
23343 } => {
23344 in_whitespace = false;
23345 let current_prefix_len = if is_first_line {
23346 first_line_prefix_len
23347 } else {
23348 subsequent_lines_prefix_len
23349 };
23350 if current_line_len + grapheme_len > wrap_column
23351 && current_line_len != current_prefix_len
23352 {
23353 wrapped_text.push_str(current_line.trim_end());
23354 wrapped_text.push('\n');
23355 is_first_line = false;
23356 current_line = subsequent_lines_prefix.clone();
23357 current_line_len = subsequent_lines_prefix_len;
23358 }
23359 current_line.push_str(token);
23360 current_line_len += grapheme_len;
23361 }
23362 WordBreakToken::InlineWhitespace {
23363 mut token,
23364 mut grapheme_len,
23365 } => {
23366 in_whitespace = true;
23367 if have_preceding_whitespace && !preserve_existing_whitespace {
23368 continue;
23369 }
23370 if !preserve_existing_whitespace {
23371 // Keep a single whitespace grapheme as-is
23372 if let Some(first) =
23373 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23374 {
23375 token = first;
23376 } else {
23377 token = " ";
23378 }
23379 grapheme_len = 1;
23380 }
23381 let current_prefix_len = if is_first_line {
23382 first_line_prefix_len
23383 } else {
23384 subsequent_lines_prefix_len
23385 };
23386 if current_line_len + grapheme_len > wrap_column {
23387 wrapped_text.push_str(current_line.trim_end());
23388 wrapped_text.push('\n');
23389 is_first_line = false;
23390 current_line = subsequent_lines_prefix.clone();
23391 current_line_len = subsequent_lines_prefix_len;
23392 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23393 current_line.push_str(token);
23394 current_line_len += grapheme_len;
23395 }
23396 }
23397 WordBreakToken::Newline => {
23398 in_whitespace = true;
23399 let current_prefix_len = if is_first_line {
23400 first_line_prefix_len
23401 } else {
23402 subsequent_lines_prefix_len
23403 };
23404 if preserve_existing_whitespace {
23405 wrapped_text.push_str(current_line.trim_end());
23406 wrapped_text.push('\n');
23407 is_first_line = false;
23408 current_line = subsequent_lines_prefix.clone();
23409 current_line_len = subsequent_lines_prefix_len;
23410 } else if have_preceding_whitespace {
23411 continue;
23412 } else if current_line_len + 1 > wrap_column
23413 && current_line_len != current_prefix_len
23414 {
23415 wrapped_text.push_str(current_line.trim_end());
23416 wrapped_text.push('\n');
23417 is_first_line = false;
23418 current_line = subsequent_lines_prefix.clone();
23419 current_line_len = subsequent_lines_prefix_len;
23420 } else if current_line_len != current_prefix_len {
23421 current_line.push(' ');
23422 current_line_len += 1;
23423 }
23424 }
23425 }
23426 }
23427
23428 if !current_line.is_empty() {
23429 wrapped_text.push_str(¤t_line);
23430 }
23431 wrapped_text
23432}
23433
23434#[test]
23435fn test_wrap_with_prefix() {
23436 assert_eq!(
23437 wrap_with_prefix(
23438 "# ".to_string(),
23439 "# ".to_string(),
23440 "abcdefg".to_string(),
23441 4,
23442 NonZeroU32::new(4).unwrap(),
23443 false,
23444 ),
23445 "# abcdefg"
23446 );
23447 assert_eq!(
23448 wrap_with_prefix(
23449 "".to_string(),
23450 "".to_string(),
23451 "\thello world".to_string(),
23452 8,
23453 NonZeroU32::new(4).unwrap(),
23454 false,
23455 ),
23456 "hello\nworld"
23457 );
23458 assert_eq!(
23459 wrap_with_prefix(
23460 "// ".to_string(),
23461 "// ".to_string(),
23462 "xx \nyy zz aa bb cc".to_string(),
23463 12,
23464 NonZeroU32::new(4).unwrap(),
23465 false,
23466 ),
23467 "// xx yy zz\n// aa bb cc"
23468 );
23469 assert_eq!(
23470 wrap_with_prefix(
23471 String::new(),
23472 String::new(),
23473 "这是什么 \n 钢笔".to_string(),
23474 3,
23475 NonZeroU32::new(4).unwrap(),
23476 false,
23477 ),
23478 "这是什\n么 钢\n笔"
23479 );
23480 assert_eq!(
23481 wrap_with_prefix(
23482 String::new(),
23483 String::new(),
23484 format!("foo{}bar", '\u{2009}'), // thin space
23485 80,
23486 NonZeroU32::new(4).unwrap(),
23487 false,
23488 ),
23489 format!("foo{}bar", '\u{2009}')
23490 );
23491}
23492
23493pub trait CollaborationHub {
23494 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23495 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23496 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23497}
23498
23499impl CollaborationHub for Entity<Project> {
23500 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23501 self.read(cx).collaborators()
23502 }
23503
23504 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23505 self.read(cx).user_store().read(cx).participant_indices()
23506 }
23507
23508 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23509 let this = self.read(cx);
23510 let user_ids = this.collaborators().values().map(|c| c.user_id);
23511 this.user_store().read(cx).participant_names(user_ids, cx)
23512 }
23513}
23514
23515pub trait SemanticsProvider {
23516 fn hover(
23517 &self,
23518 buffer: &Entity<Buffer>,
23519 position: text::Anchor,
23520 cx: &mut App,
23521 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23522
23523 fn inline_values(
23524 &self,
23525 buffer_handle: Entity<Buffer>,
23526 range: Range<text::Anchor>,
23527 cx: &mut App,
23528 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23529
23530 fn applicable_inlay_chunks(
23531 &self,
23532 buffer: &Entity<Buffer>,
23533 ranges: &[Range<text::Anchor>],
23534 cx: &mut App,
23535 ) -> Vec<Range<BufferRow>>;
23536
23537 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23538
23539 fn inlay_hints(
23540 &self,
23541 invalidate: InvalidationStrategy,
23542 buffer: Entity<Buffer>,
23543 ranges: Vec<Range<text::Anchor>>,
23544 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23545 cx: &mut App,
23546 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23547
23548 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23549
23550 fn document_highlights(
23551 &self,
23552 buffer: &Entity<Buffer>,
23553 position: text::Anchor,
23554 cx: &mut App,
23555 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23556
23557 fn definitions(
23558 &self,
23559 buffer: &Entity<Buffer>,
23560 position: text::Anchor,
23561 kind: GotoDefinitionKind,
23562 cx: &mut App,
23563 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23564
23565 fn range_for_rename(
23566 &self,
23567 buffer: &Entity<Buffer>,
23568 position: text::Anchor,
23569 cx: &mut App,
23570 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23571
23572 fn perform_rename(
23573 &self,
23574 buffer: &Entity<Buffer>,
23575 position: text::Anchor,
23576 new_name: String,
23577 cx: &mut App,
23578 ) -> Option<Task<Result<ProjectTransaction>>>;
23579}
23580
23581pub trait CompletionProvider {
23582 fn completions(
23583 &self,
23584 excerpt_id: ExcerptId,
23585 buffer: &Entity<Buffer>,
23586 buffer_position: text::Anchor,
23587 trigger: CompletionContext,
23588 window: &mut Window,
23589 cx: &mut Context<Editor>,
23590 ) -> Task<Result<Vec<CompletionResponse>>>;
23591
23592 fn resolve_completions(
23593 &self,
23594 _buffer: Entity<Buffer>,
23595 _completion_indices: Vec<usize>,
23596 _completions: Rc<RefCell<Box<[Completion]>>>,
23597 _cx: &mut Context<Editor>,
23598 ) -> Task<Result<bool>> {
23599 Task::ready(Ok(false))
23600 }
23601
23602 fn apply_additional_edits_for_completion(
23603 &self,
23604 _buffer: Entity<Buffer>,
23605 _completions: Rc<RefCell<Box<[Completion]>>>,
23606 _completion_index: usize,
23607 _push_to_history: bool,
23608 _cx: &mut Context<Editor>,
23609 ) -> Task<Result<Option<language::Transaction>>> {
23610 Task::ready(Ok(None))
23611 }
23612
23613 fn is_completion_trigger(
23614 &self,
23615 buffer: &Entity<Buffer>,
23616 position: language::Anchor,
23617 text: &str,
23618 trigger_in_words: bool,
23619 cx: &mut Context<Editor>,
23620 ) -> bool;
23621
23622 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23623
23624 fn sort_completions(&self) -> bool {
23625 true
23626 }
23627
23628 fn filter_completions(&self) -> bool {
23629 true
23630 }
23631
23632 fn show_snippets(&self) -> bool {
23633 false
23634 }
23635}
23636
23637pub trait CodeActionProvider {
23638 fn id(&self) -> Arc<str>;
23639
23640 fn code_actions(
23641 &self,
23642 buffer: &Entity<Buffer>,
23643 range: Range<text::Anchor>,
23644 window: &mut Window,
23645 cx: &mut App,
23646 ) -> Task<Result<Vec<CodeAction>>>;
23647
23648 fn apply_code_action(
23649 &self,
23650 buffer_handle: Entity<Buffer>,
23651 action: CodeAction,
23652 excerpt_id: ExcerptId,
23653 push_to_history: bool,
23654 window: &mut Window,
23655 cx: &mut App,
23656 ) -> Task<Result<ProjectTransaction>>;
23657}
23658
23659impl CodeActionProvider for Entity<Project> {
23660 fn id(&self) -> Arc<str> {
23661 "project".into()
23662 }
23663
23664 fn code_actions(
23665 &self,
23666 buffer: &Entity<Buffer>,
23667 range: Range<text::Anchor>,
23668 _window: &mut Window,
23669 cx: &mut App,
23670 ) -> Task<Result<Vec<CodeAction>>> {
23671 self.update(cx, |project, cx| {
23672 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23673 let code_actions = project.code_actions(buffer, range, None, cx);
23674 cx.background_spawn(async move {
23675 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23676 Ok(code_lens_actions
23677 .context("code lens fetch")?
23678 .into_iter()
23679 .flatten()
23680 .chain(
23681 code_actions
23682 .context("code action fetch")?
23683 .into_iter()
23684 .flatten(),
23685 )
23686 .collect())
23687 })
23688 })
23689 }
23690
23691 fn apply_code_action(
23692 &self,
23693 buffer_handle: Entity<Buffer>,
23694 action: CodeAction,
23695 _excerpt_id: ExcerptId,
23696 push_to_history: bool,
23697 _window: &mut Window,
23698 cx: &mut App,
23699 ) -> Task<Result<ProjectTransaction>> {
23700 self.update(cx, |project, cx| {
23701 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23702 })
23703 }
23704}
23705
23706fn snippet_completions(
23707 project: &Project,
23708 buffer: &Entity<Buffer>,
23709 buffer_anchor: text::Anchor,
23710 classifier: CharClassifier,
23711 cx: &mut App,
23712) -> Task<Result<CompletionResponse>> {
23713 let languages = buffer.read(cx).languages_at(buffer_anchor);
23714 let snippet_store = project.snippets().read(cx);
23715
23716 let scopes: Vec<_> = languages
23717 .iter()
23718 .filter_map(|language| {
23719 let language_name = language.lsp_id();
23720 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23721
23722 if snippets.is_empty() {
23723 None
23724 } else {
23725 Some((language.default_scope(), snippets))
23726 }
23727 })
23728 .collect();
23729
23730 if scopes.is_empty() {
23731 return Task::ready(Ok(CompletionResponse {
23732 completions: vec![],
23733 display_options: CompletionDisplayOptions::default(),
23734 is_incomplete: false,
23735 }));
23736 }
23737
23738 let snapshot = buffer.read(cx).text_snapshot();
23739 let executor = cx.background_executor().clone();
23740
23741 cx.background_spawn(async move {
23742 let is_word_char = |c| classifier.is_word(c);
23743
23744 let mut is_incomplete = false;
23745 let mut completions: Vec<Completion> = Vec::new();
23746
23747 const MAX_PREFIX_LEN: usize = 128;
23748 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23749 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23750 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23751
23752 let max_buffer_window: String = snapshot
23753 .text_for_range(window_start..buffer_offset)
23754 .collect();
23755
23756 if max_buffer_window.is_empty() {
23757 return Ok(CompletionResponse {
23758 completions: vec![],
23759 display_options: CompletionDisplayOptions::default(),
23760 is_incomplete: true,
23761 });
23762 }
23763
23764 for (_scope, snippets) in scopes.into_iter() {
23765 // Sort snippets by word count to match longer snippet prefixes first.
23766 let mut sorted_snippet_candidates = snippets
23767 .iter()
23768 .enumerate()
23769 .flat_map(|(snippet_ix, snippet)| {
23770 snippet
23771 .prefix
23772 .iter()
23773 .enumerate()
23774 .map(move |(prefix_ix, prefix)| {
23775 let word_count =
23776 snippet_candidate_suffixes(prefix, is_word_char).count();
23777 ((snippet_ix, prefix_ix), prefix, word_count)
23778 })
23779 })
23780 .collect_vec();
23781 sorted_snippet_candidates
23782 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23783
23784 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23785
23786 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23787 .take(
23788 sorted_snippet_candidates
23789 .first()
23790 .map(|(_, _, word_count)| *word_count)
23791 .unwrap_or_default(),
23792 )
23793 .collect_vec();
23794
23795 const MAX_RESULTS: usize = 100;
23796 // Each match also remembers how many characters from the buffer it consumed
23797 let mut matches: Vec<(StringMatch, usize)> = vec![];
23798
23799 let mut snippet_list_cutoff_index = 0;
23800 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23801 let word_count = buffer_index + 1;
23802 // Increase `snippet_list_cutoff_index` until we have all of the
23803 // snippets with sufficiently many words.
23804 while sorted_snippet_candidates
23805 .get(snippet_list_cutoff_index)
23806 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23807 *snippet_word_count >= word_count
23808 })
23809 {
23810 snippet_list_cutoff_index += 1;
23811 }
23812
23813 // Take only the candidates with at least `word_count` many words
23814 let snippet_candidates_at_word_len =
23815 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23816
23817 let candidates = snippet_candidates_at_word_len
23818 .iter()
23819 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23820 .enumerate() // index in `sorted_snippet_candidates`
23821 // First char must match
23822 .filter(|(_ix, prefix)| {
23823 itertools::equal(
23824 prefix
23825 .chars()
23826 .next()
23827 .into_iter()
23828 .flat_map(|c| c.to_lowercase()),
23829 buffer_window
23830 .chars()
23831 .next()
23832 .into_iter()
23833 .flat_map(|c| c.to_lowercase()),
23834 )
23835 })
23836 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23837 .collect::<Vec<StringMatchCandidate>>();
23838
23839 matches.extend(
23840 fuzzy::match_strings(
23841 &candidates,
23842 &buffer_window,
23843 buffer_window.chars().any(|c| c.is_uppercase()),
23844 true,
23845 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23846 &Default::default(),
23847 executor.clone(),
23848 )
23849 .await
23850 .into_iter()
23851 .map(|string_match| (string_match, buffer_window.len())),
23852 );
23853
23854 if matches.len() >= MAX_RESULTS {
23855 break;
23856 }
23857 }
23858
23859 let to_lsp = |point: &text::Anchor| {
23860 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23861 point_to_lsp(end)
23862 };
23863 let lsp_end = to_lsp(&buffer_anchor);
23864
23865 if matches.len() >= MAX_RESULTS {
23866 is_incomplete = true;
23867 }
23868
23869 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23870 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23871 sorted_snippet_candidates[string_match.candidate_id];
23872 let snippet = &snippets[snippet_index];
23873 let start = buffer_offset - buffer_window_len;
23874 let start = snapshot.anchor_before(start);
23875 let range = start..buffer_anchor;
23876 let lsp_start = to_lsp(&start);
23877 let lsp_range = lsp::Range {
23878 start: lsp_start,
23879 end: lsp_end,
23880 };
23881 Completion {
23882 replace_range: range,
23883 new_text: snippet.body.clone(),
23884 source: CompletionSource::Lsp {
23885 insert_range: None,
23886 server_id: LanguageServerId(usize::MAX),
23887 resolved: true,
23888 lsp_completion: Box::new(lsp::CompletionItem {
23889 label: snippet.prefix.first().unwrap().clone(),
23890 kind: Some(CompletionItemKind::SNIPPET),
23891 label_details: snippet.description.as_ref().map(|description| {
23892 lsp::CompletionItemLabelDetails {
23893 detail: Some(description.clone()),
23894 description: None,
23895 }
23896 }),
23897 insert_text_format: Some(InsertTextFormat::SNIPPET),
23898 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23899 lsp::InsertReplaceEdit {
23900 new_text: snippet.body.clone(),
23901 insert: lsp_range,
23902 replace: lsp_range,
23903 },
23904 )),
23905 filter_text: Some(snippet.body.clone()),
23906 sort_text: Some(char::MAX.to_string()),
23907 ..lsp::CompletionItem::default()
23908 }),
23909 lsp_defaults: None,
23910 },
23911 label: CodeLabel {
23912 text: matching_prefix.clone(),
23913 runs: Vec::new(),
23914 filter_range: 0..matching_prefix.len(),
23915 },
23916 icon_path: None,
23917 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23918 single_line: snippet.name.clone().into(),
23919 plain_text: snippet
23920 .description
23921 .clone()
23922 .map(|description| description.into()),
23923 }),
23924 insert_text_mode: None,
23925 confirm: None,
23926 match_start: Some(start),
23927 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23928 }
23929 }));
23930 }
23931
23932 Ok(CompletionResponse {
23933 completions,
23934 display_options: CompletionDisplayOptions::default(),
23935 is_incomplete,
23936 })
23937 })
23938}
23939
23940impl CompletionProvider for Entity<Project> {
23941 fn completions(
23942 &self,
23943 _excerpt_id: ExcerptId,
23944 buffer: &Entity<Buffer>,
23945 buffer_position: text::Anchor,
23946 options: CompletionContext,
23947 _window: &mut Window,
23948 cx: &mut Context<Editor>,
23949 ) -> Task<Result<Vec<CompletionResponse>>> {
23950 self.update(cx, |project, cx| {
23951 let task = project.completions(buffer, buffer_position, options, cx);
23952 cx.background_spawn(task)
23953 })
23954 }
23955
23956 fn resolve_completions(
23957 &self,
23958 buffer: Entity<Buffer>,
23959 completion_indices: Vec<usize>,
23960 completions: Rc<RefCell<Box<[Completion]>>>,
23961 cx: &mut Context<Editor>,
23962 ) -> Task<Result<bool>> {
23963 self.update(cx, |project, cx| {
23964 project.lsp_store().update(cx, |lsp_store, cx| {
23965 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23966 })
23967 })
23968 }
23969
23970 fn apply_additional_edits_for_completion(
23971 &self,
23972 buffer: Entity<Buffer>,
23973 completions: Rc<RefCell<Box<[Completion]>>>,
23974 completion_index: usize,
23975 push_to_history: bool,
23976 cx: &mut Context<Editor>,
23977 ) -> Task<Result<Option<language::Transaction>>> {
23978 self.update(cx, |project, cx| {
23979 project.lsp_store().update(cx, |lsp_store, cx| {
23980 lsp_store.apply_additional_edits_for_completion(
23981 buffer,
23982 completions,
23983 completion_index,
23984 push_to_history,
23985 cx,
23986 )
23987 })
23988 })
23989 }
23990
23991 fn is_completion_trigger(
23992 &self,
23993 buffer: &Entity<Buffer>,
23994 position: language::Anchor,
23995 text: &str,
23996 trigger_in_words: bool,
23997 cx: &mut Context<Editor>,
23998 ) -> bool {
23999 let mut chars = text.chars();
24000 let char = if let Some(char) = chars.next() {
24001 char
24002 } else {
24003 return false;
24004 };
24005 if chars.next().is_some() {
24006 return false;
24007 }
24008
24009 let buffer = buffer.read(cx);
24010 let snapshot = buffer.snapshot();
24011 let classifier = snapshot
24012 .char_classifier_at(position)
24013 .scope_context(Some(CharScopeContext::Completion));
24014 if trigger_in_words && classifier.is_word(char) {
24015 return true;
24016 }
24017
24018 buffer.completion_triggers().contains(text)
24019 }
24020
24021 fn show_snippets(&self) -> bool {
24022 true
24023 }
24024}
24025
24026impl SemanticsProvider for Entity<Project> {
24027 fn hover(
24028 &self,
24029 buffer: &Entity<Buffer>,
24030 position: text::Anchor,
24031 cx: &mut App,
24032 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24033 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24034 }
24035
24036 fn document_highlights(
24037 &self,
24038 buffer: &Entity<Buffer>,
24039 position: text::Anchor,
24040 cx: &mut App,
24041 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24042 Some(self.update(cx, |project, cx| {
24043 project.document_highlights(buffer, position, cx)
24044 }))
24045 }
24046
24047 fn definitions(
24048 &self,
24049 buffer: &Entity<Buffer>,
24050 position: text::Anchor,
24051 kind: GotoDefinitionKind,
24052 cx: &mut App,
24053 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24054 Some(self.update(cx, |project, cx| match kind {
24055 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24056 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24057 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24058 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24059 }))
24060 }
24061
24062 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24063 self.update(cx, |project, cx| {
24064 if project
24065 .active_debug_session(cx)
24066 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24067 {
24068 return true;
24069 }
24070
24071 buffer.update(cx, |buffer, cx| {
24072 project.any_language_server_supports_inlay_hints(buffer, cx)
24073 })
24074 })
24075 }
24076
24077 fn inline_values(
24078 &self,
24079 buffer_handle: Entity<Buffer>,
24080 range: Range<text::Anchor>,
24081 cx: &mut App,
24082 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24083 self.update(cx, |project, cx| {
24084 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24085
24086 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24087 })
24088 }
24089
24090 fn applicable_inlay_chunks(
24091 &self,
24092 buffer: &Entity<Buffer>,
24093 ranges: &[Range<text::Anchor>],
24094 cx: &mut App,
24095 ) -> Vec<Range<BufferRow>> {
24096 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24097 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24098 })
24099 }
24100
24101 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24102 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24103 lsp_store.invalidate_inlay_hints(for_buffers)
24104 });
24105 }
24106
24107 fn inlay_hints(
24108 &self,
24109 invalidate: InvalidationStrategy,
24110 buffer: Entity<Buffer>,
24111 ranges: Vec<Range<text::Anchor>>,
24112 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24113 cx: &mut App,
24114 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24115 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24116 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24117 }))
24118 }
24119
24120 fn range_for_rename(
24121 &self,
24122 buffer: &Entity<Buffer>,
24123 position: text::Anchor,
24124 cx: &mut App,
24125 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24126 Some(self.update(cx, |project, cx| {
24127 let buffer = buffer.clone();
24128 let task = project.prepare_rename(buffer.clone(), position, cx);
24129 cx.spawn(async move |_, cx| {
24130 Ok(match task.await? {
24131 PrepareRenameResponse::Success(range) => Some(range),
24132 PrepareRenameResponse::InvalidPosition => None,
24133 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24134 // Fallback on using TreeSitter info to determine identifier range
24135 buffer.read_with(cx, |buffer, _| {
24136 let snapshot = buffer.snapshot();
24137 let (range, kind) = snapshot.surrounding_word(position, None);
24138 if kind != Some(CharKind::Word) {
24139 return None;
24140 }
24141 Some(
24142 snapshot.anchor_before(range.start)
24143 ..snapshot.anchor_after(range.end),
24144 )
24145 })?
24146 }
24147 })
24148 })
24149 }))
24150 }
24151
24152 fn perform_rename(
24153 &self,
24154 buffer: &Entity<Buffer>,
24155 position: text::Anchor,
24156 new_name: String,
24157 cx: &mut App,
24158 ) -> Option<Task<Result<ProjectTransaction>>> {
24159 Some(self.update(cx, |project, cx| {
24160 project.perform_rename(buffer.clone(), position, new_name, cx)
24161 }))
24162 }
24163}
24164
24165fn consume_contiguous_rows(
24166 contiguous_row_selections: &mut Vec<Selection<Point>>,
24167 selection: &Selection<Point>,
24168 display_map: &DisplaySnapshot,
24169 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24170) -> (MultiBufferRow, MultiBufferRow) {
24171 contiguous_row_selections.push(selection.clone());
24172 let start_row = starting_row(selection, display_map);
24173 let mut end_row = ending_row(selection, display_map);
24174
24175 while let Some(next_selection) = selections.peek() {
24176 if next_selection.start.row <= end_row.0 {
24177 end_row = ending_row(next_selection, display_map);
24178 contiguous_row_selections.push(selections.next().unwrap().clone());
24179 } else {
24180 break;
24181 }
24182 }
24183 (start_row, end_row)
24184}
24185
24186fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24187 if selection.start.column > 0 {
24188 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24189 } else {
24190 MultiBufferRow(selection.start.row)
24191 }
24192}
24193
24194fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24195 if next_selection.end.column > 0 || next_selection.is_empty() {
24196 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24197 } else {
24198 MultiBufferRow(next_selection.end.row)
24199 }
24200}
24201
24202impl EditorSnapshot {
24203 pub fn remote_selections_in_range<'a>(
24204 &'a self,
24205 range: &'a Range<Anchor>,
24206 collaboration_hub: &dyn CollaborationHub,
24207 cx: &'a App,
24208 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24209 let participant_names = collaboration_hub.user_names(cx);
24210 let participant_indices = collaboration_hub.user_participant_indices(cx);
24211 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24212 let collaborators_by_replica_id = collaborators_by_peer_id
24213 .values()
24214 .map(|collaborator| (collaborator.replica_id, collaborator))
24215 .collect::<HashMap<_, _>>();
24216 self.buffer_snapshot()
24217 .selections_in_range(range, false)
24218 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24219 if replica_id == ReplicaId::AGENT {
24220 Some(RemoteSelection {
24221 replica_id,
24222 selection,
24223 cursor_shape,
24224 line_mode,
24225 collaborator_id: CollaboratorId::Agent,
24226 user_name: Some("Agent".into()),
24227 color: cx.theme().players().agent(),
24228 })
24229 } else {
24230 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24231 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24232 let user_name = participant_names.get(&collaborator.user_id).cloned();
24233 Some(RemoteSelection {
24234 replica_id,
24235 selection,
24236 cursor_shape,
24237 line_mode,
24238 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24239 user_name,
24240 color: if let Some(index) = participant_index {
24241 cx.theme().players().color_for_participant(index.0)
24242 } else {
24243 cx.theme().players().absent()
24244 },
24245 })
24246 }
24247 })
24248 }
24249
24250 pub fn hunks_for_ranges(
24251 &self,
24252 ranges: impl IntoIterator<Item = Range<Point>>,
24253 ) -> Vec<MultiBufferDiffHunk> {
24254 let mut hunks = Vec::new();
24255 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24256 HashMap::default();
24257 for query_range in ranges {
24258 let query_rows =
24259 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24260 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24261 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24262 ) {
24263 // Include deleted hunks that are adjacent to the query range, because
24264 // otherwise they would be missed.
24265 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24266 if hunk.status().is_deleted() {
24267 intersects_range |= hunk.row_range.start == query_rows.end;
24268 intersects_range |= hunk.row_range.end == query_rows.start;
24269 }
24270 if intersects_range {
24271 if !processed_buffer_rows
24272 .entry(hunk.buffer_id)
24273 .or_default()
24274 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24275 {
24276 continue;
24277 }
24278 hunks.push(hunk);
24279 }
24280 }
24281 }
24282
24283 hunks
24284 }
24285
24286 fn display_diff_hunks_for_rows<'a>(
24287 &'a self,
24288 display_rows: Range<DisplayRow>,
24289 folded_buffers: &'a HashSet<BufferId>,
24290 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24291 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24292 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24293
24294 self.buffer_snapshot()
24295 .diff_hunks_in_range(buffer_start..buffer_end)
24296 .filter_map(|hunk| {
24297 if folded_buffers.contains(&hunk.buffer_id) {
24298 return None;
24299 }
24300
24301 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24302 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24303
24304 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24305 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24306
24307 let display_hunk = if hunk_display_start.column() != 0 {
24308 DisplayDiffHunk::Folded {
24309 display_row: hunk_display_start.row(),
24310 }
24311 } else {
24312 let mut end_row = hunk_display_end.row();
24313 if hunk_display_end.column() > 0 {
24314 end_row.0 += 1;
24315 }
24316 let is_created_file = hunk.is_created_file();
24317
24318 DisplayDiffHunk::Unfolded {
24319 status: hunk.status(),
24320 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24321 ..hunk.diff_base_byte_range.end.0,
24322 word_diffs: hunk.word_diffs,
24323 display_row_range: hunk_display_start.row()..end_row,
24324 multi_buffer_range: Anchor::range_in_buffer(
24325 hunk.excerpt_id,
24326 hunk.buffer_range,
24327 ),
24328 is_created_file,
24329 }
24330 };
24331
24332 Some(display_hunk)
24333 })
24334 }
24335
24336 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24337 self.display_snapshot
24338 .buffer_snapshot()
24339 .language_at(position)
24340 }
24341
24342 pub fn is_focused(&self) -> bool {
24343 self.is_focused
24344 }
24345
24346 pub fn placeholder_text(&self) -> Option<String> {
24347 self.placeholder_display_snapshot
24348 .as_ref()
24349 .map(|display_map| display_map.text())
24350 }
24351
24352 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24353 self.scroll_anchor.scroll_position(&self.display_snapshot)
24354 }
24355
24356 fn gutter_dimensions(
24357 &self,
24358 font_id: FontId,
24359 font_size: Pixels,
24360 max_line_number_width: Pixels,
24361 cx: &App,
24362 ) -> Option<GutterDimensions> {
24363 if !self.show_gutter {
24364 return None;
24365 }
24366
24367 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24368 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24369
24370 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24371 matches!(
24372 ProjectSettings::get_global(cx).git.git_gutter,
24373 GitGutterSetting::TrackedFiles
24374 )
24375 });
24376 let gutter_settings = EditorSettings::get_global(cx).gutter;
24377 let show_line_numbers = self
24378 .show_line_numbers
24379 .unwrap_or(gutter_settings.line_numbers);
24380 let line_gutter_width = if show_line_numbers {
24381 // Avoid flicker-like gutter resizes when the line number gains another digit by
24382 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24383 let min_width_for_number_on_gutter =
24384 ch_advance * gutter_settings.min_line_number_digits as f32;
24385 max_line_number_width.max(min_width_for_number_on_gutter)
24386 } else {
24387 0.0.into()
24388 };
24389
24390 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24391 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24392
24393 let git_blame_entries_width =
24394 self.git_blame_gutter_max_author_length
24395 .map(|max_author_length| {
24396 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24397 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24398
24399 /// The number of characters to dedicate to gaps and margins.
24400 const SPACING_WIDTH: usize = 4;
24401
24402 let max_char_count = max_author_length.min(renderer.max_author_length())
24403 + ::git::SHORT_SHA_LENGTH
24404 + MAX_RELATIVE_TIMESTAMP.len()
24405 + SPACING_WIDTH;
24406
24407 ch_advance * max_char_count
24408 });
24409
24410 let is_singleton = self.buffer_snapshot().is_singleton();
24411
24412 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24413 left_padding += if !is_singleton {
24414 ch_width * 4.0
24415 } else if show_runnables || show_breakpoints {
24416 ch_width * 3.0
24417 } else if show_git_gutter && show_line_numbers {
24418 ch_width * 2.0
24419 } else if show_git_gutter || show_line_numbers {
24420 ch_width
24421 } else {
24422 px(0.)
24423 };
24424
24425 let shows_folds = is_singleton && gutter_settings.folds;
24426
24427 let right_padding = if shows_folds && show_line_numbers {
24428 ch_width * 4.0
24429 } else if shows_folds || (!is_singleton && show_line_numbers) {
24430 ch_width * 3.0
24431 } else if show_line_numbers {
24432 ch_width
24433 } else {
24434 px(0.)
24435 };
24436
24437 Some(GutterDimensions {
24438 left_padding,
24439 right_padding,
24440 width: line_gutter_width + left_padding + right_padding,
24441 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24442 git_blame_entries_width,
24443 })
24444 }
24445
24446 pub fn render_crease_toggle(
24447 &self,
24448 buffer_row: MultiBufferRow,
24449 row_contains_cursor: bool,
24450 editor: Entity<Editor>,
24451 window: &mut Window,
24452 cx: &mut App,
24453 ) -> Option<AnyElement> {
24454 let folded = self.is_line_folded(buffer_row);
24455 let mut is_foldable = false;
24456
24457 if let Some(crease) = self
24458 .crease_snapshot
24459 .query_row(buffer_row, self.buffer_snapshot())
24460 {
24461 is_foldable = true;
24462 match crease {
24463 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24464 if let Some(render_toggle) = render_toggle {
24465 let toggle_callback =
24466 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24467 if folded {
24468 editor.update(cx, |editor, cx| {
24469 editor.fold_at(buffer_row, window, cx)
24470 });
24471 } else {
24472 editor.update(cx, |editor, cx| {
24473 editor.unfold_at(buffer_row, window, cx)
24474 });
24475 }
24476 });
24477 return Some((render_toggle)(
24478 buffer_row,
24479 folded,
24480 toggle_callback,
24481 window,
24482 cx,
24483 ));
24484 }
24485 }
24486 }
24487 }
24488
24489 is_foldable |= self.starts_indent(buffer_row);
24490
24491 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24492 Some(
24493 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24494 .toggle_state(folded)
24495 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24496 if folded {
24497 this.unfold_at(buffer_row, window, cx);
24498 } else {
24499 this.fold_at(buffer_row, window, cx);
24500 }
24501 }))
24502 .into_any_element(),
24503 )
24504 } else {
24505 None
24506 }
24507 }
24508
24509 pub fn render_crease_trailer(
24510 &self,
24511 buffer_row: MultiBufferRow,
24512 window: &mut Window,
24513 cx: &mut App,
24514 ) -> Option<AnyElement> {
24515 let folded = self.is_line_folded(buffer_row);
24516 if let Crease::Inline { render_trailer, .. } = self
24517 .crease_snapshot
24518 .query_row(buffer_row, self.buffer_snapshot())?
24519 {
24520 let render_trailer = render_trailer.as_ref()?;
24521 Some(render_trailer(buffer_row, folded, window, cx))
24522 } else {
24523 None
24524 }
24525 }
24526}
24527
24528impl Deref for EditorSnapshot {
24529 type Target = DisplaySnapshot;
24530
24531 fn deref(&self) -> &Self::Target {
24532 &self.display_snapshot
24533 }
24534}
24535
24536#[derive(Clone, Debug, PartialEq, Eq)]
24537pub enum EditorEvent {
24538 InputIgnored {
24539 text: Arc<str>,
24540 },
24541 InputHandled {
24542 utf16_range_to_replace: Option<Range<isize>>,
24543 text: Arc<str>,
24544 },
24545 ExcerptsAdded {
24546 buffer: Entity<Buffer>,
24547 predecessor: ExcerptId,
24548 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24549 },
24550 ExcerptsRemoved {
24551 ids: Vec<ExcerptId>,
24552 removed_buffer_ids: Vec<BufferId>,
24553 },
24554 BufferFoldToggled {
24555 ids: Vec<ExcerptId>,
24556 folded: bool,
24557 },
24558 ExcerptsEdited {
24559 ids: Vec<ExcerptId>,
24560 },
24561 ExcerptsExpanded {
24562 ids: Vec<ExcerptId>,
24563 },
24564 BufferEdited,
24565 Edited {
24566 transaction_id: clock::Lamport,
24567 },
24568 Reparsed(BufferId),
24569 Focused,
24570 FocusedIn,
24571 Blurred,
24572 DirtyChanged,
24573 Saved,
24574 TitleChanged,
24575 SelectionsChanged {
24576 local: bool,
24577 },
24578 ScrollPositionChanged {
24579 local: bool,
24580 autoscroll: bool,
24581 },
24582 TransactionUndone {
24583 transaction_id: clock::Lamport,
24584 },
24585 TransactionBegun {
24586 transaction_id: clock::Lamport,
24587 },
24588 CursorShapeChanged,
24589 BreadcrumbsChanged,
24590 PushedToNavHistory {
24591 anchor: Anchor,
24592 is_deactivate: bool,
24593 },
24594}
24595
24596impl EventEmitter<EditorEvent> for Editor {}
24597
24598impl Focusable for Editor {
24599 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24600 self.focus_handle.clone()
24601 }
24602}
24603
24604impl Render for Editor {
24605 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24606 let settings = ThemeSettings::get_global(cx);
24607
24608 let mut text_style = match self.mode {
24609 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24610 color: cx.theme().colors().editor_foreground,
24611 font_family: settings.ui_font.family.clone(),
24612 font_features: settings.ui_font.features.clone(),
24613 font_fallbacks: settings.ui_font.fallbacks.clone(),
24614 font_size: rems(0.875).into(),
24615 font_weight: settings.ui_font.weight,
24616 line_height: relative(settings.buffer_line_height.value()),
24617 ..Default::default()
24618 },
24619 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24620 color: cx.theme().colors().editor_foreground,
24621 font_family: settings.buffer_font.family.clone(),
24622 font_features: settings.buffer_font.features.clone(),
24623 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24624 font_size: settings.buffer_font_size(cx).into(),
24625 font_weight: settings.buffer_font.weight,
24626 line_height: relative(settings.buffer_line_height.value()),
24627 ..Default::default()
24628 },
24629 };
24630 if let Some(text_style_refinement) = &self.text_style_refinement {
24631 text_style.refine(text_style_refinement)
24632 }
24633
24634 let background = match self.mode {
24635 EditorMode::SingleLine => cx.theme().system().transparent,
24636 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24637 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24638 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24639 };
24640
24641 EditorElement::new(
24642 &cx.entity(),
24643 EditorStyle {
24644 background,
24645 border: cx.theme().colors().border,
24646 local_player: cx.theme().players().local(),
24647 text: text_style,
24648 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24649 syntax: cx.theme().syntax().clone(),
24650 status: cx.theme().status().clone(),
24651 inlay_hints_style: make_inlay_hints_style(cx),
24652 edit_prediction_styles: make_suggestion_styles(cx),
24653 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24654 show_underlines: self.diagnostics_enabled(),
24655 },
24656 )
24657 }
24658}
24659
24660impl EntityInputHandler for Editor {
24661 fn text_for_range(
24662 &mut self,
24663 range_utf16: Range<usize>,
24664 adjusted_range: &mut Option<Range<usize>>,
24665 _: &mut Window,
24666 cx: &mut Context<Self>,
24667 ) -> Option<String> {
24668 let snapshot = self.buffer.read(cx).read(cx);
24669 let start = snapshot.clip_offset_utf16(
24670 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24671 Bias::Left,
24672 );
24673 let end = snapshot.clip_offset_utf16(
24674 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24675 Bias::Right,
24676 );
24677 if (start.0.0..end.0.0) != range_utf16 {
24678 adjusted_range.replace(start.0.0..end.0.0);
24679 }
24680 Some(snapshot.text_for_range(start..end).collect())
24681 }
24682
24683 fn selected_text_range(
24684 &mut self,
24685 ignore_disabled_input: bool,
24686 _: &mut Window,
24687 cx: &mut Context<Self>,
24688 ) -> Option<UTF16Selection> {
24689 // Prevent the IME menu from appearing when holding down an alphabetic key
24690 // while input is disabled.
24691 if !ignore_disabled_input && !self.input_enabled {
24692 return None;
24693 }
24694
24695 let selection = self
24696 .selections
24697 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24698 let range = selection.range();
24699
24700 Some(UTF16Selection {
24701 range: range.start.0.0..range.end.0.0,
24702 reversed: selection.reversed,
24703 })
24704 }
24705
24706 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24707 let snapshot = self.buffer.read(cx).read(cx);
24708 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24709 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24710 }
24711
24712 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24713 self.clear_highlights::<InputComposition>(cx);
24714 self.ime_transaction.take();
24715 }
24716
24717 fn replace_text_in_range(
24718 &mut self,
24719 range_utf16: Option<Range<usize>>,
24720 text: &str,
24721 window: &mut Window,
24722 cx: &mut Context<Self>,
24723 ) {
24724 if !self.input_enabled {
24725 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24726 return;
24727 }
24728
24729 self.transact(window, cx, |this, window, cx| {
24730 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24731 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24732 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24733 Some(this.selection_replacement_ranges(range_utf16, cx))
24734 } else {
24735 this.marked_text_ranges(cx)
24736 };
24737
24738 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24739 let newest_selection_id = this.selections.newest_anchor().id;
24740 this.selections
24741 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24742 .iter()
24743 .zip(ranges_to_replace.iter())
24744 .find_map(|(selection, range)| {
24745 if selection.id == newest_selection_id {
24746 Some(
24747 (range.start.0.0 as isize - selection.head().0.0 as isize)
24748 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24749 )
24750 } else {
24751 None
24752 }
24753 })
24754 });
24755
24756 cx.emit(EditorEvent::InputHandled {
24757 utf16_range_to_replace: range_to_replace,
24758 text: text.into(),
24759 });
24760
24761 if let Some(new_selected_ranges) = new_selected_ranges {
24762 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24763 selections.select_ranges(new_selected_ranges)
24764 });
24765 this.backspace(&Default::default(), window, cx);
24766 }
24767
24768 this.handle_input(text, window, cx);
24769 });
24770
24771 if let Some(transaction) = self.ime_transaction {
24772 self.buffer.update(cx, |buffer, cx| {
24773 buffer.group_until_transaction(transaction, cx);
24774 });
24775 }
24776
24777 self.unmark_text(window, cx);
24778 }
24779
24780 fn replace_and_mark_text_in_range(
24781 &mut self,
24782 range_utf16: Option<Range<usize>>,
24783 text: &str,
24784 new_selected_range_utf16: Option<Range<usize>>,
24785 window: &mut Window,
24786 cx: &mut Context<Self>,
24787 ) {
24788 if !self.input_enabled {
24789 return;
24790 }
24791
24792 let transaction = self.transact(window, cx, |this, window, cx| {
24793 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24794 let snapshot = this.buffer.read(cx).read(cx);
24795 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24796 for marked_range in &mut marked_ranges {
24797 marked_range.end = marked_range.start + relative_range_utf16.end;
24798 marked_range.start += relative_range_utf16.start;
24799 marked_range.start =
24800 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24801 marked_range.end =
24802 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24803 }
24804 }
24805 Some(marked_ranges)
24806 } else if let Some(range_utf16) = range_utf16 {
24807 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24808 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24809 Some(this.selection_replacement_ranges(range_utf16, cx))
24810 } else {
24811 None
24812 };
24813
24814 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24815 let newest_selection_id = this.selections.newest_anchor().id;
24816 this.selections
24817 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24818 .iter()
24819 .zip(ranges_to_replace.iter())
24820 .find_map(|(selection, range)| {
24821 if selection.id == newest_selection_id {
24822 Some(
24823 (range.start.0.0 as isize - selection.head().0.0 as isize)
24824 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24825 )
24826 } else {
24827 None
24828 }
24829 })
24830 });
24831
24832 cx.emit(EditorEvent::InputHandled {
24833 utf16_range_to_replace: range_to_replace,
24834 text: text.into(),
24835 });
24836
24837 if let Some(ranges) = ranges_to_replace {
24838 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24839 s.select_ranges(ranges)
24840 });
24841 }
24842
24843 let marked_ranges = {
24844 let snapshot = this.buffer.read(cx).read(cx);
24845 this.selections
24846 .disjoint_anchors_arc()
24847 .iter()
24848 .map(|selection| {
24849 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24850 })
24851 .collect::<Vec<_>>()
24852 };
24853
24854 if text.is_empty() {
24855 this.unmark_text(window, cx);
24856 } else {
24857 this.highlight_text::<InputComposition>(
24858 marked_ranges.clone(),
24859 HighlightStyle {
24860 underline: Some(UnderlineStyle {
24861 thickness: px(1.),
24862 color: None,
24863 wavy: false,
24864 }),
24865 ..Default::default()
24866 },
24867 cx,
24868 );
24869 }
24870
24871 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24872 let use_autoclose = this.use_autoclose;
24873 let use_auto_surround = this.use_auto_surround;
24874 this.set_use_autoclose(false);
24875 this.set_use_auto_surround(false);
24876 this.handle_input(text, window, cx);
24877 this.set_use_autoclose(use_autoclose);
24878 this.set_use_auto_surround(use_auto_surround);
24879
24880 if let Some(new_selected_range) = new_selected_range_utf16 {
24881 let snapshot = this.buffer.read(cx).read(cx);
24882 let new_selected_ranges = marked_ranges
24883 .into_iter()
24884 .map(|marked_range| {
24885 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24886 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24887 insertion_start.0 + new_selected_range.start,
24888 ));
24889 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24890 insertion_start.0 + new_selected_range.end,
24891 ));
24892 snapshot.clip_offset_utf16(new_start, Bias::Left)
24893 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24894 })
24895 .collect::<Vec<_>>();
24896
24897 drop(snapshot);
24898 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24899 selections.select_ranges(new_selected_ranges)
24900 });
24901 }
24902 });
24903
24904 self.ime_transaction = self.ime_transaction.or(transaction);
24905 if let Some(transaction) = self.ime_transaction {
24906 self.buffer.update(cx, |buffer, cx| {
24907 buffer.group_until_transaction(transaction, cx);
24908 });
24909 }
24910
24911 if self.text_highlights::<InputComposition>(cx).is_none() {
24912 self.ime_transaction.take();
24913 }
24914 }
24915
24916 fn bounds_for_range(
24917 &mut self,
24918 range_utf16: Range<usize>,
24919 element_bounds: gpui::Bounds<Pixels>,
24920 window: &mut Window,
24921 cx: &mut Context<Self>,
24922 ) -> Option<gpui::Bounds<Pixels>> {
24923 let text_layout_details = self.text_layout_details(window);
24924 let CharacterDimensions {
24925 em_width,
24926 em_advance,
24927 line_height,
24928 } = self.character_dimensions(window);
24929
24930 let snapshot = self.snapshot(window, cx);
24931 let scroll_position = snapshot.scroll_position();
24932 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24933
24934 let start =
24935 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24936 let x = Pixels::from(
24937 ScrollOffset::from(
24938 snapshot.x_for_display_point(start, &text_layout_details)
24939 + self.gutter_dimensions.full_width(),
24940 ) - scroll_left,
24941 );
24942 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24943
24944 Some(Bounds {
24945 origin: element_bounds.origin + point(x, y),
24946 size: size(em_width, line_height),
24947 })
24948 }
24949
24950 fn character_index_for_point(
24951 &mut self,
24952 point: gpui::Point<Pixels>,
24953 _window: &mut Window,
24954 _cx: &mut Context<Self>,
24955 ) -> Option<usize> {
24956 let position_map = self.last_position_map.as_ref()?;
24957 if !position_map.text_hitbox.contains(&point) {
24958 return None;
24959 }
24960 let display_point = position_map.point_for_position(point).previous_valid;
24961 let anchor = position_map
24962 .snapshot
24963 .display_point_to_anchor(display_point, Bias::Left);
24964 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24965 Some(utf16_offset.0.0)
24966 }
24967
24968 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24969 self.input_enabled
24970 }
24971}
24972
24973trait SelectionExt {
24974 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24975 fn spanned_rows(
24976 &self,
24977 include_end_if_at_line_start: bool,
24978 map: &DisplaySnapshot,
24979 ) -> Range<MultiBufferRow>;
24980}
24981
24982impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24983 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24984 let start = self
24985 .start
24986 .to_point(map.buffer_snapshot())
24987 .to_display_point(map);
24988 let end = self
24989 .end
24990 .to_point(map.buffer_snapshot())
24991 .to_display_point(map);
24992 if self.reversed {
24993 end..start
24994 } else {
24995 start..end
24996 }
24997 }
24998
24999 fn spanned_rows(
25000 &self,
25001 include_end_if_at_line_start: bool,
25002 map: &DisplaySnapshot,
25003 ) -> Range<MultiBufferRow> {
25004 let start = self.start.to_point(map.buffer_snapshot());
25005 let mut end = self.end.to_point(map.buffer_snapshot());
25006 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25007 end.row -= 1;
25008 }
25009
25010 let buffer_start = map.prev_line_boundary(start).0;
25011 let buffer_end = map.next_line_boundary(end).0;
25012 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25013 }
25014}
25015
25016impl<T: InvalidationRegion> InvalidationStack<T> {
25017 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25018 where
25019 S: Clone + ToOffset,
25020 {
25021 while let Some(region) = self.last() {
25022 let all_selections_inside_invalidation_ranges =
25023 if selections.len() == region.ranges().len() {
25024 selections
25025 .iter()
25026 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25027 .all(|(selection, invalidation_range)| {
25028 let head = selection.head().to_offset(buffer);
25029 invalidation_range.start <= head && invalidation_range.end >= head
25030 })
25031 } else {
25032 false
25033 };
25034
25035 if all_selections_inside_invalidation_ranges {
25036 break;
25037 } else {
25038 self.pop();
25039 }
25040 }
25041 }
25042}
25043
25044impl<T> Default for InvalidationStack<T> {
25045 fn default() -> Self {
25046 Self(Default::default())
25047 }
25048}
25049
25050impl<T> Deref for InvalidationStack<T> {
25051 type Target = Vec<T>;
25052
25053 fn deref(&self) -> &Self::Target {
25054 &self.0
25055 }
25056}
25057
25058impl<T> DerefMut for InvalidationStack<T> {
25059 fn deref_mut(&mut self) -> &mut Self::Target {
25060 &mut self.0
25061 }
25062}
25063
25064impl InvalidationRegion for SnippetState {
25065 fn ranges(&self) -> &[Range<Anchor>] {
25066 &self.ranges[self.active_index]
25067 }
25068}
25069
25070fn edit_prediction_edit_text(
25071 current_snapshot: &BufferSnapshot,
25072 edits: &[(Range<Anchor>, impl AsRef<str>)],
25073 edit_preview: &EditPreview,
25074 include_deletions: bool,
25075 cx: &App,
25076) -> HighlightedText {
25077 let edits = edits
25078 .iter()
25079 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25080 .collect::<Vec<_>>();
25081
25082 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25083}
25084
25085fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25086 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25087 // Just show the raw edit text with basic styling
25088 let mut text = String::new();
25089 let mut highlights = Vec::new();
25090
25091 let insertion_highlight_style = HighlightStyle {
25092 color: Some(cx.theme().colors().text),
25093 ..Default::default()
25094 };
25095
25096 for (_, edit_text) in edits {
25097 let start_offset = text.len();
25098 text.push_str(edit_text);
25099 let end_offset = text.len();
25100
25101 if start_offset < end_offset {
25102 highlights.push((start_offset..end_offset, insertion_highlight_style));
25103 }
25104 }
25105
25106 HighlightedText {
25107 text: text.into(),
25108 highlights,
25109 }
25110}
25111
25112pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25113 match severity {
25114 lsp::DiagnosticSeverity::ERROR => colors.error,
25115 lsp::DiagnosticSeverity::WARNING => colors.warning,
25116 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25117 lsp::DiagnosticSeverity::HINT => colors.info,
25118 _ => colors.ignored,
25119 }
25120}
25121
25122pub fn styled_runs_for_code_label<'a>(
25123 label: &'a CodeLabel,
25124 syntax_theme: &'a theme::SyntaxTheme,
25125 local_player: &'a theme::PlayerColor,
25126) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25127 let fade_out = HighlightStyle {
25128 fade_out: Some(0.35),
25129 ..Default::default()
25130 };
25131
25132 let mut prev_end = label.filter_range.end;
25133 label
25134 .runs
25135 .iter()
25136 .enumerate()
25137 .flat_map(move |(ix, (range, highlight_id))| {
25138 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25139 HighlightStyle {
25140 color: Some(local_player.cursor),
25141 ..Default::default()
25142 }
25143 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25144 HighlightStyle {
25145 background_color: Some(local_player.selection),
25146 ..Default::default()
25147 }
25148 } else if let Some(style) = highlight_id.style(syntax_theme) {
25149 style
25150 } else {
25151 return Default::default();
25152 };
25153 let muted_style = style.highlight(fade_out);
25154
25155 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25156 if range.start >= label.filter_range.end {
25157 if range.start > prev_end {
25158 runs.push((prev_end..range.start, fade_out));
25159 }
25160 runs.push((range.clone(), muted_style));
25161 } else if range.end <= label.filter_range.end {
25162 runs.push((range.clone(), style));
25163 } else {
25164 runs.push((range.start..label.filter_range.end, style));
25165 runs.push((label.filter_range.end..range.end, muted_style));
25166 }
25167 prev_end = cmp::max(prev_end, range.end);
25168
25169 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25170 runs.push((prev_end..label.text.len(), fade_out));
25171 }
25172
25173 runs
25174 })
25175}
25176
25177pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25178 let mut prev_index = 0;
25179 let mut prev_codepoint: Option<char> = None;
25180 text.char_indices()
25181 .chain([(text.len(), '\0')])
25182 .filter_map(move |(index, codepoint)| {
25183 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25184 let is_boundary = index == text.len()
25185 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25186 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25187 if is_boundary {
25188 let chunk = &text[prev_index..index];
25189 prev_index = index;
25190 Some(chunk)
25191 } else {
25192 None
25193 }
25194 })
25195}
25196
25197/// Given a string of text immediately before the cursor, iterates over possible
25198/// strings a snippet could match to. More precisely: returns an iterator over
25199/// suffixes of `text` created by splitting at word boundaries (before & after
25200/// every non-word character).
25201///
25202/// Shorter suffixes are returned first.
25203pub(crate) fn snippet_candidate_suffixes(
25204 text: &str,
25205 is_word_char: impl Fn(char) -> bool,
25206) -> impl std::iter::Iterator<Item = &str> {
25207 let mut prev_index = text.len();
25208 let mut prev_codepoint = None;
25209 text.char_indices()
25210 .rev()
25211 .chain([(0, '\0')])
25212 .filter_map(move |(index, codepoint)| {
25213 let prev_index = std::mem::replace(&mut prev_index, index);
25214 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25215 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25216 None
25217 } else {
25218 let chunk = &text[prev_index..]; // go to end of string
25219 Some(chunk)
25220 }
25221 })
25222}
25223
25224pub trait RangeToAnchorExt: Sized {
25225 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25226
25227 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25228 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25229 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25230 }
25231}
25232
25233impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25234 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25235 let start_offset = self.start.to_offset(snapshot);
25236 let end_offset = self.end.to_offset(snapshot);
25237 if start_offset == end_offset {
25238 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25239 } else {
25240 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25241 }
25242 }
25243}
25244
25245pub trait RowExt {
25246 fn as_f64(&self) -> f64;
25247
25248 fn next_row(&self) -> Self;
25249
25250 fn previous_row(&self) -> Self;
25251
25252 fn minus(&self, other: Self) -> u32;
25253}
25254
25255impl RowExt for DisplayRow {
25256 fn as_f64(&self) -> f64 {
25257 self.0 as _
25258 }
25259
25260 fn next_row(&self) -> Self {
25261 Self(self.0 + 1)
25262 }
25263
25264 fn previous_row(&self) -> Self {
25265 Self(self.0.saturating_sub(1))
25266 }
25267
25268 fn minus(&self, other: Self) -> u32 {
25269 self.0 - other.0
25270 }
25271}
25272
25273impl RowExt for MultiBufferRow {
25274 fn as_f64(&self) -> f64 {
25275 self.0 as _
25276 }
25277
25278 fn next_row(&self) -> Self {
25279 Self(self.0 + 1)
25280 }
25281
25282 fn previous_row(&self) -> Self {
25283 Self(self.0.saturating_sub(1))
25284 }
25285
25286 fn minus(&self, other: Self) -> u32 {
25287 self.0 - other.0
25288 }
25289}
25290
25291trait RowRangeExt {
25292 type Row;
25293
25294 fn len(&self) -> usize;
25295
25296 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25297}
25298
25299impl RowRangeExt for Range<MultiBufferRow> {
25300 type Row = MultiBufferRow;
25301
25302 fn len(&self) -> usize {
25303 (self.end.0 - self.start.0) as usize
25304 }
25305
25306 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25307 (self.start.0..self.end.0).map(MultiBufferRow)
25308 }
25309}
25310
25311impl RowRangeExt for Range<DisplayRow> {
25312 type Row = DisplayRow;
25313
25314 fn len(&self) -> usize {
25315 (self.end.0 - self.start.0) as usize
25316 }
25317
25318 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25319 (self.start.0..self.end.0).map(DisplayRow)
25320 }
25321}
25322
25323/// If select range has more than one line, we
25324/// just point the cursor to range.start.
25325fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25326 if range.start.row == range.end.row {
25327 range
25328 } else {
25329 range.start..range.start
25330 }
25331}
25332pub struct KillRing(ClipboardItem);
25333impl Global for KillRing {}
25334
25335const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25336
25337enum BreakpointPromptEditAction {
25338 Log,
25339 Condition,
25340 HitCondition,
25341}
25342
25343struct BreakpointPromptEditor {
25344 pub(crate) prompt: Entity<Editor>,
25345 editor: WeakEntity<Editor>,
25346 breakpoint_anchor: Anchor,
25347 breakpoint: Breakpoint,
25348 edit_action: BreakpointPromptEditAction,
25349 block_ids: HashSet<CustomBlockId>,
25350 editor_margins: Arc<Mutex<EditorMargins>>,
25351 _subscriptions: Vec<Subscription>,
25352}
25353
25354impl BreakpointPromptEditor {
25355 const MAX_LINES: u8 = 4;
25356
25357 fn new(
25358 editor: WeakEntity<Editor>,
25359 breakpoint_anchor: Anchor,
25360 breakpoint: Breakpoint,
25361 edit_action: BreakpointPromptEditAction,
25362 window: &mut Window,
25363 cx: &mut Context<Self>,
25364 ) -> Self {
25365 let base_text = match edit_action {
25366 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25367 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25368 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25369 }
25370 .map(|msg| msg.to_string())
25371 .unwrap_or_default();
25372
25373 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25374 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25375
25376 let prompt = cx.new(|cx| {
25377 let mut prompt = Editor::new(
25378 EditorMode::AutoHeight {
25379 min_lines: 1,
25380 max_lines: Some(Self::MAX_LINES as usize),
25381 },
25382 buffer,
25383 None,
25384 window,
25385 cx,
25386 );
25387 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25388 prompt.set_show_cursor_when_unfocused(false, cx);
25389 prompt.set_placeholder_text(
25390 match edit_action {
25391 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25392 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25393 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25394 },
25395 window,
25396 cx,
25397 );
25398
25399 prompt
25400 });
25401
25402 Self {
25403 prompt,
25404 editor,
25405 breakpoint_anchor,
25406 breakpoint,
25407 edit_action,
25408 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25409 block_ids: Default::default(),
25410 _subscriptions: vec![],
25411 }
25412 }
25413
25414 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25415 self.block_ids.extend(block_ids)
25416 }
25417
25418 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25419 if let Some(editor) = self.editor.upgrade() {
25420 let message = self
25421 .prompt
25422 .read(cx)
25423 .buffer
25424 .read(cx)
25425 .as_singleton()
25426 .expect("A multi buffer in breakpoint prompt isn't possible")
25427 .read(cx)
25428 .as_rope()
25429 .to_string();
25430
25431 editor.update(cx, |editor, cx| {
25432 editor.edit_breakpoint_at_anchor(
25433 self.breakpoint_anchor,
25434 self.breakpoint.clone(),
25435 match self.edit_action {
25436 BreakpointPromptEditAction::Log => {
25437 BreakpointEditAction::EditLogMessage(message.into())
25438 }
25439 BreakpointPromptEditAction::Condition => {
25440 BreakpointEditAction::EditCondition(message.into())
25441 }
25442 BreakpointPromptEditAction::HitCondition => {
25443 BreakpointEditAction::EditHitCondition(message.into())
25444 }
25445 },
25446 cx,
25447 );
25448
25449 editor.remove_blocks(self.block_ids.clone(), None, cx);
25450 cx.focus_self(window);
25451 });
25452 }
25453 }
25454
25455 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25456 self.editor
25457 .update(cx, |editor, cx| {
25458 editor.remove_blocks(self.block_ids.clone(), None, cx);
25459 window.focus(&editor.focus_handle);
25460 })
25461 .log_err();
25462 }
25463
25464 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25465 let settings = ThemeSettings::get_global(cx);
25466 let text_style = TextStyle {
25467 color: if self.prompt.read(cx).read_only(cx) {
25468 cx.theme().colors().text_disabled
25469 } else {
25470 cx.theme().colors().text
25471 },
25472 font_family: settings.buffer_font.family.clone(),
25473 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25474 font_size: settings.buffer_font_size(cx).into(),
25475 font_weight: settings.buffer_font.weight,
25476 line_height: relative(settings.buffer_line_height.value()),
25477 ..Default::default()
25478 };
25479 EditorElement::new(
25480 &self.prompt,
25481 EditorStyle {
25482 background: cx.theme().colors().editor_background,
25483 local_player: cx.theme().players().local(),
25484 text: text_style,
25485 ..Default::default()
25486 },
25487 )
25488 }
25489}
25490
25491impl Render for BreakpointPromptEditor {
25492 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25493 let editor_margins = *self.editor_margins.lock();
25494 let gutter_dimensions = editor_margins.gutter;
25495 h_flex()
25496 .key_context("Editor")
25497 .bg(cx.theme().colors().editor_background)
25498 .border_y_1()
25499 .border_color(cx.theme().status().info_border)
25500 .size_full()
25501 .py(window.line_height() / 2.5)
25502 .on_action(cx.listener(Self::confirm))
25503 .on_action(cx.listener(Self::cancel))
25504 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25505 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25506 }
25507}
25508
25509impl Focusable for BreakpointPromptEditor {
25510 fn focus_handle(&self, cx: &App) -> FocusHandle {
25511 self.prompt.focus_handle(cx)
25512 }
25513}
25514
25515fn all_edits_insertions_or_deletions(
25516 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25517 snapshot: &MultiBufferSnapshot,
25518) -> bool {
25519 let mut all_insertions = true;
25520 let mut all_deletions = true;
25521
25522 for (range, new_text) in edits.iter() {
25523 let range_is_empty = range.to_offset(snapshot).is_empty();
25524 let text_is_empty = new_text.is_empty();
25525
25526 if range_is_empty != text_is_empty {
25527 if range_is_empty {
25528 all_deletions = false;
25529 } else {
25530 all_insertions = false;
25531 }
25532 } else {
25533 return false;
25534 }
25535
25536 if !all_insertions && !all_deletions {
25537 return false;
25538 }
25539 }
25540 all_insertions || all_deletions
25541}
25542
25543struct MissingEditPredictionKeybindingTooltip;
25544
25545impl Render for MissingEditPredictionKeybindingTooltip {
25546 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25547 ui::tooltip_container(cx, |container, cx| {
25548 container
25549 .flex_shrink_0()
25550 .max_w_80()
25551 .min_h(rems_from_px(124.))
25552 .justify_between()
25553 .child(
25554 v_flex()
25555 .flex_1()
25556 .text_ui_sm(cx)
25557 .child(Label::new("Conflict with Accept Keybinding"))
25558 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25559 )
25560 .child(
25561 h_flex()
25562 .pb_1()
25563 .gap_1()
25564 .items_end()
25565 .w_full()
25566 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25567 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25568 }))
25569 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25570 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25571 })),
25572 )
25573 })
25574 }
25575}
25576
25577#[derive(Debug, Clone, Copy, PartialEq)]
25578pub struct LineHighlight {
25579 pub background: Background,
25580 pub border: Option<gpui::Hsla>,
25581 pub include_gutter: bool,
25582 pub type_id: Option<TypeId>,
25583}
25584
25585struct LineManipulationResult {
25586 pub new_text: String,
25587 pub line_count_before: usize,
25588 pub line_count_after: usize,
25589}
25590
25591fn render_diff_hunk_controls(
25592 row: u32,
25593 status: &DiffHunkStatus,
25594 hunk_range: Range<Anchor>,
25595 is_created_file: bool,
25596 line_height: Pixels,
25597 editor: &Entity<Editor>,
25598 _window: &mut Window,
25599 cx: &mut App,
25600) -> AnyElement {
25601 h_flex()
25602 .h(line_height)
25603 .mr_1()
25604 .gap_1()
25605 .px_0p5()
25606 .pb_1()
25607 .border_x_1()
25608 .border_b_1()
25609 .border_color(cx.theme().colors().border_variant)
25610 .rounded_b_lg()
25611 .bg(cx.theme().colors().editor_background)
25612 .gap_1()
25613 .block_mouse_except_scroll()
25614 .shadow_md()
25615 .child(if status.has_secondary_hunk() {
25616 Button::new(("stage", row as u64), "Stage")
25617 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25618 .tooltip({
25619 let focus_handle = editor.focus_handle(cx);
25620 move |_window, cx| {
25621 Tooltip::for_action_in(
25622 "Stage Hunk",
25623 &::git::ToggleStaged,
25624 &focus_handle,
25625 cx,
25626 )
25627 }
25628 })
25629 .on_click({
25630 let editor = editor.clone();
25631 move |_event, _window, cx| {
25632 editor.update(cx, |editor, cx| {
25633 editor.stage_or_unstage_diff_hunks(
25634 true,
25635 vec![hunk_range.start..hunk_range.start],
25636 cx,
25637 );
25638 });
25639 }
25640 })
25641 } else {
25642 Button::new(("unstage", row as u64), "Unstage")
25643 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25644 .tooltip({
25645 let focus_handle = editor.focus_handle(cx);
25646 move |_window, cx| {
25647 Tooltip::for_action_in(
25648 "Unstage Hunk",
25649 &::git::ToggleStaged,
25650 &focus_handle,
25651 cx,
25652 )
25653 }
25654 })
25655 .on_click({
25656 let editor = editor.clone();
25657 move |_event, _window, cx| {
25658 editor.update(cx, |editor, cx| {
25659 editor.stage_or_unstage_diff_hunks(
25660 false,
25661 vec![hunk_range.start..hunk_range.start],
25662 cx,
25663 );
25664 });
25665 }
25666 })
25667 })
25668 .child(
25669 Button::new(("restore", row as u64), "Restore")
25670 .tooltip({
25671 let focus_handle = editor.focus_handle(cx);
25672 move |_window, cx| {
25673 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25674 }
25675 })
25676 .on_click({
25677 let editor = editor.clone();
25678 move |_event, window, cx| {
25679 editor.update(cx, |editor, cx| {
25680 let snapshot = editor.snapshot(window, cx);
25681 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25682 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25683 });
25684 }
25685 })
25686 .disabled(is_created_file),
25687 )
25688 .when(
25689 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25690 |el| {
25691 el.child(
25692 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25693 .shape(IconButtonShape::Square)
25694 .icon_size(IconSize::Small)
25695 // .disabled(!has_multiple_hunks)
25696 .tooltip({
25697 let focus_handle = editor.focus_handle(cx);
25698 move |_window, cx| {
25699 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25700 }
25701 })
25702 .on_click({
25703 let editor = editor.clone();
25704 move |_event, window, cx| {
25705 editor.update(cx, |editor, cx| {
25706 let snapshot = editor.snapshot(window, cx);
25707 let position =
25708 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25709 editor.go_to_hunk_before_or_after_position(
25710 &snapshot,
25711 position,
25712 Direction::Next,
25713 window,
25714 cx,
25715 );
25716 editor.expand_selected_diff_hunks(cx);
25717 });
25718 }
25719 }),
25720 )
25721 .child(
25722 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25723 .shape(IconButtonShape::Square)
25724 .icon_size(IconSize::Small)
25725 // .disabled(!has_multiple_hunks)
25726 .tooltip({
25727 let focus_handle = editor.focus_handle(cx);
25728 move |_window, cx| {
25729 Tooltip::for_action_in(
25730 "Previous Hunk",
25731 &GoToPreviousHunk,
25732 &focus_handle,
25733 cx,
25734 )
25735 }
25736 })
25737 .on_click({
25738 let editor = editor.clone();
25739 move |_event, window, cx| {
25740 editor.update(cx, |editor, cx| {
25741 let snapshot = editor.snapshot(window, cx);
25742 let point =
25743 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25744 editor.go_to_hunk_before_or_after_position(
25745 &snapshot,
25746 point,
25747 Direction::Prev,
25748 window,
25749 cx,
25750 );
25751 editor.expand_selected_diff_hunks(cx);
25752 });
25753 }
25754 }),
25755 )
25756 },
25757 )
25758 .into_any_element()
25759}
25760
25761pub fn multibuffer_context_lines(cx: &App) -> u32 {
25762 EditorSettings::try_get(cx)
25763 .map(|settings| settings.excerpt_context_lines)
25764 .unwrap_or(2)
25765 .min(32)
25766}