1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79 status::FileStatus,
80};
81use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
82use anyhow::{Context as _, Result, anyhow, bail};
83use blink_manager::BlinkManager;
84use buffer_diff::DiffHunkStatus;
85use client::{Collaborator, ParticipantIndex, parse_zed_link};
86use clock::ReplicaId;
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use collections::{BTreeMap, HashMap, HashSet, VecDeque};
92use convert_case::{Case, Casing};
93use dap::TelemetrySpawnLocation;
94use display_map::*;
95use edit_prediction_types::{EditPredictionDelegate, EditPredictionDelegateHandle};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, 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 #[serde(default)]
1596 pub file_path: Option<PathBuf>,
1597 #[serde(default)]
1598 pub line_range: Option<RangeInclusive<u32>>,
1599}
1600
1601impl ClipboardSelection {
1602 pub fn for_buffer(
1603 len: usize,
1604 is_entire_line: bool,
1605 range: Range<Point>,
1606 buffer: &MultiBufferSnapshot,
1607 project: Option<&Entity<Project>>,
1608 cx: &App,
1609 ) -> Self {
1610 let first_line_indent = buffer
1611 .indent_size_for_line(MultiBufferRow(range.start.row))
1612 .len;
1613
1614 let file_path = util::maybe!({
1615 let project = project?.read(cx);
1616 let file = buffer.file_at(range.start)?;
1617 let project_path = ProjectPath {
1618 worktree_id: file.worktree_id(cx),
1619 path: file.path().clone(),
1620 };
1621 project.absolute_path(&project_path, cx)
1622 });
1623
1624 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1625
1626 Self {
1627 len,
1628 is_entire_line,
1629 first_line_indent,
1630 file_path,
1631 line_range,
1632 }
1633 }
1634}
1635
1636// selections, scroll behavior, was newest selection reversed
1637type SelectSyntaxNodeHistoryState = (
1638 Box<[Selection<MultiBufferOffset>]>,
1639 SelectSyntaxNodeScrollBehavior,
1640 bool,
1641);
1642
1643#[derive(Default)]
1644struct SelectSyntaxNodeHistory {
1645 stack: Vec<SelectSyntaxNodeHistoryState>,
1646 // disable temporarily to allow changing selections without losing the stack
1647 pub disable_clearing: bool,
1648}
1649
1650impl SelectSyntaxNodeHistory {
1651 pub fn try_clear(&mut self) {
1652 if !self.disable_clearing {
1653 self.stack.clear();
1654 }
1655 }
1656
1657 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1658 self.stack.push(selection);
1659 }
1660
1661 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1662 self.stack.pop()
1663 }
1664}
1665
1666enum SelectSyntaxNodeScrollBehavior {
1667 CursorTop,
1668 FitSelection,
1669 CursorBottom,
1670}
1671
1672#[derive(Debug)]
1673pub(crate) struct NavigationData {
1674 cursor_anchor: Anchor,
1675 cursor_position: Point,
1676 scroll_anchor: ScrollAnchor,
1677 scroll_top_row: u32,
1678}
1679
1680#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1681pub enum GotoDefinitionKind {
1682 Symbol,
1683 Declaration,
1684 Type,
1685 Implementation,
1686}
1687
1688pub enum FormatTarget {
1689 Buffers(HashSet<Entity<Buffer>>),
1690 Ranges(Vec<Range<MultiBufferPoint>>),
1691}
1692
1693pub(crate) struct FocusedBlock {
1694 id: BlockId,
1695 focus_handle: WeakFocusHandle,
1696}
1697
1698#[derive(Clone, Debug)]
1699enum JumpData {
1700 MultiBufferRow {
1701 row: MultiBufferRow,
1702 line_offset_from_top: u32,
1703 },
1704 MultiBufferPoint {
1705 excerpt_id: ExcerptId,
1706 position: Point,
1707 anchor: text::Anchor,
1708 line_offset_from_top: u32,
1709 },
1710}
1711
1712pub enum MultibufferSelectionMode {
1713 First,
1714 All,
1715}
1716
1717#[derive(Clone, Copy, Debug, Default)]
1718pub struct RewrapOptions {
1719 pub override_language_settings: bool,
1720 pub preserve_existing_whitespace: bool,
1721}
1722
1723impl Editor {
1724 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1725 let buffer = cx.new(|cx| Buffer::local("", cx));
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1728 }
1729
1730 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1731 let buffer = cx.new(|cx| Buffer::local("", cx));
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::full(), buffer, None, window, cx)
1734 }
1735
1736 pub fn auto_height(
1737 min_lines: usize,
1738 max_lines: usize,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| Buffer::local("", cx));
1743 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1744 Self::new(
1745 EditorMode::AutoHeight {
1746 min_lines,
1747 max_lines: Some(max_lines),
1748 },
1749 buffer,
1750 None,
1751 window,
1752 cx,
1753 )
1754 }
1755
1756 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1757 /// The editor grows as tall as needed to fit its content.
1758 pub fn auto_height_unbounded(
1759 min_lines: usize,
1760 window: &mut Window,
1761 cx: &mut Context<Self>,
1762 ) -> Self {
1763 let buffer = cx.new(|cx| Buffer::local("", cx));
1764 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1765 Self::new(
1766 EditorMode::AutoHeight {
1767 min_lines,
1768 max_lines: None,
1769 },
1770 buffer,
1771 None,
1772 window,
1773 cx,
1774 )
1775 }
1776
1777 pub fn for_buffer(
1778 buffer: Entity<Buffer>,
1779 project: Option<Entity<Project>>,
1780 window: &mut Window,
1781 cx: &mut Context<Self>,
1782 ) -> Self {
1783 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1784 Self::new(EditorMode::full(), buffer, project, window, cx)
1785 }
1786
1787 pub fn for_multibuffer(
1788 buffer: Entity<MultiBuffer>,
1789 project: Option<Entity<Project>>,
1790 window: &mut Window,
1791 cx: &mut Context<Self>,
1792 ) -> Self {
1793 Self::new(EditorMode::full(), buffer, project, window, cx)
1794 }
1795
1796 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1797 let mut clone = Self::new(
1798 self.mode.clone(),
1799 self.buffer.clone(),
1800 self.project.clone(),
1801 window,
1802 cx,
1803 );
1804 self.display_map.update(cx, |display_map, cx| {
1805 let snapshot = display_map.snapshot(cx);
1806 clone.display_map.update(cx, |display_map, cx| {
1807 display_map.set_state(&snapshot, cx);
1808 });
1809 });
1810 clone.folds_did_change(cx);
1811 clone.selections.clone_state(&self.selections);
1812 clone.scroll_manager.clone_state(&self.scroll_manager);
1813 clone.searchable = self.searchable;
1814 clone.read_only = self.read_only;
1815 clone
1816 }
1817
1818 pub fn new(
1819 mode: EditorMode,
1820 buffer: Entity<MultiBuffer>,
1821 project: Option<Entity<Project>>,
1822 window: &mut Window,
1823 cx: &mut Context<Self>,
1824 ) -> Self {
1825 Editor::new_internal(mode, buffer, project, None, window, cx)
1826 }
1827
1828 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1829 let multi_buffer = self.buffer().read(cx);
1830 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1831 let multi_buffer_visible_start = self
1832 .scroll_manager
1833 .anchor()
1834 .anchor
1835 .to_point(&multi_buffer_snapshot);
1836 let max_row = multi_buffer_snapshot.max_point().row;
1837
1838 let start_row = (multi_buffer_visible_start.row).min(max_row);
1839 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1840
1841 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1842 let outline_items = buffer
1843 .outline_items_containing(
1844 Point::new(start_row, 0)..Point::new(end_row, 0),
1845 true,
1846 self.style().map(|style| style.syntax.as_ref()),
1847 )
1848 .into_iter()
1849 .map(|outline_item| OutlineItem {
1850 depth: outline_item.depth,
1851 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1852 source_range_for_text: Anchor::range_in_buffer(
1853 *excerpt_id,
1854 outline_item.source_range_for_text,
1855 ),
1856 text: outline_item.text,
1857 highlight_ranges: outline_item.highlight_ranges,
1858 name_ranges: outline_item.name_ranges,
1859 body_range: outline_item
1860 .body_range
1861 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1862 annotation_range: outline_item
1863 .annotation_range
1864 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1865 });
1866 return Some(outline_items.collect());
1867 }
1868
1869 None
1870 }
1871
1872 fn new_internal(
1873 mode: EditorMode,
1874 multi_buffer: Entity<MultiBuffer>,
1875 project: Option<Entity<Project>>,
1876 display_map: Option<Entity<DisplayMap>>,
1877 window: &mut Window,
1878 cx: &mut Context<Self>,
1879 ) -> Self {
1880 debug_assert!(
1881 display_map.is_none() || mode.is_minimap(),
1882 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1883 );
1884
1885 let full_mode = mode.is_full();
1886 let is_minimap = mode.is_minimap();
1887 let diagnostics_max_severity = if full_mode {
1888 EditorSettings::get_global(cx)
1889 .diagnostics_max_severity
1890 .unwrap_or(DiagnosticSeverity::Hint)
1891 } else {
1892 DiagnosticSeverity::Off
1893 };
1894 let style = window.text_style();
1895 let font_size = style.font_size.to_pixels(window.rem_size());
1896 let editor = cx.entity().downgrade();
1897 let fold_placeholder = FoldPlaceholder {
1898 constrain_width: false,
1899 render: Arc::new(move |fold_id, fold_range, cx| {
1900 let editor = editor.clone();
1901 div()
1902 .id(fold_id)
1903 .bg(cx.theme().colors().ghost_element_background)
1904 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1905 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1906 .rounded_xs()
1907 .size_full()
1908 .cursor_pointer()
1909 .child("⋯")
1910 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1911 .on_click(move |_, _window, cx| {
1912 editor
1913 .update(cx, |editor, cx| {
1914 editor.unfold_ranges(
1915 &[fold_range.start..fold_range.end],
1916 true,
1917 false,
1918 cx,
1919 );
1920 cx.stop_propagation();
1921 })
1922 .ok();
1923 })
1924 .into_any()
1925 }),
1926 merge_adjacent: true,
1927 ..FoldPlaceholder::default()
1928 };
1929 let display_map = display_map.unwrap_or_else(|| {
1930 cx.new(|cx| {
1931 DisplayMap::new(
1932 multi_buffer.clone(),
1933 style.font(),
1934 font_size,
1935 None,
1936 FILE_HEADER_HEIGHT,
1937 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1938 fold_placeholder,
1939 diagnostics_max_severity,
1940 cx,
1941 )
1942 })
1943 });
1944
1945 let selections = SelectionsCollection::new();
1946
1947 let blink_manager = cx.new(|cx| {
1948 let mut blink_manager = BlinkManager::new(
1949 CURSOR_BLINK_INTERVAL,
1950 |cx| EditorSettings::get_global(cx).cursor_blink,
1951 cx,
1952 );
1953 if is_minimap {
1954 blink_manager.disable(cx);
1955 }
1956 blink_manager
1957 });
1958
1959 let soft_wrap_mode_override =
1960 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1961
1962 let mut project_subscriptions = Vec::new();
1963 if full_mode && let Some(project) = project.as_ref() {
1964 project_subscriptions.push(cx.subscribe_in(
1965 project,
1966 window,
1967 |editor, _, event, window, cx| match event {
1968 project::Event::RefreshCodeLens => {
1969 // we always query lens with actions, without storing them, always refreshing them
1970 }
1971 project::Event::RefreshInlayHints {
1972 server_id,
1973 request_id,
1974 } => {
1975 editor.refresh_inlay_hints(
1976 InlayHintRefreshReason::RefreshRequested {
1977 server_id: *server_id,
1978 request_id: *request_id,
1979 },
1980 cx,
1981 );
1982 }
1983 project::Event::LanguageServerRemoved(..) => {
1984 if editor.tasks_update_task.is_none() {
1985 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1986 }
1987 editor.registered_buffers.clear();
1988 editor.register_visible_buffers(cx);
1989 }
1990 project::Event::LanguageServerAdded(..) => {
1991 if editor.tasks_update_task.is_none() {
1992 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1993 }
1994 }
1995 project::Event::SnippetEdit(id, snippet_edits) => {
1996 // todo(lw): Non singletons
1997 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1998 let snapshot = buffer.read(cx).snapshot();
1999 let focus_handle = editor.focus_handle(cx);
2000 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2001 for (range, snippet) in snippet_edits {
2002 let buffer_range =
2003 language::range_from_lsp(*range).to_offset(&snapshot);
2004 editor
2005 .insert_snippet(
2006 &[MultiBufferOffset(buffer_range.start)
2007 ..MultiBufferOffset(buffer_range.end)],
2008 snippet.clone(),
2009 window,
2010 cx,
2011 )
2012 .ok();
2013 }
2014 }
2015 }
2016 }
2017 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2018 let buffer_id = *buffer_id;
2019 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2020 editor.register_buffer(buffer_id, cx);
2021 editor.update_lsp_data(Some(buffer_id), window, cx);
2022 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2023 refresh_linked_ranges(editor, window, cx);
2024 editor.refresh_code_actions(window, cx);
2025 editor.refresh_document_highlights(cx);
2026 }
2027 }
2028
2029 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2030 let Some(workspace) = editor.workspace() else {
2031 return;
2032 };
2033 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2034 else {
2035 return;
2036 };
2037
2038 if active_editor.entity_id() == cx.entity_id() {
2039 let entity_id = cx.entity_id();
2040 workspace.update(cx, |this, cx| {
2041 this.panes_mut()
2042 .iter_mut()
2043 .filter(|pane| pane.entity_id() != entity_id)
2044 .for_each(|p| {
2045 p.update(cx, |pane, _| {
2046 pane.nav_history_mut().rename_item(
2047 entity_id,
2048 project_path.clone(),
2049 abs_path.clone().into(),
2050 );
2051 })
2052 });
2053 });
2054 let edited_buffers_already_open = {
2055 let other_editors: Vec<Entity<Editor>> = workspace
2056 .read(cx)
2057 .panes()
2058 .iter()
2059 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2060 .filter(|editor| editor.entity_id() != cx.entity_id())
2061 .collect();
2062
2063 transaction.0.keys().all(|buffer| {
2064 other_editors.iter().any(|editor| {
2065 let multi_buffer = editor.read(cx).buffer();
2066 multi_buffer.read(cx).is_singleton()
2067 && multi_buffer.read(cx).as_singleton().map_or(
2068 false,
2069 |singleton| {
2070 singleton.entity_id() == buffer.entity_id()
2071 },
2072 )
2073 })
2074 })
2075 };
2076 if !edited_buffers_already_open {
2077 let workspace = workspace.downgrade();
2078 let transaction = transaction.clone();
2079 cx.defer_in(window, move |_, window, cx| {
2080 cx.spawn_in(window, async move |editor, cx| {
2081 Self::open_project_transaction(
2082 &editor,
2083 workspace,
2084 transaction,
2085 "Rename".to_string(),
2086 cx,
2087 )
2088 .await
2089 .ok()
2090 })
2091 .detach();
2092 });
2093 }
2094 }
2095 }
2096
2097 _ => {}
2098 },
2099 ));
2100 if let Some(task_inventory) = project
2101 .read(cx)
2102 .task_store()
2103 .read(cx)
2104 .task_inventory()
2105 .cloned()
2106 {
2107 project_subscriptions.push(cx.observe_in(
2108 &task_inventory,
2109 window,
2110 |editor, _, window, cx| {
2111 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2112 },
2113 ));
2114 };
2115
2116 project_subscriptions.push(cx.subscribe_in(
2117 &project.read(cx).breakpoint_store(),
2118 window,
2119 |editor, _, event, window, cx| match event {
2120 BreakpointStoreEvent::ClearDebugLines => {
2121 editor.clear_row_highlights::<ActiveDebugLine>();
2122 editor.refresh_inline_values(cx);
2123 }
2124 BreakpointStoreEvent::SetDebugLine => {
2125 if editor.go_to_active_debug_line(window, cx) {
2126 cx.stop_propagation();
2127 }
2128
2129 editor.refresh_inline_values(cx);
2130 }
2131 _ => {}
2132 },
2133 ));
2134 let git_store = project.read(cx).git_store().clone();
2135 let project = project.clone();
2136 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2137 if let GitStoreEvent::RepositoryAdded = event {
2138 this.load_diff_task = Some(
2139 update_uncommitted_diff_for_buffer(
2140 cx.entity(),
2141 &project,
2142 this.buffer.read(cx).all_buffers(),
2143 this.buffer.clone(),
2144 cx,
2145 )
2146 .shared(),
2147 );
2148 }
2149 }));
2150 }
2151
2152 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2153
2154 let inlay_hint_settings =
2155 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2156 let focus_handle = cx.focus_handle();
2157 if !is_minimap {
2158 cx.on_focus(&focus_handle, window, Self::handle_focus)
2159 .detach();
2160 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2161 .detach();
2162 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2163 .detach();
2164 cx.on_blur(&focus_handle, window, Self::handle_blur)
2165 .detach();
2166 cx.observe_pending_input(window, Self::observe_pending_input)
2167 .detach();
2168 }
2169
2170 let show_indent_guides =
2171 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2172 Some(false)
2173 } else {
2174 None
2175 };
2176
2177 let breakpoint_store = match (&mode, project.as_ref()) {
2178 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2179 _ => None,
2180 };
2181
2182 let mut code_action_providers = Vec::new();
2183 let mut load_uncommitted_diff = None;
2184 if let Some(project) = project.clone() {
2185 load_uncommitted_diff = Some(
2186 update_uncommitted_diff_for_buffer(
2187 cx.entity(),
2188 &project,
2189 multi_buffer.read(cx).all_buffers(),
2190 multi_buffer.clone(),
2191 cx,
2192 )
2193 .shared(),
2194 );
2195 code_action_providers.push(Rc::new(project) as Rc<_>);
2196 }
2197
2198 let mut editor = Self {
2199 focus_handle,
2200 show_cursor_when_unfocused: false,
2201 last_focused_descendant: None,
2202 buffer: multi_buffer.clone(),
2203 display_map: display_map.clone(),
2204 placeholder_display_map: None,
2205 selections,
2206 scroll_manager: ScrollManager::new(cx),
2207 columnar_selection_state: None,
2208 add_selections_state: None,
2209 select_next_state: None,
2210 select_prev_state: None,
2211 selection_history: SelectionHistory::default(),
2212 defer_selection_effects: false,
2213 deferred_selection_effects_state: None,
2214 autoclose_regions: Vec::new(),
2215 snippet_stack: InvalidationStack::default(),
2216 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2217 ime_transaction: None,
2218 active_diagnostics: ActiveDiagnostic::None,
2219 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2220 inline_diagnostics_update: Task::ready(()),
2221 inline_diagnostics: Vec::new(),
2222 soft_wrap_mode_override,
2223 diagnostics_max_severity,
2224 hard_wrap: None,
2225 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2226 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2227 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2228 project,
2229 blink_manager: blink_manager.clone(),
2230 show_local_selections: true,
2231 show_scrollbars: ScrollbarAxes {
2232 horizontal: full_mode,
2233 vertical: full_mode,
2234 },
2235 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2236 offset_content: !matches!(mode, EditorMode::SingleLine),
2237 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2238 show_gutter: full_mode,
2239 show_line_numbers: (!full_mode).then_some(false),
2240 use_relative_line_numbers: None,
2241 disable_expand_excerpt_buttons: !full_mode,
2242 show_git_diff_gutter: None,
2243 show_code_actions: None,
2244 show_runnables: None,
2245 show_breakpoints: None,
2246 show_wrap_guides: None,
2247 show_indent_guides,
2248 buffers_with_disabled_indent_guides: HashSet::default(),
2249 highlight_order: 0,
2250 highlighted_rows: HashMap::default(),
2251 background_highlights: HashMap::default(),
2252 gutter_highlights: HashMap::default(),
2253 scrollbar_marker_state: ScrollbarMarkerState::default(),
2254 active_indent_guides_state: ActiveIndentGuidesState::default(),
2255 nav_history: None,
2256 context_menu: RefCell::new(None),
2257 context_menu_options: None,
2258 mouse_context_menu: None,
2259 completion_tasks: Vec::new(),
2260 inline_blame_popover: None,
2261 inline_blame_popover_show_task: None,
2262 signature_help_state: SignatureHelpState::default(),
2263 auto_signature_help: None,
2264 find_all_references_task_sources: Vec::new(),
2265 next_completion_id: 0,
2266 next_inlay_id: 0,
2267 code_action_providers,
2268 available_code_actions: None,
2269 code_actions_task: None,
2270 quick_selection_highlight_task: None,
2271 debounced_selection_highlight_task: None,
2272 document_highlights_task: None,
2273 linked_editing_range_task: None,
2274 pending_rename: None,
2275 searchable: !is_minimap,
2276 cursor_shape: EditorSettings::get_global(cx)
2277 .cursor_shape
2278 .unwrap_or_default(),
2279 current_line_highlight: None,
2280 autoindent_mode: Some(AutoindentMode::EachLine),
2281 collapse_matches: false,
2282 workspace: None,
2283 input_enabled: !is_minimap,
2284 use_modal_editing: full_mode,
2285 read_only: is_minimap,
2286 use_autoclose: true,
2287 use_auto_surround: true,
2288 auto_replace_emoji_shortcode: false,
2289 jsx_tag_auto_close_enabled_in_any_buffer: false,
2290 leader_id: None,
2291 remote_id: None,
2292 hover_state: HoverState::default(),
2293 pending_mouse_down: None,
2294 hovered_link_state: None,
2295 edit_prediction_provider: None,
2296 active_edit_prediction: None,
2297 stale_edit_prediction_in_menu: None,
2298 edit_prediction_preview: EditPredictionPreview::Inactive {
2299 released_too_fast: false,
2300 },
2301 inline_diagnostics_enabled: full_mode,
2302 diagnostics_enabled: full_mode,
2303 word_completions_enabled: full_mode,
2304 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2305 gutter_hovered: false,
2306 pixel_position_of_newest_cursor: None,
2307 last_bounds: None,
2308 last_position_map: None,
2309 expect_bounds_change: None,
2310 gutter_dimensions: GutterDimensions::default(),
2311 style: None,
2312 show_cursor_names: false,
2313 hovered_cursors: HashMap::default(),
2314 next_editor_action_id: EditorActionId::default(),
2315 editor_actions: Rc::default(),
2316 edit_predictions_hidden_for_vim_mode: false,
2317 show_edit_predictions_override: None,
2318 show_completions_on_input_override: None,
2319 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2320 edit_prediction_settings: EditPredictionSettings::Disabled,
2321 edit_prediction_indent_conflict: false,
2322 edit_prediction_requires_modifier_in_indent_conflict: true,
2323 custom_context_menu: None,
2324 show_git_blame_gutter: false,
2325 show_git_blame_inline: false,
2326 show_selection_menu: None,
2327 show_git_blame_inline_delay_task: None,
2328 git_blame_inline_enabled: full_mode
2329 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2330 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2331 buffer_serialization: is_minimap.not().then(|| {
2332 BufferSerialization::new(
2333 ProjectSettings::get_global(cx)
2334 .session
2335 .restore_unsaved_buffers,
2336 )
2337 }),
2338 blame: None,
2339 blame_subscription: None,
2340 tasks: BTreeMap::default(),
2341
2342 breakpoint_store,
2343 gutter_breakpoint_indicator: (None, None),
2344 hovered_diff_hunk_row: None,
2345 _subscriptions: (!is_minimap)
2346 .then(|| {
2347 vec![
2348 cx.observe(&multi_buffer, Self::on_buffer_changed),
2349 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2350 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2351 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2352 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2353 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2354 cx.observe_window_activation(window, |editor, window, cx| {
2355 let active = window.is_window_active();
2356 editor.blink_manager.update(cx, |blink_manager, cx| {
2357 if active {
2358 blink_manager.enable(cx);
2359 } else {
2360 blink_manager.disable(cx);
2361 }
2362 });
2363 if active {
2364 editor.show_mouse_cursor(cx);
2365 }
2366 }),
2367 ]
2368 })
2369 .unwrap_or_default(),
2370 tasks_update_task: None,
2371 pull_diagnostics_task: Task::ready(()),
2372 pull_diagnostics_background_task: Task::ready(()),
2373 colors: None,
2374 refresh_colors_task: Task::ready(()),
2375 inlay_hints: None,
2376 next_color_inlay_id: 0,
2377 post_scroll_update: Task::ready(()),
2378 linked_edit_ranges: Default::default(),
2379 in_project_search: false,
2380 previous_search_ranges: None,
2381 breadcrumb_header: None,
2382 focused_block: None,
2383 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2384 addons: HashMap::default(),
2385 registered_buffers: HashMap::default(),
2386 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2387 selection_mark_mode: false,
2388 toggle_fold_multiple_buffers: Task::ready(()),
2389 serialize_selections: Task::ready(()),
2390 serialize_folds: Task::ready(()),
2391 text_style_refinement: None,
2392 load_diff_task: load_uncommitted_diff,
2393 temporary_diff_override: false,
2394 mouse_cursor_hidden: false,
2395 minimap: None,
2396 hide_mouse_mode: EditorSettings::get_global(cx)
2397 .hide_mouse
2398 .unwrap_or_default(),
2399 change_list: ChangeList::new(),
2400 mode,
2401 selection_drag_state: SelectionDragState::None,
2402 folding_newlines: Task::ready(()),
2403 lookup_key: None,
2404 select_next_is_case_sensitive: None,
2405 applicable_language_settings: HashMap::default(),
2406 accent_data: None,
2407 fetched_tree_sitter_chunks: HashMap::default(),
2408 use_base_text_line_numbers: false,
2409 };
2410
2411 if is_minimap {
2412 return editor;
2413 }
2414
2415 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2416 editor.accent_data = editor.fetch_accent_data(cx);
2417
2418 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2419 editor
2420 ._subscriptions
2421 .push(cx.observe(breakpoints, |_, _, cx| {
2422 cx.notify();
2423 }));
2424 }
2425 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2426 editor._subscriptions.extend(project_subscriptions);
2427
2428 editor._subscriptions.push(cx.subscribe_in(
2429 &cx.entity(),
2430 window,
2431 |editor, _, e: &EditorEvent, window, cx| match e {
2432 EditorEvent::ScrollPositionChanged { local, .. } => {
2433 if *local {
2434 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2435 editor.inline_blame_popover.take();
2436 let new_anchor = editor.scroll_manager.anchor();
2437 let snapshot = editor.snapshot(window, cx);
2438 editor.update_restoration_data(cx, move |data| {
2439 data.scroll_position = (
2440 new_anchor.top_row(snapshot.buffer_snapshot()),
2441 new_anchor.offset,
2442 );
2443 });
2444
2445 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2446 cx.background_executor()
2447 .timer(Duration::from_millis(50))
2448 .await;
2449 editor
2450 .update_in(cx, |editor, window, cx| {
2451 editor.register_visible_buffers(cx);
2452 editor.refresh_colors_for_visible_range(None, window, cx);
2453 editor.refresh_inlay_hints(
2454 InlayHintRefreshReason::NewLinesShown,
2455 cx,
2456 );
2457 editor.colorize_brackets(false, cx);
2458 })
2459 .ok();
2460 });
2461 }
2462 }
2463 EditorEvent::Edited { .. } => {
2464 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2465 .map(|vim_mode| vim_mode.0)
2466 .unwrap_or(false);
2467 if !vim_mode {
2468 let display_map = editor.display_snapshot(cx);
2469 let selections = editor.selections.all_adjusted_display(&display_map);
2470 let pop_state = editor
2471 .change_list
2472 .last()
2473 .map(|previous| {
2474 previous.len() == selections.len()
2475 && previous.iter().enumerate().all(|(ix, p)| {
2476 p.to_display_point(&display_map).row()
2477 == selections[ix].head().row()
2478 })
2479 })
2480 .unwrap_or(false);
2481 let new_positions = selections
2482 .into_iter()
2483 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2484 .collect();
2485 editor
2486 .change_list
2487 .push_to_change_list(pop_state, new_positions);
2488 }
2489 }
2490 _ => (),
2491 },
2492 ));
2493
2494 if let Some(dap_store) = editor
2495 .project
2496 .as_ref()
2497 .map(|project| project.read(cx).dap_store())
2498 {
2499 let weak_editor = cx.weak_entity();
2500
2501 editor
2502 ._subscriptions
2503 .push(
2504 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2505 let session_entity = cx.entity();
2506 weak_editor
2507 .update(cx, |editor, cx| {
2508 editor._subscriptions.push(
2509 cx.subscribe(&session_entity, Self::on_debug_session_event),
2510 );
2511 })
2512 .ok();
2513 }),
2514 );
2515
2516 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2517 editor
2518 ._subscriptions
2519 .push(cx.subscribe(&session, Self::on_debug_session_event));
2520 }
2521 }
2522
2523 // skip adding the initial selection to selection history
2524 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2525 editor.end_selection(window, cx);
2526 editor.selection_history.mode = SelectionHistoryMode::Normal;
2527
2528 editor.scroll_manager.show_scrollbars(window, cx);
2529 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2530
2531 if full_mode {
2532 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2533 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2534
2535 if editor.git_blame_inline_enabled {
2536 editor.start_git_blame_inline(false, window, cx);
2537 }
2538
2539 editor.go_to_active_debug_line(window, cx);
2540
2541 editor.minimap =
2542 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2543 editor.colors = Some(LspColorData::new(cx));
2544 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2545
2546 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2547 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2548 }
2549 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2550 }
2551
2552 editor
2553 }
2554
2555 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2556 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2557 }
2558
2559 pub fn deploy_mouse_context_menu(
2560 &mut self,
2561 position: gpui::Point<Pixels>,
2562 context_menu: Entity<ContextMenu>,
2563 window: &mut Window,
2564 cx: &mut Context<Self>,
2565 ) {
2566 self.mouse_context_menu = Some(MouseContextMenu::new(
2567 self,
2568 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2569 context_menu,
2570 window,
2571 cx,
2572 ));
2573 }
2574
2575 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2576 self.mouse_context_menu
2577 .as_ref()
2578 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2579 }
2580
2581 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2582 if self
2583 .selections
2584 .pending_anchor()
2585 .is_some_and(|pending_selection| {
2586 let snapshot = self.buffer().read(cx).snapshot(cx);
2587 pending_selection.range().includes(range, &snapshot)
2588 })
2589 {
2590 return true;
2591 }
2592
2593 self.selections
2594 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2595 .into_iter()
2596 .any(|selection| {
2597 // This is needed to cover a corner case, if we just check for an existing
2598 // selection in the fold range, having a cursor at the start of the fold
2599 // marks it as selected. Non-empty selections don't cause this.
2600 let length = selection.end - selection.start;
2601 length > 0
2602 })
2603 }
2604
2605 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2606 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2607 }
2608
2609 fn key_context_internal(
2610 &self,
2611 has_active_edit_prediction: bool,
2612 window: &mut Window,
2613 cx: &mut App,
2614 ) -> KeyContext {
2615 let mut key_context = KeyContext::new_with_defaults();
2616 key_context.add("Editor");
2617 let mode = match self.mode {
2618 EditorMode::SingleLine => "single_line",
2619 EditorMode::AutoHeight { .. } => "auto_height",
2620 EditorMode::Minimap { .. } => "minimap",
2621 EditorMode::Full { .. } => "full",
2622 };
2623
2624 if EditorSettings::jupyter_enabled(cx) {
2625 key_context.add("jupyter");
2626 }
2627
2628 key_context.set("mode", mode);
2629 if self.pending_rename.is_some() {
2630 key_context.add("renaming");
2631 }
2632
2633 if let Some(snippet_stack) = self.snippet_stack.last() {
2634 key_context.add("in_snippet");
2635
2636 if snippet_stack.active_index > 0 {
2637 key_context.add("has_previous_tabstop");
2638 }
2639
2640 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2641 key_context.add("has_next_tabstop");
2642 }
2643 }
2644
2645 match self.context_menu.borrow().as_ref() {
2646 Some(CodeContextMenu::Completions(menu)) => {
2647 if menu.visible() {
2648 key_context.add("menu");
2649 key_context.add("showing_completions");
2650 }
2651 }
2652 Some(CodeContextMenu::CodeActions(menu)) => {
2653 if menu.visible() {
2654 key_context.add("menu");
2655 key_context.add("showing_code_actions")
2656 }
2657 }
2658 None => {}
2659 }
2660
2661 if self.signature_help_state.has_multiple_signatures() {
2662 key_context.add("showing_signature_help");
2663 }
2664
2665 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2666 if !self.focus_handle(cx).contains_focused(window, cx)
2667 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2668 {
2669 for addon in self.addons.values() {
2670 addon.extend_key_context(&mut key_context, cx)
2671 }
2672 }
2673
2674 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2675 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2676 Some(
2677 file.full_path(cx)
2678 .extension()?
2679 .to_string_lossy()
2680 .into_owned(),
2681 )
2682 }) {
2683 key_context.set("extension", extension);
2684 }
2685 } else {
2686 key_context.add("multibuffer");
2687 }
2688
2689 if has_active_edit_prediction {
2690 if self.edit_prediction_in_conflict() {
2691 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2692 } else {
2693 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2694 key_context.add("copilot_suggestion");
2695 }
2696 }
2697
2698 if self.selection_mark_mode {
2699 key_context.add("selection_mode");
2700 }
2701
2702 let disjoint = self.selections.disjoint_anchors();
2703 let snapshot = self.snapshot(window, cx);
2704 let snapshot = snapshot.buffer_snapshot();
2705 if self.mode == EditorMode::SingleLine
2706 && let [selection] = disjoint
2707 && selection.start == selection.end
2708 && selection.end.to_offset(snapshot) == snapshot.len()
2709 {
2710 key_context.add("end_of_input");
2711 }
2712
2713 if self.has_any_expanded_diff_hunks(cx) {
2714 key_context.add("diffs_expanded");
2715 }
2716
2717 key_context
2718 }
2719
2720 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2721 self.last_bounds.as_ref()
2722 }
2723
2724 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2725 if self.mouse_cursor_hidden {
2726 self.mouse_cursor_hidden = false;
2727 cx.notify();
2728 }
2729 }
2730
2731 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2732 let hide_mouse_cursor = match origin {
2733 HideMouseCursorOrigin::TypingAction => {
2734 matches!(
2735 self.hide_mouse_mode,
2736 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2737 )
2738 }
2739 HideMouseCursorOrigin::MovementAction => {
2740 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2741 }
2742 };
2743 if self.mouse_cursor_hidden != hide_mouse_cursor {
2744 self.mouse_cursor_hidden = hide_mouse_cursor;
2745 cx.notify();
2746 }
2747 }
2748
2749 pub fn edit_prediction_in_conflict(&self) -> bool {
2750 if !self.show_edit_predictions_in_menu() {
2751 return false;
2752 }
2753
2754 let showing_completions = self
2755 .context_menu
2756 .borrow()
2757 .as_ref()
2758 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2759
2760 showing_completions
2761 || self.edit_prediction_requires_modifier()
2762 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2763 // bindings to insert tab characters.
2764 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2765 }
2766
2767 pub fn accept_edit_prediction_keybind(
2768 &self,
2769 accept_partial: bool,
2770 window: &mut Window,
2771 cx: &mut App,
2772 ) -> AcceptEditPredictionBinding {
2773 let key_context = self.key_context_internal(true, window, cx);
2774 let in_conflict = self.edit_prediction_in_conflict();
2775
2776 let bindings = if accept_partial {
2777 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2778 } else {
2779 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2780 };
2781
2782 // TODO: if the binding contains multiple keystrokes, display all of them, not
2783 // just the first one.
2784 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2785 !in_conflict
2786 || binding
2787 .keystrokes()
2788 .first()
2789 .is_some_and(|keystroke| keystroke.modifiers().modified())
2790 }))
2791 }
2792
2793 pub fn new_file(
2794 workspace: &mut Workspace,
2795 _: &workspace::NewFile,
2796 window: &mut Window,
2797 cx: &mut Context<Workspace>,
2798 ) {
2799 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2800 "Failed to create buffer",
2801 window,
2802 cx,
2803 |e, _, _| match e.error_code() {
2804 ErrorCode::RemoteUpgradeRequired => Some(format!(
2805 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2806 e.error_tag("required").unwrap_or("the latest version")
2807 )),
2808 _ => None,
2809 },
2810 );
2811 }
2812
2813 pub fn new_in_workspace(
2814 workspace: &mut Workspace,
2815 window: &mut Window,
2816 cx: &mut Context<Workspace>,
2817 ) -> Task<Result<Entity<Editor>>> {
2818 let project = workspace.project().clone();
2819 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2820
2821 cx.spawn_in(window, async move |workspace, cx| {
2822 let buffer = create.await?;
2823 workspace.update_in(cx, |workspace, window, cx| {
2824 let editor =
2825 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2826 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2827 editor
2828 })
2829 })
2830 }
2831
2832 fn new_file_vertical(
2833 workspace: &mut Workspace,
2834 _: &workspace::NewFileSplitVertical,
2835 window: &mut Window,
2836 cx: &mut Context<Workspace>,
2837 ) {
2838 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2839 }
2840
2841 fn new_file_horizontal(
2842 workspace: &mut Workspace,
2843 _: &workspace::NewFileSplitHorizontal,
2844 window: &mut Window,
2845 cx: &mut Context<Workspace>,
2846 ) {
2847 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2848 }
2849
2850 fn new_file_split(
2851 workspace: &mut Workspace,
2852 action: &workspace::NewFileSplit,
2853 window: &mut Window,
2854 cx: &mut Context<Workspace>,
2855 ) {
2856 Self::new_file_in_direction(workspace, action.0, window, cx)
2857 }
2858
2859 fn new_file_in_direction(
2860 workspace: &mut Workspace,
2861 direction: SplitDirection,
2862 window: &mut Window,
2863 cx: &mut Context<Workspace>,
2864 ) {
2865 let project = workspace.project().clone();
2866 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2867
2868 cx.spawn_in(window, async move |workspace, cx| {
2869 let buffer = create.await?;
2870 workspace.update_in(cx, move |workspace, window, cx| {
2871 workspace.split_item(
2872 direction,
2873 Box::new(
2874 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2875 ),
2876 window,
2877 cx,
2878 )
2879 })?;
2880 anyhow::Ok(())
2881 })
2882 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2883 match e.error_code() {
2884 ErrorCode::RemoteUpgradeRequired => Some(format!(
2885 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2886 e.error_tag("required").unwrap_or("the latest version")
2887 )),
2888 _ => None,
2889 }
2890 });
2891 }
2892
2893 pub fn leader_id(&self) -> Option<CollaboratorId> {
2894 self.leader_id
2895 }
2896
2897 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2898 &self.buffer
2899 }
2900
2901 pub fn project(&self) -> Option<&Entity<Project>> {
2902 self.project.as_ref()
2903 }
2904
2905 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2906 self.workspace.as_ref()?.0.upgrade()
2907 }
2908
2909 /// Returns the workspace serialization ID if this editor should be serialized.
2910 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2911 self.workspace
2912 .as_ref()
2913 .filter(|_| self.should_serialize_buffer())
2914 .and_then(|workspace| workspace.1)
2915 }
2916
2917 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2918 self.buffer().read(cx).title(cx)
2919 }
2920
2921 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2922 let git_blame_gutter_max_author_length = self
2923 .render_git_blame_gutter(cx)
2924 .then(|| {
2925 if let Some(blame) = self.blame.as_ref() {
2926 let max_author_length =
2927 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2928 Some(max_author_length)
2929 } else {
2930 None
2931 }
2932 })
2933 .flatten();
2934
2935 EditorSnapshot {
2936 mode: self.mode.clone(),
2937 show_gutter: self.show_gutter,
2938 show_line_numbers: self.show_line_numbers,
2939 show_git_diff_gutter: self.show_git_diff_gutter,
2940 show_code_actions: self.show_code_actions,
2941 show_runnables: self.show_runnables,
2942 show_breakpoints: self.show_breakpoints,
2943 git_blame_gutter_max_author_length,
2944 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2945 placeholder_display_snapshot: self
2946 .placeholder_display_map
2947 .as_ref()
2948 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2949 scroll_anchor: self.scroll_manager.anchor(),
2950 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2951 is_focused: self.focus_handle.is_focused(window),
2952 current_line_highlight: self
2953 .current_line_highlight
2954 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2955 gutter_hovered: self.gutter_hovered,
2956 }
2957 }
2958
2959 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2960 self.buffer.read(cx).language_at(point, cx)
2961 }
2962
2963 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2964 self.buffer.read(cx).read(cx).file_at(point).cloned()
2965 }
2966
2967 pub fn active_excerpt(
2968 &self,
2969 cx: &App,
2970 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2971 self.buffer
2972 .read(cx)
2973 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2974 }
2975
2976 pub fn mode(&self) -> &EditorMode {
2977 &self.mode
2978 }
2979
2980 pub fn set_mode(&mut self, mode: EditorMode) {
2981 self.mode = mode;
2982 }
2983
2984 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2985 self.collaboration_hub.as_deref()
2986 }
2987
2988 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2989 self.collaboration_hub = Some(hub);
2990 }
2991
2992 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2993 self.in_project_search = in_project_search;
2994 }
2995
2996 pub fn set_custom_context_menu(
2997 &mut self,
2998 f: impl 'static
2999 + Fn(
3000 &mut Self,
3001 DisplayPoint,
3002 &mut Window,
3003 &mut Context<Self>,
3004 ) -> Option<Entity<ui::ContextMenu>>,
3005 ) {
3006 self.custom_context_menu = Some(Box::new(f))
3007 }
3008
3009 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3010 self.completion_provider = provider;
3011 }
3012
3013 #[cfg(any(test, feature = "test-support"))]
3014 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3015 self.completion_provider.clone()
3016 }
3017
3018 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3019 self.semantics_provider.clone()
3020 }
3021
3022 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3023 self.semantics_provider = provider;
3024 }
3025
3026 pub fn set_edit_prediction_provider<T>(
3027 &mut self,
3028 provider: Option<Entity<T>>,
3029 window: &mut Window,
3030 cx: &mut Context<Self>,
3031 ) where
3032 T: EditPredictionDelegate,
3033 {
3034 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3035 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3036 if this.focus_handle.is_focused(window) {
3037 this.update_visible_edit_prediction(window, cx);
3038 }
3039 }),
3040 provider: Arc::new(provider),
3041 });
3042 self.update_edit_prediction_settings(cx);
3043 self.refresh_edit_prediction(false, false, window, cx);
3044 }
3045
3046 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3047 self.placeholder_display_map
3048 .as_ref()
3049 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3050 }
3051
3052 pub fn set_placeholder_text(
3053 &mut self,
3054 placeholder_text: &str,
3055 window: &mut Window,
3056 cx: &mut Context<Self>,
3057 ) {
3058 let multibuffer = cx
3059 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3060
3061 let style = window.text_style();
3062
3063 self.placeholder_display_map = Some(cx.new(|cx| {
3064 DisplayMap::new(
3065 multibuffer,
3066 style.font(),
3067 style.font_size.to_pixels(window.rem_size()),
3068 None,
3069 FILE_HEADER_HEIGHT,
3070 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3071 Default::default(),
3072 DiagnosticSeverity::Off,
3073 cx,
3074 )
3075 }));
3076 cx.notify();
3077 }
3078
3079 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3080 self.cursor_shape = cursor_shape;
3081
3082 // Disrupt blink for immediate user feedback that the cursor shape has changed
3083 self.blink_manager.update(cx, BlinkManager::show_cursor);
3084
3085 cx.notify();
3086 }
3087
3088 pub fn cursor_shape(&self) -> CursorShape {
3089 self.cursor_shape
3090 }
3091
3092 pub fn set_current_line_highlight(
3093 &mut self,
3094 current_line_highlight: Option<CurrentLineHighlight>,
3095 ) {
3096 self.current_line_highlight = current_line_highlight;
3097 }
3098
3099 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3100 self.collapse_matches = collapse_matches;
3101 }
3102
3103 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3104 if self.collapse_matches {
3105 return range.start..range.start;
3106 }
3107 range.clone()
3108 }
3109
3110 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3111 self.display_map.read(cx).clip_at_line_ends
3112 }
3113
3114 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3115 if self.display_map.read(cx).clip_at_line_ends != clip {
3116 self.display_map
3117 .update(cx, |map, _| map.clip_at_line_ends = clip);
3118 }
3119 }
3120
3121 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3122 self.input_enabled = input_enabled;
3123 }
3124
3125 pub fn set_edit_predictions_hidden_for_vim_mode(
3126 &mut self,
3127 hidden: bool,
3128 window: &mut Window,
3129 cx: &mut Context<Self>,
3130 ) {
3131 if hidden != self.edit_predictions_hidden_for_vim_mode {
3132 self.edit_predictions_hidden_for_vim_mode = hidden;
3133 if hidden {
3134 self.update_visible_edit_prediction(window, cx);
3135 } else {
3136 self.refresh_edit_prediction(true, false, window, cx);
3137 }
3138 }
3139 }
3140
3141 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3142 self.menu_edit_predictions_policy = value;
3143 }
3144
3145 pub fn set_autoindent(&mut self, autoindent: bool) {
3146 if autoindent {
3147 self.autoindent_mode = Some(AutoindentMode::EachLine);
3148 } else {
3149 self.autoindent_mode = None;
3150 }
3151 }
3152
3153 pub fn read_only(&self, cx: &App) -> bool {
3154 self.read_only || self.buffer.read(cx).read_only()
3155 }
3156
3157 pub fn set_read_only(&mut self, read_only: bool) {
3158 self.read_only = read_only;
3159 }
3160
3161 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3162 self.use_autoclose = autoclose;
3163 }
3164
3165 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3166 self.use_auto_surround = auto_surround;
3167 }
3168
3169 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3170 self.auto_replace_emoji_shortcode = auto_replace;
3171 }
3172
3173 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3174 self.buffer_serialization = should_serialize.then(|| {
3175 BufferSerialization::new(
3176 ProjectSettings::get_global(cx)
3177 .session
3178 .restore_unsaved_buffers,
3179 )
3180 })
3181 }
3182
3183 fn should_serialize_buffer(&self) -> bool {
3184 self.buffer_serialization.is_some()
3185 }
3186
3187 pub fn toggle_edit_predictions(
3188 &mut self,
3189 _: &ToggleEditPrediction,
3190 window: &mut Window,
3191 cx: &mut Context<Self>,
3192 ) {
3193 if self.show_edit_predictions_override.is_some() {
3194 self.set_show_edit_predictions(None, window, cx);
3195 } else {
3196 let show_edit_predictions = !self.edit_predictions_enabled();
3197 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3198 }
3199 }
3200
3201 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3202 self.show_completions_on_input_override = show_completions_on_input;
3203 }
3204
3205 pub fn set_show_edit_predictions(
3206 &mut self,
3207 show_edit_predictions: Option<bool>,
3208 window: &mut Window,
3209 cx: &mut Context<Self>,
3210 ) {
3211 self.show_edit_predictions_override = show_edit_predictions;
3212 self.update_edit_prediction_settings(cx);
3213
3214 if let Some(false) = show_edit_predictions {
3215 self.discard_edit_prediction(false, cx);
3216 } else {
3217 self.refresh_edit_prediction(false, true, window, cx);
3218 }
3219 }
3220
3221 fn edit_predictions_disabled_in_scope(
3222 &self,
3223 buffer: &Entity<Buffer>,
3224 buffer_position: language::Anchor,
3225 cx: &App,
3226 ) -> bool {
3227 let snapshot = buffer.read(cx).snapshot();
3228 let settings = snapshot.settings_at(buffer_position, cx);
3229
3230 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3231 return false;
3232 };
3233
3234 scope.override_name().is_some_and(|scope_name| {
3235 settings
3236 .edit_predictions_disabled_in
3237 .iter()
3238 .any(|s| s == scope_name)
3239 })
3240 }
3241
3242 pub fn set_use_modal_editing(&mut self, to: bool) {
3243 self.use_modal_editing = to;
3244 }
3245
3246 pub fn use_modal_editing(&self) -> bool {
3247 self.use_modal_editing
3248 }
3249
3250 fn selections_did_change(
3251 &mut self,
3252 local: bool,
3253 old_cursor_position: &Anchor,
3254 effects: SelectionEffects,
3255 window: &mut Window,
3256 cx: &mut Context<Self>,
3257 ) {
3258 window.invalidate_character_coordinates();
3259
3260 // Copy selections to primary selection buffer
3261 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3262 if local {
3263 let selections = self
3264 .selections
3265 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3266 let buffer_handle = self.buffer.read(cx).read(cx);
3267
3268 let mut text = String::new();
3269 for (index, selection) in selections.iter().enumerate() {
3270 let text_for_selection = buffer_handle
3271 .text_for_range(selection.start..selection.end)
3272 .collect::<String>();
3273
3274 text.push_str(&text_for_selection);
3275 if index != selections.len() - 1 {
3276 text.push('\n');
3277 }
3278 }
3279
3280 if !text.is_empty() {
3281 cx.write_to_primary(ClipboardItem::new_string(text));
3282 }
3283 }
3284
3285 let selection_anchors = self.selections.disjoint_anchors_arc();
3286
3287 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3288 self.buffer.update(cx, |buffer, cx| {
3289 buffer.set_active_selections(
3290 &selection_anchors,
3291 self.selections.line_mode(),
3292 self.cursor_shape,
3293 cx,
3294 )
3295 });
3296 }
3297 let display_map = self
3298 .display_map
3299 .update(cx, |display_map, cx| display_map.snapshot(cx));
3300 let buffer = display_map.buffer_snapshot();
3301 if self.selections.count() == 1 {
3302 self.add_selections_state = None;
3303 }
3304 self.select_next_state = None;
3305 self.select_prev_state = None;
3306 self.select_syntax_node_history.try_clear();
3307 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3308 self.snippet_stack.invalidate(&selection_anchors, buffer);
3309 self.take_rename(false, window, cx);
3310
3311 let newest_selection = self.selections.newest_anchor();
3312 let new_cursor_position = newest_selection.head();
3313 let selection_start = newest_selection.start;
3314
3315 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3316 self.push_to_nav_history(
3317 *old_cursor_position,
3318 Some(new_cursor_position.to_point(buffer)),
3319 false,
3320 effects.nav_history == Some(true),
3321 cx,
3322 );
3323 }
3324
3325 if local {
3326 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3327 self.register_buffer(buffer_id, cx);
3328 }
3329
3330 let mut context_menu = self.context_menu.borrow_mut();
3331 let completion_menu = match context_menu.as_ref() {
3332 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3333 Some(CodeContextMenu::CodeActions(_)) => {
3334 *context_menu = None;
3335 None
3336 }
3337 None => None,
3338 };
3339 let completion_position = completion_menu.map(|menu| menu.initial_position);
3340 drop(context_menu);
3341
3342 if effects.completions
3343 && let Some(completion_position) = completion_position
3344 {
3345 let start_offset = selection_start.to_offset(buffer);
3346 let position_matches = start_offset == completion_position.to_offset(buffer);
3347 let continue_showing = if position_matches {
3348 if self.snippet_stack.is_empty() {
3349 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3350 == Some(CharKind::Word)
3351 } else {
3352 // Snippet choices can be shown even when the cursor is in whitespace.
3353 // Dismissing the menu with actions like backspace is handled by
3354 // invalidation regions.
3355 true
3356 }
3357 } else {
3358 false
3359 };
3360
3361 if continue_showing {
3362 self.open_or_update_completions_menu(None, None, false, window, cx);
3363 } else {
3364 self.hide_context_menu(window, cx);
3365 }
3366 }
3367
3368 hide_hover(self, cx);
3369
3370 if old_cursor_position.to_display_point(&display_map).row()
3371 != new_cursor_position.to_display_point(&display_map).row()
3372 {
3373 self.available_code_actions.take();
3374 }
3375 self.refresh_code_actions(window, cx);
3376 self.refresh_document_highlights(cx);
3377 refresh_linked_ranges(self, window, cx);
3378
3379 self.refresh_selected_text_highlights(false, window, cx);
3380 self.refresh_matching_bracket_highlights(window, cx);
3381 self.update_visible_edit_prediction(window, cx);
3382 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3383 self.inline_blame_popover.take();
3384 if self.git_blame_inline_enabled {
3385 self.start_inline_blame_timer(window, cx);
3386 }
3387 }
3388
3389 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3390 cx.emit(EditorEvent::SelectionsChanged { local });
3391
3392 let selections = &self.selections.disjoint_anchors_arc();
3393 if selections.len() == 1 {
3394 cx.emit(SearchEvent::ActiveMatchChanged)
3395 }
3396 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3397 let inmemory_selections = selections
3398 .iter()
3399 .map(|s| {
3400 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3401 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3402 })
3403 .collect();
3404 self.update_restoration_data(cx, |data| {
3405 data.selections = inmemory_selections;
3406 });
3407
3408 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3409 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3410 {
3411 let snapshot = self.buffer().read(cx).snapshot(cx);
3412 let selections = selections.clone();
3413 let background_executor = cx.background_executor().clone();
3414 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3415 self.serialize_selections = cx.background_spawn(async move {
3416 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3417 let db_selections = selections
3418 .iter()
3419 .map(|selection| {
3420 (
3421 selection.start.to_offset(&snapshot).0,
3422 selection.end.to_offset(&snapshot).0,
3423 )
3424 })
3425 .collect();
3426
3427 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3428 .await
3429 .with_context(|| {
3430 format!(
3431 "persisting editor selections for editor {editor_id}, \
3432 workspace {workspace_id:?}"
3433 )
3434 })
3435 .log_err();
3436 });
3437 }
3438 }
3439
3440 cx.notify();
3441 }
3442
3443 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3444 use text::ToOffset as _;
3445 use text::ToPoint as _;
3446
3447 if self.mode.is_minimap()
3448 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3449 {
3450 return;
3451 }
3452
3453 if !self.buffer().read(cx).is_singleton() {
3454 return;
3455 }
3456
3457 let display_snapshot = self
3458 .display_map
3459 .update(cx, |display_map, cx| display_map.snapshot(cx));
3460 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3461 return;
3462 };
3463 let inmemory_folds = display_snapshot
3464 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3465 .map(|fold| {
3466 fold.range.start.text_anchor.to_point(&snapshot)
3467 ..fold.range.end.text_anchor.to_point(&snapshot)
3468 })
3469 .collect();
3470 self.update_restoration_data(cx, |data| {
3471 data.folds = inmemory_folds;
3472 });
3473
3474 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3475 return;
3476 };
3477 let background_executor = cx.background_executor().clone();
3478 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3479 let db_folds = display_snapshot
3480 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3481 .map(|fold| {
3482 (
3483 fold.range.start.text_anchor.to_offset(&snapshot),
3484 fold.range.end.text_anchor.to_offset(&snapshot),
3485 )
3486 })
3487 .collect();
3488 self.serialize_folds = cx.background_spawn(async move {
3489 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3490 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3491 .await
3492 .with_context(|| {
3493 format!(
3494 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3495 )
3496 })
3497 .log_err();
3498 });
3499 }
3500
3501 pub fn sync_selections(
3502 &mut self,
3503 other: Entity<Editor>,
3504 cx: &mut Context<Self>,
3505 ) -> gpui::Subscription {
3506 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3507 if !other_selections.is_empty() {
3508 self.selections
3509 .change_with(&self.display_snapshot(cx), |selections| {
3510 selections.select_anchors(other_selections);
3511 });
3512 }
3513
3514 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3515 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3516 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3517 if other_selections.is_empty() {
3518 return;
3519 }
3520 let snapshot = this.display_snapshot(cx);
3521 this.selections.change_with(&snapshot, |selections| {
3522 selections.select_anchors(other_selections);
3523 });
3524 }
3525 });
3526
3527 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3528 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3529 let these_selections = this.selections.disjoint_anchors().to_vec();
3530 if these_selections.is_empty() {
3531 return;
3532 }
3533 other.update(cx, |other_editor, cx| {
3534 let snapshot = other_editor.display_snapshot(cx);
3535 other_editor
3536 .selections
3537 .change_with(&snapshot, |selections| {
3538 selections.select_anchors(these_selections);
3539 })
3540 });
3541 }
3542 });
3543
3544 Subscription::join(other_subscription, this_subscription)
3545 }
3546
3547 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3548 if self.buffer().read(cx).is_singleton() {
3549 return;
3550 }
3551 let snapshot = self.buffer.read(cx).snapshot(cx);
3552 let buffer_ids: HashSet<BufferId> = self
3553 .selections
3554 .disjoint_anchor_ranges()
3555 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3556 .collect();
3557 for buffer_id in buffer_ids {
3558 self.unfold_buffer(buffer_id, cx);
3559 }
3560 }
3561
3562 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3563 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3564 /// effects of selection change occur at the end of the transaction.
3565 pub fn change_selections<R>(
3566 &mut self,
3567 effects: SelectionEffects,
3568 window: &mut Window,
3569 cx: &mut Context<Self>,
3570 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3571 ) -> R {
3572 let snapshot = self.display_snapshot(cx);
3573 if let Some(state) = &mut self.deferred_selection_effects_state {
3574 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3575 state.effects.completions = effects.completions;
3576 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3577 let (changed, result) = self.selections.change_with(&snapshot, change);
3578 state.changed |= changed;
3579 return result;
3580 }
3581 let mut state = DeferredSelectionEffectsState {
3582 changed: false,
3583 effects,
3584 old_cursor_position: self.selections.newest_anchor().head(),
3585 history_entry: SelectionHistoryEntry {
3586 selections: self.selections.disjoint_anchors_arc(),
3587 select_next_state: self.select_next_state.clone(),
3588 select_prev_state: self.select_prev_state.clone(),
3589 add_selections_state: self.add_selections_state.clone(),
3590 },
3591 };
3592 let (changed, result) = self.selections.change_with(&snapshot, change);
3593 state.changed = state.changed || changed;
3594 if self.defer_selection_effects {
3595 self.deferred_selection_effects_state = Some(state);
3596 } else {
3597 self.apply_selection_effects(state, window, cx);
3598 }
3599 result
3600 }
3601
3602 /// Defers the effects of selection change, so that the effects of multiple calls to
3603 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3604 /// to selection history and the state of popovers based on selection position aren't
3605 /// erroneously updated.
3606 pub fn with_selection_effects_deferred<R>(
3607 &mut self,
3608 window: &mut Window,
3609 cx: &mut Context<Self>,
3610 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3611 ) -> R {
3612 let already_deferred = self.defer_selection_effects;
3613 self.defer_selection_effects = true;
3614 let result = update(self, window, cx);
3615 if !already_deferred {
3616 self.defer_selection_effects = false;
3617 if let Some(state) = self.deferred_selection_effects_state.take() {
3618 self.apply_selection_effects(state, window, cx);
3619 }
3620 }
3621 result
3622 }
3623
3624 fn apply_selection_effects(
3625 &mut self,
3626 state: DeferredSelectionEffectsState,
3627 window: &mut Window,
3628 cx: &mut Context<Self>,
3629 ) {
3630 if state.changed {
3631 self.selection_history.push(state.history_entry);
3632
3633 if let Some(autoscroll) = state.effects.scroll {
3634 self.request_autoscroll(autoscroll, cx);
3635 }
3636
3637 let old_cursor_position = &state.old_cursor_position;
3638
3639 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3640
3641 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3642 self.show_signature_help(&ShowSignatureHelp, window, cx);
3643 }
3644 }
3645 }
3646
3647 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3648 where
3649 I: IntoIterator<Item = (Range<S>, T)>,
3650 S: ToOffset,
3651 T: Into<Arc<str>>,
3652 {
3653 if self.read_only(cx) {
3654 return;
3655 }
3656
3657 self.buffer
3658 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3659 }
3660
3661 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3662 where
3663 I: IntoIterator<Item = (Range<S>, T)>,
3664 S: ToOffset,
3665 T: Into<Arc<str>>,
3666 {
3667 if self.read_only(cx) {
3668 return;
3669 }
3670
3671 self.buffer.update(cx, |buffer, cx| {
3672 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3673 });
3674 }
3675
3676 pub fn edit_with_block_indent<I, S, T>(
3677 &mut self,
3678 edits: I,
3679 original_indent_columns: Vec<Option<u32>>,
3680 cx: &mut Context<Self>,
3681 ) where
3682 I: IntoIterator<Item = (Range<S>, T)>,
3683 S: ToOffset,
3684 T: Into<Arc<str>>,
3685 {
3686 if self.read_only(cx) {
3687 return;
3688 }
3689
3690 self.buffer.update(cx, |buffer, cx| {
3691 buffer.edit(
3692 edits,
3693 Some(AutoindentMode::Block {
3694 original_indent_columns,
3695 }),
3696 cx,
3697 )
3698 });
3699 }
3700
3701 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3702 self.hide_context_menu(window, cx);
3703
3704 match phase {
3705 SelectPhase::Begin {
3706 position,
3707 add,
3708 click_count,
3709 } => self.begin_selection(position, add, click_count, window, cx),
3710 SelectPhase::BeginColumnar {
3711 position,
3712 goal_column,
3713 reset,
3714 mode,
3715 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3716 SelectPhase::Extend {
3717 position,
3718 click_count,
3719 } => self.extend_selection(position, click_count, window, cx),
3720 SelectPhase::Update {
3721 position,
3722 goal_column,
3723 scroll_delta,
3724 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3725 SelectPhase::End => self.end_selection(window, cx),
3726 }
3727 }
3728
3729 fn extend_selection(
3730 &mut self,
3731 position: DisplayPoint,
3732 click_count: usize,
3733 window: &mut Window,
3734 cx: &mut Context<Self>,
3735 ) {
3736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3737 let tail = self
3738 .selections
3739 .newest::<MultiBufferOffset>(&display_map)
3740 .tail();
3741 let click_count = click_count.max(match self.selections.select_mode() {
3742 SelectMode::Character => 1,
3743 SelectMode::Word(_) => 2,
3744 SelectMode::Line(_) => 3,
3745 SelectMode::All => 4,
3746 });
3747 self.begin_selection(position, false, click_count, window, cx);
3748
3749 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3750
3751 let current_selection = match self.selections.select_mode() {
3752 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3753 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3754 };
3755
3756 let mut pending_selection = self
3757 .selections
3758 .pending_anchor()
3759 .cloned()
3760 .expect("extend_selection not called with pending selection");
3761
3762 if pending_selection
3763 .start
3764 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3765 == Ordering::Greater
3766 {
3767 pending_selection.start = current_selection.start;
3768 }
3769 if pending_selection
3770 .end
3771 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3772 == Ordering::Less
3773 {
3774 pending_selection.end = current_selection.end;
3775 pending_selection.reversed = true;
3776 }
3777
3778 let mut pending_mode = self.selections.pending_mode().unwrap();
3779 match &mut pending_mode {
3780 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3781 _ => {}
3782 }
3783
3784 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3785 SelectionEffects::scroll(Autoscroll::fit())
3786 } else {
3787 SelectionEffects::no_scroll()
3788 };
3789
3790 self.change_selections(effects, window, cx, |s| {
3791 s.set_pending(pending_selection.clone(), pending_mode);
3792 s.set_is_extending(true);
3793 });
3794 }
3795
3796 fn begin_selection(
3797 &mut self,
3798 position: DisplayPoint,
3799 add: bool,
3800 click_count: usize,
3801 window: &mut Window,
3802 cx: &mut Context<Self>,
3803 ) {
3804 if !self.focus_handle.is_focused(window) {
3805 self.last_focused_descendant = None;
3806 window.focus(&self.focus_handle);
3807 }
3808
3809 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3810 let buffer = display_map.buffer_snapshot();
3811 let position = display_map.clip_point(position, Bias::Left);
3812
3813 let start;
3814 let end;
3815 let mode;
3816 let mut auto_scroll;
3817 match click_count {
3818 1 => {
3819 start = buffer.anchor_before(position.to_point(&display_map));
3820 end = start;
3821 mode = SelectMode::Character;
3822 auto_scroll = true;
3823 }
3824 2 => {
3825 let position = display_map
3826 .clip_point(position, Bias::Left)
3827 .to_offset(&display_map, Bias::Left);
3828 let (range, _) = buffer.surrounding_word(position, None);
3829 start = buffer.anchor_before(range.start);
3830 end = buffer.anchor_before(range.end);
3831 mode = SelectMode::Word(start..end);
3832 auto_scroll = true;
3833 }
3834 3 => {
3835 let position = display_map
3836 .clip_point(position, Bias::Left)
3837 .to_point(&display_map);
3838 let line_start = display_map.prev_line_boundary(position).0;
3839 let next_line_start = buffer.clip_point(
3840 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3841 Bias::Left,
3842 );
3843 start = buffer.anchor_before(line_start);
3844 end = buffer.anchor_before(next_line_start);
3845 mode = SelectMode::Line(start..end);
3846 auto_scroll = true;
3847 }
3848 _ => {
3849 start = buffer.anchor_before(MultiBufferOffset(0));
3850 end = buffer.anchor_before(buffer.len());
3851 mode = SelectMode::All;
3852 auto_scroll = false;
3853 }
3854 }
3855 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3856
3857 let point_to_delete: Option<usize> = {
3858 let selected_points: Vec<Selection<Point>> =
3859 self.selections.disjoint_in_range(start..end, &display_map);
3860
3861 if !add || click_count > 1 {
3862 None
3863 } else if !selected_points.is_empty() {
3864 Some(selected_points[0].id)
3865 } else {
3866 let clicked_point_already_selected =
3867 self.selections.disjoint_anchors().iter().find(|selection| {
3868 selection.start.to_point(buffer) == start.to_point(buffer)
3869 || selection.end.to_point(buffer) == end.to_point(buffer)
3870 });
3871
3872 clicked_point_already_selected.map(|selection| selection.id)
3873 }
3874 };
3875
3876 let selections_count = self.selections.count();
3877 let effects = if auto_scroll {
3878 SelectionEffects::default()
3879 } else {
3880 SelectionEffects::no_scroll()
3881 };
3882
3883 self.change_selections(effects, window, cx, |s| {
3884 if let Some(point_to_delete) = point_to_delete {
3885 s.delete(point_to_delete);
3886
3887 if selections_count == 1 {
3888 s.set_pending_anchor_range(start..end, mode);
3889 }
3890 } else {
3891 if !add {
3892 s.clear_disjoint();
3893 }
3894
3895 s.set_pending_anchor_range(start..end, mode);
3896 }
3897 });
3898 }
3899
3900 fn begin_columnar_selection(
3901 &mut self,
3902 position: DisplayPoint,
3903 goal_column: u32,
3904 reset: bool,
3905 mode: ColumnarMode,
3906 window: &mut Window,
3907 cx: &mut Context<Self>,
3908 ) {
3909 if !self.focus_handle.is_focused(window) {
3910 self.last_focused_descendant = None;
3911 window.focus(&self.focus_handle);
3912 }
3913
3914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3915
3916 if reset {
3917 let pointer_position = display_map
3918 .buffer_snapshot()
3919 .anchor_before(position.to_point(&display_map));
3920
3921 self.change_selections(
3922 SelectionEffects::scroll(Autoscroll::newest()),
3923 window,
3924 cx,
3925 |s| {
3926 s.clear_disjoint();
3927 s.set_pending_anchor_range(
3928 pointer_position..pointer_position,
3929 SelectMode::Character,
3930 );
3931 },
3932 );
3933 };
3934
3935 let tail = self.selections.newest::<Point>(&display_map).tail();
3936 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3937 self.columnar_selection_state = match mode {
3938 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3939 selection_tail: selection_anchor,
3940 display_point: if reset {
3941 if position.column() != goal_column {
3942 Some(DisplayPoint::new(position.row(), goal_column))
3943 } else {
3944 None
3945 }
3946 } else {
3947 None
3948 },
3949 }),
3950 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3951 selection_tail: selection_anchor,
3952 }),
3953 };
3954
3955 if !reset {
3956 self.select_columns(position, goal_column, &display_map, window, cx);
3957 }
3958 }
3959
3960 fn update_selection(
3961 &mut self,
3962 position: DisplayPoint,
3963 goal_column: u32,
3964 scroll_delta: gpui::Point<f32>,
3965 window: &mut Window,
3966 cx: &mut Context<Self>,
3967 ) {
3968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3969
3970 if self.columnar_selection_state.is_some() {
3971 self.select_columns(position, goal_column, &display_map, window, cx);
3972 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3973 let buffer = display_map.buffer_snapshot();
3974 let head;
3975 let tail;
3976 let mode = self.selections.pending_mode().unwrap();
3977 match &mode {
3978 SelectMode::Character => {
3979 head = position.to_point(&display_map);
3980 tail = pending.tail().to_point(buffer);
3981 }
3982 SelectMode::Word(original_range) => {
3983 let offset = display_map
3984 .clip_point(position, Bias::Left)
3985 .to_offset(&display_map, Bias::Left);
3986 let original_range = original_range.to_offset(buffer);
3987
3988 let head_offset = if buffer.is_inside_word(offset, None)
3989 || original_range.contains(&offset)
3990 {
3991 let (word_range, _) = buffer.surrounding_word(offset, None);
3992 if word_range.start < original_range.start {
3993 word_range.start
3994 } else {
3995 word_range.end
3996 }
3997 } else {
3998 offset
3999 };
4000
4001 head = head_offset.to_point(buffer);
4002 if head_offset <= original_range.start {
4003 tail = original_range.end.to_point(buffer);
4004 } else {
4005 tail = original_range.start.to_point(buffer);
4006 }
4007 }
4008 SelectMode::Line(original_range) => {
4009 let original_range = original_range.to_point(display_map.buffer_snapshot());
4010
4011 let position = display_map
4012 .clip_point(position, Bias::Left)
4013 .to_point(&display_map);
4014 let line_start = display_map.prev_line_boundary(position).0;
4015 let next_line_start = buffer.clip_point(
4016 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4017 Bias::Left,
4018 );
4019
4020 if line_start < original_range.start {
4021 head = line_start
4022 } else {
4023 head = next_line_start
4024 }
4025
4026 if head <= original_range.start {
4027 tail = original_range.end;
4028 } else {
4029 tail = original_range.start;
4030 }
4031 }
4032 SelectMode::All => {
4033 return;
4034 }
4035 };
4036
4037 if head < tail {
4038 pending.start = buffer.anchor_before(head);
4039 pending.end = buffer.anchor_before(tail);
4040 pending.reversed = true;
4041 } else {
4042 pending.start = buffer.anchor_before(tail);
4043 pending.end = buffer.anchor_before(head);
4044 pending.reversed = false;
4045 }
4046
4047 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4048 s.set_pending(pending.clone(), mode);
4049 });
4050 } else {
4051 log::error!("update_selection dispatched with no pending selection");
4052 return;
4053 }
4054
4055 self.apply_scroll_delta(scroll_delta, window, cx);
4056 cx.notify();
4057 }
4058
4059 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4060 self.columnar_selection_state.take();
4061 if let Some(pending_mode) = self.selections.pending_mode() {
4062 let selections = self
4063 .selections
4064 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4066 s.select(selections);
4067 s.clear_pending();
4068 if s.is_extending() {
4069 s.set_is_extending(false);
4070 } else {
4071 s.set_select_mode(pending_mode);
4072 }
4073 });
4074 }
4075 }
4076
4077 fn select_columns(
4078 &mut self,
4079 head: DisplayPoint,
4080 goal_column: u32,
4081 display_map: &DisplaySnapshot,
4082 window: &mut Window,
4083 cx: &mut Context<Self>,
4084 ) {
4085 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4086 return;
4087 };
4088
4089 let tail = match columnar_state {
4090 ColumnarSelectionState::FromMouse {
4091 selection_tail,
4092 display_point,
4093 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4094 ColumnarSelectionState::FromSelection { selection_tail } => {
4095 selection_tail.to_display_point(display_map)
4096 }
4097 };
4098
4099 let start_row = cmp::min(tail.row(), head.row());
4100 let end_row = cmp::max(tail.row(), head.row());
4101 let start_column = cmp::min(tail.column(), goal_column);
4102 let end_column = cmp::max(tail.column(), goal_column);
4103 let reversed = start_column < tail.column();
4104
4105 let selection_ranges = (start_row.0..=end_row.0)
4106 .map(DisplayRow)
4107 .filter_map(|row| {
4108 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4109 || start_column <= display_map.line_len(row))
4110 && !display_map.is_block_line(row)
4111 {
4112 let start = display_map
4113 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4114 .to_point(display_map);
4115 let end = display_map
4116 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4117 .to_point(display_map);
4118 if reversed {
4119 Some(end..start)
4120 } else {
4121 Some(start..end)
4122 }
4123 } else {
4124 None
4125 }
4126 })
4127 .collect::<Vec<_>>();
4128 if selection_ranges.is_empty() {
4129 return;
4130 }
4131
4132 let ranges = match columnar_state {
4133 ColumnarSelectionState::FromMouse { .. } => {
4134 let mut non_empty_ranges = selection_ranges
4135 .iter()
4136 .filter(|selection_range| selection_range.start != selection_range.end)
4137 .peekable();
4138 if non_empty_ranges.peek().is_some() {
4139 non_empty_ranges.cloned().collect()
4140 } else {
4141 selection_ranges
4142 }
4143 }
4144 _ => selection_ranges,
4145 };
4146
4147 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4148 s.select_ranges(ranges);
4149 });
4150 cx.notify();
4151 }
4152
4153 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4154 self.selections
4155 .all_adjusted(snapshot)
4156 .iter()
4157 .any(|selection| !selection.is_empty())
4158 }
4159
4160 pub fn has_pending_nonempty_selection(&self) -> bool {
4161 let pending_nonempty_selection = match self.selections.pending_anchor() {
4162 Some(Selection { start, end, .. }) => start != end,
4163 None => false,
4164 };
4165
4166 pending_nonempty_selection
4167 || (self.columnar_selection_state.is_some()
4168 && self.selections.disjoint_anchors().len() > 1)
4169 }
4170
4171 pub fn has_pending_selection(&self) -> bool {
4172 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4173 }
4174
4175 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4176 self.selection_mark_mode = false;
4177 self.selection_drag_state = SelectionDragState::None;
4178
4179 if self.dismiss_menus_and_popups(true, window, cx) {
4180 cx.notify();
4181 return;
4182 }
4183 if self.clear_expanded_diff_hunks(cx) {
4184 cx.notify();
4185 return;
4186 }
4187 if self.show_git_blame_gutter {
4188 self.show_git_blame_gutter = false;
4189 cx.notify();
4190 return;
4191 }
4192
4193 if self.mode.is_full()
4194 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4195 {
4196 cx.notify();
4197 return;
4198 }
4199
4200 cx.propagate();
4201 }
4202
4203 pub fn dismiss_menus_and_popups(
4204 &mut self,
4205 is_user_requested: bool,
4206 window: &mut Window,
4207 cx: &mut Context<Self>,
4208 ) -> bool {
4209 let mut dismissed = false;
4210
4211 dismissed |= self.take_rename(false, window, cx).is_some();
4212 dismissed |= self.hide_blame_popover(true, cx);
4213 dismissed |= hide_hover(self, cx);
4214 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4215 dismissed |= self.hide_context_menu(window, cx).is_some();
4216 dismissed |= self.mouse_context_menu.take().is_some();
4217 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4218 dismissed |= self.snippet_stack.pop().is_some();
4219
4220 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4221 self.dismiss_diagnostics(cx);
4222 dismissed = true;
4223 }
4224
4225 dismissed
4226 }
4227
4228 fn linked_editing_ranges_for(
4229 &self,
4230 selection: Range<text::Anchor>,
4231 cx: &App,
4232 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4233 if self.linked_edit_ranges.is_empty() {
4234 return None;
4235 }
4236 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4237 selection.end.buffer_id.and_then(|end_buffer_id| {
4238 if selection.start.buffer_id != Some(end_buffer_id) {
4239 return None;
4240 }
4241 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4242 let snapshot = buffer.read(cx).snapshot();
4243 self.linked_edit_ranges
4244 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4245 .map(|ranges| (ranges, snapshot, buffer))
4246 })?;
4247 use text::ToOffset as TO;
4248 // find offset from the start of current range to current cursor position
4249 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4250
4251 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4252 let start_difference = start_offset - start_byte_offset;
4253 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4254 let end_difference = end_offset - start_byte_offset;
4255 // Current range has associated linked ranges.
4256 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4257 for range in linked_ranges.iter() {
4258 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4259 let end_offset = start_offset + end_difference;
4260 let start_offset = start_offset + start_difference;
4261 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4262 continue;
4263 }
4264 if self.selections.disjoint_anchor_ranges().any(|s| {
4265 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4266 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4267 {
4268 return false;
4269 }
4270 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4271 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4272 }) {
4273 continue;
4274 }
4275 let start = buffer_snapshot.anchor_after(start_offset);
4276 let end = buffer_snapshot.anchor_after(end_offset);
4277 linked_edits
4278 .entry(buffer.clone())
4279 .or_default()
4280 .push(start..end);
4281 }
4282 Some(linked_edits)
4283 }
4284
4285 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4286 let text: Arc<str> = text.into();
4287
4288 if self.read_only(cx) {
4289 return;
4290 }
4291
4292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4293
4294 self.unfold_buffers_with_selections(cx);
4295
4296 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4297 let mut bracket_inserted = false;
4298 let mut edits = Vec::new();
4299 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4300 let mut new_selections = Vec::with_capacity(selections.len());
4301 let mut new_autoclose_regions = Vec::new();
4302 let snapshot = self.buffer.read(cx).read(cx);
4303 let mut clear_linked_edit_ranges = false;
4304
4305 for (selection, autoclose_region) in
4306 self.selections_with_autoclose_regions(selections, &snapshot)
4307 {
4308 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4309 // Determine if the inserted text matches the opening or closing
4310 // bracket of any of this language's bracket pairs.
4311 let mut bracket_pair = None;
4312 let mut is_bracket_pair_start = false;
4313 let mut is_bracket_pair_end = false;
4314 if !text.is_empty() {
4315 let mut bracket_pair_matching_end = None;
4316 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4317 // and they are removing the character that triggered IME popup.
4318 for (pair, enabled) in scope.brackets() {
4319 if !pair.close && !pair.surround {
4320 continue;
4321 }
4322
4323 if enabled && pair.start.ends_with(text.as_ref()) {
4324 let prefix_len = pair.start.len() - text.len();
4325 let preceding_text_matches_prefix = prefix_len == 0
4326 || (selection.start.column >= (prefix_len as u32)
4327 && snapshot.contains_str_at(
4328 Point::new(
4329 selection.start.row,
4330 selection.start.column - (prefix_len as u32),
4331 ),
4332 &pair.start[..prefix_len],
4333 ));
4334 if preceding_text_matches_prefix {
4335 bracket_pair = Some(pair.clone());
4336 is_bracket_pair_start = true;
4337 break;
4338 }
4339 }
4340 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4341 {
4342 // take first bracket pair matching end, but don't break in case a later bracket
4343 // pair matches start
4344 bracket_pair_matching_end = Some(pair.clone());
4345 }
4346 }
4347 if let Some(end) = bracket_pair_matching_end
4348 && bracket_pair.is_none()
4349 {
4350 bracket_pair = Some(end);
4351 is_bracket_pair_end = true;
4352 }
4353 }
4354
4355 if let Some(bracket_pair) = bracket_pair {
4356 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4357 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4358 let auto_surround =
4359 self.use_auto_surround && snapshot_settings.use_auto_surround;
4360 if selection.is_empty() {
4361 if is_bracket_pair_start {
4362 // If the inserted text is a suffix of an opening bracket and the
4363 // selection is preceded by the rest of the opening bracket, then
4364 // insert the closing bracket.
4365 let following_text_allows_autoclose = snapshot
4366 .chars_at(selection.start)
4367 .next()
4368 .is_none_or(|c| scope.should_autoclose_before(c));
4369
4370 let preceding_text_allows_autoclose = selection.start.column == 0
4371 || snapshot
4372 .reversed_chars_at(selection.start)
4373 .next()
4374 .is_none_or(|c| {
4375 bracket_pair.start != bracket_pair.end
4376 || !snapshot
4377 .char_classifier_at(selection.start)
4378 .is_word(c)
4379 });
4380
4381 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4382 && bracket_pair.start.len() == 1
4383 {
4384 let target = bracket_pair.start.chars().next().unwrap();
4385 let current_line_count = snapshot
4386 .reversed_chars_at(selection.start)
4387 .take_while(|&c| c != '\n')
4388 .filter(|&c| c == target)
4389 .count();
4390 current_line_count % 2 == 1
4391 } else {
4392 false
4393 };
4394
4395 if autoclose
4396 && bracket_pair.close
4397 && following_text_allows_autoclose
4398 && preceding_text_allows_autoclose
4399 && !is_closing_quote
4400 {
4401 let anchor = snapshot.anchor_before(selection.end);
4402 new_selections.push((selection.map(|_| anchor), text.len()));
4403 new_autoclose_regions.push((
4404 anchor,
4405 text.len(),
4406 selection.id,
4407 bracket_pair.clone(),
4408 ));
4409 edits.push((
4410 selection.range(),
4411 format!("{}{}", text, bracket_pair.end).into(),
4412 ));
4413 bracket_inserted = true;
4414 continue;
4415 }
4416 }
4417
4418 if let Some(region) = autoclose_region {
4419 // If the selection is followed by an auto-inserted closing bracket,
4420 // then don't insert that closing bracket again; just move the selection
4421 // past the closing bracket.
4422 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4423 && text.as_ref() == region.pair.end.as_str()
4424 && snapshot.contains_str_at(region.range.end, text.as_ref());
4425 if should_skip {
4426 let anchor = snapshot.anchor_after(selection.end);
4427 new_selections
4428 .push((selection.map(|_| anchor), region.pair.end.len()));
4429 continue;
4430 }
4431 }
4432
4433 let always_treat_brackets_as_autoclosed = snapshot
4434 .language_settings_at(selection.start, cx)
4435 .always_treat_brackets_as_autoclosed;
4436 if always_treat_brackets_as_autoclosed
4437 && is_bracket_pair_end
4438 && snapshot.contains_str_at(selection.end, text.as_ref())
4439 {
4440 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4441 // and the inserted text is a closing bracket and the selection is followed
4442 // by the closing bracket then move the selection past the closing bracket.
4443 let anchor = snapshot.anchor_after(selection.end);
4444 new_selections.push((selection.map(|_| anchor), text.len()));
4445 continue;
4446 }
4447 }
4448 // If an opening bracket is 1 character long and is typed while
4449 // text is selected, then surround that text with the bracket pair.
4450 else if auto_surround
4451 && bracket_pair.surround
4452 && is_bracket_pair_start
4453 && bracket_pair.start.chars().count() == 1
4454 {
4455 edits.push((selection.start..selection.start, text.clone()));
4456 edits.push((
4457 selection.end..selection.end,
4458 bracket_pair.end.as_str().into(),
4459 ));
4460 bracket_inserted = true;
4461 new_selections.push((
4462 Selection {
4463 id: selection.id,
4464 start: snapshot.anchor_after(selection.start),
4465 end: snapshot.anchor_before(selection.end),
4466 reversed: selection.reversed,
4467 goal: selection.goal,
4468 },
4469 0,
4470 ));
4471 continue;
4472 }
4473 }
4474 }
4475
4476 if self.auto_replace_emoji_shortcode
4477 && selection.is_empty()
4478 && text.as_ref().ends_with(':')
4479 && let Some(possible_emoji_short_code) =
4480 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4481 && !possible_emoji_short_code.is_empty()
4482 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4483 {
4484 let emoji_shortcode_start = Point::new(
4485 selection.start.row,
4486 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4487 );
4488
4489 // Remove shortcode from buffer
4490 edits.push((
4491 emoji_shortcode_start..selection.start,
4492 "".to_string().into(),
4493 ));
4494 new_selections.push((
4495 Selection {
4496 id: selection.id,
4497 start: snapshot.anchor_after(emoji_shortcode_start),
4498 end: snapshot.anchor_before(selection.start),
4499 reversed: selection.reversed,
4500 goal: selection.goal,
4501 },
4502 0,
4503 ));
4504
4505 // Insert emoji
4506 let selection_start_anchor = snapshot.anchor_after(selection.start);
4507 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4508 edits.push((selection.start..selection.end, emoji.to_string().into()));
4509
4510 continue;
4511 }
4512
4513 // If not handling any auto-close operation, then just replace the selected
4514 // text with the given input and move the selection to the end of the
4515 // newly inserted text.
4516 let anchor = snapshot.anchor_after(selection.end);
4517 if !self.linked_edit_ranges.is_empty() {
4518 let start_anchor = snapshot.anchor_before(selection.start);
4519
4520 let is_word_char = text.chars().next().is_none_or(|char| {
4521 let classifier = snapshot
4522 .char_classifier_at(start_anchor.to_offset(&snapshot))
4523 .scope_context(Some(CharScopeContext::LinkedEdit));
4524 classifier.is_word(char)
4525 });
4526
4527 if is_word_char {
4528 if let Some(ranges) = self
4529 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4530 {
4531 for (buffer, edits) in ranges {
4532 linked_edits
4533 .entry(buffer.clone())
4534 .or_default()
4535 .extend(edits.into_iter().map(|range| (range, text.clone())));
4536 }
4537 }
4538 } else {
4539 clear_linked_edit_ranges = true;
4540 }
4541 }
4542
4543 new_selections.push((selection.map(|_| anchor), 0));
4544 edits.push((selection.start..selection.end, text.clone()));
4545 }
4546
4547 drop(snapshot);
4548
4549 self.transact(window, cx, |this, window, cx| {
4550 if clear_linked_edit_ranges {
4551 this.linked_edit_ranges.clear();
4552 }
4553 let initial_buffer_versions =
4554 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4555
4556 this.buffer.update(cx, |buffer, cx| {
4557 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4558 });
4559 for (buffer, edits) in linked_edits {
4560 buffer.update(cx, |buffer, cx| {
4561 let snapshot = buffer.snapshot();
4562 let edits = edits
4563 .into_iter()
4564 .map(|(range, text)| {
4565 use text::ToPoint as TP;
4566 let end_point = TP::to_point(&range.end, &snapshot);
4567 let start_point = TP::to_point(&range.start, &snapshot);
4568 (start_point..end_point, text)
4569 })
4570 .sorted_by_key(|(range, _)| range.start);
4571 buffer.edit(edits, None, cx);
4572 })
4573 }
4574 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4575 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4576 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4577 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4578 new_anchor_selections,
4579 &map,
4580 )
4581 .zip(new_selection_deltas)
4582 .map(|(selection, delta)| Selection {
4583 id: selection.id,
4584 start: selection.start + delta,
4585 end: selection.end + delta,
4586 reversed: selection.reversed,
4587 goal: SelectionGoal::None,
4588 })
4589 .collect::<Vec<_>>();
4590
4591 let mut i = 0;
4592 for (position, delta, selection_id, pair) in new_autoclose_regions {
4593 let position = position.to_offset(map.buffer_snapshot()) + delta;
4594 let start = map.buffer_snapshot().anchor_before(position);
4595 let end = map.buffer_snapshot().anchor_after(position);
4596 while let Some(existing_state) = this.autoclose_regions.get(i) {
4597 match existing_state
4598 .range
4599 .start
4600 .cmp(&start, map.buffer_snapshot())
4601 {
4602 Ordering::Less => i += 1,
4603 Ordering::Greater => break,
4604 Ordering::Equal => {
4605 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4606 Ordering::Less => i += 1,
4607 Ordering::Equal => break,
4608 Ordering::Greater => break,
4609 }
4610 }
4611 }
4612 }
4613 this.autoclose_regions.insert(
4614 i,
4615 AutocloseRegion {
4616 selection_id,
4617 range: start..end,
4618 pair,
4619 },
4620 );
4621 }
4622
4623 let had_active_edit_prediction = this.has_active_edit_prediction();
4624 this.change_selections(
4625 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4626 window,
4627 cx,
4628 |s| s.select(new_selections),
4629 );
4630
4631 if !bracket_inserted
4632 && let Some(on_type_format_task) =
4633 this.trigger_on_type_formatting(text.to_string(), window, cx)
4634 {
4635 on_type_format_task.detach_and_log_err(cx);
4636 }
4637
4638 let editor_settings = EditorSettings::get_global(cx);
4639 if bracket_inserted
4640 && (editor_settings.auto_signature_help
4641 || editor_settings.show_signature_help_after_edits)
4642 {
4643 this.show_signature_help(&ShowSignatureHelp, window, cx);
4644 }
4645
4646 let trigger_in_words =
4647 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4648 if this.hard_wrap.is_some() {
4649 let latest: Range<Point> = this.selections.newest(&map).range();
4650 if latest.is_empty()
4651 && this
4652 .buffer()
4653 .read(cx)
4654 .snapshot(cx)
4655 .line_len(MultiBufferRow(latest.start.row))
4656 == latest.start.column
4657 {
4658 this.rewrap_impl(
4659 RewrapOptions {
4660 override_language_settings: true,
4661 preserve_existing_whitespace: true,
4662 },
4663 cx,
4664 )
4665 }
4666 }
4667 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4668 refresh_linked_ranges(this, window, cx);
4669 this.refresh_edit_prediction(true, false, window, cx);
4670 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4671 });
4672 }
4673
4674 fn find_possible_emoji_shortcode_at_position(
4675 snapshot: &MultiBufferSnapshot,
4676 position: Point,
4677 ) -> Option<String> {
4678 let mut chars = Vec::new();
4679 let mut found_colon = false;
4680 for char in snapshot.reversed_chars_at(position).take(100) {
4681 // Found a possible emoji shortcode in the middle of the buffer
4682 if found_colon {
4683 if char.is_whitespace() {
4684 chars.reverse();
4685 return Some(chars.iter().collect());
4686 }
4687 // If the previous character is not a whitespace, we are in the middle of a word
4688 // and we only want to complete the shortcode if the word is made up of other emojis
4689 let mut containing_word = String::new();
4690 for ch in snapshot
4691 .reversed_chars_at(position)
4692 .skip(chars.len() + 1)
4693 .take(100)
4694 {
4695 if ch.is_whitespace() {
4696 break;
4697 }
4698 containing_word.push(ch);
4699 }
4700 let containing_word = containing_word.chars().rev().collect::<String>();
4701 if util::word_consists_of_emojis(containing_word.as_str()) {
4702 chars.reverse();
4703 return Some(chars.iter().collect());
4704 }
4705 }
4706
4707 if char.is_whitespace() || !char.is_ascii() {
4708 return None;
4709 }
4710 if char == ':' {
4711 found_colon = true;
4712 } else {
4713 chars.push(char);
4714 }
4715 }
4716 // Found a possible emoji shortcode at the beginning of the buffer
4717 chars.reverse();
4718 Some(chars.iter().collect())
4719 }
4720
4721 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4722 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4723 self.transact(window, cx, |this, window, cx| {
4724 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4725 let selections = this
4726 .selections
4727 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4728 let multi_buffer = this.buffer.read(cx);
4729 let buffer = multi_buffer.snapshot(cx);
4730 selections
4731 .iter()
4732 .map(|selection| {
4733 let start_point = selection.start.to_point(&buffer);
4734 let mut existing_indent =
4735 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4736 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4737 let start = selection.start;
4738 let end = selection.end;
4739 let selection_is_empty = start == end;
4740 let language_scope = buffer.language_scope_at(start);
4741 let (
4742 comment_delimiter,
4743 doc_delimiter,
4744 insert_extra_newline,
4745 indent_on_newline,
4746 indent_on_extra_newline,
4747 ) = if let Some(language) = &language_scope {
4748 let mut insert_extra_newline =
4749 insert_extra_newline_brackets(&buffer, start..end, language)
4750 || insert_extra_newline_tree_sitter(&buffer, start..end);
4751
4752 // Comment extension on newline is allowed only for cursor selections
4753 let comment_delimiter = maybe!({
4754 if !selection_is_empty {
4755 return None;
4756 }
4757
4758 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4759 return None;
4760 }
4761
4762 let delimiters = language.line_comment_prefixes();
4763 let max_len_of_delimiter =
4764 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4765 let (snapshot, range) =
4766 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4767
4768 let num_of_whitespaces = snapshot
4769 .chars_for_range(range.clone())
4770 .take_while(|c| c.is_whitespace())
4771 .count();
4772 let comment_candidate = snapshot
4773 .chars_for_range(range.clone())
4774 .skip(num_of_whitespaces)
4775 .take(max_len_of_delimiter)
4776 .collect::<String>();
4777 let (delimiter, trimmed_len) = delimiters
4778 .iter()
4779 .filter_map(|delimiter| {
4780 let prefix = delimiter.trim_end();
4781 if comment_candidate.starts_with(prefix) {
4782 Some((delimiter, prefix.len()))
4783 } else {
4784 None
4785 }
4786 })
4787 .max_by_key(|(_, len)| *len)?;
4788
4789 if let Some(BlockCommentConfig {
4790 start: block_start, ..
4791 }) = language.block_comment()
4792 {
4793 let block_start_trimmed = block_start.trim_end();
4794 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4795 let line_content = snapshot
4796 .chars_for_range(range)
4797 .skip(num_of_whitespaces)
4798 .take(block_start_trimmed.len())
4799 .collect::<String>();
4800
4801 if line_content.starts_with(block_start_trimmed) {
4802 return None;
4803 }
4804 }
4805 }
4806
4807 let cursor_is_placed_after_comment_marker =
4808 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4809 if cursor_is_placed_after_comment_marker {
4810 Some(delimiter.clone())
4811 } else {
4812 None
4813 }
4814 });
4815
4816 let mut indent_on_newline = IndentSize::spaces(0);
4817 let mut indent_on_extra_newline = IndentSize::spaces(0);
4818
4819 let doc_delimiter = maybe!({
4820 if !selection_is_empty {
4821 return None;
4822 }
4823
4824 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4825 return None;
4826 }
4827
4828 let BlockCommentConfig {
4829 start: start_tag,
4830 end: end_tag,
4831 prefix: delimiter,
4832 tab_size: len,
4833 } = language.documentation_comment()?;
4834 let is_within_block_comment = buffer
4835 .language_scope_at(start_point)
4836 .is_some_and(|scope| scope.override_name() == Some("comment"));
4837 if !is_within_block_comment {
4838 return None;
4839 }
4840
4841 let (snapshot, range) =
4842 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4843
4844 let num_of_whitespaces = snapshot
4845 .chars_for_range(range.clone())
4846 .take_while(|c| c.is_whitespace())
4847 .count();
4848
4849 // 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.
4850 let column = start_point.column;
4851 let cursor_is_after_start_tag = {
4852 let start_tag_len = start_tag.len();
4853 let start_tag_line = snapshot
4854 .chars_for_range(range.clone())
4855 .skip(num_of_whitespaces)
4856 .take(start_tag_len)
4857 .collect::<String>();
4858 if start_tag_line.starts_with(start_tag.as_ref()) {
4859 num_of_whitespaces + start_tag_len <= column as usize
4860 } else {
4861 false
4862 }
4863 };
4864
4865 let cursor_is_after_delimiter = {
4866 let delimiter_trim = delimiter.trim_end();
4867 let delimiter_line = snapshot
4868 .chars_for_range(range.clone())
4869 .skip(num_of_whitespaces)
4870 .take(delimiter_trim.len())
4871 .collect::<String>();
4872 if delimiter_line.starts_with(delimiter_trim) {
4873 num_of_whitespaces + delimiter_trim.len() <= column as usize
4874 } else {
4875 false
4876 }
4877 };
4878
4879 let cursor_is_before_end_tag_if_exists = {
4880 let mut char_position = 0u32;
4881 let mut end_tag_offset = None;
4882
4883 'outer: for chunk in snapshot.text_for_range(range) {
4884 if let Some(byte_pos) = chunk.find(&**end_tag) {
4885 let chars_before_match =
4886 chunk[..byte_pos].chars().count() as u32;
4887 end_tag_offset =
4888 Some(char_position + chars_before_match);
4889 break 'outer;
4890 }
4891 char_position += chunk.chars().count() as u32;
4892 }
4893
4894 if let Some(end_tag_offset) = end_tag_offset {
4895 let cursor_is_before_end_tag = column <= end_tag_offset;
4896 if cursor_is_after_start_tag {
4897 if cursor_is_before_end_tag {
4898 insert_extra_newline = true;
4899 }
4900 let cursor_is_at_start_of_end_tag =
4901 column == end_tag_offset;
4902 if cursor_is_at_start_of_end_tag {
4903 indent_on_extra_newline.len = *len;
4904 }
4905 }
4906 cursor_is_before_end_tag
4907 } else {
4908 true
4909 }
4910 };
4911
4912 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4913 && cursor_is_before_end_tag_if_exists
4914 {
4915 if cursor_is_after_start_tag {
4916 indent_on_newline.len = *len;
4917 }
4918 Some(delimiter.clone())
4919 } else {
4920 None
4921 }
4922 });
4923
4924 (
4925 comment_delimiter,
4926 doc_delimiter,
4927 insert_extra_newline,
4928 indent_on_newline,
4929 indent_on_extra_newline,
4930 )
4931 } else {
4932 (
4933 None,
4934 None,
4935 false,
4936 IndentSize::default(),
4937 IndentSize::default(),
4938 )
4939 };
4940
4941 let prevent_auto_indent = doc_delimiter.is_some();
4942 let delimiter = comment_delimiter.or(doc_delimiter);
4943
4944 let capacity_for_delimiter =
4945 delimiter.as_deref().map(str::len).unwrap_or_default();
4946 let mut new_text = String::with_capacity(
4947 1 + capacity_for_delimiter
4948 + existing_indent.len as usize
4949 + indent_on_newline.len as usize
4950 + indent_on_extra_newline.len as usize,
4951 );
4952 new_text.push('\n');
4953 new_text.extend(existing_indent.chars());
4954 new_text.extend(indent_on_newline.chars());
4955
4956 if let Some(delimiter) = &delimiter {
4957 new_text.push_str(delimiter);
4958 }
4959
4960 if insert_extra_newline {
4961 new_text.push('\n');
4962 new_text.extend(existing_indent.chars());
4963 new_text.extend(indent_on_extra_newline.chars());
4964 }
4965
4966 let anchor = buffer.anchor_after(end);
4967 let new_selection = selection.map(|_| anchor);
4968 (
4969 ((start..end, new_text), prevent_auto_indent),
4970 (insert_extra_newline, new_selection),
4971 )
4972 })
4973 .unzip()
4974 };
4975
4976 let mut auto_indent_edits = Vec::new();
4977 let mut edits = Vec::new();
4978 for (edit, prevent_auto_indent) in edits_with_flags {
4979 if prevent_auto_indent {
4980 edits.push(edit);
4981 } else {
4982 auto_indent_edits.push(edit);
4983 }
4984 }
4985 if !edits.is_empty() {
4986 this.edit(edits, cx);
4987 }
4988 if !auto_indent_edits.is_empty() {
4989 this.edit_with_autoindent(auto_indent_edits, cx);
4990 }
4991
4992 let buffer = this.buffer.read(cx).snapshot(cx);
4993 let new_selections = selection_info
4994 .into_iter()
4995 .map(|(extra_newline_inserted, new_selection)| {
4996 let mut cursor = new_selection.end.to_point(&buffer);
4997 if extra_newline_inserted {
4998 cursor.row -= 1;
4999 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5000 }
5001 new_selection.map(|_| cursor)
5002 })
5003 .collect();
5004
5005 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5006 this.refresh_edit_prediction(true, false, window, cx);
5007 });
5008 }
5009
5010 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5012
5013 let buffer = self.buffer.read(cx);
5014 let snapshot = buffer.snapshot(cx);
5015
5016 let mut edits = Vec::new();
5017 let mut rows = Vec::new();
5018
5019 for (rows_inserted, selection) in self
5020 .selections
5021 .all_adjusted(&self.display_snapshot(cx))
5022 .into_iter()
5023 .enumerate()
5024 {
5025 let cursor = selection.head();
5026 let row = cursor.row;
5027
5028 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5029
5030 let newline = "\n".to_string();
5031 edits.push((start_of_line..start_of_line, newline));
5032
5033 rows.push(row + rows_inserted as u32);
5034 }
5035
5036 self.transact(window, cx, |editor, window, cx| {
5037 editor.edit(edits, cx);
5038
5039 editor.change_selections(Default::default(), window, cx, |s| {
5040 let mut index = 0;
5041 s.move_cursors_with(|map, _, _| {
5042 let row = rows[index];
5043 index += 1;
5044
5045 let point = Point::new(row, 0);
5046 let boundary = map.next_line_boundary(point).1;
5047 let clipped = map.clip_point(boundary, Bias::Left);
5048
5049 (clipped, SelectionGoal::None)
5050 });
5051 });
5052
5053 let mut indent_edits = Vec::new();
5054 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5055 for row in rows {
5056 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5057 for (row, indent) in indents {
5058 if indent.len == 0 {
5059 continue;
5060 }
5061
5062 let text = match indent.kind {
5063 IndentKind::Space => " ".repeat(indent.len as usize),
5064 IndentKind::Tab => "\t".repeat(indent.len as usize),
5065 };
5066 let point = Point::new(row.0, 0);
5067 indent_edits.push((point..point, text));
5068 }
5069 }
5070 editor.edit(indent_edits, cx);
5071 });
5072 }
5073
5074 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5076
5077 let buffer = self.buffer.read(cx);
5078 let snapshot = buffer.snapshot(cx);
5079
5080 let mut edits = Vec::new();
5081 let mut rows = Vec::new();
5082 let mut rows_inserted = 0;
5083
5084 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5085 let cursor = selection.head();
5086 let row = cursor.row;
5087
5088 let point = Point::new(row + 1, 0);
5089 let start_of_line = snapshot.clip_point(point, Bias::Left);
5090
5091 let newline = "\n".to_string();
5092 edits.push((start_of_line..start_of_line, newline));
5093
5094 rows_inserted += 1;
5095 rows.push(row + rows_inserted);
5096 }
5097
5098 self.transact(window, cx, |editor, window, cx| {
5099 editor.edit(edits, cx);
5100
5101 editor.change_selections(Default::default(), window, cx, |s| {
5102 let mut index = 0;
5103 s.move_cursors_with(|map, _, _| {
5104 let row = rows[index];
5105 index += 1;
5106
5107 let point = Point::new(row, 0);
5108 let boundary = map.next_line_boundary(point).1;
5109 let clipped = map.clip_point(boundary, Bias::Left);
5110
5111 (clipped, SelectionGoal::None)
5112 });
5113 });
5114
5115 let mut indent_edits = Vec::new();
5116 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5117 for row in rows {
5118 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5119 for (row, indent) in indents {
5120 if indent.len == 0 {
5121 continue;
5122 }
5123
5124 let text = match indent.kind {
5125 IndentKind::Space => " ".repeat(indent.len as usize),
5126 IndentKind::Tab => "\t".repeat(indent.len as usize),
5127 };
5128 let point = Point::new(row.0, 0);
5129 indent_edits.push((point..point, text));
5130 }
5131 }
5132 editor.edit(indent_edits, cx);
5133 });
5134 }
5135
5136 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5137 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5138 original_indent_columns: Vec::new(),
5139 });
5140 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5141 }
5142
5143 fn insert_with_autoindent_mode(
5144 &mut self,
5145 text: &str,
5146 autoindent_mode: Option<AutoindentMode>,
5147 window: &mut Window,
5148 cx: &mut Context<Self>,
5149 ) {
5150 if self.read_only(cx) {
5151 return;
5152 }
5153
5154 let text: Arc<str> = text.into();
5155 self.transact(window, cx, |this, window, cx| {
5156 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5157 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5158 let anchors = {
5159 let snapshot = buffer.read(cx);
5160 old_selections
5161 .iter()
5162 .map(|s| {
5163 let anchor = snapshot.anchor_after(s.head());
5164 s.map(|_| anchor)
5165 })
5166 .collect::<Vec<_>>()
5167 };
5168 buffer.edit(
5169 old_selections
5170 .iter()
5171 .map(|s| (s.start..s.end, text.clone())),
5172 autoindent_mode,
5173 cx,
5174 );
5175 anchors
5176 });
5177
5178 this.change_selections(Default::default(), window, cx, |s| {
5179 s.select_anchors(selection_anchors);
5180 });
5181
5182 cx.notify();
5183 });
5184 }
5185
5186 fn trigger_completion_on_input(
5187 &mut self,
5188 text: &str,
5189 trigger_in_words: bool,
5190 window: &mut Window,
5191 cx: &mut Context<Self>,
5192 ) {
5193 let completions_source = self
5194 .context_menu
5195 .borrow()
5196 .as_ref()
5197 .and_then(|menu| match menu {
5198 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5199 CodeContextMenu::CodeActions(_) => None,
5200 });
5201
5202 match completions_source {
5203 Some(CompletionsMenuSource::Words { .. }) => {
5204 self.open_or_update_completions_menu(
5205 Some(CompletionsMenuSource::Words {
5206 ignore_threshold: false,
5207 }),
5208 None,
5209 trigger_in_words,
5210 window,
5211 cx,
5212 );
5213 }
5214 _ => self.open_or_update_completions_menu(
5215 None,
5216 Some(text.to_owned()).filter(|x| !x.is_empty()),
5217 true,
5218 window,
5219 cx,
5220 ),
5221 }
5222 }
5223
5224 /// If any empty selections is touching the start of its innermost containing autoclose
5225 /// region, expand it to select the brackets.
5226 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5227 let selections = self
5228 .selections
5229 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5230 let buffer = self.buffer.read(cx).read(cx);
5231 let new_selections = self
5232 .selections_with_autoclose_regions(selections, &buffer)
5233 .map(|(mut selection, region)| {
5234 if !selection.is_empty() {
5235 return selection;
5236 }
5237
5238 if let Some(region) = region {
5239 let mut range = region.range.to_offset(&buffer);
5240 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5241 range.start -= region.pair.start.len();
5242 if buffer.contains_str_at(range.start, ®ion.pair.start)
5243 && buffer.contains_str_at(range.end, ®ion.pair.end)
5244 {
5245 range.end += region.pair.end.len();
5246 selection.start = range.start;
5247 selection.end = range.end;
5248
5249 return selection;
5250 }
5251 }
5252 }
5253
5254 let always_treat_brackets_as_autoclosed = buffer
5255 .language_settings_at(selection.start, cx)
5256 .always_treat_brackets_as_autoclosed;
5257
5258 if !always_treat_brackets_as_autoclosed {
5259 return selection;
5260 }
5261
5262 if let Some(scope) = buffer.language_scope_at(selection.start) {
5263 for (pair, enabled) in scope.brackets() {
5264 if !enabled || !pair.close {
5265 continue;
5266 }
5267
5268 if buffer.contains_str_at(selection.start, &pair.end) {
5269 let pair_start_len = pair.start.len();
5270 if buffer.contains_str_at(
5271 selection.start.saturating_sub_usize(pair_start_len),
5272 &pair.start,
5273 ) {
5274 selection.start -= pair_start_len;
5275 selection.end += pair.end.len();
5276
5277 return selection;
5278 }
5279 }
5280 }
5281 }
5282
5283 selection
5284 })
5285 .collect();
5286
5287 drop(buffer);
5288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5289 selections.select(new_selections)
5290 });
5291 }
5292
5293 /// Iterate the given selections, and for each one, find the smallest surrounding
5294 /// autoclose region. This uses the ordering of the selections and the autoclose
5295 /// regions to avoid repeated comparisons.
5296 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5297 &'a self,
5298 selections: impl IntoIterator<Item = Selection<D>>,
5299 buffer: &'a MultiBufferSnapshot,
5300 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5301 let mut i = 0;
5302 let mut regions = self.autoclose_regions.as_slice();
5303 selections.into_iter().map(move |selection| {
5304 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5305
5306 let mut enclosing = None;
5307 while let Some(pair_state) = regions.get(i) {
5308 if pair_state.range.end.to_offset(buffer) < range.start {
5309 regions = ®ions[i + 1..];
5310 i = 0;
5311 } else if pair_state.range.start.to_offset(buffer) > range.end {
5312 break;
5313 } else {
5314 if pair_state.selection_id == selection.id {
5315 enclosing = Some(pair_state);
5316 }
5317 i += 1;
5318 }
5319 }
5320
5321 (selection, enclosing)
5322 })
5323 }
5324
5325 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5326 fn invalidate_autoclose_regions(
5327 &mut self,
5328 mut selections: &[Selection<Anchor>],
5329 buffer: &MultiBufferSnapshot,
5330 ) {
5331 self.autoclose_regions.retain(|state| {
5332 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5333 return false;
5334 }
5335
5336 let mut i = 0;
5337 while let Some(selection) = selections.get(i) {
5338 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5339 selections = &selections[1..];
5340 continue;
5341 }
5342 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5343 break;
5344 }
5345 if selection.id == state.selection_id {
5346 return true;
5347 } else {
5348 i += 1;
5349 }
5350 }
5351 false
5352 });
5353 }
5354
5355 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5356 let offset = position.to_offset(buffer);
5357 let (word_range, kind) =
5358 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5359 if offset > word_range.start && kind == Some(CharKind::Word) {
5360 Some(
5361 buffer
5362 .text_for_range(word_range.start..offset)
5363 .collect::<String>(),
5364 )
5365 } else {
5366 None
5367 }
5368 }
5369
5370 pub fn visible_excerpts(
5371 &self,
5372 lsp_related_only: bool,
5373 cx: &mut Context<Editor>,
5374 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5375 let project = self.project().cloned();
5376 let multi_buffer = self.buffer().read(cx);
5377 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5378 let multi_buffer_visible_start = self
5379 .scroll_manager
5380 .anchor()
5381 .anchor
5382 .to_point(&multi_buffer_snapshot);
5383 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5384 multi_buffer_visible_start
5385 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5386 Bias::Left,
5387 );
5388 multi_buffer_snapshot
5389 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5390 .into_iter()
5391 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5392 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5393 if !lsp_related_only {
5394 return Some((
5395 excerpt_id,
5396 (
5397 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5398 buffer.version().clone(),
5399 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5400 ),
5401 ));
5402 }
5403
5404 let project = project.as_ref()?.read(cx);
5405 let buffer_file = project::File::from_dyn(buffer.file())?;
5406 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5407 let worktree_entry = buffer_worktree
5408 .read(cx)
5409 .entry_for_id(buffer_file.project_entry_id()?)?;
5410 if worktree_entry.is_ignored {
5411 None
5412 } else {
5413 Some((
5414 excerpt_id,
5415 (
5416 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5417 buffer.version().clone(),
5418 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5419 ),
5420 ))
5421 }
5422 })
5423 .collect()
5424 }
5425
5426 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5427 TextLayoutDetails {
5428 text_system: window.text_system().clone(),
5429 editor_style: self.style.clone().unwrap(),
5430 rem_size: window.rem_size(),
5431 scroll_anchor: self.scroll_manager.anchor(),
5432 visible_rows: self.visible_line_count(),
5433 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5434 }
5435 }
5436
5437 fn trigger_on_type_formatting(
5438 &self,
5439 input: String,
5440 window: &mut Window,
5441 cx: &mut Context<Self>,
5442 ) -> Option<Task<Result<()>>> {
5443 if input.len() != 1 {
5444 return None;
5445 }
5446
5447 let project = self.project()?;
5448 let position = self.selections.newest_anchor().head();
5449 let (buffer, buffer_position) = self
5450 .buffer
5451 .read(cx)
5452 .text_anchor_for_position(position, cx)?;
5453
5454 let settings = language_settings::language_settings(
5455 buffer
5456 .read(cx)
5457 .language_at(buffer_position)
5458 .map(|l| l.name()),
5459 buffer.read(cx).file(),
5460 cx,
5461 );
5462 if !settings.use_on_type_format {
5463 return None;
5464 }
5465
5466 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5467 // hence we do LSP request & edit on host side only — add formats to host's history.
5468 let push_to_lsp_host_history = true;
5469 // If this is not the host, append its history with new edits.
5470 let push_to_client_history = project.read(cx).is_via_collab();
5471
5472 let on_type_formatting = project.update(cx, |project, cx| {
5473 project.on_type_format(
5474 buffer.clone(),
5475 buffer_position,
5476 input,
5477 push_to_lsp_host_history,
5478 cx,
5479 )
5480 });
5481 Some(cx.spawn_in(window, async move |editor, cx| {
5482 if let Some(transaction) = on_type_formatting.await? {
5483 if push_to_client_history {
5484 buffer
5485 .update(cx, |buffer, _| {
5486 buffer.push_transaction(transaction, Instant::now());
5487 buffer.finalize_last_transaction();
5488 })
5489 .ok();
5490 }
5491 editor.update(cx, |editor, cx| {
5492 editor.refresh_document_highlights(cx);
5493 })?;
5494 }
5495 Ok(())
5496 }))
5497 }
5498
5499 pub fn show_word_completions(
5500 &mut self,
5501 _: &ShowWordCompletions,
5502 window: &mut Window,
5503 cx: &mut Context<Self>,
5504 ) {
5505 self.open_or_update_completions_menu(
5506 Some(CompletionsMenuSource::Words {
5507 ignore_threshold: true,
5508 }),
5509 None,
5510 false,
5511 window,
5512 cx,
5513 );
5514 }
5515
5516 pub fn show_completions(
5517 &mut self,
5518 _: &ShowCompletions,
5519 window: &mut Window,
5520 cx: &mut Context<Self>,
5521 ) {
5522 self.open_or_update_completions_menu(None, None, false, window, cx);
5523 }
5524
5525 fn open_or_update_completions_menu(
5526 &mut self,
5527 requested_source: Option<CompletionsMenuSource>,
5528 trigger: Option<String>,
5529 trigger_in_words: bool,
5530 window: &mut Window,
5531 cx: &mut Context<Self>,
5532 ) {
5533 if self.pending_rename.is_some() {
5534 return;
5535 }
5536
5537 let completions_source = self
5538 .context_menu
5539 .borrow()
5540 .as_ref()
5541 .and_then(|menu| match menu {
5542 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5543 CodeContextMenu::CodeActions(_) => None,
5544 });
5545
5546 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5547
5548 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5549 // inserted and selected. To handle that case, the start of the selection is used so that
5550 // the menu starts with all choices.
5551 let position = self
5552 .selections
5553 .newest_anchor()
5554 .start
5555 .bias_right(&multibuffer_snapshot);
5556 if position.diff_base_anchor.is_some() {
5557 return;
5558 }
5559 let buffer_position = multibuffer_snapshot.anchor_before(position);
5560 let Some(buffer) = buffer_position
5561 .text_anchor
5562 .buffer_id
5563 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5564 else {
5565 return;
5566 };
5567 let buffer_snapshot = buffer.read(cx).snapshot();
5568
5569 let menu_is_open = matches!(
5570 self.context_menu.borrow().as_ref(),
5571 Some(CodeContextMenu::Completions(_))
5572 );
5573
5574 let language = buffer_snapshot
5575 .language_at(buffer_position.text_anchor)
5576 .map(|language| language.name());
5577
5578 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5579 let completion_settings = language_settings.completions.clone();
5580
5581 let show_completions_on_input = self
5582 .show_completions_on_input_override
5583 .unwrap_or(language_settings.show_completions_on_input);
5584 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5585 return;
5586 }
5587
5588 let query: Option<Arc<String>> =
5589 Self::completion_query(&multibuffer_snapshot, buffer_position)
5590 .map(|query| query.into());
5591
5592 drop(multibuffer_snapshot);
5593
5594 // Hide the current completions menu when query is empty. Without this, cached
5595 // completions from before the trigger char may be reused (#32774).
5596 if query.is_none() && menu_is_open {
5597 self.hide_context_menu(window, cx);
5598 }
5599
5600 let mut ignore_word_threshold = false;
5601 let provider = match requested_source {
5602 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5603 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5604 ignore_word_threshold = ignore_threshold;
5605 None
5606 }
5607 Some(CompletionsMenuSource::SnippetChoices)
5608 | Some(CompletionsMenuSource::SnippetsOnly) => {
5609 log::error!("bug: SnippetChoices requested_source is not handled");
5610 None
5611 }
5612 };
5613
5614 let sort_completions = provider
5615 .as_ref()
5616 .is_some_and(|provider| provider.sort_completions());
5617
5618 let filter_completions = provider
5619 .as_ref()
5620 .is_none_or(|provider| provider.filter_completions());
5621
5622 let was_snippets_only = matches!(
5623 completions_source,
5624 Some(CompletionsMenuSource::SnippetsOnly)
5625 );
5626
5627 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5628 if filter_completions {
5629 menu.filter(
5630 query.clone().unwrap_or_default(),
5631 buffer_position.text_anchor,
5632 &buffer,
5633 provider.clone(),
5634 window,
5635 cx,
5636 );
5637 }
5638 // When `is_incomplete` is false, no need to re-query completions when the current query
5639 // is a suffix of the initial query.
5640 let was_complete = !menu.is_incomplete;
5641 if was_complete && !was_snippets_only {
5642 // If the new query is a suffix of the old query (typing more characters) and
5643 // the previous result was complete, the existing completions can be filtered.
5644 //
5645 // Note that snippet completions are always complete.
5646 let query_matches = match (&menu.initial_query, &query) {
5647 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5648 (None, _) => true,
5649 _ => false,
5650 };
5651 if query_matches {
5652 let position_matches = if menu.initial_position == position {
5653 true
5654 } else {
5655 let snapshot = self.buffer.read(cx).read(cx);
5656 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5657 };
5658 if position_matches {
5659 return;
5660 }
5661 }
5662 }
5663 };
5664
5665 let Anchor {
5666 excerpt_id: buffer_excerpt_id,
5667 text_anchor: buffer_position,
5668 ..
5669 } = buffer_position;
5670
5671 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5672 buffer_snapshot.surrounding_word(buffer_position, None)
5673 {
5674 let word_to_exclude = buffer_snapshot
5675 .text_for_range(word_range.clone())
5676 .collect::<String>();
5677 (
5678 buffer_snapshot.anchor_before(word_range.start)
5679 ..buffer_snapshot.anchor_after(buffer_position),
5680 Some(word_to_exclude),
5681 )
5682 } else {
5683 (buffer_position..buffer_position, None)
5684 };
5685
5686 let show_completion_documentation = buffer_snapshot
5687 .settings_at(buffer_position, cx)
5688 .show_completion_documentation;
5689
5690 // The document can be large, so stay in reasonable bounds when searching for words,
5691 // otherwise completion pop-up might be slow to appear.
5692 const WORD_LOOKUP_ROWS: u32 = 5_000;
5693 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5694 let min_word_search = buffer_snapshot.clip_point(
5695 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5696 Bias::Left,
5697 );
5698 let max_word_search = buffer_snapshot.clip_point(
5699 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5700 Bias::Right,
5701 );
5702 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5703 ..buffer_snapshot.point_to_offset(max_word_search);
5704
5705 let skip_digits = query
5706 .as_ref()
5707 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5708
5709 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5710 trigger.as_ref().is_none_or(|trigger| {
5711 provider.is_completion_trigger(
5712 &buffer,
5713 position.text_anchor,
5714 trigger,
5715 trigger_in_words,
5716 cx,
5717 )
5718 })
5719 });
5720
5721 let provider_responses = if let Some(provider) = &provider
5722 && load_provider_completions
5723 {
5724 let trigger_character =
5725 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5726 let completion_context = CompletionContext {
5727 trigger_kind: match &trigger_character {
5728 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5729 None => CompletionTriggerKind::INVOKED,
5730 },
5731 trigger_character,
5732 };
5733
5734 provider.completions(
5735 buffer_excerpt_id,
5736 &buffer,
5737 buffer_position,
5738 completion_context,
5739 window,
5740 cx,
5741 )
5742 } else {
5743 Task::ready(Ok(Vec::new()))
5744 };
5745
5746 let load_word_completions = if !self.word_completions_enabled {
5747 false
5748 } else if requested_source
5749 == Some(CompletionsMenuSource::Words {
5750 ignore_threshold: true,
5751 })
5752 {
5753 true
5754 } else {
5755 load_provider_completions
5756 && completion_settings.words != WordsCompletionMode::Disabled
5757 && (ignore_word_threshold || {
5758 let words_min_length = completion_settings.words_min_length;
5759 // check whether word has at least `words_min_length` characters
5760 let query_chars = query.iter().flat_map(|q| q.chars());
5761 query_chars.take(words_min_length).count() == words_min_length
5762 })
5763 };
5764
5765 let mut words = if load_word_completions {
5766 cx.background_spawn({
5767 let buffer_snapshot = buffer_snapshot.clone();
5768 async move {
5769 buffer_snapshot.words_in_range(WordsQuery {
5770 fuzzy_contents: None,
5771 range: word_search_range,
5772 skip_digits,
5773 })
5774 }
5775 })
5776 } else {
5777 Task::ready(BTreeMap::default())
5778 };
5779
5780 let snippets = if let Some(provider) = &provider
5781 && provider.show_snippets()
5782 && let Some(project) = self.project()
5783 {
5784 let char_classifier = buffer_snapshot
5785 .char_classifier_at(buffer_position)
5786 .scope_context(Some(CharScopeContext::Completion));
5787 project.update(cx, |project, cx| {
5788 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5789 })
5790 } else {
5791 Task::ready(Ok(CompletionResponse {
5792 completions: Vec::new(),
5793 display_options: Default::default(),
5794 is_incomplete: false,
5795 }))
5796 };
5797
5798 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5799
5800 let id = post_inc(&mut self.next_completion_id);
5801 let task = cx.spawn_in(window, async move |editor, cx| {
5802 let Ok(()) = editor.update(cx, |this, _| {
5803 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5804 }) else {
5805 return;
5806 };
5807
5808 // TODO: Ideally completions from different sources would be selectively re-queried, so
5809 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5810 let mut completions = Vec::new();
5811 let mut is_incomplete = false;
5812 let mut display_options: Option<CompletionDisplayOptions> = None;
5813 if let Some(provider_responses) = provider_responses.await.log_err()
5814 && !provider_responses.is_empty()
5815 {
5816 for response in provider_responses {
5817 completions.extend(response.completions);
5818 is_incomplete = is_incomplete || response.is_incomplete;
5819 match display_options.as_mut() {
5820 None => {
5821 display_options = Some(response.display_options);
5822 }
5823 Some(options) => options.merge(&response.display_options),
5824 }
5825 }
5826 if completion_settings.words == WordsCompletionMode::Fallback {
5827 words = Task::ready(BTreeMap::default());
5828 }
5829 }
5830 let display_options = display_options.unwrap_or_default();
5831
5832 let mut words = words.await;
5833 if let Some(word_to_exclude) = &word_to_exclude {
5834 words.remove(word_to_exclude);
5835 }
5836 for lsp_completion in &completions {
5837 words.remove(&lsp_completion.new_text);
5838 }
5839 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5840 replace_range: word_replace_range.clone(),
5841 new_text: word.clone(),
5842 label: CodeLabel::plain(word, None),
5843 match_start: None,
5844 snippet_deduplication_key: None,
5845 icon_path: None,
5846 documentation: None,
5847 source: CompletionSource::BufferWord {
5848 word_range,
5849 resolved: false,
5850 },
5851 insert_text_mode: Some(InsertTextMode::AS_IS),
5852 confirm: None,
5853 }));
5854
5855 completions.extend(
5856 snippets
5857 .await
5858 .into_iter()
5859 .flat_map(|response| response.completions),
5860 );
5861
5862 let menu = if completions.is_empty() {
5863 None
5864 } else {
5865 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5866 let languages = editor
5867 .workspace
5868 .as_ref()
5869 .and_then(|(workspace, _)| workspace.upgrade())
5870 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5871 let menu = CompletionsMenu::new(
5872 id,
5873 requested_source.unwrap_or(if load_provider_completions {
5874 CompletionsMenuSource::Normal
5875 } else {
5876 CompletionsMenuSource::SnippetsOnly
5877 }),
5878 sort_completions,
5879 show_completion_documentation,
5880 position,
5881 query.clone(),
5882 is_incomplete,
5883 buffer.clone(),
5884 completions.into(),
5885 display_options,
5886 snippet_sort_order,
5887 languages,
5888 language,
5889 cx,
5890 );
5891
5892 let query = if filter_completions { query } else { None };
5893 let matches_task = menu.do_async_filtering(
5894 query.unwrap_or_default(),
5895 buffer_position,
5896 &buffer,
5897 cx,
5898 );
5899 (menu, matches_task)
5900 }) else {
5901 return;
5902 };
5903
5904 let matches = matches_task.await;
5905
5906 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5907 // Newer menu already set, so exit.
5908 if let Some(CodeContextMenu::Completions(prev_menu)) =
5909 editor.context_menu.borrow().as_ref()
5910 && prev_menu.id > id
5911 {
5912 return;
5913 };
5914
5915 // Only valid to take prev_menu because either the new menu is immediately set
5916 // below, or the menu is hidden.
5917 if let Some(CodeContextMenu::Completions(prev_menu)) =
5918 editor.context_menu.borrow_mut().take()
5919 {
5920 let position_matches =
5921 if prev_menu.initial_position == menu.initial_position {
5922 true
5923 } else {
5924 let snapshot = editor.buffer.read(cx).read(cx);
5925 prev_menu.initial_position.to_offset(&snapshot)
5926 == menu.initial_position.to_offset(&snapshot)
5927 };
5928 if position_matches {
5929 // Preserve markdown cache before `set_filter_results` because it will
5930 // try to populate the documentation cache.
5931 menu.preserve_markdown_cache(prev_menu);
5932 }
5933 };
5934
5935 menu.set_filter_results(matches, provider, window, cx);
5936 }) else {
5937 return;
5938 };
5939
5940 menu.visible().then_some(menu)
5941 };
5942
5943 editor
5944 .update_in(cx, |editor, window, cx| {
5945 if editor.focus_handle.is_focused(window)
5946 && let Some(menu) = menu
5947 {
5948 *editor.context_menu.borrow_mut() =
5949 Some(CodeContextMenu::Completions(menu));
5950
5951 crate::hover_popover::hide_hover(editor, cx);
5952 if editor.show_edit_predictions_in_menu() {
5953 editor.update_visible_edit_prediction(window, cx);
5954 } else {
5955 editor.discard_edit_prediction(false, cx);
5956 }
5957
5958 cx.notify();
5959 return;
5960 }
5961
5962 if editor.completion_tasks.len() <= 1 {
5963 // If there are no more completion tasks and the last menu was empty, we should hide it.
5964 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5965 // If it was already hidden and we don't show edit predictions in the menu,
5966 // we should also show the edit prediction when available.
5967 if was_hidden && editor.show_edit_predictions_in_menu() {
5968 editor.update_visible_edit_prediction(window, cx);
5969 }
5970 }
5971 })
5972 .ok();
5973 });
5974
5975 self.completion_tasks.push((id, task));
5976 }
5977
5978 #[cfg(feature = "test-support")]
5979 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5980 let menu = self.context_menu.borrow();
5981 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5982 let completions = menu.completions.borrow();
5983 Some(completions.to_vec())
5984 } else {
5985 None
5986 }
5987 }
5988
5989 pub fn with_completions_menu_matching_id<R>(
5990 &self,
5991 id: CompletionId,
5992 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5993 ) -> R {
5994 let mut context_menu = self.context_menu.borrow_mut();
5995 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5996 return f(None);
5997 };
5998 if completions_menu.id != id {
5999 return f(None);
6000 }
6001 f(Some(completions_menu))
6002 }
6003
6004 pub fn confirm_completion(
6005 &mut self,
6006 action: &ConfirmCompletion,
6007 window: &mut Window,
6008 cx: &mut Context<Self>,
6009 ) -> Option<Task<Result<()>>> {
6010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6011 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6012 }
6013
6014 pub fn confirm_completion_insert(
6015 &mut self,
6016 _: &ConfirmCompletionInsert,
6017 window: &mut Window,
6018 cx: &mut Context<Self>,
6019 ) -> Option<Task<Result<()>>> {
6020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6021 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6022 }
6023
6024 pub fn confirm_completion_replace(
6025 &mut self,
6026 _: &ConfirmCompletionReplace,
6027 window: &mut Window,
6028 cx: &mut Context<Self>,
6029 ) -> Option<Task<Result<()>>> {
6030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6031 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6032 }
6033
6034 pub fn compose_completion(
6035 &mut self,
6036 action: &ComposeCompletion,
6037 window: &mut Window,
6038 cx: &mut Context<Self>,
6039 ) -> Option<Task<Result<()>>> {
6040 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6041 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6042 }
6043
6044 fn do_completion(
6045 &mut self,
6046 item_ix: Option<usize>,
6047 intent: CompletionIntent,
6048 window: &mut Window,
6049 cx: &mut Context<Editor>,
6050 ) -> Option<Task<Result<()>>> {
6051 use language::ToOffset as _;
6052
6053 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6054 else {
6055 return None;
6056 };
6057
6058 let candidate_id = {
6059 let entries = completions_menu.entries.borrow();
6060 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6061 if self.show_edit_predictions_in_menu() {
6062 self.discard_edit_prediction(true, cx);
6063 }
6064 mat.candidate_id
6065 };
6066
6067 let completion = completions_menu
6068 .completions
6069 .borrow()
6070 .get(candidate_id)?
6071 .clone();
6072 cx.stop_propagation();
6073
6074 let buffer_handle = completions_menu.buffer.clone();
6075
6076 let CompletionEdit {
6077 new_text,
6078 snippet,
6079 replace_range,
6080 } = process_completion_for_edit(
6081 &completion,
6082 intent,
6083 &buffer_handle,
6084 &completions_menu.initial_position.text_anchor,
6085 cx,
6086 );
6087
6088 let buffer = buffer_handle.read(cx);
6089 let snapshot = self.buffer.read(cx).snapshot(cx);
6090 let newest_anchor = self.selections.newest_anchor();
6091 let replace_range_multibuffer = {
6092 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6093 excerpt.map_range_from_buffer(replace_range.clone())
6094 };
6095 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6096 return None;
6097 }
6098
6099 let old_text = buffer
6100 .text_for_range(replace_range.clone())
6101 .collect::<String>();
6102 let lookbehind = newest_anchor
6103 .start
6104 .text_anchor
6105 .to_offset(buffer)
6106 .saturating_sub(replace_range.start.0);
6107 let lookahead = replace_range
6108 .end
6109 .0
6110 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6111 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6112 let suffix = &old_text[lookbehind.min(old_text.len())..];
6113
6114 let selections = self
6115 .selections
6116 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6117 let mut ranges = Vec::new();
6118 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6119
6120 for selection in &selections {
6121 let range = if selection.id == newest_anchor.id {
6122 replace_range_multibuffer.clone()
6123 } else {
6124 let mut range = selection.range();
6125
6126 // if prefix is present, don't duplicate it
6127 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6128 range.start = range.start.saturating_sub_usize(lookbehind);
6129
6130 // if suffix is also present, mimic the newest cursor and replace it
6131 if selection.id != newest_anchor.id
6132 && snapshot.contains_str_at(range.end, suffix)
6133 {
6134 range.end += lookahead;
6135 }
6136 }
6137 range
6138 };
6139
6140 ranges.push(range.clone());
6141
6142 if !self.linked_edit_ranges.is_empty() {
6143 let start_anchor = snapshot.anchor_before(range.start);
6144 let end_anchor = snapshot.anchor_after(range.end);
6145 if let Some(ranges) = self
6146 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6147 {
6148 for (buffer, edits) in ranges {
6149 linked_edits
6150 .entry(buffer.clone())
6151 .or_default()
6152 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6153 }
6154 }
6155 }
6156 }
6157
6158 let common_prefix_len = old_text
6159 .chars()
6160 .zip(new_text.chars())
6161 .take_while(|(a, b)| a == b)
6162 .map(|(a, _)| a.len_utf8())
6163 .sum::<usize>();
6164
6165 cx.emit(EditorEvent::InputHandled {
6166 utf16_range_to_replace: None,
6167 text: new_text[common_prefix_len..].into(),
6168 });
6169
6170 self.transact(window, cx, |editor, window, cx| {
6171 if let Some(mut snippet) = snippet {
6172 snippet.text = new_text.to_string();
6173 editor
6174 .insert_snippet(&ranges, snippet, window, cx)
6175 .log_err();
6176 } else {
6177 editor.buffer.update(cx, |multi_buffer, cx| {
6178 let auto_indent = match completion.insert_text_mode {
6179 Some(InsertTextMode::AS_IS) => None,
6180 _ => editor.autoindent_mode.clone(),
6181 };
6182 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6183 multi_buffer.edit(edits, auto_indent, cx);
6184 });
6185 }
6186 for (buffer, edits) in linked_edits {
6187 buffer.update(cx, |buffer, cx| {
6188 let snapshot = buffer.snapshot();
6189 let edits = edits
6190 .into_iter()
6191 .map(|(range, text)| {
6192 use text::ToPoint as TP;
6193 let end_point = TP::to_point(&range.end, &snapshot);
6194 let start_point = TP::to_point(&range.start, &snapshot);
6195 (start_point..end_point, text)
6196 })
6197 .sorted_by_key(|(range, _)| range.start);
6198 buffer.edit(edits, None, cx);
6199 })
6200 }
6201
6202 editor.refresh_edit_prediction(true, false, window, cx);
6203 });
6204 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6205
6206 let show_new_completions_on_confirm = completion
6207 .confirm
6208 .as_ref()
6209 .is_some_and(|confirm| confirm(intent, window, cx));
6210 if show_new_completions_on_confirm {
6211 self.open_or_update_completions_menu(None, None, false, window, cx);
6212 }
6213
6214 let provider = self.completion_provider.as_ref()?;
6215
6216 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6217 let command = lsp_store.as_ref().and_then(|lsp_store| {
6218 let CompletionSource::Lsp {
6219 lsp_completion,
6220 server_id,
6221 ..
6222 } = &completion.source
6223 else {
6224 return None;
6225 };
6226 let lsp_command = lsp_completion.command.as_ref()?;
6227 let available_commands = lsp_store
6228 .read(cx)
6229 .lsp_server_capabilities
6230 .get(server_id)
6231 .and_then(|server_capabilities| {
6232 server_capabilities
6233 .execute_command_provider
6234 .as_ref()
6235 .map(|options| options.commands.as_slice())
6236 })?;
6237 if available_commands.contains(&lsp_command.command) {
6238 Some(CodeAction {
6239 server_id: *server_id,
6240 range: language::Anchor::MIN..language::Anchor::MIN,
6241 lsp_action: LspAction::Command(lsp_command.clone()),
6242 resolved: false,
6243 })
6244 } else {
6245 None
6246 }
6247 });
6248
6249 drop(completion);
6250 let apply_edits = provider.apply_additional_edits_for_completion(
6251 buffer_handle.clone(),
6252 completions_menu.completions.clone(),
6253 candidate_id,
6254 true,
6255 cx,
6256 );
6257
6258 let editor_settings = EditorSettings::get_global(cx);
6259 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6260 // After the code completion is finished, users often want to know what signatures are needed.
6261 // so we should automatically call signature_help
6262 self.show_signature_help(&ShowSignatureHelp, window, cx);
6263 }
6264
6265 Some(cx.spawn_in(window, async move |editor, cx| {
6266 apply_edits.await?;
6267
6268 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6269 let title = command.lsp_action.title().to_owned();
6270 let project_transaction = lsp_store
6271 .update(cx, |lsp_store, cx| {
6272 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6273 })?
6274 .await
6275 .context("applying post-completion command")?;
6276 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6277 Self::open_project_transaction(
6278 &editor,
6279 workspace.downgrade(),
6280 project_transaction,
6281 title,
6282 cx,
6283 )
6284 .await?;
6285 }
6286 }
6287
6288 Ok(())
6289 }))
6290 }
6291
6292 pub fn toggle_code_actions(
6293 &mut self,
6294 action: &ToggleCodeActions,
6295 window: &mut Window,
6296 cx: &mut Context<Self>,
6297 ) {
6298 let quick_launch = action.quick_launch;
6299 let mut context_menu = self.context_menu.borrow_mut();
6300 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6301 if code_actions.deployed_from == action.deployed_from {
6302 // Toggle if we're selecting the same one
6303 *context_menu = None;
6304 cx.notify();
6305 return;
6306 } else {
6307 // Otherwise, clear it and start a new one
6308 *context_menu = None;
6309 cx.notify();
6310 }
6311 }
6312 drop(context_menu);
6313 let snapshot = self.snapshot(window, cx);
6314 let deployed_from = action.deployed_from.clone();
6315 let action = action.clone();
6316 self.completion_tasks.clear();
6317 self.discard_edit_prediction(false, cx);
6318
6319 let multibuffer_point = match &action.deployed_from {
6320 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6321 DisplayPoint::new(*row, 0).to_point(&snapshot)
6322 }
6323 _ => self
6324 .selections
6325 .newest::<Point>(&snapshot.display_snapshot)
6326 .head(),
6327 };
6328 let Some((buffer, buffer_row)) = snapshot
6329 .buffer_snapshot()
6330 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6331 .and_then(|(buffer_snapshot, range)| {
6332 self.buffer()
6333 .read(cx)
6334 .buffer(buffer_snapshot.remote_id())
6335 .map(|buffer| (buffer, range.start.row))
6336 })
6337 else {
6338 return;
6339 };
6340 let buffer_id = buffer.read(cx).remote_id();
6341 let tasks = self
6342 .tasks
6343 .get(&(buffer_id, buffer_row))
6344 .map(|t| Arc::new(t.to_owned()));
6345
6346 if !self.focus_handle.is_focused(window) {
6347 return;
6348 }
6349 let project = self.project.clone();
6350
6351 let code_actions_task = match deployed_from {
6352 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6353 _ => self.code_actions(buffer_row, window, cx),
6354 };
6355
6356 let runnable_task = match deployed_from {
6357 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6358 _ => {
6359 let mut task_context_task = Task::ready(None);
6360 if let Some(tasks) = &tasks
6361 && let Some(project) = project
6362 {
6363 task_context_task =
6364 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6365 }
6366
6367 cx.spawn_in(window, {
6368 let buffer = buffer.clone();
6369 async move |editor, cx| {
6370 let task_context = task_context_task.await;
6371
6372 let resolved_tasks =
6373 tasks
6374 .zip(task_context.clone())
6375 .map(|(tasks, task_context)| ResolvedTasks {
6376 templates: tasks.resolve(&task_context).collect(),
6377 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6378 multibuffer_point.row,
6379 tasks.column,
6380 )),
6381 });
6382 let debug_scenarios = editor
6383 .update(cx, |editor, cx| {
6384 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6385 })?
6386 .await;
6387 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6388 }
6389 })
6390 }
6391 };
6392
6393 cx.spawn_in(window, async move |editor, cx| {
6394 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6395 let code_actions = code_actions_task.await;
6396 let spawn_straight_away = quick_launch
6397 && resolved_tasks
6398 .as_ref()
6399 .is_some_and(|tasks| tasks.templates.len() == 1)
6400 && code_actions
6401 .as_ref()
6402 .is_none_or(|actions| actions.is_empty())
6403 && debug_scenarios.is_empty();
6404
6405 editor.update_in(cx, |editor, window, cx| {
6406 crate::hover_popover::hide_hover(editor, cx);
6407 let actions = CodeActionContents::new(
6408 resolved_tasks,
6409 code_actions,
6410 debug_scenarios,
6411 task_context.unwrap_or_default(),
6412 );
6413
6414 // Don't show the menu if there are no actions available
6415 if actions.is_empty() {
6416 cx.notify();
6417 return Task::ready(Ok(()));
6418 }
6419
6420 *editor.context_menu.borrow_mut() =
6421 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6422 buffer,
6423 actions,
6424 selected_item: Default::default(),
6425 scroll_handle: UniformListScrollHandle::default(),
6426 deployed_from,
6427 }));
6428 cx.notify();
6429 if spawn_straight_away
6430 && let Some(task) = editor.confirm_code_action(
6431 &ConfirmCodeAction { item_ix: Some(0) },
6432 window,
6433 cx,
6434 )
6435 {
6436 return task;
6437 }
6438
6439 Task::ready(Ok(()))
6440 })
6441 })
6442 .detach_and_log_err(cx);
6443 }
6444
6445 fn debug_scenarios(
6446 &mut self,
6447 resolved_tasks: &Option<ResolvedTasks>,
6448 buffer: &Entity<Buffer>,
6449 cx: &mut App,
6450 ) -> Task<Vec<task::DebugScenario>> {
6451 maybe!({
6452 let project = self.project()?;
6453 let dap_store = project.read(cx).dap_store();
6454 let mut scenarios = vec![];
6455 let resolved_tasks = resolved_tasks.as_ref()?;
6456 let buffer = buffer.read(cx);
6457 let language = buffer.language()?;
6458 let file = buffer.file();
6459 let debug_adapter = language_settings(language.name().into(), file, cx)
6460 .debuggers
6461 .first()
6462 .map(SharedString::from)
6463 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6464
6465 dap_store.update(cx, |dap_store, cx| {
6466 for (_, task) in &resolved_tasks.templates {
6467 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6468 task.original_task().clone(),
6469 debug_adapter.clone().into(),
6470 task.display_label().to_owned().into(),
6471 cx,
6472 );
6473 scenarios.push(maybe_scenario);
6474 }
6475 });
6476 Some(cx.background_spawn(async move {
6477 futures::future::join_all(scenarios)
6478 .await
6479 .into_iter()
6480 .flatten()
6481 .collect::<Vec<_>>()
6482 }))
6483 })
6484 .unwrap_or_else(|| Task::ready(vec![]))
6485 }
6486
6487 fn code_actions(
6488 &mut self,
6489 buffer_row: u32,
6490 window: &mut Window,
6491 cx: &mut Context<Self>,
6492 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6493 let mut task = self.code_actions_task.take();
6494 cx.spawn_in(window, async move |editor, cx| {
6495 while let Some(prev_task) = task {
6496 prev_task.await.log_err();
6497 task = editor
6498 .update(cx, |this, _| this.code_actions_task.take())
6499 .ok()?;
6500 }
6501
6502 editor
6503 .update(cx, |editor, cx| {
6504 editor
6505 .available_code_actions
6506 .clone()
6507 .and_then(|(location, code_actions)| {
6508 let snapshot = location.buffer.read(cx).snapshot();
6509 let point_range = location.range.to_point(&snapshot);
6510 let point_range = point_range.start.row..=point_range.end.row;
6511 if point_range.contains(&buffer_row) {
6512 Some(code_actions)
6513 } else {
6514 None
6515 }
6516 })
6517 })
6518 .ok()
6519 .flatten()
6520 })
6521 }
6522
6523 pub fn confirm_code_action(
6524 &mut self,
6525 action: &ConfirmCodeAction,
6526 window: &mut Window,
6527 cx: &mut Context<Self>,
6528 ) -> Option<Task<Result<()>>> {
6529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6530
6531 let actions_menu =
6532 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6533 menu
6534 } else {
6535 return None;
6536 };
6537
6538 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6539 let action = actions_menu.actions.get(action_ix)?;
6540 let title = action.label();
6541 let buffer = actions_menu.buffer;
6542 let workspace = self.workspace()?;
6543
6544 match action {
6545 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6546 workspace.update(cx, |workspace, cx| {
6547 workspace.schedule_resolved_task(
6548 task_source_kind,
6549 resolved_task,
6550 false,
6551 window,
6552 cx,
6553 );
6554
6555 Some(Task::ready(Ok(())))
6556 })
6557 }
6558 CodeActionsItem::CodeAction {
6559 excerpt_id,
6560 action,
6561 provider,
6562 } => {
6563 let apply_code_action =
6564 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6565 let workspace = workspace.downgrade();
6566 Some(cx.spawn_in(window, async move |editor, cx| {
6567 let project_transaction = apply_code_action.await?;
6568 Self::open_project_transaction(
6569 &editor,
6570 workspace,
6571 project_transaction,
6572 title,
6573 cx,
6574 )
6575 .await
6576 }))
6577 }
6578 CodeActionsItem::DebugScenario(scenario) => {
6579 let context = actions_menu.actions.context;
6580
6581 workspace.update(cx, |workspace, cx| {
6582 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6583 workspace.start_debug_session(
6584 scenario,
6585 context,
6586 Some(buffer),
6587 None,
6588 window,
6589 cx,
6590 );
6591 });
6592 Some(Task::ready(Ok(())))
6593 }
6594 }
6595 }
6596
6597 pub async fn open_project_transaction(
6598 editor: &WeakEntity<Editor>,
6599 workspace: WeakEntity<Workspace>,
6600 transaction: ProjectTransaction,
6601 title: String,
6602 cx: &mut AsyncWindowContext,
6603 ) -> Result<()> {
6604 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6605 cx.update(|_, cx| {
6606 entries.sort_unstable_by_key(|(buffer, _)| {
6607 buffer.read(cx).file().map(|f| f.path().clone())
6608 });
6609 })?;
6610 if entries.is_empty() {
6611 return Ok(());
6612 }
6613
6614 // If the project transaction's edits are all contained within this editor, then
6615 // avoid opening a new editor to display them.
6616
6617 if let [(buffer, transaction)] = &*entries {
6618 let excerpt = editor.update(cx, |editor, cx| {
6619 editor
6620 .buffer()
6621 .read(cx)
6622 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6623 })?;
6624 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6625 && excerpted_buffer == *buffer
6626 {
6627 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6628 let excerpt_range = excerpt_range.to_offset(buffer);
6629 buffer
6630 .edited_ranges_for_transaction::<usize>(transaction)
6631 .all(|range| {
6632 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6633 })
6634 })?;
6635
6636 if all_edits_within_excerpt {
6637 return Ok(());
6638 }
6639 }
6640 }
6641
6642 let mut ranges_to_highlight = Vec::new();
6643 let excerpt_buffer = cx.new(|cx| {
6644 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6645 for (buffer_handle, transaction) in &entries {
6646 let edited_ranges = buffer_handle
6647 .read(cx)
6648 .edited_ranges_for_transaction::<Point>(transaction)
6649 .collect::<Vec<_>>();
6650 let (ranges, _) = multibuffer.set_excerpts_for_path(
6651 PathKey::for_buffer(buffer_handle, cx),
6652 buffer_handle.clone(),
6653 edited_ranges,
6654 multibuffer_context_lines(cx),
6655 cx,
6656 );
6657
6658 ranges_to_highlight.extend(ranges);
6659 }
6660 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6661 multibuffer
6662 })?;
6663
6664 workspace.update_in(cx, |workspace, window, cx| {
6665 let project = workspace.project().clone();
6666 let editor =
6667 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6668 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6669 editor.update(cx, |editor, cx| {
6670 editor.highlight_background::<Self>(
6671 &ranges_to_highlight,
6672 |_, theme| theme.colors().editor_highlighted_line_background,
6673 cx,
6674 );
6675 });
6676 })?;
6677
6678 Ok(())
6679 }
6680
6681 pub fn clear_code_action_providers(&mut self) {
6682 self.code_action_providers.clear();
6683 self.available_code_actions.take();
6684 }
6685
6686 pub fn add_code_action_provider(
6687 &mut self,
6688 provider: Rc<dyn CodeActionProvider>,
6689 window: &mut Window,
6690 cx: &mut Context<Self>,
6691 ) {
6692 if self
6693 .code_action_providers
6694 .iter()
6695 .any(|existing_provider| existing_provider.id() == provider.id())
6696 {
6697 return;
6698 }
6699
6700 self.code_action_providers.push(provider);
6701 self.refresh_code_actions(window, cx);
6702 }
6703
6704 pub fn remove_code_action_provider(
6705 &mut self,
6706 id: Arc<str>,
6707 window: &mut Window,
6708 cx: &mut Context<Self>,
6709 ) {
6710 self.code_action_providers
6711 .retain(|provider| provider.id() != id);
6712 self.refresh_code_actions(window, cx);
6713 }
6714
6715 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6716 !self.code_action_providers.is_empty()
6717 && EditorSettings::get_global(cx).toolbar.code_actions
6718 }
6719
6720 pub fn has_available_code_actions(&self) -> bool {
6721 self.available_code_actions
6722 .as_ref()
6723 .is_some_and(|(_, actions)| !actions.is_empty())
6724 }
6725
6726 fn render_inline_code_actions(
6727 &self,
6728 icon_size: ui::IconSize,
6729 display_row: DisplayRow,
6730 is_active: bool,
6731 cx: &mut Context<Self>,
6732 ) -> AnyElement {
6733 let show_tooltip = !self.context_menu_visible();
6734 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6735 .icon_size(icon_size)
6736 .shape(ui::IconButtonShape::Square)
6737 .icon_color(ui::Color::Hidden)
6738 .toggle_state(is_active)
6739 .when(show_tooltip, |this| {
6740 this.tooltip({
6741 let focus_handle = self.focus_handle.clone();
6742 move |_window, cx| {
6743 Tooltip::for_action_in(
6744 "Toggle Code Actions",
6745 &ToggleCodeActions {
6746 deployed_from: None,
6747 quick_launch: false,
6748 },
6749 &focus_handle,
6750 cx,
6751 )
6752 }
6753 })
6754 })
6755 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6756 window.focus(&editor.focus_handle(cx));
6757 editor.toggle_code_actions(
6758 &crate::actions::ToggleCodeActions {
6759 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6760 display_row,
6761 )),
6762 quick_launch: false,
6763 },
6764 window,
6765 cx,
6766 );
6767 }))
6768 .into_any_element()
6769 }
6770
6771 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6772 &self.context_menu
6773 }
6774
6775 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6776 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6777 cx.background_executor()
6778 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6779 .await;
6780
6781 let (start_buffer, start, _, end, newest_selection) = this
6782 .update(cx, |this, cx| {
6783 let newest_selection = this.selections.newest_anchor().clone();
6784 if newest_selection.head().diff_base_anchor.is_some() {
6785 return None;
6786 }
6787 let display_snapshot = this.display_snapshot(cx);
6788 let newest_selection_adjusted =
6789 this.selections.newest_adjusted(&display_snapshot);
6790 let buffer = this.buffer.read(cx);
6791
6792 let (start_buffer, start) =
6793 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6794 let (end_buffer, end) =
6795 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6796
6797 Some((start_buffer, start, end_buffer, end, newest_selection))
6798 })?
6799 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6800 .context(
6801 "Expected selection to lie in a single buffer when refreshing code actions",
6802 )?;
6803 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6804 let providers = this.code_action_providers.clone();
6805 let tasks = this
6806 .code_action_providers
6807 .iter()
6808 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6809 .collect::<Vec<_>>();
6810 (providers, tasks)
6811 })?;
6812
6813 let mut actions = Vec::new();
6814 for (provider, provider_actions) in
6815 providers.into_iter().zip(future::join_all(tasks).await)
6816 {
6817 if let Some(provider_actions) = provider_actions.log_err() {
6818 actions.extend(provider_actions.into_iter().map(|action| {
6819 AvailableCodeAction {
6820 excerpt_id: newest_selection.start.excerpt_id,
6821 action,
6822 provider: provider.clone(),
6823 }
6824 }));
6825 }
6826 }
6827
6828 this.update(cx, |this, cx| {
6829 this.available_code_actions = if actions.is_empty() {
6830 None
6831 } else {
6832 Some((
6833 Location {
6834 buffer: start_buffer,
6835 range: start..end,
6836 },
6837 actions.into(),
6838 ))
6839 };
6840 cx.notify();
6841 })
6842 }));
6843 }
6844
6845 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6846 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6847 self.show_git_blame_inline = false;
6848
6849 self.show_git_blame_inline_delay_task =
6850 Some(cx.spawn_in(window, async move |this, cx| {
6851 cx.background_executor().timer(delay).await;
6852
6853 this.update(cx, |this, cx| {
6854 this.show_git_blame_inline = true;
6855 cx.notify();
6856 })
6857 .log_err();
6858 }));
6859 }
6860 }
6861
6862 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6863 let snapshot = self.snapshot(window, cx);
6864 let cursor = self
6865 .selections
6866 .newest::<Point>(&snapshot.display_snapshot)
6867 .head();
6868 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6869 else {
6870 return;
6871 };
6872
6873 if self.blame.is_none() {
6874 self.start_git_blame(true, window, cx);
6875 }
6876 let Some(blame) = self.blame.as_ref() else {
6877 return;
6878 };
6879
6880 let row_info = RowInfo {
6881 buffer_id: Some(buffer.remote_id()),
6882 buffer_row: Some(point.row),
6883 ..Default::default()
6884 };
6885 let Some((buffer, blame_entry)) = blame
6886 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6887 .flatten()
6888 else {
6889 return;
6890 };
6891
6892 let anchor = self.selections.newest_anchor().head();
6893 let position = self.to_pixel_point(anchor, &snapshot, window);
6894 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6895 self.show_blame_popover(
6896 buffer,
6897 &blame_entry,
6898 position + last_bounds.origin,
6899 true,
6900 cx,
6901 );
6902 };
6903 }
6904
6905 fn show_blame_popover(
6906 &mut self,
6907 buffer: BufferId,
6908 blame_entry: &BlameEntry,
6909 position: gpui::Point<Pixels>,
6910 ignore_timeout: bool,
6911 cx: &mut Context<Self>,
6912 ) {
6913 if let Some(state) = &mut self.inline_blame_popover {
6914 state.hide_task.take();
6915 } else {
6916 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6917 let blame_entry = blame_entry.clone();
6918 let show_task = cx.spawn(async move |editor, cx| {
6919 if !ignore_timeout {
6920 cx.background_executor()
6921 .timer(std::time::Duration::from_millis(blame_popover_delay))
6922 .await;
6923 }
6924 editor
6925 .update(cx, |editor, cx| {
6926 editor.inline_blame_popover_show_task.take();
6927 let Some(blame) = editor.blame.as_ref() else {
6928 return;
6929 };
6930 let blame = blame.read(cx);
6931 let details = blame.details_for_entry(buffer, &blame_entry);
6932 let markdown = cx.new(|cx| {
6933 Markdown::new(
6934 details
6935 .as_ref()
6936 .map(|message| message.message.clone())
6937 .unwrap_or_default(),
6938 None,
6939 None,
6940 cx,
6941 )
6942 });
6943 editor.inline_blame_popover = Some(InlineBlamePopover {
6944 position,
6945 hide_task: None,
6946 popover_bounds: None,
6947 popover_state: InlineBlamePopoverState {
6948 scroll_handle: ScrollHandle::new(),
6949 commit_message: details,
6950 markdown,
6951 },
6952 keyboard_grace: ignore_timeout,
6953 });
6954 cx.notify();
6955 })
6956 .ok();
6957 });
6958 self.inline_blame_popover_show_task = Some(show_task);
6959 }
6960 }
6961
6962 pub fn has_mouse_context_menu(&self) -> bool {
6963 self.mouse_context_menu.is_some()
6964 }
6965
6966 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6967 self.inline_blame_popover_show_task.take();
6968 if let Some(state) = &mut self.inline_blame_popover {
6969 let hide_task = cx.spawn(async move |editor, cx| {
6970 if !ignore_timeout {
6971 cx.background_executor()
6972 .timer(std::time::Duration::from_millis(100))
6973 .await;
6974 }
6975 editor
6976 .update(cx, |editor, cx| {
6977 editor.inline_blame_popover.take();
6978 cx.notify();
6979 })
6980 .ok();
6981 });
6982 state.hide_task = Some(hide_task);
6983 true
6984 } else {
6985 false
6986 }
6987 }
6988
6989 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6990 if self.pending_rename.is_some() {
6991 return None;
6992 }
6993
6994 let provider = self.semantics_provider.clone()?;
6995 let buffer = self.buffer.read(cx);
6996 let newest_selection = self.selections.newest_anchor().clone();
6997 let cursor_position = newest_selection.head();
6998 let (cursor_buffer, cursor_buffer_position) =
6999 buffer.text_anchor_for_position(cursor_position, cx)?;
7000 let (tail_buffer, tail_buffer_position) =
7001 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7002 if cursor_buffer != tail_buffer {
7003 return None;
7004 }
7005
7006 let snapshot = cursor_buffer.read(cx).snapshot();
7007 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7008 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7009 if start_word_range != end_word_range {
7010 self.document_highlights_task.take();
7011 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7012 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7013 return None;
7014 }
7015
7016 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7017 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7018 cx.background_executor()
7019 .timer(Duration::from_millis(debounce))
7020 .await;
7021
7022 let highlights = if let Some(highlights) = cx
7023 .update(|cx| {
7024 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7025 })
7026 .ok()
7027 .flatten()
7028 {
7029 highlights.await.log_err()
7030 } else {
7031 None
7032 };
7033
7034 if let Some(highlights) = highlights {
7035 this.update(cx, |this, cx| {
7036 if this.pending_rename.is_some() {
7037 return;
7038 }
7039
7040 let buffer = this.buffer.read(cx);
7041 if buffer
7042 .text_anchor_for_position(cursor_position, cx)
7043 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7044 {
7045 return;
7046 }
7047
7048 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7049 let mut write_ranges = Vec::new();
7050 let mut read_ranges = Vec::new();
7051 for highlight in highlights {
7052 let buffer_id = cursor_buffer.read(cx).remote_id();
7053 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7054 {
7055 let start = highlight
7056 .range
7057 .start
7058 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7059 let end = highlight
7060 .range
7061 .end
7062 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7063 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7064 continue;
7065 }
7066
7067 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7068 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7069 write_ranges.push(range);
7070 } else {
7071 read_ranges.push(range);
7072 }
7073 }
7074 }
7075
7076 this.highlight_background::<DocumentHighlightRead>(
7077 &read_ranges,
7078 |_, theme| theme.colors().editor_document_highlight_read_background,
7079 cx,
7080 );
7081 this.highlight_background::<DocumentHighlightWrite>(
7082 &write_ranges,
7083 |_, theme| theme.colors().editor_document_highlight_write_background,
7084 cx,
7085 );
7086 cx.notify();
7087 })
7088 .log_err();
7089 }
7090 }));
7091 None
7092 }
7093
7094 fn prepare_highlight_query_from_selection(
7095 &mut self,
7096 window: &Window,
7097 cx: &mut Context<Editor>,
7098 ) -> Option<(String, Range<Anchor>)> {
7099 if matches!(self.mode, EditorMode::SingleLine) {
7100 return None;
7101 }
7102 if !EditorSettings::get_global(cx).selection_highlight {
7103 return None;
7104 }
7105 if self.selections.count() != 1 || self.selections.line_mode() {
7106 return None;
7107 }
7108 let snapshot = self.snapshot(window, cx);
7109 let selection = self.selections.newest::<Point>(&snapshot);
7110 // If the selection spans multiple rows OR it is empty
7111 if selection.start.row != selection.end.row
7112 || selection.start.column == selection.end.column
7113 {
7114 return None;
7115 }
7116 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7117 let query = snapshot
7118 .buffer_snapshot()
7119 .text_for_range(selection_anchor_range.clone())
7120 .collect::<String>();
7121 if query.trim().is_empty() {
7122 return None;
7123 }
7124 Some((query, selection_anchor_range))
7125 }
7126
7127 fn update_selection_occurrence_highlights(
7128 &mut self,
7129 query_text: String,
7130 query_range: Range<Anchor>,
7131 multi_buffer_range_to_query: Range<Point>,
7132 use_debounce: bool,
7133 window: &mut Window,
7134 cx: &mut Context<Editor>,
7135 ) -> Task<()> {
7136 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7137 cx.spawn_in(window, async move |editor, cx| {
7138 if use_debounce {
7139 cx.background_executor()
7140 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7141 .await;
7142 }
7143 let match_task = cx.background_spawn(async move {
7144 let buffer_ranges = multi_buffer_snapshot
7145 .range_to_buffer_ranges(multi_buffer_range_to_query)
7146 .into_iter()
7147 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7148 let mut match_ranges = Vec::new();
7149 let Ok(regex) = project::search::SearchQuery::text(
7150 query_text.clone(),
7151 false,
7152 false,
7153 false,
7154 Default::default(),
7155 Default::default(),
7156 false,
7157 None,
7158 ) else {
7159 return Vec::default();
7160 };
7161 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7162 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7163 match_ranges.extend(
7164 regex
7165 .search(
7166 buffer_snapshot,
7167 Some(search_range.start.0..search_range.end.0),
7168 )
7169 .await
7170 .into_iter()
7171 .filter_map(|match_range| {
7172 let match_start = buffer_snapshot
7173 .anchor_after(search_range.start + match_range.start);
7174 let match_end = buffer_snapshot
7175 .anchor_before(search_range.start + match_range.end);
7176 let match_anchor_range =
7177 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7178 (match_anchor_range != query_range).then_some(match_anchor_range)
7179 }),
7180 );
7181 }
7182 match_ranges
7183 });
7184 let match_ranges = match_task.await;
7185 editor
7186 .update_in(cx, |editor, _, cx| {
7187 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7188 if !match_ranges.is_empty() {
7189 editor.highlight_background::<SelectedTextHighlight>(
7190 &match_ranges,
7191 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7192 cx,
7193 )
7194 }
7195 })
7196 .log_err();
7197 })
7198 }
7199
7200 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7201 struct NewlineFold;
7202 let type_id = std::any::TypeId::of::<NewlineFold>();
7203 if !self.mode.is_single_line() {
7204 return;
7205 }
7206 let snapshot = self.snapshot(window, cx);
7207 if snapshot.buffer_snapshot().max_point().row == 0 {
7208 return;
7209 }
7210 let task = cx.background_spawn(async move {
7211 let new_newlines = snapshot
7212 .buffer_chars_at(MultiBufferOffset(0))
7213 .filter_map(|(c, i)| {
7214 if c == '\n' {
7215 Some(
7216 snapshot.buffer_snapshot().anchor_after(i)
7217 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7218 )
7219 } else {
7220 None
7221 }
7222 })
7223 .collect::<Vec<_>>();
7224 let existing_newlines = snapshot
7225 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7226 .filter_map(|fold| {
7227 if fold.placeholder.type_tag == Some(type_id) {
7228 Some(fold.range.start..fold.range.end)
7229 } else {
7230 None
7231 }
7232 })
7233 .collect::<Vec<_>>();
7234
7235 (new_newlines, existing_newlines)
7236 });
7237 self.folding_newlines = cx.spawn(async move |this, cx| {
7238 let (new_newlines, existing_newlines) = task.await;
7239 if new_newlines == existing_newlines {
7240 return;
7241 }
7242 let placeholder = FoldPlaceholder {
7243 render: Arc::new(move |_, _, cx| {
7244 div()
7245 .bg(cx.theme().status().hint_background)
7246 .border_b_1()
7247 .size_full()
7248 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7249 .border_color(cx.theme().status().hint)
7250 .child("\\n")
7251 .into_any()
7252 }),
7253 constrain_width: false,
7254 merge_adjacent: false,
7255 type_tag: Some(type_id),
7256 };
7257 let creases = new_newlines
7258 .into_iter()
7259 .map(|range| Crease::simple(range, placeholder.clone()))
7260 .collect();
7261 this.update(cx, |this, cx| {
7262 this.display_map.update(cx, |display_map, cx| {
7263 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7264 display_map.fold(creases, cx);
7265 });
7266 })
7267 .ok();
7268 });
7269 }
7270
7271 fn refresh_selected_text_highlights(
7272 &mut self,
7273 on_buffer_edit: bool,
7274 window: &mut Window,
7275 cx: &mut Context<Editor>,
7276 ) {
7277 let Some((query_text, query_range)) =
7278 self.prepare_highlight_query_from_selection(window, cx)
7279 else {
7280 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7281 self.quick_selection_highlight_task.take();
7282 self.debounced_selection_highlight_task.take();
7283 return;
7284 };
7285 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7286 if on_buffer_edit
7287 || self
7288 .quick_selection_highlight_task
7289 .as_ref()
7290 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7291 {
7292 let multi_buffer_visible_start = self
7293 .scroll_manager
7294 .anchor()
7295 .anchor
7296 .to_point(&multi_buffer_snapshot);
7297 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7298 multi_buffer_visible_start
7299 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7300 Bias::Left,
7301 );
7302 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7303 self.quick_selection_highlight_task = Some((
7304 query_range.clone(),
7305 self.update_selection_occurrence_highlights(
7306 query_text.clone(),
7307 query_range.clone(),
7308 multi_buffer_visible_range,
7309 false,
7310 window,
7311 cx,
7312 ),
7313 ));
7314 }
7315 if on_buffer_edit
7316 || self
7317 .debounced_selection_highlight_task
7318 .as_ref()
7319 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7320 {
7321 let multi_buffer_start = multi_buffer_snapshot
7322 .anchor_before(MultiBufferOffset(0))
7323 .to_point(&multi_buffer_snapshot);
7324 let multi_buffer_end = multi_buffer_snapshot
7325 .anchor_after(multi_buffer_snapshot.len())
7326 .to_point(&multi_buffer_snapshot);
7327 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7328 self.debounced_selection_highlight_task = Some((
7329 query_range.clone(),
7330 self.update_selection_occurrence_highlights(
7331 query_text,
7332 query_range,
7333 multi_buffer_full_range,
7334 true,
7335 window,
7336 cx,
7337 ),
7338 ));
7339 }
7340 }
7341
7342 pub fn refresh_edit_prediction(
7343 &mut self,
7344 debounce: bool,
7345 user_requested: bool,
7346 window: &mut Window,
7347 cx: &mut Context<Self>,
7348 ) -> Option<()> {
7349 if DisableAiSettings::get_global(cx).disable_ai {
7350 return None;
7351 }
7352
7353 let provider = self.edit_prediction_provider()?;
7354 let cursor = self.selections.newest_anchor().head();
7355 let (buffer, cursor_buffer_position) =
7356 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7357
7358 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7359 self.discard_edit_prediction(false, cx);
7360 return None;
7361 }
7362
7363 self.update_visible_edit_prediction(window, cx);
7364
7365 if !user_requested
7366 && (!self.should_show_edit_predictions()
7367 || !self.is_focused(window)
7368 || buffer.read(cx).is_empty())
7369 {
7370 self.discard_edit_prediction(false, cx);
7371 return None;
7372 }
7373
7374 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7375 Some(())
7376 }
7377
7378 fn show_edit_predictions_in_menu(&self) -> bool {
7379 match self.edit_prediction_settings {
7380 EditPredictionSettings::Disabled => false,
7381 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7382 }
7383 }
7384
7385 pub fn edit_predictions_enabled(&self) -> bool {
7386 match self.edit_prediction_settings {
7387 EditPredictionSettings::Disabled => false,
7388 EditPredictionSettings::Enabled { .. } => true,
7389 }
7390 }
7391
7392 fn edit_prediction_requires_modifier(&self) -> bool {
7393 match self.edit_prediction_settings {
7394 EditPredictionSettings::Disabled => false,
7395 EditPredictionSettings::Enabled {
7396 preview_requires_modifier,
7397 ..
7398 } => preview_requires_modifier,
7399 }
7400 }
7401
7402 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7403 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7404 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7405 self.discard_edit_prediction(false, cx);
7406 } else {
7407 let selection = self.selections.newest_anchor();
7408 let cursor = selection.head();
7409
7410 if let Some((buffer, cursor_buffer_position)) =
7411 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7412 {
7413 self.edit_prediction_settings =
7414 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7415 }
7416 }
7417 }
7418
7419 fn edit_prediction_settings_at_position(
7420 &self,
7421 buffer: &Entity<Buffer>,
7422 buffer_position: language::Anchor,
7423 cx: &App,
7424 ) -> EditPredictionSettings {
7425 if !self.mode.is_full()
7426 || !self.show_edit_predictions_override.unwrap_or(true)
7427 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7428 {
7429 return EditPredictionSettings::Disabled;
7430 }
7431
7432 let buffer = buffer.read(cx);
7433
7434 let file = buffer.file();
7435
7436 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7437 return EditPredictionSettings::Disabled;
7438 };
7439
7440 let by_provider = matches!(
7441 self.menu_edit_predictions_policy,
7442 MenuEditPredictionsPolicy::ByProvider
7443 );
7444
7445 let show_in_menu = by_provider
7446 && self
7447 .edit_prediction_provider
7448 .as_ref()
7449 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7450
7451 let preview_requires_modifier =
7452 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7453
7454 EditPredictionSettings::Enabled {
7455 show_in_menu,
7456 preview_requires_modifier,
7457 }
7458 }
7459
7460 fn should_show_edit_predictions(&self) -> bool {
7461 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7462 }
7463
7464 pub fn edit_prediction_preview_is_active(&self) -> bool {
7465 matches!(
7466 self.edit_prediction_preview,
7467 EditPredictionPreview::Active { .. }
7468 )
7469 }
7470
7471 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7472 let cursor = self.selections.newest_anchor().head();
7473 if let Some((buffer, cursor_position)) =
7474 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7475 {
7476 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7477 } else {
7478 false
7479 }
7480 }
7481
7482 pub fn supports_minimap(&self, cx: &App) -> bool {
7483 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7484 }
7485
7486 fn edit_predictions_enabled_in_buffer(
7487 &self,
7488 buffer: &Entity<Buffer>,
7489 buffer_position: language::Anchor,
7490 cx: &App,
7491 ) -> bool {
7492 maybe!({
7493 if self.read_only(cx) {
7494 return Some(false);
7495 }
7496 let provider = self.edit_prediction_provider()?;
7497 if !provider.is_enabled(buffer, buffer_position, cx) {
7498 return Some(false);
7499 }
7500 let buffer = buffer.read(cx);
7501 let Some(file) = buffer.file() else {
7502 return Some(true);
7503 };
7504 let settings = all_language_settings(Some(file), cx);
7505 Some(settings.edit_predictions_enabled_for_file(file, cx))
7506 })
7507 .unwrap_or(false)
7508 }
7509
7510 fn cycle_edit_prediction(
7511 &mut self,
7512 direction: Direction,
7513 window: &mut Window,
7514 cx: &mut Context<Self>,
7515 ) -> Option<()> {
7516 let provider = self.edit_prediction_provider()?;
7517 let cursor = self.selections.newest_anchor().head();
7518 let (buffer, cursor_buffer_position) =
7519 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7520 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7521 return None;
7522 }
7523
7524 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7525 self.update_visible_edit_prediction(window, cx);
7526
7527 Some(())
7528 }
7529
7530 pub fn show_edit_prediction(
7531 &mut self,
7532 _: &ShowEditPrediction,
7533 window: &mut Window,
7534 cx: &mut Context<Self>,
7535 ) {
7536 if !self.has_active_edit_prediction() {
7537 self.refresh_edit_prediction(false, true, window, cx);
7538 return;
7539 }
7540
7541 self.update_visible_edit_prediction(window, cx);
7542 }
7543
7544 pub fn display_cursor_names(
7545 &mut self,
7546 _: &DisplayCursorNames,
7547 window: &mut Window,
7548 cx: &mut Context<Self>,
7549 ) {
7550 self.show_cursor_names(window, cx);
7551 }
7552
7553 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7554 self.show_cursor_names = true;
7555 cx.notify();
7556 cx.spawn_in(window, async move |this, cx| {
7557 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7558 this.update(cx, |this, cx| {
7559 this.show_cursor_names = false;
7560 cx.notify()
7561 })
7562 .ok()
7563 })
7564 .detach();
7565 }
7566
7567 pub fn next_edit_prediction(
7568 &mut self,
7569 _: &NextEditPrediction,
7570 window: &mut Window,
7571 cx: &mut Context<Self>,
7572 ) {
7573 if self.has_active_edit_prediction() {
7574 self.cycle_edit_prediction(Direction::Next, window, cx);
7575 } else {
7576 let is_copilot_disabled = self
7577 .refresh_edit_prediction(false, true, window, cx)
7578 .is_none();
7579 if is_copilot_disabled {
7580 cx.propagate();
7581 }
7582 }
7583 }
7584
7585 pub fn previous_edit_prediction(
7586 &mut self,
7587 _: &PreviousEditPrediction,
7588 window: &mut Window,
7589 cx: &mut Context<Self>,
7590 ) {
7591 if self.has_active_edit_prediction() {
7592 self.cycle_edit_prediction(Direction::Prev, window, cx);
7593 } else {
7594 let is_copilot_disabled = self
7595 .refresh_edit_prediction(false, true, window, cx)
7596 .is_none();
7597 if is_copilot_disabled {
7598 cx.propagate();
7599 }
7600 }
7601 }
7602
7603 pub fn accept_edit_prediction(
7604 &mut self,
7605 _: &AcceptEditPrediction,
7606 window: &mut Window,
7607 cx: &mut Context<Self>,
7608 ) {
7609 if self.show_edit_predictions_in_menu() {
7610 self.hide_context_menu(window, cx);
7611 }
7612
7613 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7614 return;
7615 };
7616
7617 match &active_edit_prediction.completion {
7618 EditPrediction::MoveWithin { target, .. } => {
7619 let target = *target;
7620
7621 if let Some(position_map) = &self.last_position_map {
7622 if position_map
7623 .visible_row_range
7624 .contains(&target.to_display_point(&position_map.snapshot).row())
7625 || !self.edit_prediction_requires_modifier()
7626 {
7627 self.unfold_ranges(&[target..target], true, false, cx);
7628 // Note that this is also done in vim's handler of the Tab action.
7629 self.change_selections(
7630 SelectionEffects::scroll(Autoscroll::newest()),
7631 window,
7632 cx,
7633 |selections| {
7634 selections.select_anchor_ranges([target..target]);
7635 },
7636 );
7637 self.clear_row_highlights::<EditPredictionPreview>();
7638
7639 self.edit_prediction_preview
7640 .set_previous_scroll_position(None);
7641 } else {
7642 self.edit_prediction_preview
7643 .set_previous_scroll_position(Some(
7644 position_map.snapshot.scroll_anchor,
7645 ));
7646
7647 self.highlight_rows::<EditPredictionPreview>(
7648 target..target,
7649 cx.theme().colors().editor_highlighted_line_background,
7650 RowHighlightOptions {
7651 autoscroll: true,
7652 ..Default::default()
7653 },
7654 cx,
7655 );
7656 self.request_autoscroll(Autoscroll::fit(), cx);
7657 }
7658 }
7659 }
7660 EditPrediction::MoveOutside { snapshot, target } => {
7661 if let Some(workspace) = self.workspace() {
7662 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7663 .detach_and_log_err(cx);
7664 }
7665 }
7666 EditPrediction::Edit { edits, .. } => {
7667 self.report_edit_prediction_event(
7668 active_edit_prediction.completion_id.clone(),
7669 true,
7670 cx,
7671 );
7672
7673 if let Some(provider) = self.edit_prediction_provider() {
7674 provider.accept(cx);
7675 }
7676
7677 // Store the transaction ID and selections before applying the edit
7678 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7679
7680 let snapshot = self.buffer.read(cx).snapshot(cx);
7681 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7682
7683 self.buffer.update(cx, |buffer, cx| {
7684 buffer.edit(edits.iter().cloned(), None, cx)
7685 });
7686
7687 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7688 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7689 });
7690
7691 let selections = self.selections.disjoint_anchors_arc();
7692 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7693 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7694 if has_new_transaction {
7695 self.selection_history
7696 .insert_transaction(transaction_id_now, selections);
7697 }
7698 }
7699
7700 self.update_visible_edit_prediction(window, cx);
7701 if self.active_edit_prediction.is_none() {
7702 self.refresh_edit_prediction(true, true, window, cx);
7703 }
7704
7705 cx.notify();
7706 }
7707 }
7708
7709 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7710 }
7711
7712 pub fn accept_partial_edit_prediction(
7713 &mut self,
7714 _: &AcceptPartialEditPrediction,
7715 window: &mut Window,
7716 cx: &mut Context<Self>,
7717 ) {
7718 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7719 return;
7720 };
7721 if self.selections.count() != 1 {
7722 return;
7723 }
7724
7725 match &active_edit_prediction.completion {
7726 EditPrediction::MoveWithin { target, .. } => {
7727 let target = *target;
7728 self.change_selections(
7729 SelectionEffects::scroll(Autoscroll::newest()),
7730 window,
7731 cx,
7732 |selections| {
7733 selections.select_anchor_ranges([target..target]);
7734 },
7735 );
7736 }
7737 EditPrediction::MoveOutside { snapshot, target } => {
7738 if let Some(workspace) = self.workspace() {
7739 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7740 .detach_and_log_err(cx);
7741 }
7742 }
7743 EditPrediction::Edit { edits, .. } => {
7744 self.report_edit_prediction_event(
7745 active_edit_prediction.completion_id.clone(),
7746 true,
7747 cx,
7748 );
7749
7750 // Find an insertion that starts at the cursor position.
7751 let snapshot = self.buffer.read(cx).snapshot(cx);
7752 let cursor_offset = self
7753 .selections
7754 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7755 .head();
7756 let insertion = edits.iter().find_map(|(range, text)| {
7757 let range = range.to_offset(&snapshot);
7758 if range.is_empty() && range.start == cursor_offset {
7759 Some(text)
7760 } else {
7761 None
7762 }
7763 });
7764
7765 if let Some(text) = insertion {
7766 let mut partial_completion = text
7767 .chars()
7768 .by_ref()
7769 .take_while(|c| c.is_alphabetic())
7770 .collect::<String>();
7771 if partial_completion.is_empty() {
7772 partial_completion = text
7773 .chars()
7774 .by_ref()
7775 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7776 .collect::<String>();
7777 }
7778
7779 cx.emit(EditorEvent::InputHandled {
7780 utf16_range_to_replace: None,
7781 text: partial_completion.clone().into(),
7782 });
7783
7784 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7785
7786 self.refresh_edit_prediction(true, true, window, cx);
7787 cx.notify();
7788 } else {
7789 self.accept_edit_prediction(&Default::default(), window, cx);
7790 }
7791 }
7792 }
7793 }
7794
7795 fn discard_edit_prediction(
7796 &mut self,
7797 should_report_edit_prediction_event: bool,
7798 cx: &mut Context<Self>,
7799 ) -> bool {
7800 if should_report_edit_prediction_event {
7801 let completion_id = self
7802 .active_edit_prediction
7803 .as_ref()
7804 .and_then(|active_completion| active_completion.completion_id.clone());
7805
7806 self.report_edit_prediction_event(completion_id, false, cx);
7807 }
7808
7809 if let Some(provider) = self.edit_prediction_provider() {
7810 provider.discard(cx);
7811 }
7812
7813 self.take_active_edit_prediction(cx)
7814 }
7815
7816 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7817 let Some(provider) = self.edit_prediction_provider() else {
7818 return;
7819 };
7820
7821 let Some((_, buffer, _)) = self
7822 .buffer
7823 .read(cx)
7824 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7825 else {
7826 return;
7827 };
7828
7829 let extension = buffer
7830 .read(cx)
7831 .file()
7832 .and_then(|file| Some(file.path().extension()?.to_string()));
7833
7834 let event_type = match accepted {
7835 true => "Edit Prediction Accepted",
7836 false => "Edit Prediction Discarded",
7837 };
7838 telemetry::event!(
7839 event_type,
7840 provider = provider.name(),
7841 prediction_id = id,
7842 suggestion_accepted = accepted,
7843 file_extension = extension,
7844 );
7845 }
7846
7847 fn open_editor_at_anchor(
7848 snapshot: &language::BufferSnapshot,
7849 target: language::Anchor,
7850 workspace: &Entity<Workspace>,
7851 window: &mut Window,
7852 cx: &mut App,
7853 ) -> Task<Result<()>> {
7854 workspace.update(cx, |workspace, cx| {
7855 let path = snapshot.file().map(|file| file.full_path(cx));
7856 let Some(path) =
7857 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7858 else {
7859 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7860 };
7861 let target = text::ToPoint::to_point(&target, snapshot);
7862 let item = workspace.open_path(path, None, true, window, cx);
7863 window.spawn(cx, async move |cx| {
7864 let Some(editor) = item.await?.downcast::<Editor>() else {
7865 return Ok(());
7866 };
7867 editor
7868 .update_in(cx, |editor, window, cx| {
7869 editor.go_to_singleton_buffer_point(target, window, cx);
7870 })
7871 .ok();
7872 anyhow::Ok(())
7873 })
7874 })
7875 }
7876
7877 pub fn has_active_edit_prediction(&self) -> bool {
7878 self.active_edit_prediction.is_some()
7879 }
7880
7881 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7882 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7883 return false;
7884 };
7885
7886 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7887 self.clear_highlights::<EditPredictionHighlight>(cx);
7888 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7889 true
7890 }
7891
7892 /// Returns true when we're displaying the edit prediction popover below the cursor
7893 /// like we are not previewing and the LSP autocomplete menu is visible
7894 /// or we are in `when_holding_modifier` mode.
7895 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7896 if self.edit_prediction_preview_is_active()
7897 || !self.show_edit_predictions_in_menu()
7898 || !self.edit_predictions_enabled()
7899 {
7900 return false;
7901 }
7902
7903 if self.has_visible_completions_menu() {
7904 return true;
7905 }
7906
7907 has_completion && self.edit_prediction_requires_modifier()
7908 }
7909
7910 fn handle_modifiers_changed(
7911 &mut self,
7912 modifiers: Modifiers,
7913 position_map: &PositionMap,
7914 window: &mut Window,
7915 cx: &mut Context<Self>,
7916 ) {
7917 // Ensure that the edit prediction preview is updated, even when not
7918 // enabled, if there's an active edit prediction preview.
7919 if self.show_edit_predictions_in_menu()
7920 || matches!(
7921 self.edit_prediction_preview,
7922 EditPredictionPreview::Active { .. }
7923 )
7924 {
7925 self.update_edit_prediction_preview(&modifiers, window, cx);
7926 }
7927
7928 self.update_selection_mode(&modifiers, position_map, window, cx);
7929
7930 let mouse_position = window.mouse_position();
7931 if !position_map.text_hitbox.is_hovered(window) {
7932 return;
7933 }
7934
7935 self.update_hovered_link(
7936 position_map.point_for_position(mouse_position),
7937 &position_map.snapshot,
7938 modifiers,
7939 window,
7940 cx,
7941 )
7942 }
7943
7944 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7945 match EditorSettings::get_global(cx).multi_cursor_modifier {
7946 MultiCursorModifier::Alt => modifiers.secondary(),
7947 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7948 }
7949 }
7950
7951 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7952 match EditorSettings::get_global(cx).multi_cursor_modifier {
7953 MultiCursorModifier::Alt => modifiers.alt,
7954 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7955 }
7956 }
7957
7958 fn columnar_selection_mode(
7959 modifiers: &Modifiers,
7960 cx: &mut Context<Self>,
7961 ) -> Option<ColumnarMode> {
7962 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7963 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7964 Some(ColumnarMode::FromMouse)
7965 } else if Self::is_alt_pressed(modifiers, cx) {
7966 Some(ColumnarMode::FromSelection)
7967 } else {
7968 None
7969 }
7970 } else {
7971 None
7972 }
7973 }
7974
7975 fn update_selection_mode(
7976 &mut self,
7977 modifiers: &Modifiers,
7978 position_map: &PositionMap,
7979 window: &mut Window,
7980 cx: &mut Context<Self>,
7981 ) {
7982 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7983 return;
7984 };
7985 if self.selections.pending_anchor().is_none() {
7986 return;
7987 }
7988
7989 let mouse_position = window.mouse_position();
7990 let point_for_position = position_map.point_for_position(mouse_position);
7991 let position = point_for_position.previous_valid;
7992
7993 self.select(
7994 SelectPhase::BeginColumnar {
7995 position,
7996 reset: false,
7997 mode,
7998 goal_column: point_for_position.exact_unclipped.column(),
7999 },
8000 window,
8001 cx,
8002 );
8003 }
8004
8005 fn update_edit_prediction_preview(
8006 &mut self,
8007 modifiers: &Modifiers,
8008 window: &mut Window,
8009 cx: &mut Context<Self>,
8010 ) {
8011 let mut modifiers_held = false;
8012 if let Some(accept_keystroke) = self
8013 .accept_edit_prediction_keybind(false, window, cx)
8014 .keystroke()
8015 {
8016 modifiers_held = modifiers_held
8017 || (accept_keystroke.modifiers() == modifiers
8018 && accept_keystroke.modifiers().modified());
8019 };
8020 if let Some(accept_partial_keystroke) = self
8021 .accept_edit_prediction_keybind(true, window, cx)
8022 .keystroke()
8023 {
8024 modifiers_held = modifiers_held
8025 || (accept_partial_keystroke.modifiers() == modifiers
8026 && accept_partial_keystroke.modifiers().modified());
8027 }
8028
8029 if modifiers_held {
8030 if matches!(
8031 self.edit_prediction_preview,
8032 EditPredictionPreview::Inactive { .. }
8033 ) {
8034 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8035 provider.provider.did_show(cx)
8036 }
8037
8038 self.edit_prediction_preview = EditPredictionPreview::Active {
8039 previous_scroll_position: None,
8040 since: Instant::now(),
8041 };
8042
8043 self.update_visible_edit_prediction(window, cx);
8044 cx.notify();
8045 }
8046 } else if let EditPredictionPreview::Active {
8047 previous_scroll_position,
8048 since,
8049 } = self.edit_prediction_preview
8050 {
8051 if let (Some(previous_scroll_position), Some(position_map)) =
8052 (previous_scroll_position, self.last_position_map.as_ref())
8053 {
8054 self.set_scroll_position(
8055 previous_scroll_position
8056 .scroll_position(&position_map.snapshot.display_snapshot),
8057 window,
8058 cx,
8059 );
8060 }
8061
8062 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8063 released_too_fast: since.elapsed() < Duration::from_millis(200),
8064 };
8065 self.clear_row_highlights::<EditPredictionPreview>();
8066 self.update_visible_edit_prediction(window, cx);
8067 cx.notify();
8068 }
8069 }
8070
8071 fn update_visible_edit_prediction(
8072 &mut self,
8073 _window: &mut Window,
8074 cx: &mut Context<Self>,
8075 ) -> Option<()> {
8076 if DisableAiSettings::get_global(cx).disable_ai {
8077 return None;
8078 }
8079
8080 if self.ime_transaction.is_some() {
8081 self.discard_edit_prediction(false, cx);
8082 return None;
8083 }
8084
8085 let selection = self.selections.newest_anchor();
8086 let cursor = selection.head();
8087 let multibuffer = self.buffer.read(cx).snapshot(cx);
8088 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8089 let excerpt_id = cursor.excerpt_id;
8090
8091 let show_in_menu = self.show_edit_predictions_in_menu();
8092 let completions_menu_has_precedence = !show_in_menu
8093 && (self.context_menu.borrow().is_some()
8094 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8095
8096 if completions_menu_has_precedence
8097 || !offset_selection.is_empty()
8098 || self
8099 .active_edit_prediction
8100 .as_ref()
8101 .is_some_and(|completion| {
8102 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8103 return false;
8104 };
8105 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8106 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8107 !invalidation_range.contains(&offset_selection.head())
8108 })
8109 {
8110 self.discard_edit_prediction(false, cx);
8111 return None;
8112 }
8113
8114 self.take_active_edit_prediction(cx);
8115 let Some(provider) = self.edit_prediction_provider() else {
8116 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8117 return None;
8118 };
8119
8120 let (buffer, cursor_buffer_position) =
8121 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8122
8123 self.edit_prediction_settings =
8124 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8125
8126 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8127
8128 if self.edit_prediction_indent_conflict {
8129 let cursor_point = cursor.to_point(&multibuffer);
8130 let mut suggested_indent = None;
8131 multibuffer.suggested_indents_callback(
8132 cursor_point.row..cursor_point.row + 1,
8133 |_, indent| {
8134 suggested_indent = Some(indent);
8135 ControlFlow::Break(())
8136 },
8137 cx,
8138 );
8139
8140 if let Some(indent) = suggested_indent
8141 && indent.len == cursor_point.column
8142 {
8143 self.edit_prediction_indent_conflict = false;
8144 }
8145 }
8146
8147 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8148
8149 let (completion_id, edits, edit_preview) = match edit_prediction {
8150 edit_prediction_types::EditPrediction::Local {
8151 id,
8152 edits,
8153 edit_preview,
8154 } => (id, edits, edit_preview),
8155 edit_prediction_types::EditPrediction::Jump {
8156 id,
8157 snapshot,
8158 target,
8159 } => {
8160 self.stale_edit_prediction_in_menu = None;
8161 self.active_edit_prediction = Some(EditPredictionState {
8162 inlay_ids: vec![],
8163 completion: EditPrediction::MoveOutside { snapshot, target },
8164 completion_id: id,
8165 invalidation_range: None,
8166 });
8167 cx.notify();
8168 return Some(());
8169 }
8170 };
8171
8172 let edits = edits
8173 .into_iter()
8174 .flat_map(|(range, new_text)| {
8175 Some((
8176 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8177 new_text,
8178 ))
8179 })
8180 .collect::<Vec<_>>();
8181 if edits.is_empty() {
8182 return None;
8183 }
8184
8185 let first_edit_start = edits.first().unwrap().0.start;
8186 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8187 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8188
8189 let last_edit_end = edits.last().unwrap().0.end;
8190 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8191 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8192
8193 let cursor_row = cursor.to_point(&multibuffer).row;
8194
8195 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8196
8197 let mut inlay_ids = Vec::new();
8198 let invalidation_row_range;
8199 let move_invalidation_row_range = if cursor_row < edit_start_row {
8200 Some(cursor_row..edit_end_row)
8201 } else if cursor_row > edit_end_row {
8202 Some(edit_start_row..cursor_row)
8203 } else {
8204 None
8205 };
8206 let supports_jump = self
8207 .edit_prediction_provider
8208 .as_ref()
8209 .map(|provider| provider.provider.supports_jump_to_edit())
8210 .unwrap_or(true);
8211
8212 let is_move = supports_jump
8213 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8214 let completion = if is_move {
8215 invalidation_row_range =
8216 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8217 let target = first_edit_start;
8218 EditPrediction::MoveWithin { target, snapshot }
8219 } else {
8220 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8221 && !self.edit_predictions_hidden_for_vim_mode;
8222
8223 if show_completions_in_buffer {
8224 if let Some(provider) = &self.edit_prediction_provider {
8225 provider.provider.did_show(cx);
8226 }
8227 if edits
8228 .iter()
8229 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8230 {
8231 let mut inlays = Vec::new();
8232 for (range, new_text) in &edits {
8233 let inlay = Inlay::edit_prediction(
8234 post_inc(&mut self.next_inlay_id),
8235 range.start,
8236 new_text.as_ref(),
8237 );
8238 inlay_ids.push(inlay.id);
8239 inlays.push(inlay);
8240 }
8241
8242 self.splice_inlays(&[], inlays, cx);
8243 } else {
8244 let background_color = cx.theme().status().deleted_background;
8245 self.highlight_text::<EditPredictionHighlight>(
8246 edits.iter().map(|(range, _)| range.clone()).collect(),
8247 HighlightStyle {
8248 background_color: Some(background_color),
8249 ..Default::default()
8250 },
8251 cx,
8252 );
8253 }
8254 }
8255
8256 invalidation_row_range = edit_start_row..edit_end_row;
8257
8258 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8259 if provider.show_tab_accept_marker() {
8260 EditDisplayMode::TabAccept
8261 } else {
8262 EditDisplayMode::Inline
8263 }
8264 } else {
8265 EditDisplayMode::DiffPopover
8266 };
8267
8268 EditPrediction::Edit {
8269 edits,
8270 edit_preview,
8271 display_mode,
8272 snapshot,
8273 }
8274 };
8275
8276 let invalidation_range = multibuffer
8277 .anchor_before(Point::new(invalidation_row_range.start, 0))
8278 ..multibuffer.anchor_after(Point::new(
8279 invalidation_row_range.end,
8280 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8281 ));
8282
8283 self.stale_edit_prediction_in_menu = None;
8284 self.active_edit_prediction = Some(EditPredictionState {
8285 inlay_ids,
8286 completion,
8287 completion_id,
8288 invalidation_range: Some(invalidation_range),
8289 });
8290
8291 cx.notify();
8292
8293 Some(())
8294 }
8295
8296 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8297 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8298 }
8299
8300 fn clear_tasks(&mut self) {
8301 self.tasks.clear()
8302 }
8303
8304 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8305 if self.tasks.insert(key, value).is_some() {
8306 // This case should hopefully be rare, but just in case...
8307 log::error!(
8308 "multiple different run targets found on a single line, only the last target will be rendered"
8309 )
8310 }
8311 }
8312
8313 /// Get all display points of breakpoints that will be rendered within editor
8314 ///
8315 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8316 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8317 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8318 fn active_breakpoints(
8319 &self,
8320 range: Range<DisplayRow>,
8321 window: &mut Window,
8322 cx: &mut Context<Self>,
8323 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8324 let mut breakpoint_display_points = HashMap::default();
8325
8326 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8327 return breakpoint_display_points;
8328 };
8329
8330 let snapshot = self.snapshot(window, cx);
8331
8332 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8333 let Some(project) = self.project() else {
8334 return breakpoint_display_points;
8335 };
8336
8337 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8338 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8339
8340 for (buffer_snapshot, range, excerpt_id) in
8341 multi_buffer_snapshot.range_to_buffer_ranges(range)
8342 {
8343 let Some(buffer) = project
8344 .read(cx)
8345 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8346 else {
8347 continue;
8348 };
8349 let breakpoints = breakpoint_store.read(cx).breakpoints(
8350 &buffer,
8351 Some(
8352 buffer_snapshot.anchor_before(range.start)
8353 ..buffer_snapshot.anchor_after(range.end),
8354 ),
8355 buffer_snapshot,
8356 cx,
8357 );
8358 for (breakpoint, state) in breakpoints {
8359 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8360 let position = multi_buffer_anchor
8361 .to_point(&multi_buffer_snapshot)
8362 .to_display_point(&snapshot);
8363
8364 breakpoint_display_points.insert(
8365 position.row(),
8366 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8367 );
8368 }
8369 }
8370
8371 breakpoint_display_points
8372 }
8373
8374 fn breakpoint_context_menu(
8375 &self,
8376 anchor: Anchor,
8377 window: &mut Window,
8378 cx: &mut Context<Self>,
8379 ) -> Entity<ui::ContextMenu> {
8380 let weak_editor = cx.weak_entity();
8381 let focus_handle = self.focus_handle(cx);
8382
8383 let row = self
8384 .buffer
8385 .read(cx)
8386 .snapshot(cx)
8387 .summary_for_anchor::<Point>(&anchor)
8388 .row;
8389
8390 let breakpoint = self
8391 .breakpoint_at_row(row, window, cx)
8392 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8393
8394 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8395 "Edit Log Breakpoint"
8396 } else {
8397 "Set Log Breakpoint"
8398 };
8399
8400 let condition_breakpoint_msg = if breakpoint
8401 .as_ref()
8402 .is_some_and(|bp| bp.1.condition.is_some())
8403 {
8404 "Edit Condition Breakpoint"
8405 } else {
8406 "Set Condition Breakpoint"
8407 };
8408
8409 let hit_condition_breakpoint_msg = if breakpoint
8410 .as_ref()
8411 .is_some_and(|bp| bp.1.hit_condition.is_some())
8412 {
8413 "Edit Hit Condition Breakpoint"
8414 } else {
8415 "Set Hit Condition Breakpoint"
8416 };
8417
8418 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8419 "Unset Breakpoint"
8420 } else {
8421 "Set Breakpoint"
8422 };
8423
8424 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8425
8426 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8427 BreakpointState::Enabled => Some("Disable"),
8428 BreakpointState::Disabled => Some("Enable"),
8429 });
8430
8431 let (anchor, breakpoint) =
8432 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8433
8434 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8435 menu.on_blur_subscription(Subscription::new(|| {}))
8436 .context(focus_handle)
8437 .when(run_to_cursor, |this| {
8438 let weak_editor = weak_editor.clone();
8439 this.entry("Run to cursor", None, move |window, cx| {
8440 weak_editor
8441 .update(cx, |editor, cx| {
8442 editor.change_selections(
8443 SelectionEffects::no_scroll(),
8444 window,
8445 cx,
8446 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8447 );
8448 })
8449 .ok();
8450
8451 window.dispatch_action(Box::new(RunToCursor), cx);
8452 })
8453 .separator()
8454 })
8455 .when_some(toggle_state_msg, |this, msg| {
8456 this.entry(msg, None, {
8457 let weak_editor = weak_editor.clone();
8458 let breakpoint = breakpoint.clone();
8459 move |_window, cx| {
8460 weak_editor
8461 .update(cx, |this, cx| {
8462 this.edit_breakpoint_at_anchor(
8463 anchor,
8464 breakpoint.as_ref().clone(),
8465 BreakpointEditAction::InvertState,
8466 cx,
8467 );
8468 })
8469 .log_err();
8470 }
8471 })
8472 })
8473 .entry(set_breakpoint_msg, None, {
8474 let weak_editor = weak_editor.clone();
8475 let breakpoint = breakpoint.clone();
8476 move |_window, cx| {
8477 weak_editor
8478 .update(cx, |this, cx| {
8479 this.edit_breakpoint_at_anchor(
8480 anchor,
8481 breakpoint.as_ref().clone(),
8482 BreakpointEditAction::Toggle,
8483 cx,
8484 );
8485 })
8486 .log_err();
8487 }
8488 })
8489 .entry(log_breakpoint_msg, None, {
8490 let breakpoint = breakpoint.clone();
8491 let weak_editor = weak_editor.clone();
8492 move |window, cx| {
8493 weak_editor
8494 .update(cx, |this, cx| {
8495 this.add_edit_breakpoint_block(
8496 anchor,
8497 breakpoint.as_ref(),
8498 BreakpointPromptEditAction::Log,
8499 window,
8500 cx,
8501 );
8502 })
8503 .log_err();
8504 }
8505 })
8506 .entry(condition_breakpoint_msg, None, {
8507 let breakpoint = breakpoint.clone();
8508 let weak_editor = weak_editor.clone();
8509 move |window, cx| {
8510 weak_editor
8511 .update(cx, |this, cx| {
8512 this.add_edit_breakpoint_block(
8513 anchor,
8514 breakpoint.as_ref(),
8515 BreakpointPromptEditAction::Condition,
8516 window,
8517 cx,
8518 );
8519 })
8520 .log_err();
8521 }
8522 })
8523 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8524 weak_editor
8525 .update(cx, |this, cx| {
8526 this.add_edit_breakpoint_block(
8527 anchor,
8528 breakpoint.as_ref(),
8529 BreakpointPromptEditAction::HitCondition,
8530 window,
8531 cx,
8532 );
8533 })
8534 .log_err();
8535 })
8536 })
8537 }
8538
8539 fn render_breakpoint(
8540 &self,
8541 position: Anchor,
8542 row: DisplayRow,
8543 breakpoint: &Breakpoint,
8544 state: Option<BreakpointSessionState>,
8545 cx: &mut Context<Self>,
8546 ) -> IconButton {
8547 let is_rejected = state.is_some_and(|s| !s.verified);
8548 // Is it a breakpoint that shows up when hovering over gutter?
8549 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8550 (false, false),
8551 |PhantomBreakpointIndicator {
8552 is_active,
8553 display_row,
8554 collides_with_existing_breakpoint,
8555 }| {
8556 (
8557 is_active && display_row == row,
8558 collides_with_existing_breakpoint,
8559 )
8560 },
8561 );
8562
8563 let (color, icon) = {
8564 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8565 (false, false) => ui::IconName::DebugBreakpoint,
8566 (true, false) => ui::IconName::DebugLogBreakpoint,
8567 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8568 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8569 };
8570
8571 let color = cx.theme().colors();
8572
8573 let color = if is_phantom {
8574 if collides_with_existing {
8575 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8576 } else {
8577 Color::Hint
8578 }
8579 } else if is_rejected {
8580 Color::Disabled
8581 } else {
8582 Color::Debugger
8583 };
8584
8585 (color, icon)
8586 };
8587
8588 let breakpoint = Arc::from(breakpoint.clone());
8589
8590 let alt_as_text = gpui::Keystroke {
8591 modifiers: Modifiers::secondary_key(),
8592 ..Default::default()
8593 };
8594 let primary_action_text = if breakpoint.is_disabled() {
8595 "Enable breakpoint"
8596 } else if is_phantom && !collides_with_existing {
8597 "Set breakpoint"
8598 } else {
8599 "Unset breakpoint"
8600 };
8601 let focus_handle = self.focus_handle.clone();
8602
8603 let meta = if is_rejected {
8604 SharedString::from("No executable code is associated with this line.")
8605 } else if collides_with_existing && !breakpoint.is_disabled() {
8606 SharedString::from(format!(
8607 "{alt_as_text}-click to disable,\nright-click for more options."
8608 ))
8609 } else {
8610 SharedString::from("Right-click for more options.")
8611 };
8612 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8613 .icon_size(IconSize::XSmall)
8614 .size(ui::ButtonSize::None)
8615 .when(is_rejected, |this| {
8616 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8617 })
8618 .icon_color(color)
8619 .style(ButtonStyle::Transparent)
8620 .on_click(cx.listener({
8621 move |editor, event: &ClickEvent, window, cx| {
8622 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8623 BreakpointEditAction::InvertState
8624 } else {
8625 BreakpointEditAction::Toggle
8626 };
8627
8628 window.focus(&editor.focus_handle(cx));
8629 editor.edit_breakpoint_at_anchor(
8630 position,
8631 breakpoint.as_ref().clone(),
8632 edit_action,
8633 cx,
8634 );
8635 }
8636 }))
8637 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8638 editor.set_breakpoint_context_menu(
8639 row,
8640 Some(position),
8641 event.position(),
8642 window,
8643 cx,
8644 );
8645 }))
8646 .tooltip(move |_window, cx| {
8647 Tooltip::with_meta_in(
8648 primary_action_text,
8649 Some(&ToggleBreakpoint),
8650 meta.clone(),
8651 &focus_handle,
8652 cx,
8653 )
8654 })
8655 }
8656
8657 fn build_tasks_context(
8658 project: &Entity<Project>,
8659 buffer: &Entity<Buffer>,
8660 buffer_row: u32,
8661 tasks: &Arc<RunnableTasks>,
8662 cx: &mut Context<Self>,
8663 ) -> Task<Option<task::TaskContext>> {
8664 let position = Point::new(buffer_row, tasks.column);
8665 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8666 let location = Location {
8667 buffer: buffer.clone(),
8668 range: range_start..range_start,
8669 };
8670 // Fill in the environmental variables from the tree-sitter captures
8671 let mut captured_task_variables = TaskVariables::default();
8672 for (capture_name, value) in tasks.extra_variables.clone() {
8673 captured_task_variables.insert(
8674 task::VariableName::Custom(capture_name.into()),
8675 value.clone(),
8676 );
8677 }
8678 project.update(cx, |project, cx| {
8679 project.task_store().update(cx, |task_store, cx| {
8680 task_store.task_context_for_location(captured_task_variables, location, cx)
8681 })
8682 })
8683 }
8684
8685 pub fn spawn_nearest_task(
8686 &mut self,
8687 action: &SpawnNearestTask,
8688 window: &mut Window,
8689 cx: &mut Context<Self>,
8690 ) {
8691 let Some((workspace, _)) = self.workspace.clone() else {
8692 return;
8693 };
8694 let Some(project) = self.project.clone() else {
8695 return;
8696 };
8697
8698 // Try to find a closest, enclosing node using tree-sitter that has a task
8699 let Some((buffer, buffer_row, tasks)) = self
8700 .find_enclosing_node_task(cx)
8701 // Or find the task that's closest in row-distance.
8702 .or_else(|| self.find_closest_task(cx))
8703 else {
8704 return;
8705 };
8706
8707 let reveal_strategy = action.reveal;
8708 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8709 cx.spawn_in(window, async move |_, cx| {
8710 let context = task_context.await?;
8711 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8712
8713 let resolved = &mut resolved_task.resolved;
8714 resolved.reveal = reveal_strategy;
8715
8716 workspace
8717 .update_in(cx, |workspace, window, cx| {
8718 workspace.schedule_resolved_task(
8719 task_source_kind,
8720 resolved_task,
8721 false,
8722 window,
8723 cx,
8724 );
8725 })
8726 .ok()
8727 })
8728 .detach();
8729 }
8730
8731 fn find_closest_task(
8732 &mut self,
8733 cx: &mut Context<Self>,
8734 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8735 let cursor_row = self
8736 .selections
8737 .newest_adjusted(&self.display_snapshot(cx))
8738 .head()
8739 .row;
8740
8741 let ((buffer_id, row), tasks) = self
8742 .tasks
8743 .iter()
8744 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8745
8746 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8747 let tasks = Arc::new(tasks.to_owned());
8748 Some((buffer, *row, tasks))
8749 }
8750
8751 fn find_enclosing_node_task(
8752 &mut self,
8753 cx: &mut Context<Self>,
8754 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8755 let snapshot = self.buffer.read(cx).snapshot(cx);
8756 let offset = self
8757 .selections
8758 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8759 .head();
8760 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8761 let offset = excerpt.map_offset_to_buffer(offset);
8762 let buffer_id = excerpt.buffer().remote_id();
8763
8764 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8765 let mut cursor = layer.node().walk();
8766
8767 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8768 if cursor.node().end_byte() == offset.0 {
8769 cursor.goto_next_sibling();
8770 }
8771 }
8772
8773 // Ascend to the smallest ancestor that contains the range and has a task.
8774 loop {
8775 let node = cursor.node();
8776 let node_range = node.byte_range();
8777 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8778
8779 // Check if this node contains our offset
8780 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8781 // If it contains offset, check for task
8782 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8783 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8784 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8785 }
8786 }
8787
8788 if !cursor.goto_parent() {
8789 break;
8790 }
8791 }
8792 None
8793 }
8794
8795 fn render_run_indicator(
8796 &self,
8797 _style: &EditorStyle,
8798 is_active: bool,
8799 row: DisplayRow,
8800 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8801 cx: &mut Context<Self>,
8802 ) -> IconButton {
8803 let color = Color::Muted;
8804 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8805
8806 IconButton::new(
8807 ("run_indicator", row.0 as usize),
8808 ui::IconName::PlayOutlined,
8809 )
8810 .shape(ui::IconButtonShape::Square)
8811 .icon_size(IconSize::XSmall)
8812 .icon_color(color)
8813 .toggle_state(is_active)
8814 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8815 let quick_launch = match e {
8816 ClickEvent::Keyboard(_) => true,
8817 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8818 };
8819
8820 window.focus(&editor.focus_handle(cx));
8821 editor.toggle_code_actions(
8822 &ToggleCodeActions {
8823 deployed_from: Some(CodeActionSource::RunMenu(row)),
8824 quick_launch,
8825 },
8826 window,
8827 cx,
8828 );
8829 }))
8830 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8831 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8832 }))
8833 }
8834
8835 pub fn context_menu_visible(&self) -> bool {
8836 !self.edit_prediction_preview_is_active()
8837 && self
8838 .context_menu
8839 .borrow()
8840 .as_ref()
8841 .is_some_and(|menu| menu.visible())
8842 }
8843
8844 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8845 self.context_menu
8846 .borrow()
8847 .as_ref()
8848 .map(|menu| menu.origin())
8849 }
8850
8851 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8852 self.context_menu_options = Some(options);
8853 }
8854
8855 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8856 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8857
8858 fn render_edit_prediction_popover(
8859 &mut self,
8860 text_bounds: &Bounds<Pixels>,
8861 content_origin: gpui::Point<Pixels>,
8862 right_margin: Pixels,
8863 editor_snapshot: &EditorSnapshot,
8864 visible_row_range: Range<DisplayRow>,
8865 scroll_top: ScrollOffset,
8866 scroll_bottom: ScrollOffset,
8867 line_layouts: &[LineWithInvisibles],
8868 line_height: Pixels,
8869 scroll_position: gpui::Point<ScrollOffset>,
8870 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8871 newest_selection_head: Option<DisplayPoint>,
8872 editor_width: Pixels,
8873 style: &EditorStyle,
8874 window: &mut Window,
8875 cx: &mut App,
8876 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8877 if self.mode().is_minimap() {
8878 return None;
8879 }
8880 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8881
8882 if self.edit_prediction_visible_in_cursor_popover(true) {
8883 return None;
8884 }
8885
8886 match &active_edit_prediction.completion {
8887 EditPrediction::MoveWithin { target, .. } => {
8888 let target_display_point = target.to_display_point(editor_snapshot);
8889
8890 if self.edit_prediction_requires_modifier() {
8891 if !self.edit_prediction_preview_is_active() {
8892 return None;
8893 }
8894
8895 self.render_edit_prediction_modifier_jump_popover(
8896 text_bounds,
8897 content_origin,
8898 visible_row_range,
8899 line_layouts,
8900 line_height,
8901 scroll_pixel_position,
8902 newest_selection_head,
8903 target_display_point,
8904 window,
8905 cx,
8906 )
8907 } else {
8908 self.render_edit_prediction_eager_jump_popover(
8909 text_bounds,
8910 content_origin,
8911 editor_snapshot,
8912 visible_row_range,
8913 scroll_top,
8914 scroll_bottom,
8915 line_height,
8916 scroll_pixel_position,
8917 target_display_point,
8918 editor_width,
8919 window,
8920 cx,
8921 )
8922 }
8923 }
8924 EditPrediction::Edit {
8925 display_mode: EditDisplayMode::Inline,
8926 ..
8927 } => None,
8928 EditPrediction::Edit {
8929 display_mode: EditDisplayMode::TabAccept,
8930 edits,
8931 ..
8932 } => {
8933 let range = &edits.first()?.0;
8934 let target_display_point = range.end.to_display_point(editor_snapshot);
8935
8936 self.render_edit_prediction_end_of_line_popover(
8937 "Accept",
8938 editor_snapshot,
8939 visible_row_range,
8940 target_display_point,
8941 line_height,
8942 scroll_pixel_position,
8943 content_origin,
8944 editor_width,
8945 window,
8946 cx,
8947 )
8948 }
8949 EditPrediction::Edit {
8950 edits,
8951 edit_preview,
8952 display_mode: EditDisplayMode::DiffPopover,
8953 snapshot,
8954 } => self.render_edit_prediction_diff_popover(
8955 text_bounds,
8956 content_origin,
8957 right_margin,
8958 editor_snapshot,
8959 visible_row_range,
8960 line_layouts,
8961 line_height,
8962 scroll_position,
8963 scroll_pixel_position,
8964 newest_selection_head,
8965 editor_width,
8966 style,
8967 edits,
8968 edit_preview,
8969 snapshot,
8970 window,
8971 cx,
8972 ),
8973 EditPrediction::MoveOutside { snapshot, .. } => {
8974 let mut element = self
8975 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8976 .into_any();
8977
8978 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8979 let origin_x = text_bounds.size.width - size.width - px(30.);
8980 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8981 element.prepaint_at(origin, window, cx);
8982
8983 Some((element, origin))
8984 }
8985 }
8986 }
8987
8988 fn render_edit_prediction_modifier_jump_popover(
8989 &mut self,
8990 text_bounds: &Bounds<Pixels>,
8991 content_origin: gpui::Point<Pixels>,
8992 visible_row_range: Range<DisplayRow>,
8993 line_layouts: &[LineWithInvisibles],
8994 line_height: Pixels,
8995 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8996 newest_selection_head: Option<DisplayPoint>,
8997 target_display_point: DisplayPoint,
8998 window: &mut Window,
8999 cx: &mut App,
9000 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9001 let scrolled_content_origin =
9002 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9003
9004 const SCROLL_PADDING_Y: Pixels = px(12.);
9005
9006 if target_display_point.row() < visible_row_range.start {
9007 return self.render_edit_prediction_scroll_popover(
9008 |_| SCROLL_PADDING_Y,
9009 IconName::ArrowUp,
9010 visible_row_range,
9011 line_layouts,
9012 newest_selection_head,
9013 scrolled_content_origin,
9014 window,
9015 cx,
9016 );
9017 } else if target_display_point.row() >= visible_row_range.end {
9018 return self.render_edit_prediction_scroll_popover(
9019 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9020 IconName::ArrowDown,
9021 visible_row_range,
9022 line_layouts,
9023 newest_selection_head,
9024 scrolled_content_origin,
9025 window,
9026 cx,
9027 );
9028 }
9029
9030 const POLE_WIDTH: Pixels = px(2.);
9031
9032 let line_layout =
9033 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9034 let target_column = target_display_point.column() as usize;
9035
9036 let target_x = line_layout.x_for_index(target_column);
9037 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9038 - scroll_pixel_position.y;
9039
9040 let flag_on_right = target_x < text_bounds.size.width / 2.;
9041
9042 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9043 border_color.l += 0.001;
9044
9045 let mut element = v_flex()
9046 .items_end()
9047 .when(flag_on_right, |el| el.items_start())
9048 .child(if flag_on_right {
9049 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9050 .rounded_bl(px(0.))
9051 .rounded_tl(px(0.))
9052 .border_l_2()
9053 .border_color(border_color)
9054 } else {
9055 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9056 .rounded_br(px(0.))
9057 .rounded_tr(px(0.))
9058 .border_r_2()
9059 .border_color(border_color)
9060 })
9061 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9062 .into_any();
9063
9064 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9065
9066 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9067 - point(
9068 if flag_on_right {
9069 POLE_WIDTH
9070 } else {
9071 size.width - POLE_WIDTH
9072 },
9073 size.height - line_height,
9074 );
9075
9076 origin.x = origin.x.max(content_origin.x);
9077
9078 element.prepaint_at(origin, window, cx);
9079
9080 Some((element, origin))
9081 }
9082
9083 fn render_edit_prediction_scroll_popover(
9084 &mut self,
9085 to_y: impl Fn(Size<Pixels>) -> Pixels,
9086 scroll_icon: IconName,
9087 visible_row_range: Range<DisplayRow>,
9088 line_layouts: &[LineWithInvisibles],
9089 newest_selection_head: Option<DisplayPoint>,
9090 scrolled_content_origin: gpui::Point<Pixels>,
9091 window: &mut Window,
9092 cx: &mut App,
9093 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9094 let mut element = self
9095 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9096 .into_any();
9097
9098 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9099
9100 let cursor = newest_selection_head?;
9101 let cursor_row_layout =
9102 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9103 let cursor_column = cursor.column() as usize;
9104
9105 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9106
9107 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9108
9109 element.prepaint_at(origin, window, cx);
9110 Some((element, origin))
9111 }
9112
9113 fn render_edit_prediction_eager_jump_popover(
9114 &mut self,
9115 text_bounds: &Bounds<Pixels>,
9116 content_origin: gpui::Point<Pixels>,
9117 editor_snapshot: &EditorSnapshot,
9118 visible_row_range: Range<DisplayRow>,
9119 scroll_top: ScrollOffset,
9120 scroll_bottom: ScrollOffset,
9121 line_height: Pixels,
9122 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9123 target_display_point: DisplayPoint,
9124 editor_width: Pixels,
9125 window: &mut Window,
9126 cx: &mut App,
9127 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9128 if target_display_point.row().as_f64() < scroll_top {
9129 let mut element = self
9130 .render_edit_prediction_line_popover(
9131 "Jump to Edit",
9132 Some(IconName::ArrowUp),
9133 window,
9134 cx,
9135 )
9136 .into_any();
9137
9138 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9139 let offset = point(
9140 (text_bounds.size.width - size.width) / 2.,
9141 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9142 );
9143
9144 let origin = text_bounds.origin + offset;
9145 element.prepaint_at(origin, window, cx);
9146 Some((element, origin))
9147 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9148 let mut element = self
9149 .render_edit_prediction_line_popover(
9150 "Jump to Edit",
9151 Some(IconName::ArrowDown),
9152 window,
9153 cx,
9154 )
9155 .into_any();
9156
9157 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9158 let offset = point(
9159 (text_bounds.size.width - size.width) / 2.,
9160 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9161 );
9162
9163 let origin = text_bounds.origin + offset;
9164 element.prepaint_at(origin, window, cx);
9165 Some((element, origin))
9166 } else {
9167 self.render_edit_prediction_end_of_line_popover(
9168 "Jump to Edit",
9169 editor_snapshot,
9170 visible_row_range,
9171 target_display_point,
9172 line_height,
9173 scroll_pixel_position,
9174 content_origin,
9175 editor_width,
9176 window,
9177 cx,
9178 )
9179 }
9180 }
9181
9182 fn render_edit_prediction_end_of_line_popover(
9183 self: &mut Editor,
9184 label: &'static str,
9185 editor_snapshot: &EditorSnapshot,
9186 visible_row_range: Range<DisplayRow>,
9187 target_display_point: DisplayPoint,
9188 line_height: Pixels,
9189 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9190 content_origin: gpui::Point<Pixels>,
9191 editor_width: Pixels,
9192 window: &mut Window,
9193 cx: &mut App,
9194 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9195 let target_line_end = DisplayPoint::new(
9196 target_display_point.row(),
9197 editor_snapshot.line_len(target_display_point.row()),
9198 );
9199
9200 let mut element = self
9201 .render_edit_prediction_line_popover(label, None, window, cx)
9202 .into_any();
9203
9204 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9205
9206 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9207
9208 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9209 let mut origin = start_point
9210 + line_origin
9211 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9212 origin.x = origin.x.max(content_origin.x);
9213
9214 let max_x = content_origin.x + editor_width - size.width;
9215
9216 if origin.x > max_x {
9217 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9218
9219 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9220 origin.y += offset;
9221 IconName::ArrowUp
9222 } else {
9223 origin.y -= offset;
9224 IconName::ArrowDown
9225 };
9226
9227 element = self
9228 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9229 .into_any();
9230
9231 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9232
9233 origin.x = content_origin.x + editor_width - size.width - px(2.);
9234 }
9235
9236 element.prepaint_at(origin, window, cx);
9237 Some((element, origin))
9238 }
9239
9240 fn render_edit_prediction_diff_popover(
9241 self: &Editor,
9242 text_bounds: &Bounds<Pixels>,
9243 content_origin: gpui::Point<Pixels>,
9244 right_margin: Pixels,
9245 editor_snapshot: &EditorSnapshot,
9246 visible_row_range: Range<DisplayRow>,
9247 line_layouts: &[LineWithInvisibles],
9248 line_height: Pixels,
9249 scroll_position: gpui::Point<ScrollOffset>,
9250 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9251 newest_selection_head: Option<DisplayPoint>,
9252 editor_width: Pixels,
9253 style: &EditorStyle,
9254 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9255 edit_preview: &Option<language::EditPreview>,
9256 snapshot: &language::BufferSnapshot,
9257 window: &mut Window,
9258 cx: &mut App,
9259 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9260 let edit_start = edits
9261 .first()
9262 .unwrap()
9263 .0
9264 .start
9265 .to_display_point(editor_snapshot);
9266 let edit_end = edits
9267 .last()
9268 .unwrap()
9269 .0
9270 .end
9271 .to_display_point(editor_snapshot);
9272
9273 let is_visible = visible_row_range.contains(&edit_start.row())
9274 || visible_row_range.contains(&edit_end.row());
9275 if !is_visible {
9276 return None;
9277 }
9278
9279 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9280 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9281 } else {
9282 // Fallback for providers without edit_preview
9283 crate::edit_prediction_fallback_text(edits, cx)
9284 };
9285
9286 let styled_text = highlighted_edits.to_styled_text(&style.text);
9287 let line_count = highlighted_edits.text.lines().count();
9288
9289 const BORDER_WIDTH: Pixels = px(1.);
9290
9291 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9292 let has_keybind = keybind.is_some();
9293
9294 let mut element = h_flex()
9295 .items_start()
9296 .child(
9297 h_flex()
9298 .bg(cx.theme().colors().editor_background)
9299 .border(BORDER_WIDTH)
9300 .shadow_xs()
9301 .border_color(cx.theme().colors().border)
9302 .rounded_l_lg()
9303 .when(line_count > 1, |el| el.rounded_br_lg())
9304 .pr_1()
9305 .child(styled_text),
9306 )
9307 .child(
9308 h_flex()
9309 .h(line_height + BORDER_WIDTH * 2.)
9310 .px_1p5()
9311 .gap_1()
9312 // Workaround: For some reason, there's a gap if we don't do this
9313 .ml(-BORDER_WIDTH)
9314 .shadow(vec![gpui::BoxShadow {
9315 color: gpui::black().opacity(0.05),
9316 offset: point(px(1.), px(1.)),
9317 blur_radius: px(2.),
9318 spread_radius: px(0.),
9319 }])
9320 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9321 .border(BORDER_WIDTH)
9322 .border_color(cx.theme().colors().border)
9323 .rounded_r_lg()
9324 .id("edit_prediction_diff_popover_keybind")
9325 .when(!has_keybind, |el| {
9326 let status_colors = cx.theme().status();
9327
9328 el.bg(status_colors.error_background)
9329 .border_color(status_colors.error.opacity(0.6))
9330 .child(Icon::new(IconName::Info).color(Color::Error))
9331 .cursor_default()
9332 .hoverable_tooltip(move |_window, cx| {
9333 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9334 })
9335 })
9336 .children(keybind),
9337 )
9338 .into_any();
9339
9340 let longest_row =
9341 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9342 let longest_line_width = if visible_row_range.contains(&longest_row) {
9343 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9344 } else {
9345 layout_line(
9346 longest_row,
9347 editor_snapshot,
9348 style,
9349 editor_width,
9350 |_| false,
9351 window,
9352 cx,
9353 )
9354 .width
9355 };
9356
9357 let viewport_bounds =
9358 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9359 right: -right_margin,
9360 ..Default::default()
9361 });
9362
9363 let x_after_longest = Pixels::from(
9364 ScrollPixelOffset::from(
9365 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9366 ) - scroll_pixel_position.x,
9367 );
9368
9369 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9370
9371 // Fully visible if it can be displayed within the window (allow overlapping other
9372 // panes). However, this is only allowed if the popover starts within text_bounds.
9373 let can_position_to_the_right = x_after_longest < text_bounds.right()
9374 && x_after_longest + element_bounds.width < viewport_bounds.right();
9375
9376 let mut origin = if can_position_to_the_right {
9377 point(
9378 x_after_longest,
9379 text_bounds.origin.y
9380 + Pixels::from(
9381 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9382 - scroll_pixel_position.y,
9383 ),
9384 )
9385 } else {
9386 let cursor_row = newest_selection_head.map(|head| head.row());
9387 let above_edit = edit_start
9388 .row()
9389 .0
9390 .checked_sub(line_count as u32)
9391 .map(DisplayRow);
9392 let below_edit = Some(edit_end.row() + 1);
9393 let above_cursor =
9394 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9395 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9396
9397 // Place the edit popover adjacent to the edit if there is a location
9398 // available that is onscreen and does not obscure the cursor. Otherwise,
9399 // place it adjacent to the cursor.
9400 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9401 .into_iter()
9402 .flatten()
9403 .find(|&start_row| {
9404 let end_row = start_row + line_count as u32;
9405 visible_row_range.contains(&start_row)
9406 && visible_row_range.contains(&end_row)
9407 && cursor_row
9408 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9409 })?;
9410
9411 content_origin
9412 + point(
9413 Pixels::from(-scroll_pixel_position.x),
9414 Pixels::from(
9415 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9416 ),
9417 )
9418 };
9419
9420 origin.x -= BORDER_WIDTH;
9421
9422 window.defer_draw(element, origin, 1);
9423
9424 // Do not return an element, since it will already be drawn due to defer_draw.
9425 None
9426 }
9427
9428 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9429 px(30.)
9430 }
9431
9432 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9433 if self.read_only(cx) {
9434 cx.theme().players().read_only()
9435 } else {
9436 self.style.as_ref().unwrap().local_player
9437 }
9438 }
9439
9440 fn render_edit_prediction_accept_keybind(
9441 &self,
9442 window: &mut Window,
9443 cx: &mut App,
9444 ) -> Option<AnyElement> {
9445 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9446 let accept_keystroke = accept_binding.keystroke()?;
9447
9448 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9449
9450 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9451 Color::Accent
9452 } else {
9453 Color::Muted
9454 };
9455
9456 h_flex()
9457 .px_0p5()
9458 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9459 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9460 .text_size(TextSize::XSmall.rems(cx))
9461 .child(h_flex().children(ui::render_modifiers(
9462 accept_keystroke.modifiers(),
9463 PlatformStyle::platform(),
9464 Some(modifiers_color),
9465 Some(IconSize::XSmall.rems().into()),
9466 true,
9467 )))
9468 .when(is_platform_style_mac, |parent| {
9469 parent.child(accept_keystroke.key().to_string())
9470 })
9471 .when(!is_platform_style_mac, |parent| {
9472 parent.child(
9473 Key::new(
9474 util::capitalize(accept_keystroke.key()),
9475 Some(Color::Default),
9476 )
9477 .size(Some(IconSize::XSmall.rems().into())),
9478 )
9479 })
9480 .into_any()
9481 .into()
9482 }
9483
9484 fn render_edit_prediction_line_popover(
9485 &self,
9486 label: impl Into<SharedString>,
9487 icon: Option<IconName>,
9488 window: &mut Window,
9489 cx: &mut App,
9490 ) -> Stateful<Div> {
9491 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9492
9493 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9494 let has_keybind = keybind.is_some();
9495
9496 h_flex()
9497 .id("ep-line-popover")
9498 .py_0p5()
9499 .pl_1()
9500 .pr(padding_right)
9501 .gap_1()
9502 .rounded_md()
9503 .border_1()
9504 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9505 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9506 .shadow_xs()
9507 .when(!has_keybind, |el| {
9508 let status_colors = cx.theme().status();
9509
9510 el.bg(status_colors.error_background)
9511 .border_color(status_colors.error.opacity(0.6))
9512 .pl_2()
9513 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9514 .cursor_default()
9515 .hoverable_tooltip(move |_window, cx| {
9516 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9517 })
9518 })
9519 .children(keybind)
9520 .child(
9521 Label::new(label)
9522 .size(LabelSize::Small)
9523 .when(!has_keybind, |el| {
9524 el.color(cx.theme().status().error.into()).strikethrough()
9525 }),
9526 )
9527 .when(!has_keybind, |el| {
9528 el.child(
9529 h_flex().ml_1().child(
9530 Icon::new(IconName::Info)
9531 .size(IconSize::Small)
9532 .color(cx.theme().status().error.into()),
9533 ),
9534 )
9535 })
9536 .when_some(icon, |element, icon| {
9537 element.child(
9538 div()
9539 .mt(px(1.5))
9540 .child(Icon::new(icon).size(IconSize::Small)),
9541 )
9542 })
9543 }
9544
9545 fn render_edit_prediction_jump_outside_popover(
9546 &self,
9547 snapshot: &BufferSnapshot,
9548 window: &mut Window,
9549 cx: &mut App,
9550 ) -> Stateful<Div> {
9551 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9552 let has_keybind = keybind.is_some();
9553
9554 let file_name = snapshot
9555 .file()
9556 .map(|file| SharedString::new(file.file_name(cx)))
9557 .unwrap_or(SharedString::new_static("untitled"));
9558
9559 h_flex()
9560 .id("ep-jump-outside-popover")
9561 .py_1()
9562 .px_2()
9563 .gap_1()
9564 .rounded_md()
9565 .border_1()
9566 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9567 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9568 .shadow_xs()
9569 .when(!has_keybind, |el| {
9570 let status_colors = cx.theme().status();
9571
9572 el.bg(status_colors.error_background)
9573 .border_color(status_colors.error.opacity(0.6))
9574 .pl_2()
9575 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9576 .cursor_default()
9577 .hoverable_tooltip(move |_window, cx| {
9578 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9579 })
9580 })
9581 .children(keybind)
9582 .child(
9583 Label::new(file_name)
9584 .size(LabelSize::Small)
9585 .buffer_font(cx)
9586 .when(!has_keybind, |el| {
9587 el.color(cx.theme().status().error.into()).strikethrough()
9588 }),
9589 )
9590 .when(!has_keybind, |el| {
9591 el.child(
9592 h_flex().ml_1().child(
9593 Icon::new(IconName::Info)
9594 .size(IconSize::Small)
9595 .color(cx.theme().status().error.into()),
9596 ),
9597 )
9598 })
9599 .child(
9600 div()
9601 .mt(px(1.5))
9602 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9603 )
9604 }
9605
9606 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9607 let accent_color = cx.theme().colors().text_accent;
9608 let editor_bg_color = cx.theme().colors().editor_background;
9609 editor_bg_color.blend(accent_color.opacity(0.1))
9610 }
9611
9612 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9613 let accent_color = cx.theme().colors().text_accent;
9614 let editor_bg_color = cx.theme().colors().editor_background;
9615 editor_bg_color.blend(accent_color.opacity(0.6))
9616 }
9617 fn get_prediction_provider_icon_name(
9618 provider: &Option<RegisteredEditPredictionDelegate>,
9619 ) -> IconName {
9620 match provider {
9621 Some(provider) => match provider.provider.name() {
9622 "copilot" => IconName::Copilot,
9623 "supermaven" => IconName::Supermaven,
9624 _ => IconName::ZedPredict,
9625 },
9626 None => IconName::ZedPredict,
9627 }
9628 }
9629
9630 fn render_edit_prediction_cursor_popover(
9631 &self,
9632 min_width: Pixels,
9633 max_width: Pixels,
9634 cursor_point: Point,
9635 style: &EditorStyle,
9636 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9637 _window: &Window,
9638 cx: &mut Context<Editor>,
9639 ) -> Option<AnyElement> {
9640 let provider = self.edit_prediction_provider.as_ref()?;
9641 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9642
9643 let is_refreshing = provider.provider.is_refreshing(cx);
9644
9645 fn pending_completion_container(icon: IconName) -> Div {
9646 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9647 }
9648
9649 let completion = match &self.active_edit_prediction {
9650 Some(prediction) => {
9651 if !self.has_visible_completions_menu() {
9652 const RADIUS: Pixels = px(6.);
9653 const BORDER_WIDTH: Pixels = px(1.);
9654
9655 return Some(
9656 h_flex()
9657 .elevation_2(cx)
9658 .border(BORDER_WIDTH)
9659 .border_color(cx.theme().colors().border)
9660 .when(accept_keystroke.is_none(), |el| {
9661 el.border_color(cx.theme().status().error)
9662 })
9663 .rounded(RADIUS)
9664 .rounded_tl(px(0.))
9665 .overflow_hidden()
9666 .child(div().px_1p5().child(match &prediction.completion {
9667 EditPrediction::MoveWithin { target, snapshot } => {
9668 use text::ToPoint as _;
9669 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9670 {
9671 Icon::new(IconName::ZedPredictDown)
9672 } else {
9673 Icon::new(IconName::ZedPredictUp)
9674 }
9675 }
9676 EditPrediction::MoveOutside { .. } => {
9677 // TODO [zeta2] custom icon for external jump?
9678 Icon::new(provider_icon)
9679 }
9680 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9681 }))
9682 .child(
9683 h_flex()
9684 .gap_1()
9685 .py_1()
9686 .px_2()
9687 .rounded_r(RADIUS - BORDER_WIDTH)
9688 .border_l_1()
9689 .border_color(cx.theme().colors().border)
9690 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9691 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9692 el.child(
9693 Label::new("Hold")
9694 .size(LabelSize::Small)
9695 .when(accept_keystroke.is_none(), |el| {
9696 el.strikethrough()
9697 })
9698 .line_height_style(LineHeightStyle::UiLabel),
9699 )
9700 })
9701 .id("edit_prediction_cursor_popover_keybind")
9702 .when(accept_keystroke.is_none(), |el| {
9703 let status_colors = cx.theme().status();
9704
9705 el.bg(status_colors.error_background)
9706 .border_color(status_colors.error.opacity(0.6))
9707 .child(Icon::new(IconName::Info).color(Color::Error))
9708 .cursor_default()
9709 .hoverable_tooltip(move |_window, cx| {
9710 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9711 .into()
9712 })
9713 })
9714 .when_some(
9715 accept_keystroke.as_ref(),
9716 |el, accept_keystroke| {
9717 el.child(h_flex().children(ui::render_modifiers(
9718 accept_keystroke.modifiers(),
9719 PlatformStyle::platform(),
9720 Some(Color::Default),
9721 Some(IconSize::XSmall.rems().into()),
9722 false,
9723 )))
9724 },
9725 ),
9726 )
9727 .into_any(),
9728 );
9729 }
9730
9731 self.render_edit_prediction_cursor_popover_preview(
9732 prediction,
9733 cursor_point,
9734 style,
9735 cx,
9736 )?
9737 }
9738
9739 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9740 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9741 stale_completion,
9742 cursor_point,
9743 style,
9744 cx,
9745 )?,
9746
9747 None => pending_completion_container(provider_icon)
9748 .child(Label::new("...").size(LabelSize::Small)),
9749 },
9750
9751 None => pending_completion_container(provider_icon)
9752 .child(Label::new("...").size(LabelSize::Small)),
9753 };
9754
9755 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9756 completion
9757 .with_animation(
9758 "loading-completion",
9759 Animation::new(Duration::from_secs(2))
9760 .repeat()
9761 .with_easing(pulsating_between(0.4, 0.8)),
9762 |label, delta| label.opacity(delta),
9763 )
9764 .into_any_element()
9765 } else {
9766 completion.into_any_element()
9767 };
9768
9769 let has_completion = self.active_edit_prediction.is_some();
9770
9771 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9772 Some(
9773 h_flex()
9774 .min_w(min_width)
9775 .max_w(max_width)
9776 .flex_1()
9777 .elevation_2(cx)
9778 .border_color(cx.theme().colors().border)
9779 .child(
9780 div()
9781 .flex_1()
9782 .py_1()
9783 .px_2()
9784 .overflow_hidden()
9785 .child(completion),
9786 )
9787 .when_some(accept_keystroke, |el, accept_keystroke| {
9788 if !accept_keystroke.modifiers().modified() {
9789 return el;
9790 }
9791
9792 el.child(
9793 h_flex()
9794 .h_full()
9795 .border_l_1()
9796 .rounded_r_lg()
9797 .border_color(cx.theme().colors().border)
9798 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9799 .gap_1()
9800 .py_1()
9801 .px_2()
9802 .child(
9803 h_flex()
9804 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9805 .when(is_platform_style_mac, |parent| parent.gap_1())
9806 .child(h_flex().children(ui::render_modifiers(
9807 accept_keystroke.modifiers(),
9808 PlatformStyle::platform(),
9809 Some(if !has_completion {
9810 Color::Muted
9811 } else {
9812 Color::Default
9813 }),
9814 None,
9815 false,
9816 ))),
9817 )
9818 .child(Label::new("Preview").into_any_element())
9819 .opacity(if has_completion { 1.0 } else { 0.4 }),
9820 )
9821 })
9822 .into_any(),
9823 )
9824 }
9825
9826 fn render_edit_prediction_cursor_popover_preview(
9827 &self,
9828 completion: &EditPredictionState,
9829 cursor_point: Point,
9830 style: &EditorStyle,
9831 cx: &mut Context<Editor>,
9832 ) -> Option<Div> {
9833 use text::ToPoint as _;
9834
9835 fn render_relative_row_jump(
9836 prefix: impl Into<String>,
9837 current_row: u32,
9838 target_row: u32,
9839 ) -> Div {
9840 let (row_diff, arrow) = if target_row < current_row {
9841 (current_row - target_row, IconName::ArrowUp)
9842 } else {
9843 (target_row - current_row, IconName::ArrowDown)
9844 };
9845
9846 h_flex()
9847 .child(
9848 Label::new(format!("{}{}", prefix.into(), row_diff))
9849 .color(Color::Muted)
9850 .size(LabelSize::Small),
9851 )
9852 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9853 }
9854
9855 let supports_jump = self
9856 .edit_prediction_provider
9857 .as_ref()
9858 .map(|provider| provider.provider.supports_jump_to_edit())
9859 .unwrap_or(true);
9860
9861 match &completion.completion {
9862 EditPrediction::MoveWithin {
9863 target, snapshot, ..
9864 } => {
9865 if !supports_jump {
9866 return None;
9867 }
9868
9869 Some(
9870 h_flex()
9871 .px_2()
9872 .gap_2()
9873 .flex_1()
9874 .child(
9875 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9876 Icon::new(IconName::ZedPredictDown)
9877 } else {
9878 Icon::new(IconName::ZedPredictUp)
9879 },
9880 )
9881 .child(Label::new("Jump to Edit")),
9882 )
9883 }
9884 EditPrediction::MoveOutside { snapshot, .. } => {
9885 let file_name = snapshot
9886 .file()
9887 .map(|file| file.file_name(cx))
9888 .unwrap_or("untitled");
9889 Some(
9890 h_flex()
9891 .px_2()
9892 .gap_2()
9893 .flex_1()
9894 .child(Icon::new(IconName::ZedPredict))
9895 .child(Label::new(format!("Jump to {file_name}"))),
9896 )
9897 }
9898 EditPrediction::Edit {
9899 edits,
9900 edit_preview,
9901 snapshot,
9902 display_mode: _,
9903 } => {
9904 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9905
9906 let (highlighted_edits, has_more_lines) =
9907 if let Some(edit_preview) = edit_preview.as_ref() {
9908 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9909 .first_line_preview()
9910 } else {
9911 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9912 };
9913
9914 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9915 .with_default_highlights(&style.text, highlighted_edits.highlights);
9916
9917 let preview = h_flex()
9918 .gap_1()
9919 .min_w_16()
9920 .child(styled_text)
9921 .when(has_more_lines, |parent| parent.child("…"));
9922
9923 let left = if supports_jump && first_edit_row != cursor_point.row {
9924 render_relative_row_jump("", cursor_point.row, first_edit_row)
9925 .into_any_element()
9926 } else {
9927 let icon_name =
9928 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9929 Icon::new(icon_name).into_any_element()
9930 };
9931
9932 Some(
9933 h_flex()
9934 .h_full()
9935 .flex_1()
9936 .gap_2()
9937 .pr_1()
9938 .overflow_x_hidden()
9939 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9940 .child(left)
9941 .child(preview),
9942 )
9943 }
9944 }
9945 }
9946
9947 pub fn render_context_menu(
9948 &self,
9949 style: &EditorStyle,
9950 max_height_in_lines: u32,
9951 window: &mut Window,
9952 cx: &mut Context<Editor>,
9953 ) -> Option<AnyElement> {
9954 let menu = self.context_menu.borrow();
9955 let menu = menu.as_ref()?;
9956 if !menu.visible() {
9957 return None;
9958 };
9959 Some(menu.render(style, max_height_in_lines, window, cx))
9960 }
9961
9962 fn render_context_menu_aside(
9963 &mut self,
9964 max_size: Size<Pixels>,
9965 window: &mut Window,
9966 cx: &mut Context<Editor>,
9967 ) -> Option<AnyElement> {
9968 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9969 if menu.visible() {
9970 menu.render_aside(max_size, window, cx)
9971 } else {
9972 None
9973 }
9974 })
9975 }
9976
9977 fn hide_context_menu(
9978 &mut self,
9979 window: &mut Window,
9980 cx: &mut Context<Self>,
9981 ) -> Option<CodeContextMenu> {
9982 cx.notify();
9983 self.completion_tasks.clear();
9984 let context_menu = self.context_menu.borrow_mut().take();
9985 self.stale_edit_prediction_in_menu.take();
9986 self.update_visible_edit_prediction(window, cx);
9987 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9988 && let Some(completion_provider) = &self.completion_provider
9989 {
9990 completion_provider.selection_changed(None, window, cx);
9991 }
9992 context_menu
9993 }
9994
9995 fn show_snippet_choices(
9996 &mut self,
9997 choices: &Vec<String>,
9998 selection: Range<Anchor>,
9999 cx: &mut Context<Self>,
10000 ) {
10001 let Some((_, buffer, _)) = self
10002 .buffer()
10003 .read(cx)
10004 .excerpt_containing(selection.start, cx)
10005 else {
10006 return;
10007 };
10008 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10009 else {
10010 return;
10011 };
10012 if buffer != end_buffer {
10013 log::error!("expected anchor range to have matching buffer IDs");
10014 return;
10015 }
10016
10017 let id = post_inc(&mut self.next_completion_id);
10018 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10019 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
10020 CompletionsMenu::new_snippet_choices(
10021 id,
10022 true,
10023 choices,
10024 selection,
10025 buffer,
10026 snippet_sort_order,
10027 ),
10028 ));
10029 }
10030
10031 pub fn insert_snippet(
10032 &mut self,
10033 insertion_ranges: &[Range<MultiBufferOffset>],
10034 snippet: Snippet,
10035 window: &mut Window,
10036 cx: &mut Context<Self>,
10037 ) -> Result<()> {
10038 struct Tabstop<T> {
10039 is_end_tabstop: bool,
10040 ranges: Vec<Range<T>>,
10041 choices: Option<Vec<String>>,
10042 }
10043
10044 let tabstops = self.buffer.update(cx, |buffer, cx| {
10045 let snippet_text: Arc<str> = snippet.text.clone().into();
10046 let edits = insertion_ranges
10047 .iter()
10048 .cloned()
10049 .map(|range| (range, snippet_text.clone()));
10050 let autoindent_mode = AutoindentMode::Block {
10051 original_indent_columns: Vec::new(),
10052 };
10053 buffer.edit(edits, Some(autoindent_mode), cx);
10054
10055 let snapshot = &*buffer.read(cx);
10056 let snippet = &snippet;
10057 snippet
10058 .tabstops
10059 .iter()
10060 .map(|tabstop| {
10061 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10062 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10063 });
10064 let mut tabstop_ranges = tabstop
10065 .ranges
10066 .iter()
10067 .flat_map(|tabstop_range| {
10068 let mut delta = 0_isize;
10069 insertion_ranges.iter().map(move |insertion_range| {
10070 let insertion_start = insertion_range.start + delta;
10071 delta += snippet.text.len() as isize
10072 - (insertion_range.end - insertion_range.start) as isize;
10073
10074 let start =
10075 (insertion_start + tabstop_range.start).min(snapshot.len());
10076 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10077 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10078 })
10079 })
10080 .collect::<Vec<_>>();
10081 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10082
10083 Tabstop {
10084 is_end_tabstop,
10085 ranges: tabstop_ranges,
10086 choices: tabstop.choices.clone(),
10087 }
10088 })
10089 .collect::<Vec<_>>()
10090 });
10091 if let Some(tabstop) = tabstops.first() {
10092 self.change_selections(Default::default(), window, cx, |s| {
10093 // Reverse order so that the first range is the newest created selection.
10094 // Completions will use it and autoscroll will prioritize it.
10095 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10096 });
10097
10098 if let Some(choices) = &tabstop.choices
10099 && let Some(selection) = tabstop.ranges.first()
10100 {
10101 self.show_snippet_choices(choices, selection.clone(), cx)
10102 }
10103
10104 // If we're already at the last tabstop and it's at the end of the snippet,
10105 // we're done, we don't need to keep the state around.
10106 if !tabstop.is_end_tabstop {
10107 let choices = tabstops
10108 .iter()
10109 .map(|tabstop| tabstop.choices.clone())
10110 .collect();
10111
10112 let ranges = tabstops
10113 .into_iter()
10114 .map(|tabstop| tabstop.ranges)
10115 .collect::<Vec<_>>();
10116
10117 self.snippet_stack.push(SnippetState {
10118 active_index: 0,
10119 ranges,
10120 choices,
10121 });
10122 }
10123
10124 // Check whether the just-entered snippet ends with an auto-closable bracket.
10125 if self.autoclose_regions.is_empty() {
10126 let snapshot = self.buffer.read(cx).snapshot(cx);
10127 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10128 let selection_head = selection.head();
10129 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10130 continue;
10131 };
10132
10133 let mut bracket_pair = None;
10134 let max_lookup_length = scope
10135 .brackets()
10136 .map(|(pair, _)| {
10137 pair.start
10138 .as_str()
10139 .chars()
10140 .count()
10141 .max(pair.end.as_str().chars().count())
10142 })
10143 .max();
10144 if let Some(max_lookup_length) = max_lookup_length {
10145 let next_text = snapshot
10146 .chars_at(selection_head)
10147 .take(max_lookup_length)
10148 .collect::<String>();
10149 let prev_text = snapshot
10150 .reversed_chars_at(selection_head)
10151 .take(max_lookup_length)
10152 .collect::<String>();
10153
10154 for (pair, enabled) in scope.brackets() {
10155 if enabled
10156 && pair.close
10157 && prev_text.starts_with(pair.start.as_str())
10158 && next_text.starts_with(pair.end.as_str())
10159 {
10160 bracket_pair = Some(pair.clone());
10161 break;
10162 }
10163 }
10164 }
10165
10166 if let Some(pair) = bracket_pair {
10167 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10168 let autoclose_enabled =
10169 self.use_autoclose && snapshot_settings.use_autoclose;
10170 if autoclose_enabled {
10171 let start = snapshot.anchor_after(selection_head);
10172 let end = snapshot.anchor_after(selection_head);
10173 self.autoclose_regions.push(AutocloseRegion {
10174 selection_id: selection.id,
10175 range: start..end,
10176 pair,
10177 });
10178 }
10179 }
10180 }
10181 }
10182 }
10183 Ok(())
10184 }
10185
10186 pub fn move_to_next_snippet_tabstop(
10187 &mut self,
10188 window: &mut Window,
10189 cx: &mut Context<Self>,
10190 ) -> bool {
10191 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10192 }
10193
10194 pub fn move_to_prev_snippet_tabstop(
10195 &mut self,
10196 window: &mut Window,
10197 cx: &mut Context<Self>,
10198 ) -> bool {
10199 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10200 }
10201
10202 pub fn move_to_snippet_tabstop(
10203 &mut self,
10204 bias: Bias,
10205 window: &mut Window,
10206 cx: &mut Context<Self>,
10207 ) -> bool {
10208 if let Some(mut snippet) = self.snippet_stack.pop() {
10209 match bias {
10210 Bias::Left => {
10211 if snippet.active_index > 0 {
10212 snippet.active_index -= 1;
10213 } else {
10214 self.snippet_stack.push(snippet);
10215 return false;
10216 }
10217 }
10218 Bias::Right => {
10219 if snippet.active_index + 1 < snippet.ranges.len() {
10220 snippet.active_index += 1;
10221 } else {
10222 self.snippet_stack.push(snippet);
10223 return false;
10224 }
10225 }
10226 }
10227 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10228 self.change_selections(Default::default(), window, cx, |s| {
10229 // Reverse order so that the first range is the newest created selection.
10230 // Completions will use it and autoscroll will prioritize it.
10231 s.select_ranges(current_ranges.iter().rev().cloned())
10232 });
10233
10234 if let Some(choices) = &snippet.choices[snippet.active_index]
10235 && let Some(selection) = current_ranges.first()
10236 {
10237 self.show_snippet_choices(choices, selection.clone(), cx);
10238 }
10239
10240 // If snippet state is not at the last tabstop, push it back on the stack
10241 if snippet.active_index + 1 < snippet.ranges.len() {
10242 self.snippet_stack.push(snippet);
10243 }
10244 return true;
10245 }
10246 }
10247
10248 false
10249 }
10250
10251 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10252 self.transact(window, cx, |this, window, cx| {
10253 this.select_all(&SelectAll, window, cx);
10254 this.insert("", window, cx);
10255 });
10256 }
10257
10258 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10263 self.transact(window, cx, |this, window, cx| {
10264 this.select_autoclose_pair(window, cx);
10265
10266 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10267
10268 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10269 if !this.linked_edit_ranges.is_empty() {
10270 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10271 let snapshot = this.buffer.read(cx).snapshot(cx);
10272
10273 for selection in selections.iter() {
10274 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10275 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10276 if selection_start.buffer_id != selection_end.buffer_id {
10277 continue;
10278 }
10279 if let Some(ranges) =
10280 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10281 {
10282 for (buffer, entries) in ranges {
10283 linked_ranges.entry(buffer).or_default().extend(entries);
10284 }
10285 }
10286 }
10287 }
10288
10289 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10290 for selection in &mut selections {
10291 if selection.is_empty() {
10292 let old_head = selection.head();
10293 let mut new_head =
10294 movement::left(&display_map, old_head.to_display_point(&display_map))
10295 .to_point(&display_map);
10296 if let Some((buffer, line_buffer_range)) = display_map
10297 .buffer_snapshot()
10298 .buffer_line_for_row(MultiBufferRow(old_head.row))
10299 {
10300 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10301 let indent_len = match indent_size.kind {
10302 IndentKind::Space => {
10303 buffer.settings_at(line_buffer_range.start, cx).tab_size
10304 }
10305 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10306 };
10307 if old_head.column <= indent_size.len && old_head.column > 0 {
10308 let indent_len = indent_len.get();
10309 new_head = cmp::min(
10310 new_head,
10311 MultiBufferPoint::new(
10312 old_head.row,
10313 ((old_head.column - 1) / indent_len) * indent_len,
10314 ),
10315 );
10316 }
10317 }
10318
10319 selection.set_head(new_head, SelectionGoal::None);
10320 }
10321 }
10322
10323 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10324 this.insert("", window, cx);
10325 let empty_str: Arc<str> = Arc::from("");
10326 for (buffer, edits) in linked_ranges {
10327 let snapshot = buffer.read(cx).snapshot();
10328 use text::ToPoint as TP;
10329
10330 let edits = edits
10331 .into_iter()
10332 .map(|range| {
10333 let end_point = TP::to_point(&range.end, &snapshot);
10334 let mut start_point = TP::to_point(&range.start, &snapshot);
10335
10336 if end_point == start_point {
10337 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10338 .saturating_sub(1);
10339 start_point =
10340 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10341 };
10342
10343 (start_point..end_point, empty_str.clone())
10344 })
10345 .sorted_by_key(|(range, _)| range.start)
10346 .collect::<Vec<_>>();
10347 buffer.update(cx, |this, cx| {
10348 this.edit(edits, None, cx);
10349 })
10350 }
10351 this.refresh_edit_prediction(true, false, window, cx);
10352 refresh_linked_ranges(this, window, cx);
10353 });
10354 }
10355
10356 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10357 if self.read_only(cx) {
10358 return;
10359 }
10360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10361 self.transact(window, cx, |this, window, cx| {
10362 this.change_selections(Default::default(), window, cx, |s| {
10363 s.move_with(|map, selection| {
10364 if selection.is_empty() {
10365 let cursor = movement::right(map, selection.head());
10366 selection.end = cursor;
10367 selection.reversed = true;
10368 selection.goal = SelectionGoal::None;
10369 }
10370 })
10371 });
10372 this.insert("", window, cx);
10373 this.refresh_edit_prediction(true, false, window, cx);
10374 });
10375 }
10376
10377 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10378 if self.mode.is_single_line() {
10379 cx.propagate();
10380 return;
10381 }
10382
10383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10384 if self.move_to_prev_snippet_tabstop(window, cx) {
10385 return;
10386 }
10387 self.outdent(&Outdent, window, cx);
10388 }
10389
10390 pub fn next_snippet_tabstop(
10391 &mut self,
10392 _: &NextSnippetTabstop,
10393 window: &mut Window,
10394 cx: &mut Context<Self>,
10395 ) {
10396 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10397 cx.propagate();
10398 return;
10399 }
10400
10401 if self.move_to_next_snippet_tabstop(window, cx) {
10402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10403 return;
10404 }
10405 cx.propagate();
10406 }
10407
10408 pub fn previous_snippet_tabstop(
10409 &mut self,
10410 _: &PreviousSnippetTabstop,
10411 window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10415 cx.propagate();
10416 return;
10417 }
10418
10419 if self.move_to_prev_snippet_tabstop(window, cx) {
10420 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10421 return;
10422 }
10423 cx.propagate();
10424 }
10425
10426 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10427 if self.mode.is_single_line() {
10428 cx.propagate();
10429 return;
10430 }
10431
10432 if self.move_to_next_snippet_tabstop(window, cx) {
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 return;
10435 }
10436 if self.read_only(cx) {
10437 return;
10438 }
10439 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10440 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10441 let buffer = self.buffer.read(cx);
10442 let snapshot = buffer.snapshot(cx);
10443 let rows_iter = selections.iter().map(|s| s.head().row);
10444 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10445
10446 let has_some_cursor_in_whitespace = selections
10447 .iter()
10448 .filter(|selection| selection.is_empty())
10449 .any(|selection| {
10450 let cursor = selection.head();
10451 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10452 cursor.column < current_indent.len
10453 });
10454
10455 let mut edits = Vec::new();
10456 let mut prev_edited_row = 0;
10457 let mut row_delta = 0;
10458 for selection in &mut selections {
10459 if selection.start.row != prev_edited_row {
10460 row_delta = 0;
10461 }
10462 prev_edited_row = selection.end.row;
10463
10464 // If the selection is non-empty, then increase the indentation of the selected lines.
10465 if !selection.is_empty() {
10466 row_delta =
10467 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10468 continue;
10469 }
10470
10471 let cursor = selection.head();
10472 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10473 if let Some(suggested_indent) =
10474 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10475 {
10476 // Don't do anything if already at suggested indent
10477 // and there is any other cursor which is not
10478 if has_some_cursor_in_whitespace
10479 && cursor.column == current_indent.len
10480 && current_indent.len == suggested_indent.len
10481 {
10482 continue;
10483 }
10484
10485 // Adjust line and move cursor to suggested indent
10486 // if cursor is not at suggested indent
10487 if cursor.column < suggested_indent.len
10488 && cursor.column <= current_indent.len
10489 && current_indent.len <= suggested_indent.len
10490 {
10491 selection.start = Point::new(cursor.row, suggested_indent.len);
10492 selection.end = selection.start;
10493 if row_delta == 0 {
10494 edits.extend(Buffer::edit_for_indent_size_adjustment(
10495 cursor.row,
10496 current_indent,
10497 suggested_indent,
10498 ));
10499 row_delta = suggested_indent.len - current_indent.len;
10500 }
10501 continue;
10502 }
10503
10504 // If current indent is more than suggested indent
10505 // only move cursor to current indent and skip indent
10506 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10507 selection.start = Point::new(cursor.row, current_indent.len);
10508 selection.end = selection.start;
10509 continue;
10510 }
10511 }
10512
10513 // Otherwise, insert a hard or soft tab.
10514 let settings = buffer.language_settings_at(cursor, cx);
10515 let tab_size = if settings.hard_tabs {
10516 IndentSize::tab()
10517 } else {
10518 let tab_size = settings.tab_size.get();
10519 let indent_remainder = snapshot
10520 .text_for_range(Point::new(cursor.row, 0)..cursor)
10521 .flat_map(str::chars)
10522 .fold(row_delta % tab_size, |counter: u32, c| {
10523 if c == '\t' {
10524 0
10525 } else {
10526 (counter + 1) % tab_size
10527 }
10528 });
10529
10530 let chars_to_next_tab_stop = tab_size - indent_remainder;
10531 IndentSize::spaces(chars_to_next_tab_stop)
10532 };
10533 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10534 selection.end = selection.start;
10535 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10536 row_delta += tab_size.len;
10537 }
10538
10539 self.transact(window, cx, |this, window, cx| {
10540 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10541 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10542 this.refresh_edit_prediction(true, false, window, cx);
10543 });
10544 }
10545
10546 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10547 if self.read_only(cx) {
10548 return;
10549 }
10550 if self.mode.is_single_line() {
10551 cx.propagate();
10552 return;
10553 }
10554
10555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10556 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10557 let mut prev_edited_row = 0;
10558 let mut row_delta = 0;
10559 let mut edits = Vec::new();
10560 let buffer = self.buffer.read(cx);
10561 let snapshot = buffer.snapshot(cx);
10562 for selection in &mut selections {
10563 if selection.start.row != prev_edited_row {
10564 row_delta = 0;
10565 }
10566 prev_edited_row = selection.end.row;
10567
10568 row_delta =
10569 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10570 }
10571
10572 self.transact(window, cx, |this, window, cx| {
10573 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10574 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10575 });
10576 }
10577
10578 fn indent_selection(
10579 buffer: &MultiBuffer,
10580 snapshot: &MultiBufferSnapshot,
10581 selection: &mut Selection<Point>,
10582 edits: &mut Vec<(Range<Point>, String)>,
10583 delta_for_start_row: u32,
10584 cx: &App,
10585 ) -> u32 {
10586 let settings = buffer.language_settings_at(selection.start, cx);
10587 let tab_size = settings.tab_size.get();
10588 let indent_kind = if settings.hard_tabs {
10589 IndentKind::Tab
10590 } else {
10591 IndentKind::Space
10592 };
10593 let mut start_row = selection.start.row;
10594 let mut end_row = selection.end.row + 1;
10595
10596 // If a selection ends at the beginning of a line, don't indent
10597 // that last line.
10598 if selection.end.column == 0 && selection.end.row > selection.start.row {
10599 end_row -= 1;
10600 }
10601
10602 // Avoid re-indenting a row that has already been indented by a
10603 // previous selection, but still update this selection's column
10604 // to reflect that indentation.
10605 if delta_for_start_row > 0 {
10606 start_row += 1;
10607 selection.start.column += delta_for_start_row;
10608 if selection.end.row == selection.start.row {
10609 selection.end.column += delta_for_start_row;
10610 }
10611 }
10612
10613 let mut delta_for_end_row = 0;
10614 let has_multiple_rows = start_row + 1 != end_row;
10615 for row in start_row..end_row {
10616 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10617 let indent_delta = match (current_indent.kind, indent_kind) {
10618 (IndentKind::Space, IndentKind::Space) => {
10619 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10620 IndentSize::spaces(columns_to_next_tab_stop)
10621 }
10622 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10623 (_, IndentKind::Tab) => IndentSize::tab(),
10624 };
10625
10626 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10627 0
10628 } else {
10629 selection.start.column
10630 };
10631 let row_start = Point::new(row, start);
10632 edits.push((
10633 row_start..row_start,
10634 indent_delta.chars().collect::<String>(),
10635 ));
10636
10637 // Update this selection's endpoints to reflect the indentation.
10638 if row == selection.start.row {
10639 selection.start.column += indent_delta.len;
10640 }
10641 if row == selection.end.row {
10642 selection.end.column += indent_delta.len;
10643 delta_for_end_row = indent_delta.len;
10644 }
10645 }
10646
10647 if selection.start.row == selection.end.row {
10648 delta_for_start_row + delta_for_end_row
10649 } else {
10650 delta_for_end_row
10651 }
10652 }
10653
10654 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10655 if self.read_only(cx) {
10656 return;
10657 }
10658 if self.mode.is_single_line() {
10659 cx.propagate();
10660 return;
10661 }
10662
10663 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10665 let selections = self.selections.all::<Point>(&display_map);
10666 let mut deletion_ranges = Vec::new();
10667 let mut last_outdent = None;
10668 {
10669 let buffer = self.buffer.read(cx);
10670 let snapshot = buffer.snapshot(cx);
10671 for selection in &selections {
10672 let settings = buffer.language_settings_at(selection.start, cx);
10673 let tab_size = settings.tab_size.get();
10674 let mut rows = selection.spanned_rows(false, &display_map);
10675
10676 // Avoid re-outdenting a row that has already been outdented by a
10677 // previous selection.
10678 if let Some(last_row) = last_outdent
10679 && last_row == rows.start
10680 {
10681 rows.start = rows.start.next_row();
10682 }
10683 let has_multiple_rows = rows.len() > 1;
10684 for row in rows.iter_rows() {
10685 let indent_size = snapshot.indent_size_for_line(row);
10686 if indent_size.len > 0 {
10687 let deletion_len = match indent_size.kind {
10688 IndentKind::Space => {
10689 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10690 if columns_to_prev_tab_stop == 0 {
10691 tab_size
10692 } else {
10693 columns_to_prev_tab_stop
10694 }
10695 }
10696 IndentKind::Tab => 1,
10697 };
10698 let start = if has_multiple_rows
10699 || deletion_len > selection.start.column
10700 || indent_size.len < selection.start.column
10701 {
10702 0
10703 } else {
10704 selection.start.column - deletion_len
10705 };
10706 deletion_ranges.push(
10707 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10708 );
10709 last_outdent = Some(row);
10710 }
10711 }
10712 }
10713 }
10714
10715 self.transact(window, cx, |this, window, cx| {
10716 this.buffer.update(cx, |buffer, cx| {
10717 let empty_str: Arc<str> = Arc::default();
10718 buffer.edit(
10719 deletion_ranges
10720 .into_iter()
10721 .map(|range| (range, empty_str.clone())),
10722 None,
10723 cx,
10724 );
10725 });
10726 let selections = this
10727 .selections
10728 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10729 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10730 });
10731 }
10732
10733 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10734 if self.read_only(cx) {
10735 return;
10736 }
10737 if self.mode.is_single_line() {
10738 cx.propagate();
10739 return;
10740 }
10741
10742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10743 let selections = self
10744 .selections
10745 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10746 .into_iter()
10747 .map(|s| s.range());
10748
10749 self.transact(window, cx, |this, window, cx| {
10750 this.buffer.update(cx, |buffer, cx| {
10751 buffer.autoindent_ranges(selections, cx);
10752 });
10753 let selections = this
10754 .selections
10755 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10756 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10757 });
10758 }
10759
10760 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10762 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10763 let selections = self.selections.all::<Point>(&display_map);
10764
10765 let mut new_cursors = Vec::new();
10766 let mut edit_ranges = Vec::new();
10767 let mut selections = selections.iter().peekable();
10768 while let Some(selection) = selections.next() {
10769 let mut rows = selection.spanned_rows(false, &display_map);
10770
10771 // Accumulate contiguous regions of rows that we want to delete.
10772 while let Some(next_selection) = selections.peek() {
10773 let next_rows = next_selection.spanned_rows(false, &display_map);
10774 if next_rows.start <= rows.end {
10775 rows.end = next_rows.end;
10776 selections.next().unwrap();
10777 } else {
10778 break;
10779 }
10780 }
10781
10782 let buffer = display_map.buffer_snapshot();
10783 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10784 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10785 // If there's a line after the range, delete the \n from the end of the row range
10786 (
10787 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10788 rows.end,
10789 )
10790 } else {
10791 // If there isn't a line after the range, delete the \n from the line before the
10792 // start of the row range
10793 edit_start = edit_start.saturating_sub_usize(1);
10794 (buffer.len(), rows.start.previous_row())
10795 };
10796
10797 let text_layout_details = self.text_layout_details(window);
10798 let x = display_map.x_for_display_point(
10799 selection.head().to_display_point(&display_map),
10800 &text_layout_details,
10801 );
10802 let row = Point::new(target_row.0, 0)
10803 .to_display_point(&display_map)
10804 .row();
10805 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10806
10807 new_cursors.push((
10808 selection.id,
10809 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10810 SelectionGoal::None,
10811 ));
10812 edit_ranges.push(edit_start..edit_end);
10813 }
10814
10815 self.transact(window, cx, |this, window, cx| {
10816 let buffer = this.buffer.update(cx, |buffer, cx| {
10817 let empty_str: Arc<str> = Arc::default();
10818 buffer.edit(
10819 edit_ranges
10820 .into_iter()
10821 .map(|range| (range, empty_str.clone())),
10822 None,
10823 cx,
10824 );
10825 buffer.snapshot(cx)
10826 });
10827 let new_selections = new_cursors
10828 .into_iter()
10829 .map(|(id, cursor, goal)| {
10830 let cursor = cursor.to_point(&buffer);
10831 Selection {
10832 id,
10833 start: cursor,
10834 end: cursor,
10835 reversed: false,
10836 goal,
10837 }
10838 })
10839 .collect();
10840
10841 this.change_selections(Default::default(), window, cx, |s| {
10842 s.select(new_selections);
10843 });
10844 });
10845 }
10846
10847 pub fn join_lines_impl(
10848 &mut self,
10849 insert_whitespace: bool,
10850 window: &mut Window,
10851 cx: &mut Context<Self>,
10852 ) {
10853 if self.read_only(cx) {
10854 return;
10855 }
10856 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10857 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10858 let start = MultiBufferRow(selection.start.row);
10859 // Treat single line selections as if they include the next line. Otherwise this action
10860 // would do nothing for single line selections individual cursors.
10861 let end = if selection.start.row == selection.end.row {
10862 MultiBufferRow(selection.start.row + 1)
10863 } else {
10864 MultiBufferRow(selection.end.row)
10865 };
10866
10867 if let Some(last_row_range) = row_ranges.last_mut()
10868 && start <= last_row_range.end
10869 {
10870 last_row_range.end = end;
10871 continue;
10872 }
10873 row_ranges.push(start..end);
10874 }
10875
10876 let snapshot = self.buffer.read(cx).snapshot(cx);
10877 let mut cursor_positions = Vec::new();
10878 for row_range in &row_ranges {
10879 let anchor = snapshot.anchor_before(Point::new(
10880 row_range.end.previous_row().0,
10881 snapshot.line_len(row_range.end.previous_row()),
10882 ));
10883 cursor_positions.push(anchor..anchor);
10884 }
10885
10886 self.transact(window, cx, |this, window, cx| {
10887 for row_range in row_ranges.into_iter().rev() {
10888 for row in row_range.iter_rows().rev() {
10889 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10890 let next_line_row = row.next_row();
10891 let indent = snapshot.indent_size_for_line(next_line_row);
10892 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10893
10894 let replace =
10895 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10896 " "
10897 } else {
10898 ""
10899 };
10900
10901 this.buffer.update(cx, |buffer, cx| {
10902 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10903 });
10904 }
10905 }
10906
10907 this.change_selections(Default::default(), window, cx, |s| {
10908 s.select_anchor_ranges(cursor_positions)
10909 });
10910 });
10911 }
10912
10913 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10915 self.join_lines_impl(true, window, cx);
10916 }
10917
10918 pub fn sort_lines_case_sensitive(
10919 &mut self,
10920 _: &SortLinesCaseSensitive,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10925 }
10926
10927 pub fn sort_lines_by_length(
10928 &mut self,
10929 _: &SortLinesByLength,
10930 window: &mut Window,
10931 cx: &mut Context<Self>,
10932 ) {
10933 self.manipulate_immutable_lines(window, cx, |lines| {
10934 lines.sort_by_key(|&line| line.chars().count())
10935 })
10936 }
10937
10938 pub fn sort_lines_case_insensitive(
10939 &mut self,
10940 _: &SortLinesCaseInsensitive,
10941 window: &mut Window,
10942 cx: &mut Context<Self>,
10943 ) {
10944 self.manipulate_immutable_lines(window, cx, |lines| {
10945 lines.sort_by_key(|line| line.to_lowercase())
10946 })
10947 }
10948
10949 pub fn unique_lines_case_insensitive(
10950 &mut self,
10951 _: &UniqueLinesCaseInsensitive,
10952 window: &mut Window,
10953 cx: &mut Context<Self>,
10954 ) {
10955 self.manipulate_immutable_lines(window, cx, |lines| {
10956 let mut seen = HashSet::default();
10957 lines.retain(|line| seen.insert(line.to_lowercase()));
10958 })
10959 }
10960
10961 pub fn unique_lines_case_sensitive(
10962 &mut self,
10963 _: &UniqueLinesCaseSensitive,
10964 window: &mut Window,
10965 cx: &mut Context<Self>,
10966 ) {
10967 self.manipulate_immutable_lines(window, cx, |lines| {
10968 let mut seen = HashSet::default();
10969 lines.retain(|line| seen.insert(*line));
10970 })
10971 }
10972
10973 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10974 let snapshot = self.buffer.read(cx).snapshot(cx);
10975 for selection in self.selections.disjoint_anchors_arc().iter() {
10976 if snapshot
10977 .language_at(selection.start)
10978 .and_then(|lang| lang.config().wrap_characters.as_ref())
10979 .is_some()
10980 {
10981 return true;
10982 }
10983 }
10984 false
10985 }
10986
10987 fn wrap_selections_in_tag(
10988 &mut self,
10989 _: &WrapSelectionsInTag,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10994
10995 let snapshot = self.buffer.read(cx).snapshot(cx);
10996
10997 let mut edits = Vec::new();
10998 let mut boundaries = Vec::new();
10999
11000 for selection in self
11001 .selections
11002 .all_adjusted(&self.display_snapshot(cx))
11003 .iter()
11004 {
11005 let Some(wrap_config) = snapshot
11006 .language_at(selection.start)
11007 .and_then(|lang| lang.config().wrap_characters.clone())
11008 else {
11009 continue;
11010 };
11011
11012 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11013 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11014
11015 let start_before = snapshot.anchor_before(selection.start);
11016 let end_after = snapshot.anchor_after(selection.end);
11017
11018 edits.push((start_before..start_before, open_tag));
11019 edits.push((end_after..end_after, close_tag));
11020
11021 boundaries.push((
11022 start_before,
11023 end_after,
11024 wrap_config.start_prefix.len(),
11025 wrap_config.end_suffix.len(),
11026 ));
11027 }
11028
11029 if edits.is_empty() {
11030 return;
11031 }
11032
11033 self.transact(window, cx, |this, window, cx| {
11034 let buffer = this.buffer.update(cx, |buffer, cx| {
11035 buffer.edit(edits, None, cx);
11036 buffer.snapshot(cx)
11037 });
11038
11039 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11040 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11041 boundaries.into_iter()
11042 {
11043 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11044 let close_offset = end_after
11045 .to_offset(&buffer)
11046 .saturating_sub_usize(end_suffix_len);
11047 new_selections.push(open_offset..open_offset);
11048 new_selections.push(close_offset..close_offset);
11049 }
11050
11051 this.change_selections(Default::default(), window, cx, |s| {
11052 s.select_ranges(new_selections);
11053 });
11054
11055 this.request_autoscroll(Autoscroll::fit(), cx);
11056 });
11057 }
11058
11059 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11060 let Some(project) = self.project.clone() else {
11061 return;
11062 };
11063 self.reload(project, window, cx)
11064 .detach_and_notify_err(window, cx);
11065 }
11066
11067 pub fn restore_file(
11068 &mut self,
11069 _: &::git::RestoreFile,
11070 window: &mut Window,
11071 cx: &mut Context<Self>,
11072 ) {
11073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11074 let mut buffer_ids = HashSet::default();
11075 let snapshot = self.buffer().read(cx).snapshot(cx);
11076 for selection in self
11077 .selections
11078 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11079 {
11080 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11081 }
11082
11083 let buffer = self.buffer().read(cx);
11084 let ranges = buffer_ids
11085 .into_iter()
11086 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11087 .collect::<Vec<_>>();
11088
11089 self.restore_hunks_in_ranges(ranges, window, cx);
11090 }
11091
11092 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11093 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11094 let selections = self
11095 .selections
11096 .all(&self.display_snapshot(cx))
11097 .into_iter()
11098 .map(|s| s.range())
11099 .collect();
11100 self.restore_hunks_in_ranges(selections, window, cx);
11101 }
11102
11103 pub fn restore_hunks_in_ranges(
11104 &mut self,
11105 ranges: Vec<Range<Point>>,
11106 window: &mut Window,
11107 cx: &mut Context<Editor>,
11108 ) {
11109 let mut revert_changes = HashMap::default();
11110 let chunk_by = self
11111 .snapshot(window, cx)
11112 .hunks_for_ranges(ranges)
11113 .into_iter()
11114 .chunk_by(|hunk| hunk.buffer_id);
11115 for (buffer_id, hunks) in &chunk_by {
11116 let hunks = hunks.collect::<Vec<_>>();
11117 for hunk in &hunks {
11118 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11119 }
11120 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11121 }
11122 drop(chunk_by);
11123 if !revert_changes.is_empty() {
11124 self.transact(window, cx, |editor, window, cx| {
11125 editor.restore(revert_changes, window, cx);
11126 });
11127 }
11128 }
11129
11130 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11131 if let Some(status) = self
11132 .addons
11133 .iter()
11134 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11135 {
11136 return Some(status);
11137 }
11138 self.project
11139 .as_ref()?
11140 .read(cx)
11141 .status_for_buffer_id(buffer_id, cx)
11142 }
11143
11144 pub fn open_active_item_in_terminal(
11145 &mut self,
11146 _: &OpenInTerminal,
11147 window: &mut Window,
11148 cx: &mut Context<Self>,
11149 ) {
11150 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11151 let project_path = buffer.read(cx).project_path(cx)?;
11152 let project = self.project()?.read(cx);
11153 let entry = project.entry_for_path(&project_path, cx)?;
11154 let parent = match &entry.canonical_path {
11155 Some(canonical_path) => canonical_path.to_path_buf(),
11156 None => project.absolute_path(&project_path, cx)?,
11157 }
11158 .parent()?
11159 .to_path_buf();
11160 Some(parent)
11161 }) {
11162 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11163 }
11164 }
11165
11166 fn set_breakpoint_context_menu(
11167 &mut self,
11168 display_row: DisplayRow,
11169 position: Option<Anchor>,
11170 clicked_point: gpui::Point<Pixels>,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 let source = self
11175 .buffer
11176 .read(cx)
11177 .snapshot(cx)
11178 .anchor_before(Point::new(display_row.0, 0u32));
11179
11180 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11181
11182 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11183 self,
11184 source,
11185 clicked_point,
11186 context_menu,
11187 window,
11188 cx,
11189 );
11190 }
11191
11192 fn add_edit_breakpoint_block(
11193 &mut self,
11194 anchor: Anchor,
11195 breakpoint: &Breakpoint,
11196 edit_action: BreakpointPromptEditAction,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 let weak_editor = cx.weak_entity();
11201 let bp_prompt = cx.new(|cx| {
11202 BreakpointPromptEditor::new(
11203 weak_editor,
11204 anchor,
11205 breakpoint.clone(),
11206 edit_action,
11207 window,
11208 cx,
11209 )
11210 });
11211
11212 let height = bp_prompt.update(cx, |this, cx| {
11213 this.prompt
11214 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11215 });
11216 let cloned_prompt = bp_prompt.clone();
11217 let blocks = vec![BlockProperties {
11218 style: BlockStyle::Sticky,
11219 placement: BlockPlacement::Above(anchor),
11220 height: Some(height),
11221 render: Arc::new(move |cx| {
11222 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11223 cloned_prompt.clone().into_any_element()
11224 }),
11225 priority: 0,
11226 }];
11227
11228 let focus_handle = bp_prompt.focus_handle(cx);
11229 window.focus(&focus_handle);
11230
11231 let block_ids = self.insert_blocks(blocks, None, cx);
11232 bp_prompt.update(cx, |prompt, _| {
11233 prompt.add_block_ids(block_ids);
11234 });
11235 }
11236
11237 pub(crate) fn breakpoint_at_row(
11238 &self,
11239 row: u32,
11240 window: &mut Window,
11241 cx: &mut Context<Self>,
11242 ) -> Option<(Anchor, Breakpoint)> {
11243 let snapshot = self.snapshot(window, cx);
11244 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11245
11246 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11247 }
11248
11249 pub(crate) fn breakpoint_at_anchor(
11250 &self,
11251 breakpoint_position: Anchor,
11252 snapshot: &EditorSnapshot,
11253 cx: &mut Context<Self>,
11254 ) -> Option<(Anchor, Breakpoint)> {
11255 let buffer = self
11256 .buffer
11257 .read(cx)
11258 .buffer_for_anchor(breakpoint_position, cx)?;
11259
11260 let enclosing_excerpt = breakpoint_position.excerpt_id;
11261 let buffer_snapshot = buffer.read(cx).snapshot();
11262
11263 let row = buffer_snapshot
11264 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11265 .row;
11266
11267 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11268 let anchor_end = snapshot
11269 .buffer_snapshot()
11270 .anchor_after(Point::new(row, line_len));
11271
11272 self.breakpoint_store
11273 .as_ref()?
11274 .read_with(cx, |breakpoint_store, cx| {
11275 breakpoint_store
11276 .breakpoints(
11277 &buffer,
11278 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11279 &buffer_snapshot,
11280 cx,
11281 )
11282 .next()
11283 .and_then(|(bp, _)| {
11284 let breakpoint_row = buffer_snapshot
11285 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11286 .row;
11287
11288 if breakpoint_row == row {
11289 snapshot
11290 .buffer_snapshot()
11291 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11292 .map(|position| (position, bp.bp.clone()))
11293 } else {
11294 None
11295 }
11296 })
11297 })
11298 }
11299
11300 pub fn edit_log_breakpoint(
11301 &mut self,
11302 _: &EditLogBreakpoint,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 if self.breakpoint_store.is_none() {
11307 return;
11308 }
11309
11310 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11311 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11312 message: None,
11313 state: BreakpointState::Enabled,
11314 condition: None,
11315 hit_condition: None,
11316 });
11317
11318 self.add_edit_breakpoint_block(
11319 anchor,
11320 &breakpoint,
11321 BreakpointPromptEditAction::Log,
11322 window,
11323 cx,
11324 );
11325 }
11326 }
11327
11328 fn breakpoints_at_cursors(
11329 &self,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11333 let snapshot = self.snapshot(window, cx);
11334 let cursors = self
11335 .selections
11336 .disjoint_anchors_arc()
11337 .iter()
11338 .map(|selection| {
11339 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11340
11341 let breakpoint_position = self
11342 .breakpoint_at_row(cursor_position.row, window, cx)
11343 .map(|bp| bp.0)
11344 .unwrap_or_else(|| {
11345 snapshot
11346 .display_snapshot
11347 .buffer_snapshot()
11348 .anchor_after(Point::new(cursor_position.row, 0))
11349 });
11350
11351 let breakpoint = self
11352 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11353 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11354
11355 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11356 })
11357 // 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.
11358 .collect::<HashMap<Anchor, _>>();
11359
11360 cursors.into_iter().collect()
11361 }
11362
11363 pub fn enable_breakpoint(
11364 &mut self,
11365 _: &crate::actions::EnableBreakpoint,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 if self.breakpoint_store.is_none() {
11370 return;
11371 }
11372
11373 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11374 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11375 continue;
11376 };
11377 self.edit_breakpoint_at_anchor(
11378 anchor,
11379 breakpoint,
11380 BreakpointEditAction::InvertState,
11381 cx,
11382 );
11383 }
11384 }
11385
11386 pub fn disable_breakpoint(
11387 &mut self,
11388 _: &crate::actions::DisableBreakpoint,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) {
11392 if self.breakpoint_store.is_none() {
11393 return;
11394 }
11395
11396 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11397 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11398 continue;
11399 };
11400 self.edit_breakpoint_at_anchor(
11401 anchor,
11402 breakpoint,
11403 BreakpointEditAction::InvertState,
11404 cx,
11405 );
11406 }
11407 }
11408
11409 pub fn toggle_breakpoint(
11410 &mut self,
11411 _: &crate::actions::ToggleBreakpoint,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 if self.breakpoint_store.is_none() {
11416 return;
11417 }
11418
11419 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11420 if let Some(breakpoint) = breakpoint {
11421 self.edit_breakpoint_at_anchor(
11422 anchor,
11423 breakpoint,
11424 BreakpointEditAction::Toggle,
11425 cx,
11426 );
11427 } else {
11428 self.edit_breakpoint_at_anchor(
11429 anchor,
11430 Breakpoint::new_standard(),
11431 BreakpointEditAction::Toggle,
11432 cx,
11433 );
11434 }
11435 }
11436 }
11437
11438 pub fn edit_breakpoint_at_anchor(
11439 &mut self,
11440 breakpoint_position: Anchor,
11441 breakpoint: Breakpoint,
11442 edit_action: BreakpointEditAction,
11443 cx: &mut Context<Self>,
11444 ) {
11445 let Some(breakpoint_store) = &self.breakpoint_store else {
11446 return;
11447 };
11448
11449 let Some(buffer) = self
11450 .buffer
11451 .read(cx)
11452 .buffer_for_anchor(breakpoint_position, cx)
11453 else {
11454 return;
11455 };
11456
11457 breakpoint_store.update(cx, |breakpoint_store, cx| {
11458 breakpoint_store.toggle_breakpoint(
11459 buffer,
11460 BreakpointWithPosition {
11461 position: breakpoint_position.text_anchor,
11462 bp: breakpoint,
11463 },
11464 edit_action,
11465 cx,
11466 );
11467 });
11468
11469 cx.notify();
11470 }
11471
11472 #[cfg(any(test, feature = "test-support"))]
11473 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11474 self.breakpoint_store.clone()
11475 }
11476
11477 pub fn prepare_restore_change(
11478 &self,
11479 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11480 hunk: &MultiBufferDiffHunk,
11481 cx: &mut App,
11482 ) -> Option<()> {
11483 if hunk.is_created_file() {
11484 return None;
11485 }
11486 let buffer = self.buffer.read(cx);
11487 let diff = buffer.diff_for(hunk.buffer_id)?;
11488 let buffer = buffer.buffer(hunk.buffer_id)?;
11489 let buffer = buffer.read(cx);
11490 let original_text = diff
11491 .read(cx)
11492 .base_text()
11493 .as_rope()
11494 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11495 let buffer_snapshot = buffer.snapshot();
11496 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11497 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11498 probe
11499 .0
11500 .start
11501 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11502 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11503 }) {
11504 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11505 Some(())
11506 } else {
11507 None
11508 }
11509 }
11510
11511 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11512 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11513 }
11514
11515 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11516 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11517 }
11518
11519 fn manipulate_lines<M>(
11520 &mut self,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 mut manipulate: M,
11524 ) where
11525 M: FnMut(&str) -> LineManipulationResult,
11526 {
11527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11528
11529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11530 let buffer = self.buffer.read(cx).snapshot(cx);
11531
11532 let mut edits = Vec::new();
11533
11534 let selections = self.selections.all::<Point>(&display_map);
11535 let mut selections = selections.iter().peekable();
11536 let mut contiguous_row_selections = Vec::new();
11537 let mut new_selections = Vec::new();
11538 let mut added_lines = 0;
11539 let mut removed_lines = 0;
11540
11541 while let Some(selection) = selections.next() {
11542 let (start_row, end_row) = consume_contiguous_rows(
11543 &mut contiguous_row_selections,
11544 selection,
11545 &display_map,
11546 &mut selections,
11547 );
11548
11549 let start_point = Point::new(start_row.0, 0);
11550 let end_point = Point::new(
11551 end_row.previous_row().0,
11552 buffer.line_len(end_row.previous_row()),
11553 );
11554 let text = buffer
11555 .text_for_range(start_point..end_point)
11556 .collect::<String>();
11557
11558 let LineManipulationResult {
11559 new_text,
11560 line_count_before,
11561 line_count_after,
11562 } = manipulate(&text);
11563
11564 edits.push((start_point..end_point, new_text));
11565
11566 // Selections must change based on added and removed line count
11567 let start_row =
11568 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11569 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11570 new_selections.push(Selection {
11571 id: selection.id,
11572 start: start_row,
11573 end: end_row,
11574 goal: SelectionGoal::None,
11575 reversed: selection.reversed,
11576 });
11577
11578 if line_count_after > line_count_before {
11579 added_lines += line_count_after - line_count_before;
11580 } else if line_count_before > line_count_after {
11581 removed_lines += line_count_before - line_count_after;
11582 }
11583 }
11584
11585 self.transact(window, cx, |this, window, cx| {
11586 let buffer = this.buffer.update(cx, |buffer, cx| {
11587 buffer.edit(edits, None, cx);
11588 buffer.snapshot(cx)
11589 });
11590
11591 // Recalculate offsets on newly edited buffer
11592 let new_selections = new_selections
11593 .iter()
11594 .map(|s| {
11595 let start_point = Point::new(s.start.0, 0);
11596 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11597 Selection {
11598 id: s.id,
11599 start: buffer.point_to_offset(start_point),
11600 end: buffer.point_to_offset(end_point),
11601 goal: s.goal,
11602 reversed: s.reversed,
11603 }
11604 })
11605 .collect();
11606
11607 this.change_selections(Default::default(), window, cx, |s| {
11608 s.select(new_selections);
11609 });
11610
11611 this.request_autoscroll(Autoscroll::fit(), cx);
11612 });
11613 }
11614
11615 fn manipulate_immutable_lines<Fn>(
11616 &mut self,
11617 window: &mut Window,
11618 cx: &mut Context<Self>,
11619 mut callback: Fn,
11620 ) where
11621 Fn: FnMut(&mut Vec<&str>),
11622 {
11623 self.manipulate_lines(window, cx, |text| {
11624 let mut lines: Vec<&str> = text.split('\n').collect();
11625 let line_count_before = lines.len();
11626
11627 callback(&mut lines);
11628
11629 LineManipulationResult {
11630 new_text: lines.join("\n"),
11631 line_count_before,
11632 line_count_after: lines.len(),
11633 }
11634 });
11635 }
11636
11637 fn manipulate_mutable_lines<Fn>(
11638 &mut self,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 mut callback: Fn,
11642 ) where
11643 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11644 {
11645 self.manipulate_lines(window, cx, |text| {
11646 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11647 let line_count_before = lines.len();
11648
11649 callback(&mut lines);
11650
11651 LineManipulationResult {
11652 new_text: lines.join("\n"),
11653 line_count_before,
11654 line_count_after: lines.len(),
11655 }
11656 });
11657 }
11658
11659 pub fn convert_indentation_to_spaces(
11660 &mut self,
11661 _: &ConvertIndentationToSpaces,
11662 window: &mut Window,
11663 cx: &mut Context<Self>,
11664 ) {
11665 let settings = self.buffer.read(cx).language_settings(cx);
11666 let tab_size = settings.tab_size.get() as usize;
11667
11668 self.manipulate_mutable_lines(window, cx, |lines| {
11669 // Allocates a reasonably sized scratch buffer once for the whole loop
11670 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11671 // Avoids recomputing spaces that could be inserted many times
11672 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11673 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11674 .collect();
11675
11676 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11677 let mut chars = line.as_ref().chars();
11678 let mut col = 0;
11679 let mut changed = false;
11680
11681 for ch in chars.by_ref() {
11682 match ch {
11683 ' ' => {
11684 reindented_line.push(' ');
11685 col += 1;
11686 }
11687 '\t' => {
11688 // \t are converted to spaces depending on the current column
11689 let spaces_len = tab_size - (col % tab_size);
11690 reindented_line.extend(&space_cache[spaces_len - 1]);
11691 col += spaces_len;
11692 changed = true;
11693 }
11694 _ => {
11695 // If we dont append before break, the character is consumed
11696 reindented_line.push(ch);
11697 break;
11698 }
11699 }
11700 }
11701
11702 if !changed {
11703 reindented_line.clear();
11704 continue;
11705 }
11706 // Append the rest of the line and replace old reference with new one
11707 reindented_line.extend(chars);
11708 *line = Cow::Owned(reindented_line.clone());
11709 reindented_line.clear();
11710 }
11711 });
11712 }
11713
11714 pub fn convert_indentation_to_tabs(
11715 &mut self,
11716 _: &ConvertIndentationToTabs,
11717 window: &mut Window,
11718 cx: &mut Context<Self>,
11719 ) {
11720 let settings = self.buffer.read(cx).language_settings(cx);
11721 let tab_size = settings.tab_size.get() as usize;
11722
11723 self.manipulate_mutable_lines(window, cx, |lines| {
11724 // Allocates a reasonably sized buffer once for the whole loop
11725 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11726 // Avoids recomputing spaces that could be inserted many times
11727 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11728 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11729 .collect();
11730
11731 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11732 let mut chars = line.chars();
11733 let mut spaces_count = 0;
11734 let mut first_non_indent_char = None;
11735 let mut changed = false;
11736
11737 for ch in chars.by_ref() {
11738 match ch {
11739 ' ' => {
11740 // Keep track of spaces. Append \t when we reach tab_size
11741 spaces_count += 1;
11742 changed = true;
11743 if spaces_count == tab_size {
11744 reindented_line.push('\t');
11745 spaces_count = 0;
11746 }
11747 }
11748 '\t' => {
11749 reindented_line.push('\t');
11750 spaces_count = 0;
11751 }
11752 _ => {
11753 // Dont append it yet, we might have remaining spaces
11754 first_non_indent_char = Some(ch);
11755 break;
11756 }
11757 }
11758 }
11759
11760 if !changed {
11761 reindented_line.clear();
11762 continue;
11763 }
11764 // Remaining spaces that didn't make a full tab stop
11765 if spaces_count > 0 {
11766 reindented_line.extend(&space_cache[spaces_count - 1]);
11767 }
11768 // If we consume an extra character that was not indentation, add it back
11769 if let Some(extra_char) = first_non_indent_char {
11770 reindented_line.push(extra_char);
11771 }
11772 // Append the rest of the line and replace old reference with new one
11773 reindented_line.extend(chars);
11774 *line = Cow::Owned(reindented_line.clone());
11775 reindented_line.clear();
11776 }
11777 });
11778 }
11779
11780 pub fn convert_to_upper_case(
11781 &mut self,
11782 _: &ConvertToUpperCase,
11783 window: &mut Window,
11784 cx: &mut Context<Self>,
11785 ) {
11786 self.manipulate_text(window, cx, |text| text.to_uppercase())
11787 }
11788
11789 pub fn convert_to_lower_case(
11790 &mut self,
11791 _: &ConvertToLowerCase,
11792 window: &mut Window,
11793 cx: &mut Context<Self>,
11794 ) {
11795 self.manipulate_text(window, cx, |text| text.to_lowercase())
11796 }
11797
11798 pub fn convert_to_title_case(
11799 &mut self,
11800 _: &ConvertToTitleCase,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) {
11804 self.manipulate_text(window, cx, |text| {
11805 text.split('\n')
11806 .map(|line| line.to_case(Case::Title))
11807 .join("\n")
11808 })
11809 }
11810
11811 pub fn convert_to_snake_case(
11812 &mut self,
11813 _: &ConvertToSnakeCase,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11818 }
11819
11820 pub fn convert_to_kebab_case(
11821 &mut self,
11822 _: &ConvertToKebabCase,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11827 }
11828
11829 pub fn convert_to_upper_camel_case(
11830 &mut self,
11831 _: &ConvertToUpperCamelCase,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 ) {
11835 self.manipulate_text(window, cx, |text| {
11836 text.split('\n')
11837 .map(|line| line.to_case(Case::UpperCamel))
11838 .join("\n")
11839 })
11840 }
11841
11842 pub fn convert_to_lower_camel_case(
11843 &mut self,
11844 _: &ConvertToLowerCamelCase,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11849 }
11850
11851 pub fn convert_to_opposite_case(
11852 &mut self,
11853 _: &ConvertToOppositeCase,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.manipulate_text(window, cx, |text| {
11858 text.chars()
11859 .fold(String::with_capacity(text.len()), |mut t, c| {
11860 if c.is_uppercase() {
11861 t.extend(c.to_lowercase());
11862 } else {
11863 t.extend(c.to_uppercase());
11864 }
11865 t
11866 })
11867 })
11868 }
11869
11870 pub fn convert_to_sentence_case(
11871 &mut self,
11872 _: &ConvertToSentenceCase,
11873 window: &mut Window,
11874 cx: &mut Context<Self>,
11875 ) {
11876 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11877 }
11878
11879 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11880 self.manipulate_text(window, cx, |text| {
11881 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11882 if has_upper_case_characters {
11883 text.to_lowercase()
11884 } else {
11885 text.to_uppercase()
11886 }
11887 })
11888 }
11889
11890 pub fn convert_to_rot13(
11891 &mut self,
11892 _: &ConvertToRot13,
11893 window: &mut Window,
11894 cx: &mut Context<Self>,
11895 ) {
11896 self.manipulate_text(window, cx, |text| {
11897 text.chars()
11898 .map(|c| match c {
11899 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11900 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11901 _ => c,
11902 })
11903 .collect()
11904 })
11905 }
11906
11907 pub fn convert_to_rot47(
11908 &mut self,
11909 _: &ConvertToRot47,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 self.manipulate_text(window, cx, |text| {
11914 text.chars()
11915 .map(|c| {
11916 let code_point = c as u32;
11917 if code_point >= 33 && code_point <= 126 {
11918 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11919 }
11920 c
11921 })
11922 .collect()
11923 })
11924 }
11925
11926 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11927 where
11928 Fn: FnMut(&str) -> String,
11929 {
11930 let buffer = self.buffer.read(cx).snapshot(cx);
11931
11932 let mut new_selections = Vec::new();
11933 let mut edits = Vec::new();
11934 let mut selection_adjustment = 0isize;
11935
11936 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11937 let selection_is_empty = selection.is_empty();
11938
11939 let (start, end) = if selection_is_empty {
11940 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11941 (word_range.start, word_range.end)
11942 } else {
11943 (
11944 buffer.point_to_offset(selection.start),
11945 buffer.point_to_offset(selection.end),
11946 )
11947 };
11948
11949 let text = buffer.text_for_range(start..end).collect::<String>();
11950 let old_length = text.len() as isize;
11951 let text = callback(&text);
11952
11953 new_selections.push(Selection {
11954 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11955 end: MultiBufferOffset(
11956 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11957 ),
11958 goal: SelectionGoal::None,
11959 id: selection.id,
11960 reversed: selection.reversed,
11961 });
11962
11963 selection_adjustment += old_length - text.len() as isize;
11964
11965 edits.push((start..end, text));
11966 }
11967
11968 self.transact(window, cx, |this, window, cx| {
11969 this.buffer.update(cx, |buffer, cx| {
11970 buffer.edit(edits, None, cx);
11971 });
11972
11973 this.change_selections(Default::default(), window, cx, |s| {
11974 s.select(new_selections);
11975 });
11976
11977 this.request_autoscroll(Autoscroll::fit(), cx);
11978 });
11979 }
11980
11981 pub fn move_selection_on_drop(
11982 &mut self,
11983 selection: &Selection<Anchor>,
11984 target: DisplayPoint,
11985 is_cut: bool,
11986 window: &mut Window,
11987 cx: &mut Context<Self>,
11988 ) {
11989 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11990 let buffer = display_map.buffer_snapshot();
11991 let mut edits = Vec::new();
11992 let insert_point = display_map
11993 .clip_point(target, Bias::Left)
11994 .to_point(&display_map);
11995 let text = buffer
11996 .text_for_range(selection.start..selection.end)
11997 .collect::<String>();
11998 if is_cut {
11999 edits.push(((selection.start..selection.end), String::new()));
12000 }
12001 let insert_anchor = buffer.anchor_before(insert_point);
12002 edits.push(((insert_anchor..insert_anchor), text));
12003 let last_edit_start = insert_anchor.bias_left(buffer);
12004 let last_edit_end = insert_anchor.bias_right(buffer);
12005 self.transact(window, cx, |this, window, cx| {
12006 this.buffer.update(cx, |buffer, cx| {
12007 buffer.edit(edits, None, cx);
12008 });
12009 this.change_selections(Default::default(), window, cx, |s| {
12010 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12011 });
12012 });
12013 }
12014
12015 pub fn clear_selection_drag_state(&mut self) {
12016 self.selection_drag_state = SelectionDragState::None;
12017 }
12018
12019 pub fn duplicate(
12020 &mut self,
12021 upwards: bool,
12022 whole_lines: bool,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12027
12028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12029 let buffer = display_map.buffer_snapshot();
12030 let selections = self.selections.all::<Point>(&display_map);
12031
12032 let mut edits = Vec::new();
12033 let mut selections_iter = selections.iter().peekable();
12034 while let Some(selection) = selections_iter.next() {
12035 let mut rows = selection.spanned_rows(false, &display_map);
12036 // duplicate line-wise
12037 if whole_lines || selection.start == selection.end {
12038 // Avoid duplicating the same lines twice.
12039 while let Some(next_selection) = selections_iter.peek() {
12040 let next_rows = next_selection.spanned_rows(false, &display_map);
12041 if next_rows.start < rows.end {
12042 rows.end = next_rows.end;
12043 selections_iter.next().unwrap();
12044 } else {
12045 break;
12046 }
12047 }
12048
12049 // Copy the text from the selected row region and splice it either at the start
12050 // or end of the region.
12051 let start = Point::new(rows.start.0, 0);
12052 let end = Point::new(
12053 rows.end.previous_row().0,
12054 buffer.line_len(rows.end.previous_row()),
12055 );
12056
12057 let mut text = buffer.text_for_range(start..end).collect::<String>();
12058
12059 let insert_location = if upwards {
12060 // When duplicating upward, we need to insert before the current line.
12061 // If we're on the last line and it doesn't end with a newline,
12062 // we need to add a newline before the duplicated content.
12063 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12064 && buffer.max_point().column > 0
12065 && !text.ends_with('\n');
12066
12067 if needs_leading_newline {
12068 text.insert(0, '\n');
12069 end
12070 } else {
12071 text.push('\n');
12072 Point::new(rows.start.0, 0)
12073 }
12074 } else {
12075 text.push('\n');
12076 start
12077 };
12078 edits.push((insert_location..insert_location, text));
12079 } else {
12080 // duplicate character-wise
12081 let start = selection.start;
12082 let end = selection.end;
12083 let text = buffer.text_for_range(start..end).collect::<String>();
12084 edits.push((selection.end..selection.end, text));
12085 }
12086 }
12087
12088 self.transact(window, cx, |this, window, cx| {
12089 this.buffer.update(cx, |buffer, cx| {
12090 buffer.edit(edits, None, cx);
12091 });
12092
12093 // When duplicating upward with whole lines, move the cursor to the duplicated line
12094 if upwards && whole_lines {
12095 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12096
12097 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12098 let mut new_ranges = Vec::new();
12099 let selections = s.all::<Point>(&display_map);
12100 let mut selections_iter = selections.iter().peekable();
12101
12102 while let Some(first_selection) = selections_iter.next() {
12103 // Group contiguous selections together to find the total row span
12104 let mut group_selections = vec![first_selection];
12105 let mut rows = first_selection.spanned_rows(false, &display_map);
12106
12107 while let Some(next_selection) = selections_iter.peek() {
12108 let next_rows = next_selection.spanned_rows(false, &display_map);
12109 if next_rows.start < rows.end {
12110 rows.end = next_rows.end;
12111 group_selections.push(selections_iter.next().unwrap());
12112 } else {
12113 break;
12114 }
12115 }
12116
12117 let row_count = rows.end.0 - rows.start.0;
12118
12119 // Move all selections in this group up by the total number of duplicated rows
12120 for selection in group_selections {
12121 let new_start = Point::new(
12122 selection.start.row.saturating_sub(row_count),
12123 selection.start.column,
12124 );
12125
12126 let new_end = Point::new(
12127 selection.end.row.saturating_sub(row_count),
12128 selection.end.column,
12129 );
12130
12131 new_ranges.push(new_start..new_end);
12132 }
12133 }
12134
12135 s.select_ranges(new_ranges);
12136 });
12137 }
12138
12139 this.request_autoscroll(Autoscroll::fit(), cx);
12140 });
12141 }
12142
12143 pub fn duplicate_line_up(
12144 &mut self,
12145 _: &DuplicateLineUp,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 self.duplicate(true, true, window, cx);
12150 }
12151
12152 pub fn duplicate_line_down(
12153 &mut self,
12154 _: &DuplicateLineDown,
12155 window: &mut Window,
12156 cx: &mut Context<Self>,
12157 ) {
12158 self.duplicate(false, true, window, cx);
12159 }
12160
12161 pub fn duplicate_selection(
12162 &mut self,
12163 _: &DuplicateSelection,
12164 window: &mut Window,
12165 cx: &mut Context<Self>,
12166 ) {
12167 self.duplicate(false, false, window, cx);
12168 }
12169
12170 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12172 if self.mode.is_single_line() {
12173 cx.propagate();
12174 return;
12175 }
12176
12177 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12178 let buffer = self.buffer.read(cx).snapshot(cx);
12179
12180 let mut edits = Vec::new();
12181 let mut unfold_ranges = Vec::new();
12182 let mut refold_creases = Vec::new();
12183
12184 let selections = self.selections.all::<Point>(&display_map);
12185 let mut selections = selections.iter().peekable();
12186 let mut contiguous_row_selections = Vec::new();
12187 let mut new_selections = Vec::new();
12188
12189 while let Some(selection) = selections.next() {
12190 // Find all the selections that span a contiguous row range
12191 let (start_row, end_row) = consume_contiguous_rows(
12192 &mut contiguous_row_selections,
12193 selection,
12194 &display_map,
12195 &mut selections,
12196 );
12197
12198 // Move the text spanned by the row range to be before the line preceding the row range
12199 if start_row.0 > 0 {
12200 let range_to_move = Point::new(
12201 start_row.previous_row().0,
12202 buffer.line_len(start_row.previous_row()),
12203 )
12204 ..Point::new(
12205 end_row.previous_row().0,
12206 buffer.line_len(end_row.previous_row()),
12207 );
12208 let insertion_point = display_map
12209 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12210 .0;
12211
12212 // Don't move lines across excerpts
12213 if buffer
12214 .excerpt_containing(insertion_point..range_to_move.end)
12215 .is_some()
12216 {
12217 let text = buffer
12218 .text_for_range(range_to_move.clone())
12219 .flat_map(|s| s.chars())
12220 .skip(1)
12221 .chain(['\n'])
12222 .collect::<String>();
12223
12224 edits.push((
12225 buffer.anchor_after(range_to_move.start)
12226 ..buffer.anchor_before(range_to_move.end),
12227 String::new(),
12228 ));
12229 let insertion_anchor = buffer.anchor_after(insertion_point);
12230 edits.push((insertion_anchor..insertion_anchor, text));
12231
12232 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12233
12234 // Move selections up
12235 new_selections.extend(contiguous_row_selections.drain(..).map(
12236 |mut selection| {
12237 selection.start.row -= row_delta;
12238 selection.end.row -= row_delta;
12239 selection
12240 },
12241 ));
12242
12243 // Move folds up
12244 unfold_ranges.push(range_to_move.clone());
12245 for fold in display_map.folds_in_range(
12246 buffer.anchor_before(range_to_move.start)
12247 ..buffer.anchor_after(range_to_move.end),
12248 ) {
12249 let mut start = fold.range.start.to_point(&buffer);
12250 let mut end = fold.range.end.to_point(&buffer);
12251 start.row -= row_delta;
12252 end.row -= row_delta;
12253 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12254 }
12255 }
12256 }
12257
12258 // If we didn't move line(s), preserve the existing selections
12259 new_selections.append(&mut contiguous_row_selections);
12260 }
12261
12262 self.transact(window, cx, |this, window, cx| {
12263 this.unfold_ranges(&unfold_ranges, true, true, cx);
12264 this.buffer.update(cx, |buffer, cx| {
12265 for (range, text) in edits {
12266 buffer.edit([(range, text)], None, cx);
12267 }
12268 });
12269 this.fold_creases(refold_creases, true, window, cx);
12270 this.change_selections(Default::default(), window, cx, |s| {
12271 s.select(new_selections);
12272 })
12273 });
12274 }
12275
12276 pub fn move_line_down(
12277 &mut self,
12278 _: &MoveLineDown,
12279 window: &mut Window,
12280 cx: &mut Context<Self>,
12281 ) {
12282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12283 if self.mode.is_single_line() {
12284 cx.propagate();
12285 return;
12286 }
12287
12288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12289 let buffer = self.buffer.read(cx).snapshot(cx);
12290
12291 let mut edits = Vec::new();
12292 let mut unfold_ranges = Vec::new();
12293 let mut refold_creases = Vec::new();
12294
12295 let selections = self.selections.all::<Point>(&display_map);
12296 let mut selections = selections.iter().peekable();
12297 let mut contiguous_row_selections = Vec::new();
12298 let mut new_selections = Vec::new();
12299
12300 while let Some(selection) = selections.next() {
12301 // Find all the selections that span a contiguous row range
12302 let (start_row, end_row) = consume_contiguous_rows(
12303 &mut contiguous_row_selections,
12304 selection,
12305 &display_map,
12306 &mut selections,
12307 );
12308
12309 // Move the text spanned by the row range to be after the last line of the row range
12310 if end_row.0 <= buffer.max_point().row {
12311 let range_to_move =
12312 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12313 let insertion_point = display_map
12314 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12315 .0;
12316
12317 // Don't move lines across excerpt boundaries
12318 if buffer
12319 .excerpt_containing(range_to_move.start..insertion_point)
12320 .is_some()
12321 {
12322 let mut text = String::from("\n");
12323 text.extend(buffer.text_for_range(range_to_move.clone()));
12324 text.pop(); // Drop trailing newline
12325 edits.push((
12326 buffer.anchor_after(range_to_move.start)
12327 ..buffer.anchor_before(range_to_move.end),
12328 String::new(),
12329 ));
12330 let insertion_anchor = buffer.anchor_after(insertion_point);
12331 edits.push((insertion_anchor..insertion_anchor, text));
12332
12333 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12334
12335 // Move selections down
12336 new_selections.extend(contiguous_row_selections.drain(..).map(
12337 |mut selection| {
12338 selection.start.row += row_delta;
12339 selection.end.row += row_delta;
12340 selection
12341 },
12342 ));
12343
12344 // Move folds down
12345 unfold_ranges.push(range_to_move.clone());
12346 for fold in display_map.folds_in_range(
12347 buffer.anchor_before(range_to_move.start)
12348 ..buffer.anchor_after(range_to_move.end),
12349 ) {
12350 let mut start = fold.range.start.to_point(&buffer);
12351 let mut end = fold.range.end.to_point(&buffer);
12352 start.row += row_delta;
12353 end.row += row_delta;
12354 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12355 }
12356 }
12357 }
12358
12359 // If we didn't move line(s), preserve the existing selections
12360 new_selections.append(&mut contiguous_row_selections);
12361 }
12362
12363 self.transact(window, cx, |this, window, cx| {
12364 this.unfold_ranges(&unfold_ranges, true, true, cx);
12365 this.buffer.update(cx, |buffer, cx| {
12366 for (range, text) in edits {
12367 buffer.edit([(range, text)], None, cx);
12368 }
12369 });
12370 this.fold_creases(refold_creases, true, window, cx);
12371 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12372 });
12373 }
12374
12375 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12377 let text_layout_details = &self.text_layout_details(window);
12378 self.transact(window, cx, |this, window, cx| {
12379 let edits = this.change_selections(Default::default(), window, cx, |s| {
12380 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12381 s.move_with(|display_map, selection| {
12382 if !selection.is_empty() {
12383 return;
12384 }
12385
12386 let mut head = selection.head();
12387 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12388 if head.column() == display_map.line_len(head.row()) {
12389 transpose_offset = display_map
12390 .buffer_snapshot()
12391 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12392 }
12393
12394 if transpose_offset == MultiBufferOffset(0) {
12395 return;
12396 }
12397
12398 *head.column_mut() += 1;
12399 head = display_map.clip_point(head, Bias::Right);
12400 let goal = SelectionGoal::HorizontalPosition(
12401 display_map
12402 .x_for_display_point(head, text_layout_details)
12403 .into(),
12404 );
12405 selection.collapse_to(head, goal);
12406
12407 let transpose_start = display_map
12408 .buffer_snapshot()
12409 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12410 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12411 let transpose_end = display_map
12412 .buffer_snapshot()
12413 .clip_offset(transpose_offset + 1usize, Bias::Right);
12414 if let Some(ch) = display_map
12415 .buffer_snapshot()
12416 .chars_at(transpose_start)
12417 .next()
12418 {
12419 edits.push((transpose_start..transpose_offset, String::new()));
12420 edits.push((transpose_end..transpose_end, ch.to_string()));
12421 }
12422 }
12423 });
12424 edits
12425 });
12426 this.buffer
12427 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12428 let selections = this
12429 .selections
12430 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12431 this.change_selections(Default::default(), window, cx, |s| {
12432 s.select(selections);
12433 });
12434 });
12435 }
12436
12437 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12439 if self.mode.is_single_line() {
12440 cx.propagate();
12441 return;
12442 }
12443
12444 self.rewrap_impl(RewrapOptions::default(), cx)
12445 }
12446
12447 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12448 let buffer = self.buffer.read(cx).snapshot(cx);
12449 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12450
12451 #[derive(Clone, Debug, PartialEq)]
12452 enum CommentFormat {
12453 /// single line comment, with prefix for line
12454 Line(String),
12455 /// single line within a block comment, with prefix for line
12456 BlockLine(String),
12457 /// a single line of a block comment that includes the initial delimiter
12458 BlockCommentWithStart(BlockCommentConfig),
12459 /// a single line of a block comment that includes the ending delimiter
12460 BlockCommentWithEnd(BlockCommentConfig),
12461 }
12462
12463 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12464 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12465 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12466 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12467 .peekable();
12468
12469 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12470 row
12471 } else {
12472 return Vec::new();
12473 };
12474
12475 let language_settings = buffer.language_settings_at(selection.head(), cx);
12476 let language_scope = buffer.language_scope_at(selection.head());
12477
12478 let indent_and_prefix_for_row =
12479 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12480 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12481 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12482 &language_scope
12483 {
12484 let indent_end = Point::new(row, indent.len);
12485 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12486 let line_text_after_indent = buffer
12487 .text_for_range(indent_end..line_end)
12488 .collect::<String>();
12489
12490 let is_within_comment_override = buffer
12491 .language_scope_at(indent_end)
12492 .is_some_and(|scope| scope.override_name() == Some("comment"));
12493 let comment_delimiters = if is_within_comment_override {
12494 // we are within a comment syntax node, but we don't
12495 // yet know what kind of comment: block, doc or line
12496 match (
12497 language_scope.documentation_comment(),
12498 language_scope.block_comment(),
12499 ) {
12500 (Some(config), _) | (_, Some(config))
12501 if buffer.contains_str_at(indent_end, &config.start) =>
12502 {
12503 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12504 }
12505 (Some(config), _) | (_, Some(config))
12506 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12507 {
12508 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12509 }
12510 (Some(config), _) | (_, Some(config))
12511 if buffer.contains_str_at(indent_end, &config.prefix) =>
12512 {
12513 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12514 }
12515 (_, _) => language_scope
12516 .line_comment_prefixes()
12517 .iter()
12518 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12519 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12520 }
12521 } else {
12522 // we not in an overridden comment node, but we may
12523 // be within a non-overridden line comment node
12524 language_scope
12525 .line_comment_prefixes()
12526 .iter()
12527 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12528 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12529 };
12530
12531 let rewrap_prefix = language_scope
12532 .rewrap_prefixes()
12533 .iter()
12534 .find_map(|prefix_regex| {
12535 prefix_regex.find(&line_text_after_indent).map(|mat| {
12536 if mat.start() == 0 {
12537 Some(mat.as_str().to_string())
12538 } else {
12539 None
12540 }
12541 })
12542 })
12543 .flatten();
12544 (comment_delimiters, rewrap_prefix)
12545 } else {
12546 (None, None)
12547 };
12548 (indent, comment_prefix, rewrap_prefix)
12549 };
12550
12551 let mut ranges = Vec::new();
12552 let from_empty_selection = selection.is_empty();
12553
12554 let mut current_range_start = first_row;
12555 let mut prev_row = first_row;
12556 let (
12557 mut current_range_indent,
12558 mut current_range_comment_delimiters,
12559 mut current_range_rewrap_prefix,
12560 ) = indent_and_prefix_for_row(first_row);
12561
12562 for row in non_blank_rows_iter.skip(1) {
12563 let has_paragraph_break = row > prev_row + 1;
12564
12565 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12566 indent_and_prefix_for_row(row);
12567
12568 let has_indent_change = row_indent != current_range_indent;
12569 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12570
12571 let has_boundary_change = has_comment_change
12572 || row_rewrap_prefix.is_some()
12573 || (has_indent_change && current_range_comment_delimiters.is_some());
12574
12575 if has_paragraph_break || has_boundary_change {
12576 ranges.push((
12577 language_settings.clone(),
12578 Point::new(current_range_start, 0)
12579 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12580 current_range_indent,
12581 current_range_comment_delimiters.clone(),
12582 current_range_rewrap_prefix.clone(),
12583 from_empty_selection,
12584 ));
12585 current_range_start = row;
12586 current_range_indent = row_indent;
12587 current_range_comment_delimiters = row_comment_delimiters;
12588 current_range_rewrap_prefix = row_rewrap_prefix;
12589 }
12590 prev_row = row;
12591 }
12592
12593 ranges.push((
12594 language_settings.clone(),
12595 Point::new(current_range_start, 0)
12596 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12597 current_range_indent,
12598 current_range_comment_delimiters,
12599 current_range_rewrap_prefix,
12600 from_empty_selection,
12601 ));
12602
12603 ranges
12604 });
12605
12606 let mut edits = Vec::new();
12607 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12608
12609 for (
12610 language_settings,
12611 wrap_range,
12612 mut indent_size,
12613 comment_prefix,
12614 rewrap_prefix,
12615 from_empty_selection,
12616 ) in wrap_ranges
12617 {
12618 let mut start_row = wrap_range.start.row;
12619 let mut end_row = wrap_range.end.row;
12620
12621 // Skip selections that overlap with a range that has already been rewrapped.
12622 let selection_range = start_row..end_row;
12623 if rewrapped_row_ranges
12624 .iter()
12625 .any(|range| range.overlaps(&selection_range))
12626 {
12627 continue;
12628 }
12629
12630 let tab_size = language_settings.tab_size;
12631
12632 let (line_prefix, inside_comment) = match &comment_prefix {
12633 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12634 (Some(prefix.as_str()), true)
12635 }
12636 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12637 (Some(prefix.as_ref()), true)
12638 }
12639 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12640 start: _,
12641 end: _,
12642 prefix,
12643 tab_size,
12644 })) => {
12645 indent_size.len += tab_size;
12646 (Some(prefix.as_ref()), true)
12647 }
12648 None => (None, false),
12649 };
12650 let indent_prefix = indent_size.chars().collect::<String>();
12651 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12652
12653 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12654 RewrapBehavior::InComments => inside_comment,
12655 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12656 RewrapBehavior::Anywhere => true,
12657 };
12658
12659 let should_rewrap = options.override_language_settings
12660 || allow_rewrap_based_on_language
12661 || self.hard_wrap.is_some();
12662 if !should_rewrap {
12663 continue;
12664 }
12665
12666 if from_empty_selection {
12667 'expand_upwards: while start_row > 0 {
12668 let prev_row = start_row - 1;
12669 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12670 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12671 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12672 {
12673 start_row = prev_row;
12674 } else {
12675 break 'expand_upwards;
12676 }
12677 }
12678
12679 'expand_downwards: while end_row < buffer.max_point().row {
12680 let next_row = end_row + 1;
12681 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12682 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12683 && !buffer.is_line_blank(MultiBufferRow(next_row))
12684 {
12685 end_row = next_row;
12686 } else {
12687 break 'expand_downwards;
12688 }
12689 }
12690 }
12691
12692 let start = Point::new(start_row, 0);
12693 let start_offset = ToOffset::to_offset(&start, &buffer);
12694 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12695 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12696 let mut first_line_delimiter = None;
12697 let mut last_line_delimiter = None;
12698 let Some(lines_without_prefixes) = selection_text
12699 .lines()
12700 .enumerate()
12701 .map(|(ix, line)| {
12702 let line_trimmed = line.trim_start();
12703 if rewrap_prefix.is_some() && ix > 0 {
12704 Ok(line_trimmed)
12705 } else if let Some(
12706 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12707 start,
12708 prefix,
12709 end,
12710 tab_size,
12711 })
12712 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12713 start,
12714 prefix,
12715 end,
12716 tab_size,
12717 }),
12718 ) = &comment_prefix
12719 {
12720 let line_trimmed = line_trimmed
12721 .strip_prefix(start.as_ref())
12722 .map(|s| {
12723 let mut indent_size = indent_size;
12724 indent_size.len -= tab_size;
12725 let indent_prefix: String = indent_size.chars().collect();
12726 first_line_delimiter = Some((indent_prefix, start));
12727 s.trim_start()
12728 })
12729 .unwrap_or(line_trimmed);
12730 let line_trimmed = line_trimmed
12731 .strip_suffix(end.as_ref())
12732 .map(|s| {
12733 last_line_delimiter = Some(end);
12734 s.trim_end()
12735 })
12736 .unwrap_or(line_trimmed);
12737 let line_trimmed = line_trimmed
12738 .strip_prefix(prefix.as_ref())
12739 .unwrap_or(line_trimmed);
12740 Ok(line_trimmed)
12741 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12742 line_trimmed.strip_prefix(prefix).with_context(|| {
12743 format!("line did not start with prefix {prefix:?}: {line:?}")
12744 })
12745 } else {
12746 line_trimmed
12747 .strip_prefix(&line_prefix.trim_start())
12748 .with_context(|| {
12749 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12750 })
12751 }
12752 })
12753 .collect::<Result<Vec<_>, _>>()
12754 .log_err()
12755 else {
12756 continue;
12757 };
12758
12759 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12760 buffer
12761 .language_settings_at(Point::new(start_row, 0), cx)
12762 .preferred_line_length as usize
12763 });
12764
12765 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12766 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12767 } else {
12768 line_prefix.clone()
12769 };
12770
12771 let wrapped_text = {
12772 let mut wrapped_text = wrap_with_prefix(
12773 line_prefix,
12774 subsequent_lines_prefix,
12775 lines_without_prefixes.join("\n"),
12776 wrap_column,
12777 tab_size,
12778 options.preserve_existing_whitespace,
12779 );
12780
12781 if let Some((indent, delimiter)) = first_line_delimiter {
12782 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12783 }
12784 if let Some(last_line) = last_line_delimiter {
12785 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12786 }
12787
12788 wrapped_text
12789 };
12790
12791 // TODO: should always use char-based diff while still supporting cursor behavior that
12792 // matches vim.
12793 let mut diff_options = DiffOptions::default();
12794 if options.override_language_settings {
12795 diff_options.max_word_diff_len = 0;
12796 diff_options.max_word_diff_line_count = 0;
12797 } else {
12798 diff_options.max_word_diff_len = usize::MAX;
12799 diff_options.max_word_diff_line_count = usize::MAX;
12800 }
12801
12802 for (old_range, new_text) in
12803 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12804 {
12805 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12806 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12807 edits.push((edit_start..edit_end, new_text));
12808 }
12809
12810 rewrapped_row_ranges.push(start_row..=end_row);
12811 }
12812
12813 self.buffer
12814 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12815 }
12816
12817 pub fn cut_common(
12818 &mut self,
12819 cut_no_selection_line: bool,
12820 window: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) -> ClipboardItem {
12823 let mut text = String::new();
12824 let buffer = self.buffer.read(cx).snapshot(cx);
12825 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12826 let mut clipboard_selections = Vec::with_capacity(selections.len());
12827 {
12828 let max_point = buffer.max_point();
12829 let mut is_first = true;
12830 let mut prev_selection_was_entire_line = false;
12831 for selection in &mut selections {
12832 let is_entire_line =
12833 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12834 if is_entire_line {
12835 selection.start = Point::new(selection.start.row, 0);
12836 if !selection.is_empty() && selection.end.column == 0 {
12837 selection.end = cmp::min(max_point, selection.end);
12838 } else {
12839 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12840 }
12841 selection.goal = SelectionGoal::None;
12842 }
12843 if is_first {
12844 is_first = false;
12845 } else if !prev_selection_was_entire_line {
12846 text += "\n";
12847 }
12848 prev_selection_was_entire_line = is_entire_line;
12849 let mut len = 0;
12850 for chunk in buffer.text_for_range(selection.start..selection.end) {
12851 text.push_str(chunk);
12852 len += chunk.len();
12853 }
12854
12855 clipboard_selections.push(ClipboardSelection::for_buffer(
12856 len,
12857 is_entire_line,
12858 selection.range(),
12859 &buffer,
12860 self.project.as_ref(),
12861 cx,
12862 ));
12863 }
12864 }
12865
12866 self.transact(window, cx, |this, window, cx| {
12867 this.change_selections(Default::default(), window, cx, |s| {
12868 s.select(selections);
12869 });
12870 this.insert("", window, cx);
12871 });
12872 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12873 }
12874
12875 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12876 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12877 let item = self.cut_common(true, window, cx);
12878 cx.write_to_clipboard(item);
12879 }
12880
12881 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12884 s.move_with(|snapshot, sel| {
12885 if sel.is_empty() {
12886 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12887 }
12888 if sel.is_empty() {
12889 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12890 }
12891 });
12892 });
12893 let item = self.cut_common(false, window, cx);
12894 cx.set_global(KillRing(item))
12895 }
12896
12897 pub fn kill_ring_yank(
12898 &mut self,
12899 _: &KillRingYank,
12900 window: &mut Window,
12901 cx: &mut Context<Self>,
12902 ) {
12903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12904 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12905 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12906 (kill_ring.text().to_string(), kill_ring.metadata_json())
12907 } else {
12908 return;
12909 }
12910 } else {
12911 return;
12912 };
12913 self.do_paste(&text, metadata, false, window, cx);
12914 }
12915
12916 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12917 self.do_copy(true, cx);
12918 }
12919
12920 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12921 self.do_copy(false, cx);
12922 }
12923
12924 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12925 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12926 let buffer = self.buffer.read(cx).read(cx);
12927 let mut text = String::new();
12928
12929 let mut clipboard_selections = Vec::with_capacity(selections.len());
12930 {
12931 let max_point = buffer.max_point();
12932 let mut is_first = true;
12933 let mut prev_selection_was_entire_line = false;
12934 for selection in &selections {
12935 let mut start = selection.start;
12936 let mut end = selection.end;
12937 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12938 let mut add_trailing_newline = false;
12939 if is_entire_line {
12940 start = Point::new(start.row, 0);
12941 let next_line_start = Point::new(end.row + 1, 0);
12942 if next_line_start <= max_point {
12943 end = next_line_start;
12944 } else {
12945 // We're on the last line without a trailing newline.
12946 // Copy to the end of the line and add a newline afterwards.
12947 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12948 add_trailing_newline = true;
12949 }
12950 }
12951
12952 let mut trimmed_selections = Vec::new();
12953 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12954 let row = MultiBufferRow(start.row);
12955 let first_indent = buffer.indent_size_for_line(row);
12956 if first_indent.len == 0 || start.column > first_indent.len {
12957 trimmed_selections.push(start..end);
12958 } else {
12959 trimmed_selections.push(
12960 Point::new(row.0, first_indent.len)
12961 ..Point::new(row.0, buffer.line_len(row)),
12962 );
12963 for row in start.row + 1..=end.row {
12964 let mut line_len = buffer.line_len(MultiBufferRow(row));
12965 if row == end.row {
12966 line_len = end.column;
12967 }
12968 if line_len == 0 {
12969 trimmed_selections
12970 .push(Point::new(row, 0)..Point::new(row, line_len));
12971 continue;
12972 }
12973 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12974 if row_indent_size.len >= first_indent.len {
12975 trimmed_selections.push(
12976 Point::new(row, first_indent.len)..Point::new(row, line_len),
12977 );
12978 } else {
12979 trimmed_selections.clear();
12980 trimmed_selections.push(start..end);
12981 break;
12982 }
12983 }
12984 }
12985 } else {
12986 trimmed_selections.push(start..end);
12987 }
12988
12989 for trimmed_range in trimmed_selections {
12990 if is_first {
12991 is_first = false;
12992 } else if !prev_selection_was_entire_line {
12993 text += "\n";
12994 }
12995 prev_selection_was_entire_line = is_entire_line;
12996 let mut len = 0;
12997 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12998 text.push_str(chunk);
12999 len += chunk.len();
13000 }
13001 if add_trailing_newline {
13002 text.push('\n');
13003 len += 1;
13004 }
13005 clipboard_selections.push(ClipboardSelection::for_buffer(
13006 len,
13007 is_entire_line,
13008 trimmed_range,
13009 &buffer,
13010 self.project.as_ref(),
13011 cx,
13012 ));
13013 }
13014 }
13015 }
13016
13017 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13018 text,
13019 clipboard_selections,
13020 ));
13021 }
13022
13023 pub fn do_paste(
13024 &mut self,
13025 text: &String,
13026 clipboard_selections: Option<Vec<ClipboardSelection>>,
13027 handle_entire_lines: bool,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) {
13031 if self.read_only(cx) {
13032 return;
13033 }
13034
13035 let clipboard_text = Cow::Borrowed(text.as_str());
13036
13037 self.transact(window, cx, |this, window, cx| {
13038 let had_active_edit_prediction = this.has_active_edit_prediction();
13039 let display_map = this.display_snapshot(cx);
13040 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13041 let cursor_offset = this
13042 .selections
13043 .last::<MultiBufferOffset>(&display_map)
13044 .head();
13045
13046 if let Some(mut clipboard_selections) = clipboard_selections {
13047 let all_selections_were_entire_line =
13048 clipboard_selections.iter().all(|s| s.is_entire_line);
13049 let first_selection_indent_column =
13050 clipboard_selections.first().map(|s| s.first_line_indent);
13051 if clipboard_selections.len() != old_selections.len() {
13052 clipboard_selections.drain(..);
13053 }
13054 let mut auto_indent_on_paste = true;
13055
13056 this.buffer.update(cx, |buffer, cx| {
13057 let snapshot = buffer.read(cx);
13058 auto_indent_on_paste = snapshot
13059 .language_settings_at(cursor_offset, cx)
13060 .auto_indent_on_paste;
13061
13062 let mut start_offset = 0;
13063 let mut edits = Vec::new();
13064 let mut original_indent_columns = Vec::new();
13065 for (ix, selection) in old_selections.iter().enumerate() {
13066 let to_insert;
13067 let entire_line;
13068 let original_indent_column;
13069 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13070 let end_offset = start_offset + clipboard_selection.len;
13071 to_insert = &clipboard_text[start_offset..end_offset];
13072 entire_line = clipboard_selection.is_entire_line;
13073 start_offset = if entire_line {
13074 end_offset
13075 } else {
13076 end_offset + 1
13077 };
13078 original_indent_column = Some(clipboard_selection.first_line_indent);
13079 } else {
13080 to_insert = &*clipboard_text;
13081 entire_line = all_selections_were_entire_line;
13082 original_indent_column = first_selection_indent_column
13083 }
13084
13085 let (range, to_insert) =
13086 if selection.is_empty() && handle_entire_lines && entire_line {
13087 // If the corresponding selection was empty when this slice of the
13088 // clipboard text was written, then the entire line containing the
13089 // selection was copied. If this selection is also currently empty,
13090 // then paste the line before the current line of the buffer.
13091 let column = selection.start.to_point(&snapshot).column as usize;
13092 let line_start = selection.start - column;
13093 (line_start..line_start, Cow::Borrowed(to_insert))
13094 } else {
13095 let language = snapshot.language_at(selection.head());
13096 let range = selection.range();
13097 if let Some(language) = language
13098 && language.name() == "Markdown".into()
13099 {
13100 edit_for_markdown_paste(
13101 &snapshot,
13102 range,
13103 to_insert,
13104 url::Url::parse(to_insert).ok(),
13105 )
13106 } else {
13107 (range, Cow::Borrowed(to_insert))
13108 }
13109 };
13110
13111 edits.push((range, to_insert));
13112 original_indent_columns.push(original_indent_column);
13113 }
13114 drop(snapshot);
13115
13116 buffer.edit(
13117 edits,
13118 if auto_indent_on_paste {
13119 Some(AutoindentMode::Block {
13120 original_indent_columns,
13121 })
13122 } else {
13123 None
13124 },
13125 cx,
13126 );
13127 });
13128
13129 let selections = this
13130 .selections
13131 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13132 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13133 } else {
13134 let url = url::Url::parse(&clipboard_text).ok();
13135
13136 let auto_indent_mode = if !clipboard_text.is_empty() {
13137 Some(AutoindentMode::Block {
13138 original_indent_columns: Vec::new(),
13139 })
13140 } else {
13141 None
13142 };
13143
13144 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13145 let snapshot = buffer.snapshot(cx);
13146
13147 let anchors = old_selections
13148 .iter()
13149 .map(|s| {
13150 let anchor = snapshot.anchor_after(s.head());
13151 s.map(|_| anchor)
13152 })
13153 .collect::<Vec<_>>();
13154
13155 let mut edits = Vec::new();
13156
13157 for selection in old_selections.iter() {
13158 let language = snapshot.language_at(selection.head());
13159 let range = selection.range();
13160
13161 let (edit_range, edit_text) = if let Some(language) = language
13162 && language.name() == "Markdown".into()
13163 {
13164 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13165 } else {
13166 (range, clipboard_text.clone())
13167 };
13168
13169 edits.push((edit_range, edit_text));
13170 }
13171
13172 drop(snapshot);
13173 buffer.edit(edits, auto_indent_mode, cx);
13174
13175 anchors
13176 });
13177
13178 this.change_selections(Default::default(), window, cx, |s| {
13179 s.select_anchors(selection_anchors);
13180 });
13181 }
13182
13183 // 🤔 | .. | show_in_menu |
13184 // | .. | true true
13185 // | had_edit_prediction | false true
13186
13187 let trigger_in_words =
13188 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13189
13190 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13191 });
13192 }
13193
13194 pub fn diff_clipboard_with_selection(
13195 &mut self,
13196 _: &DiffClipboardWithSelection,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 let selections = self
13201 .selections
13202 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13203
13204 if selections.is_empty() {
13205 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13206 return;
13207 };
13208
13209 let clipboard_text = match cx.read_from_clipboard() {
13210 Some(item) => match item.entries().first() {
13211 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13212 _ => None,
13213 },
13214 None => None,
13215 };
13216
13217 let Some(clipboard_text) = clipboard_text else {
13218 log::warn!("Clipboard doesn't contain text.");
13219 return;
13220 };
13221
13222 window.dispatch_action(
13223 Box::new(DiffClipboardWithSelectionData {
13224 clipboard_text,
13225 editor: cx.entity(),
13226 }),
13227 cx,
13228 );
13229 }
13230
13231 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13233 if let Some(item) = cx.read_from_clipboard() {
13234 let entries = item.entries();
13235
13236 match entries.first() {
13237 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13238 // of all the pasted entries.
13239 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13240 .do_paste(
13241 clipboard_string.text(),
13242 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13243 true,
13244 window,
13245 cx,
13246 ),
13247 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13248 }
13249 }
13250 }
13251
13252 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13253 if self.read_only(cx) {
13254 return;
13255 }
13256
13257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13258
13259 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13260 if let Some((selections, _)) =
13261 self.selection_history.transaction(transaction_id).cloned()
13262 {
13263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13264 s.select_anchors(selections.to_vec());
13265 });
13266 } else {
13267 log::error!(
13268 "No entry in selection_history found for undo. \
13269 This may correspond to a bug where undo does not update the selection. \
13270 If this is occurring, please add details to \
13271 https://github.com/zed-industries/zed/issues/22692"
13272 );
13273 }
13274 self.request_autoscroll(Autoscroll::fit(), cx);
13275 self.unmark_text(window, cx);
13276 self.refresh_edit_prediction(true, false, window, cx);
13277 cx.emit(EditorEvent::Edited { transaction_id });
13278 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13279 }
13280 }
13281
13282 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13283 if self.read_only(cx) {
13284 return;
13285 }
13286
13287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13288
13289 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13290 if let Some((_, Some(selections))) =
13291 self.selection_history.transaction(transaction_id).cloned()
13292 {
13293 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13294 s.select_anchors(selections.to_vec());
13295 });
13296 } else {
13297 log::error!(
13298 "No entry in selection_history found for redo. \
13299 This may correspond to a bug where undo does not update the selection. \
13300 If this is occurring, please add details to \
13301 https://github.com/zed-industries/zed/issues/22692"
13302 );
13303 }
13304 self.request_autoscroll(Autoscroll::fit(), cx);
13305 self.unmark_text(window, cx);
13306 self.refresh_edit_prediction(true, false, window, cx);
13307 cx.emit(EditorEvent::Edited { transaction_id });
13308 }
13309 }
13310
13311 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13312 self.buffer
13313 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13314 }
13315
13316 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13317 self.buffer
13318 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13319 }
13320
13321 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13323 self.change_selections(Default::default(), window, cx, |s| {
13324 s.move_with(|map, selection| {
13325 let cursor = if selection.is_empty() {
13326 movement::left(map, selection.start)
13327 } else {
13328 selection.start
13329 };
13330 selection.collapse_to(cursor, SelectionGoal::None);
13331 });
13332 })
13333 }
13334
13335 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13337 self.change_selections(Default::default(), window, cx, |s| {
13338 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13339 })
13340 }
13341
13342 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13344 self.change_selections(Default::default(), window, cx, |s| {
13345 s.move_with(|map, selection| {
13346 let cursor = if selection.is_empty() {
13347 movement::right(map, selection.end)
13348 } else {
13349 selection.end
13350 };
13351 selection.collapse_to(cursor, SelectionGoal::None)
13352 });
13353 })
13354 }
13355
13356 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13358 self.change_selections(Default::default(), window, cx, |s| {
13359 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13360 });
13361 }
13362
13363 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13364 if self.take_rename(true, window, cx).is_some() {
13365 return;
13366 }
13367
13368 if self.mode.is_single_line() {
13369 cx.propagate();
13370 return;
13371 }
13372
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374
13375 let text_layout_details = &self.text_layout_details(window);
13376 let selection_count = self.selections.count();
13377 let first_selection = self.selections.first_anchor();
13378
13379 self.change_selections(Default::default(), window, cx, |s| {
13380 s.move_with(|map, selection| {
13381 if !selection.is_empty() {
13382 selection.goal = SelectionGoal::None;
13383 }
13384 let (cursor, goal) = movement::up(
13385 map,
13386 selection.start,
13387 selection.goal,
13388 false,
13389 text_layout_details,
13390 );
13391 selection.collapse_to(cursor, goal);
13392 });
13393 });
13394
13395 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13396 {
13397 cx.propagate();
13398 }
13399 }
13400
13401 pub fn move_up_by_lines(
13402 &mut self,
13403 action: &MoveUpByLines,
13404 window: &mut Window,
13405 cx: &mut Context<Self>,
13406 ) {
13407 if self.take_rename(true, window, cx).is_some() {
13408 return;
13409 }
13410
13411 if self.mode.is_single_line() {
13412 cx.propagate();
13413 return;
13414 }
13415
13416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13417
13418 let text_layout_details = &self.text_layout_details(window);
13419
13420 self.change_selections(Default::default(), window, cx, |s| {
13421 s.move_with(|map, selection| {
13422 if !selection.is_empty() {
13423 selection.goal = SelectionGoal::None;
13424 }
13425 let (cursor, goal) = movement::up_by_rows(
13426 map,
13427 selection.start,
13428 action.lines,
13429 selection.goal,
13430 false,
13431 text_layout_details,
13432 );
13433 selection.collapse_to(cursor, goal);
13434 });
13435 })
13436 }
13437
13438 pub fn move_down_by_lines(
13439 &mut self,
13440 action: &MoveDownByLines,
13441 window: &mut Window,
13442 cx: &mut Context<Self>,
13443 ) {
13444 if self.take_rename(true, window, cx).is_some() {
13445 return;
13446 }
13447
13448 if self.mode.is_single_line() {
13449 cx.propagate();
13450 return;
13451 }
13452
13453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13454
13455 let text_layout_details = &self.text_layout_details(window);
13456
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_with(|map, selection| {
13459 if !selection.is_empty() {
13460 selection.goal = SelectionGoal::None;
13461 }
13462 let (cursor, goal) = movement::down_by_rows(
13463 map,
13464 selection.start,
13465 action.lines,
13466 selection.goal,
13467 false,
13468 text_layout_details,
13469 );
13470 selection.collapse_to(cursor, goal);
13471 });
13472 })
13473 }
13474
13475 pub fn select_down_by_lines(
13476 &mut self,
13477 action: &SelectDownByLines,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13482 let text_layout_details = &self.text_layout_details(window);
13483 self.change_selections(Default::default(), window, cx, |s| {
13484 s.move_heads_with(|map, head, goal| {
13485 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13486 })
13487 })
13488 }
13489
13490 pub fn select_up_by_lines(
13491 &mut self,
13492 action: &SelectUpByLines,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13497 let text_layout_details = &self.text_layout_details(window);
13498 self.change_selections(Default::default(), window, cx, |s| {
13499 s.move_heads_with(|map, head, goal| {
13500 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13501 })
13502 })
13503 }
13504
13505 pub fn select_page_up(
13506 &mut self,
13507 _: &SelectPageUp,
13508 window: &mut Window,
13509 cx: &mut Context<Self>,
13510 ) {
13511 let Some(row_count) = self.visible_row_count() else {
13512 return;
13513 };
13514
13515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13516
13517 let text_layout_details = &self.text_layout_details(window);
13518
13519 self.change_selections(Default::default(), window, cx, |s| {
13520 s.move_heads_with(|map, head, goal| {
13521 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13522 })
13523 })
13524 }
13525
13526 pub fn move_page_up(
13527 &mut self,
13528 action: &MovePageUp,
13529 window: &mut Window,
13530 cx: &mut Context<Self>,
13531 ) {
13532 if self.take_rename(true, window, cx).is_some() {
13533 return;
13534 }
13535
13536 if self
13537 .context_menu
13538 .borrow_mut()
13539 .as_mut()
13540 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13541 .unwrap_or(false)
13542 {
13543 return;
13544 }
13545
13546 if matches!(self.mode, EditorMode::SingleLine) {
13547 cx.propagate();
13548 return;
13549 }
13550
13551 let Some(row_count) = self.visible_row_count() else {
13552 return;
13553 };
13554
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13556
13557 let effects = if action.center_cursor {
13558 SelectionEffects::scroll(Autoscroll::center())
13559 } else {
13560 SelectionEffects::default()
13561 };
13562
13563 let text_layout_details = &self.text_layout_details(window);
13564
13565 self.change_selections(effects, window, cx, |s| {
13566 s.move_with(|map, selection| {
13567 if !selection.is_empty() {
13568 selection.goal = SelectionGoal::None;
13569 }
13570 let (cursor, goal) = movement::up_by_rows(
13571 map,
13572 selection.end,
13573 row_count,
13574 selection.goal,
13575 false,
13576 text_layout_details,
13577 );
13578 selection.collapse_to(cursor, goal);
13579 });
13580 });
13581 }
13582
13583 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13585 let text_layout_details = &self.text_layout_details(window);
13586 self.change_selections(Default::default(), window, cx, |s| {
13587 s.move_heads_with(|map, head, goal| {
13588 movement::up(map, head, goal, false, text_layout_details)
13589 })
13590 })
13591 }
13592
13593 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13594 self.take_rename(true, window, cx);
13595
13596 if self.mode.is_single_line() {
13597 cx.propagate();
13598 return;
13599 }
13600
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602
13603 let text_layout_details = &self.text_layout_details(window);
13604 let selection_count = self.selections.count();
13605 let first_selection = self.selections.first_anchor();
13606
13607 self.change_selections(Default::default(), window, cx, |s| {
13608 s.move_with(|map, selection| {
13609 if !selection.is_empty() {
13610 selection.goal = SelectionGoal::None;
13611 }
13612 let (cursor, goal) = movement::down(
13613 map,
13614 selection.end,
13615 selection.goal,
13616 false,
13617 text_layout_details,
13618 );
13619 selection.collapse_to(cursor, goal);
13620 });
13621 });
13622
13623 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13624 {
13625 cx.propagate();
13626 }
13627 }
13628
13629 pub fn select_page_down(
13630 &mut self,
13631 _: &SelectPageDown,
13632 window: &mut Window,
13633 cx: &mut Context<Self>,
13634 ) {
13635 let Some(row_count) = self.visible_row_count() else {
13636 return;
13637 };
13638
13639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13640
13641 let text_layout_details = &self.text_layout_details(window);
13642
13643 self.change_selections(Default::default(), window, cx, |s| {
13644 s.move_heads_with(|map, head, goal| {
13645 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13646 })
13647 })
13648 }
13649
13650 pub fn move_page_down(
13651 &mut self,
13652 action: &MovePageDown,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 if self.take_rename(true, window, cx).is_some() {
13657 return;
13658 }
13659
13660 if self
13661 .context_menu
13662 .borrow_mut()
13663 .as_mut()
13664 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13665 .unwrap_or(false)
13666 {
13667 return;
13668 }
13669
13670 if matches!(self.mode, EditorMode::SingleLine) {
13671 cx.propagate();
13672 return;
13673 }
13674
13675 let Some(row_count) = self.visible_row_count() else {
13676 return;
13677 };
13678
13679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13680
13681 let effects = if action.center_cursor {
13682 SelectionEffects::scroll(Autoscroll::center())
13683 } else {
13684 SelectionEffects::default()
13685 };
13686
13687 let text_layout_details = &self.text_layout_details(window);
13688 self.change_selections(effects, window, cx, |s| {
13689 s.move_with(|map, selection| {
13690 if !selection.is_empty() {
13691 selection.goal = SelectionGoal::None;
13692 }
13693 let (cursor, goal) = movement::down_by_rows(
13694 map,
13695 selection.end,
13696 row_count,
13697 selection.goal,
13698 false,
13699 text_layout_details,
13700 );
13701 selection.collapse_to(cursor, goal);
13702 });
13703 });
13704 }
13705
13706 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13708 let text_layout_details = &self.text_layout_details(window);
13709 self.change_selections(Default::default(), window, cx, |s| {
13710 s.move_heads_with(|map, head, goal| {
13711 movement::down(map, head, goal, false, text_layout_details)
13712 })
13713 });
13714 }
13715
13716 pub fn context_menu_first(
13717 &mut self,
13718 _: &ContextMenuFirst,
13719 window: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13723 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13724 }
13725 }
13726
13727 pub fn context_menu_prev(
13728 &mut self,
13729 _: &ContextMenuPrevious,
13730 window: &mut Window,
13731 cx: &mut Context<Self>,
13732 ) {
13733 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13734 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13735 }
13736 }
13737
13738 pub fn context_menu_next(
13739 &mut self,
13740 _: &ContextMenuNext,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13745 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13746 }
13747 }
13748
13749 pub fn context_menu_last(
13750 &mut self,
13751 _: &ContextMenuLast,
13752 window: &mut Window,
13753 cx: &mut Context<Self>,
13754 ) {
13755 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13756 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13757 }
13758 }
13759
13760 pub fn signature_help_prev(
13761 &mut self,
13762 _: &SignatureHelpPrevious,
13763 _: &mut Window,
13764 cx: &mut Context<Self>,
13765 ) {
13766 if let Some(popover) = self.signature_help_state.popover_mut() {
13767 if popover.current_signature == 0 {
13768 popover.current_signature = popover.signatures.len() - 1;
13769 } else {
13770 popover.current_signature -= 1;
13771 }
13772 cx.notify();
13773 }
13774 }
13775
13776 pub fn signature_help_next(
13777 &mut self,
13778 _: &SignatureHelpNext,
13779 _: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) {
13782 if let Some(popover) = self.signature_help_state.popover_mut() {
13783 if popover.current_signature + 1 == popover.signatures.len() {
13784 popover.current_signature = 0;
13785 } else {
13786 popover.current_signature += 1;
13787 }
13788 cx.notify();
13789 }
13790 }
13791
13792 pub fn move_to_previous_word_start(
13793 &mut self,
13794 _: &MoveToPreviousWordStart,
13795 window: &mut Window,
13796 cx: &mut Context<Self>,
13797 ) {
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13799 self.change_selections(Default::default(), window, cx, |s| {
13800 s.move_cursors_with(|map, head, _| {
13801 (
13802 movement::previous_word_start(map, head),
13803 SelectionGoal::None,
13804 )
13805 });
13806 })
13807 }
13808
13809 pub fn move_to_previous_subword_start(
13810 &mut self,
13811 _: &MoveToPreviousSubwordStart,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) {
13815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13816 self.change_selections(Default::default(), window, cx, |s| {
13817 s.move_cursors_with(|map, head, _| {
13818 (
13819 movement::previous_subword_start(map, head),
13820 SelectionGoal::None,
13821 )
13822 });
13823 })
13824 }
13825
13826 pub fn select_to_previous_word_start(
13827 &mut self,
13828 _: &SelectToPreviousWordStart,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13833 self.change_selections(Default::default(), window, cx, |s| {
13834 s.move_heads_with(|map, head, _| {
13835 (
13836 movement::previous_word_start(map, head),
13837 SelectionGoal::None,
13838 )
13839 });
13840 })
13841 }
13842
13843 pub fn select_to_previous_subword_start(
13844 &mut self,
13845 _: &SelectToPreviousSubwordStart,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) {
13849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13850 self.change_selections(Default::default(), window, cx, |s| {
13851 s.move_heads_with(|map, head, _| {
13852 (
13853 movement::previous_subword_start(map, head),
13854 SelectionGoal::None,
13855 )
13856 });
13857 })
13858 }
13859
13860 pub fn delete_to_previous_word_start(
13861 &mut self,
13862 action: &DeleteToPreviousWordStart,
13863 window: &mut Window,
13864 cx: &mut Context<Self>,
13865 ) {
13866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13867 self.transact(window, cx, |this, window, cx| {
13868 this.select_autoclose_pair(window, cx);
13869 this.change_selections(Default::default(), window, cx, |s| {
13870 s.move_with(|map, selection| {
13871 if selection.is_empty() {
13872 let mut cursor = if action.ignore_newlines {
13873 movement::previous_word_start(map, selection.head())
13874 } else {
13875 movement::previous_word_start_or_newline(map, selection.head())
13876 };
13877 cursor = movement::adjust_greedy_deletion(
13878 map,
13879 selection.head(),
13880 cursor,
13881 action.ignore_brackets,
13882 );
13883 selection.set_head(cursor, SelectionGoal::None);
13884 }
13885 });
13886 });
13887 this.insert("", window, cx);
13888 });
13889 }
13890
13891 pub fn delete_to_previous_subword_start(
13892 &mut self,
13893 _: &DeleteToPreviousSubwordStart,
13894 window: &mut Window,
13895 cx: &mut Context<Self>,
13896 ) {
13897 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13898 self.transact(window, cx, |this, window, cx| {
13899 this.select_autoclose_pair(window, cx);
13900 this.change_selections(Default::default(), window, cx, |s| {
13901 s.move_with(|map, selection| {
13902 if selection.is_empty() {
13903 let mut cursor = movement::previous_subword_start(map, selection.head());
13904 cursor =
13905 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13906 selection.set_head(cursor, SelectionGoal::None);
13907 }
13908 });
13909 });
13910 this.insert("", window, cx);
13911 });
13912 }
13913
13914 pub fn move_to_next_word_end(
13915 &mut self,
13916 _: &MoveToNextWordEnd,
13917 window: &mut Window,
13918 cx: &mut Context<Self>,
13919 ) {
13920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13921 self.change_selections(Default::default(), window, cx, |s| {
13922 s.move_cursors_with(|map, head, _| {
13923 (movement::next_word_end(map, head), SelectionGoal::None)
13924 });
13925 })
13926 }
13927
13928 pub fn move_to_next_subword_end(
13929 &mut self,
13930 _: &MoveToNextSubwordEnd,
13931 window: &mut Window,
13932 cx: &mut Context<Self>,
13933 ) {
13934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13935 self.change_selections(Default::default(), window, cx, |s| {
13936 s.move_cursors_with(|map, head, _| {
13937 (movement::next_subword_end(map, head), SelectionGoal::None)
13938 });
13939 })
13940 }
13941
13942 pub fn select_to_next_word_end(
13943 &mut self,
13944 _: &SelectToNextWordEnd,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13949 self.change_selections(Default::default(), window, cx, |s| {
13950 s.move_heads_with(|map, head, _| {
13951 (movement::next_word_end(map, head), SelectionGoal::None)
13952 });
13953 })
13954 }
13955
13956 pub fn select_to_next_subword_end(
13957 &mut self,
13958 _: &SelectToNextSubwordEnd,
13959 window: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) {
13962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13963 self.change_selections(Default::default(), window, cx, |s| {
13964 s.move_heads_with(|map, head, _| {
13965 (movement::next_subword_end(map, head), SelectionGoal::None)
13966 });
13967 })
13968 }
13969
13970 pub fn delete_to_next_word_end(
13971 &mut self,
13972 action: &DeleteToNextWordEnd,
13973 window: &mut Window,
13974 cx: &mut Context<Self>,
13975 ) {
13976 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13977 self.transact(window, cx, |this, window, cx| {
13978 this.change_selections(Default::default(), window, cx, |s| {
13979 s.move_with(|map, selection| {
13980 if selection.is_empty() {
13981 let mut cursor = if action.ignore_newlines {
13982 movement::next_word_end(map, selection.head())
13983 } else {
13984 movement::next_word_end_or_newline(map, selection.head())
13985 };
13986 cursor = movement::adjust_greedy_deletion(
13987 map,
13988 selection.head(),
13989 cursor,
13990 action.ignore_brackets,
13991 );
13992 selection.set_head(cursor, SelectionGoal::None);
13993 }
13994 });
13995 });
13996 this.insert("", window, cx);
13997 });
13998 }
13999
14000 pub fn delete_to_next_subword_end(
14001 &mut self,
14002 _: &DeleteToNextSubwordEnd,
14003 window: &mut Window,
14004 cx: &mut Context<Self>,
14005 ) {
14006 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14007 self.transact(window, cx, |this, window, cx| {
14008 this.change_selections(Default::default(), window, cx, |s| {
14009 s.move_with(|map, selection| {
14010 if selection.is_empty() {
14011 let mut cursor = movement::next_subword_end(map, selection.head());
14012 cursor =
14013 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14014 selection.set_head(cursor, SelectionGoal::None);
14015 }
14016 });
14017 });
14018 this.insert("", window, cx);
14019 });
14020 }
14021
14022 pub fn move_to_beginning_of_line(
14023 &mut self,
14024 action: &MoveToBeginningOfLine,
14025 window: &mut Window,
14026 cx: &mut Context<Self>,
14027 ) {
14028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14029 self.change_selections(Default::default(), window, cx, |s| {
14030 s.move_cursors_with(|map, head, _| {
14031 (
14032 movement::indented_line_beginning(
14033 map,
14034 head,
14035 action.stop_at_soft_wraps,
14036 action.stop_at_indent,
14037 ),
14038 SelectionGoal::None,
14039 )
14040 });
14041 })
14042 }
14043
14044 pub fn select_to_beginning_of_line(
14045 &mut self,
14046 action: &SelectToBeginningOfLine,
14047 window: &mut Window,
14048 cx: &mut Context<Self>,
14049 ) {
14050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14051 self.change_selections(Default::default(), window, cx, |s| {
14052 s.move_heads_with(|map, head, _| {
14053 (
14054 movement::indented_line_beginning(
14055 map,
14056 head,
14057 action.stop_at_soft_wraps,
14058 action.stop_at_indent,
14059 ),
14060 SelectionGoal::None,
14061 )
14062 });
14063 });
14064 }
14065
14066 pub fn delete_to_beginning_of_line(
14067 &mut self,
14068 action: &DeleteToBeginningOfLine,
14069 window: &mut Window,
14070 cx: &mut Context<Self>,
14071 ) {
14072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14073 self.transact(window, cx, |this, window, cx| {
14074 this.change_selections(Default::default(), window, cx, |s| {
14075 s.move_with(|_, selection| {
14076 selection.reversed = true;
14077 });
14078 });
14079
14080 this.select_to_beginning_of_line(
14081 &SelectToBeginningOfLine {
14082 stop_at_soft_wraps: false,
14083 stop_at_indent: action.stop_at_indent,
14084 },
14085 window,
14086 cx,
14087 );
14088 this.backspace(&Backspace, window, cx);
14089 });
14090 }
14091
14092 pub fn move_to_end_of_line(
14093 &mut self,
14094 action: &MoveToEndOfLine,
14095 window: &mut Window,
14096 cx: &mut Context<Self>,
14097 ) {
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 self.change_selections(Default::default(), window, cx, |s| {
14100 s.move_cursors_with(|map, head, _| {
14101 (
14102 movement::line_end(map, head, action.stop_at_soft_wraps),
14103 SelectionGoal::None,
14104 )
14105 });
14106 })
14107 }
14108
14109 pub fn select_to_end_of_line(
14110 &mut self,
14111 action: &SelectToEndOfLine,
14112 window: &mut Window,
14113 cx: &mut Context<Self>,
14114 ) {
14115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14116 self.change_selections(Default::default(), window, cx, |s| {
14117 s.move_heads_with(|map, head, _| {
14118 (
14119 movement::line_end(map, head, action.stop_at_soft_wraps),
14120 SelectionGoal::None,
14121 )
14122 });
14123 })
14124 }
14125
14126 pub fn delete_to_end_of_line(
14127 &mut self,
14128 _: &DeleteToEndOfLine,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) {
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14133 self.transact(window, cx, |this, window, cx| {
14134 this.select_to_end_of_line(
14135 &SelectToEndOfLine {
14136 stop_at_soft_wraps: false,
14137 },
14138 window,
14139 cx,
14140 );
14141 this.delete(&Delete, window, cx);
14142 });
14143 }
14144
14145 pub fn cut_to_end_of_line(
14146 &mut self,
14147 action: &CutToEndOfLine,
14148 window: &mut Window,
14149 cx: &mut Context<Self>,
14150 ) {
14151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14152 self.transact(window, cx, |this, window, cx| {
14153 this.select_to_end_of_line(
14154 &SelectToEndOfLine {
14155 stop_at_soft_wraps: false,
14156 },
14157 window,
14158 cx,
14159 );
14160 if !action.stop_at_newlines {
14161 this.change_selections(Default::default(), window, cx, |s| {
14162 s.move_with(|_, sel| {
14163 if sel.is_empty() {
14164 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14165 }
14166 });
14167 });
14168 }
14169 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14170 let item = this.cut_common(false, window, cx);
14171 cx.write_to_clipboard(item);
14172 });
14173 }
14174
14175 pub fn move_to_start_of_paragraph(
14176 &mut self,
14177 _: &MoveToStartOfParagraph,
14178 window: &mut Window,
14179 cx: &mut Context<Self>,
14180 ) {
14181 if matches!(self.mode, EditorMode::SingleLine) {
14182 cx.propagate();
14183 return;
14184 }
14185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14186 self.change_selections(Default::default(), window, cx, |s| {
14187 s.move_with(|map, selection| {
14188 selection.collapse_to(
14189 movement::start_of_paragraph(map, selection.head(), 1),
14190 SelectionGoal::None,
14191 )
14192 });
14193 })
14194 }
14195
14196 pub fn move_to_end_of_paragraph(
14197 &mut self,
14198 _: &MoveToEndOfParagraph,
14199 window: &mut Window,
14200 cx: &mut Context<Self>,
14201 ) {
14202 if matches!(self.mode, EditorMode::SingleLine) {
14203 cx.propagate();
14204 return;
14205 }
14206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14207 self.change_selections(Default::default(), window, cx, |s| {
14208 s.move_with(|map, selection| {
14209 selection.collapse_to(
14210 movement::end_of_paragraph(map, selection.head(), 1),
14211 SelectionGoal::None,
14212 )
14213 });
14214 })
14215 }
14216
14217 pub fn select_to_start_of_paragraph(
14218 &mut self,
14219 _: &SelectToStartOfParagraph,
14220 window: &mut Window,
14221 cx: &mut Context<Self>,
14222 ) {
14223 if matches!(self.mode, EditorMode::SingleLine) {
14224 cx.propagate();
14225 return;
14226 }
14227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14228 self.change_selections(Default::default(), window, cx, |s| {
14229 s.move_heads_with(|map, head, _| {
14230 (
14231 movement::start_of_paragraph(map, head, 1),
14232 SelectionGoal::None,
14233 )
14234 });
14235 })
14236 }
14237
14238 pub fn select_to_end_of_paragraph(
14239 &mut self,
14240 _: &SelectToEndOfParagraph,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) {
14244 if matches!(self.mode, EditorMode::SingleLine) {
14245 cx.propagate();
14246 return;
14247 }
14248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14249 self.change_selections(Default::default(), window, cx, |s| {
14250 s.move_heads_with(|map, head, _| {
14251 (
14252 movement::end_of_paragraph(map, head, 1),
14253 SelectionGoal::None,
14254 )
14255 });
14256 })
14257 }
14258
14259 pub fn move_to_start_of_excerpt(
14260 &mut self,
14261 _: &MoveToStartOfExcerpt,
14262 window: &mut Window,
14263 cx: &mut Context<Self>,
14264 ) {
14265 if matches!(self.mode, EditorMode::SingleLine) {
14266 cx.propagate();
14267 return;
14268 }
14269 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14270 self.change_selections(Default::default(), window, cx, |s| {
14271 s.move_with(|map, selection| {
14272 selection.collapse_to(
14273 movement::start_of_excerpt(
14274 map,
14275 selection.head(),
14276 workspace::searchable::Direction::Prev,
14277 ),
14278 SelectionGoal::None,
14279 )
14280 });
14281 })
14282 }
14283
14284 pub fn move_to_start_of_next_excerpt(
14285 &mut self,
14286 _: &MoveToStartOfNextExcerpt,
14287 window: &mut Window,
14288 cx: &mut Context<Self>,
14289 ) {
14290 if matches!(self.mode, EditorMode::SingleLine) {
14291 cx.propagate();
14292 return;
14293 }
14294
14295 self.change_selections(Default::default(), window, cx, |s| {
14296 s.move_with(|map, selection| {
14297 selection.collapse_to(
14298 movement::start_of_excerpt(
14299 map,
14300 selection.head(),
14301 workspace::searchable::Direction::Next,
14302 ),
14303 SelectionGoal::None,
14304 )
14305 });
14306 })
14307 }
14308
14309 pub fn move_to_end_of_excerpt(
14310 &mut self,
14311 _: &MoveToEndOfExcerpt,
14312 window: &mut Window,
14313 cx: &mut Context<Self>,
14314 ) {
14315 if matches!(self.mode, EditorMode::SingleLine) {
14316 cx.propagate();
14317 return;
14318 }
14319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14320 self.change_selections(Default::default(), window, cx, |s| {
14321 s.move_with(|map, selection| {
14322 selection.collapse_to(
14323 movement::end_of_excerpt(
14324 map,
14325 selection.head(),
14326 workspace::searchable::Direction::Next,
14327 ),
14328 SelectionGoal::None,
14329 )
14330 });
14331 })
14332 }
14333
14334 pub fn move_to_end_of_previous_excerpt(
14335 &mut self,
14336 _: &MoveToEndOfPreviousExcerpt,
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_with(|map, selection| {
14347 selection.collapse_to(
14348 movement::end_of_excerpt(
14349 map,
14350 selection.head(),
14351 workspace::searchable::Direction::Prev,
14352 ),
14353 SelectionGoal::None,
14354 )
14355 });
14356 })
14357 }
14358
14359 pub fn select_to_start_of_excerpt(
14360 &mut self,
14361 _: &SelectToStartOfExcerpt,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 if matches!(self.mode, EditorMode::SingleLine) {
14366 cx.propagate();
14367 return;
14368 }
14369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14370 self.change_selections(Default::default(), window, cx, |s| {
14371 s.move_heads_with(|map, head, _| {
14372 (
14373 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14374 SelectionGoal::None,
14375 )
14376 });
14377 })
14378 }
14379
14380 pub fn select_to_start_of_next_excerpt(
14381 &mut self,
14382 _: &SelectToStartOfNextExcerpt,
14383 window: &mut Window,
14384 cx: &mut Context<Self>,
14385 ) {
14386 if matches!(self.mode, EditorMode::SingleLine) {
14387 cx.propagate();
14388 return;
14389 }
14390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14391 self.change_selections(Default::default(), window, cx, |s| {
14392 s.move_heads_with(|map, head, _| {
14393 (
14394 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14395 SelectionGoal::None,
14396 )
14397 });
14398 })
14399 }
14400
14401 pub fn select_to_end_of_excerpt(
14402 &mut self,
14403 _: &SelectToEndOfExcerpt,
14404 window: &mut Window,
14405 cx: &mut Context<Self>,
14406 ) {
14407 if matches!(self.mode, EditorMode::SingleLine) {
14408 cx.propagate();
14409 return;
14410 }
14411 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14412 self.change_selections(Default::default(), window, cx, |s| {
14413 s.move_heads_with(|map, head, _| {
14414 (
14415 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14416 SelectionGoal::None,
14417 )
14418 });
14419 })
14420 }
14421
14422 pub fn select_to_end_of_previous_excerpt(
14423 &mut self,
14424 _: &SelectToEndOfPreviousExcerpt,
14425 window: &mut Window,
14426 cx: &mut Context<Self>,
14427 ) {
14428 if matches!(self.mode, EditorMode::SingleLine) {
14429 cx.propagate();
14430 return;
14431 }
14432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14433 self.change_selections(Default::default(), window, cx, |s| {
14434 s.move_heads_with(|map, head, _| {
14435 (
14436 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14437 SelectionGoal::None,
14438 )
14439 });
14440 })
14441 }
14442
14443 pub fn move_to_beginning(
14444 &mut self,
14445 _: &MoveToBeginning,
14446 window: &mut Window,
14447 cx: &mut Context<Self>,
14448 ) {
14449 if matches!(self.mode, EditorMode::SingleLine) {
14450 cx.propagate();
14451 return;
14452 }
14453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14454 self.change_selections(Default::default(), window, cx, |s| {
14455 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14456 });
14457 }
14458
14459 pub fn select_to_beginning(
14460 &mut self,
14461 _: &SelectToBeginning,
14462 window: &mut Window,
14463 cx: &mut Context<Self>,
14464 ) {
14465 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14466 selection.set_head(Point::zero(), SelectionGoal::None);
14467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14468 self.change_selections(Default::default(), window, cx, |s| {
14469 s.select(vec![selection]);
14470 });
14471 }
14472
14473 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14474 if matches!(self.mode, EditorMode::SingleLine) {
14475 cx.propagate();
14476 return;
14477 }
14478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14479 let cursor = self.buffer.read(cx).read(cx).len();
14480 self.change_selections(Default::default(), window, cx, |s| {
14481 s.select_ranges(vec![cursor..cursor])
14482 });
14483 }
14484
14485 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14486 self.nav_history = nav_history;
14487 }
14488
14489 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14490 self.nav_history.as_ref()
14491 }
14492
14493 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14494 self.push_to_nav_history(
14495 self.selections.newest_anchor().head(),
14496 None,
14497 false,
14498 true,
14499 cx,
14500 );
14501 }
14502
14503 fn push_to_nav_history(
14504 &mut self,
14505 cursor_anchor: Anchor,
14506 new_position: Option<Point>,
14507 is_deactivate: bool,
14508 always: bool,
14509 cx: &mut Context<Self>,
14510 ) {
14511 if let Some(nav_history) = self.nav_history.as_mut() {
14512 let buffer = self.buffer.read(cx).read(cx);
14513 let cursor_position = cursor_anchor.to_point(&buffer);
14514 let scroll_state = self.scroll_manager.anchor();
14515 let scroll_top_row = scroll_state.top_row(&buffer);
14516 drop(buffer);
14517
14518 if let Some(new_position) = new_position {
14519 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14520 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14521 return;
14522 }
14523 }
14524
14525 nav_history.push(
14526 Some(NavigationData {
14527 cursor_anchor,
14528 cursor_position,
14529 scroll_anchor: scroll_state,
14530 scroll_top_row,
14531 }),
14532 cx,
14533 );
14534 cx.emit(EditorEvent::PushedToNavHistory {
14535 anchor: cursor_anchor,
14536 is_deactivate,
14537 })
14538 }
14539 }
14540
14541 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14543 let buffer = self.buffer.read(cx).snapshot(cx);
14544 let mut selection = self
14545 .selections
14546 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14547 selection.set_head(buffer.len(), SelectionGoal::None);
14548 self.change_selections(Default::default(), window, cx, |s| {
14549 s.select(vec![selection]);
14550 });
14551 }
14552
14553 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14555 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14556 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14557 });
14558 }
14559
14560 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14563 let mut selections = self.selections.all::<Point>(&display_map);
14564 let max_point = display_map.buffer_snapshot().max_point();
14565 for selection in &mut selections {
14566 let rows = selection.spanned_rows(true, &display_map);
14567 selection.start = Point::new(rows.start.0, 0);
14568 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14569 selection.reversed = false;
14570 }
14571 self.change_selections(Default::default(), window, cx, |s| {
14572 s.select(selections);
14573 });
14574 }
14575
14576 pub fn split_selection_into_lines(
14577 &mut self,
14578 action: &SplitSelectionIntoLines,
14579 window: &mut Window,
14580 cx: &mut Context<Self>,
14581 ) {
14582 let selections = self
14583 .selections
14584 .all::<Point>(&self.display_snapshot(cx))
14585 .into_iter()
14586 .map(|selection| selection.start..selection.end)
14587 .collect::<Vec<_>>();
14588 self.unfold_ranges(&selections, true, true, cx);
14589
14590 let mut new_selection_ranges = Vec::new();
14591 {
14592 let buffer = self.buffer.read(cx).read(cx);
14593 for selection in selections {
14594 for row in selection.start.row..selection.end.row {
14595 let line_start = Point::new(row, 0);
14596 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14597
14598 if action.keep_selections {
14599 // Keep the selection range for each line
14600 let selection_start = if row == selection.start.row {
14601 selection.start
14602 } else {
14603 line_start
14604 };
14605 new_selection_ranges.push(selection_start..line_end);
14606 } else {
14607 // Collapse to cursor at end of line
14608 new_selection_ranges.push(line_end..line_end);
14609 }
14610 }
14611
14612 let is_multiline_selection = selection.start.row != selection.end.row;
14613 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14614 // so this action feels more ergonomic when paired with other selection operations
14615 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14616 if !should_skip_last {
14617 if action.keep_selections {
14618 if is_multiline_selection {
14619 let line_start = Point::new(selection.end.row, 0);
14620 new_selection_ranges.push(line_start..selection.end);
14621 } else {
14622 new_selection_ranges.push(selection.start..selection.end);
14623 }
14624 } else {
14625 new_selection_ranges.push(selection.end..selection.end);
14626 }
14627 }
14628 }
14629 }
14630 self.change_selections(Default::default(), window, cx, |s| {
14631 s.select_ranges(new_selection_ranges);
14632 });
14633 }
14634
14635 pub fn add_selection_above(
14636 &mut self,
14637 action: &AddSelectionAbove,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 self.add_selection(true, action.skip_soft_wrap, window, cx);
14642 }
14643
14644 pub fn add_selection_below(
14645 &mut self,
14646 action: &AddSelectionBelow,
14647 window: &mut Window,
14648 cx: &mut Context<Self>,
14649 ) {
14650 self.add_selection(false, action.skip_soft_wrap, window, cx);
14651 }
14652
14653 fn add_selection(
14654 &mut self,
14655 above: bool,
14656 skip_soft_wrap: bool,
14657 window: &mut Window,
14658 cx: &mut Context<Self>,
14659 ) {
14660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14661
14662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14663 let all_selections = self.selections.all::<Point>(&display_map);
14664 let text_layout_details = self.text_layout_details(window);
14665
14666 let (mut columnar_selections, new_selections_to_columnarize) = {
14667 if let Some(state) = self.add_selections_state.as_ref() {
14668 let columnar_selection_ids: HashSet<_> = state
14669 .groups
14670 .iter()
14671 .flat_map(|group| group.stack.iter())
14672 .copied()
14673 .collect();
14674
14675 all_selections
14676 .into_iter()
14677 .partition(|s| columnar_selection_ids.contains(&s.id))
14678 } else {
14679 (Vec::new(), all_selections)
14680 }
14681 };
14682
14683 let mut state = self
14684 .add_selections_state
14685 .take()
14686 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14687
14688 for selection in new_selections_to_columnarize {
14689 let range = selection.display_range(&display_map).sorted();
14690 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14691 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14692 let positions = start_x.min(end_x)..start_x.max(end_x);
14693 let mut stack = Vec::new();
14694 for row in range.start.row().0..=range.end.row().0 {
14695 if let Some(selection) = self.selections.build_columnar_selection(
14696 &display_map,
14697 DisplayRow(row),
14698 &positions,
14699 selection.reversed,
14700 &text_layout_details,
14701 ) {
14702 stack.push(selection.id);
14703 columnar_selections.push(selection);
14704 }
14705 }
14706 if !stack.is_empty() {
14707 if above {
14708 stack.reverse();
14709 }
14710 state.groups.push(AddSelectionsGroup { above, stack });
14711 }
14712 }
14713
14714 let mut final_selections = Vec::new();
14715 let end_row = if above {
14716 DisplayRow(0)
14717 } else {
14718 display_map.max_point().row()
14719 };
14720
14721 let mut last_added_item_per_group = HashMap::default();
14722 for group in state.groups.iter_mut() {
14723 if let Some(last_id) = group.stack.last() {
14724 last_added_item_per_group.insert(*last_id, group);
14725 }
14726 }
14727
14728 for selection in columnar_selections {
14729 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14730 if above == group.above {
14731 let range = selection.display_range(&display_map).sorted();
14732 debug_assert_eq!(range.start.row(), range.end.row());
14733 let mut row = range.start.row();
14734 let positions =
14735 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14736 Pixels::from(start)..Pixels::from(end)
14737 } else {
14738 let start_x =
14739 display_map.x_for_display_point(range.start, &text_layout_details);
14740 let end_x =
14741 display_map.x_for_display_point(range.end, &text_layout_details);
14742 start_x.min(end_x)..start_x.max(end_x)
14743 };
14744
14745 let mut maybe_new_selection = None;
14746 let direction = if above { -1 } else { 1 };
14747
14748 while row != end_row {
14749 if skip_soft_wrap {
14750 row = display_map
14751 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14752 .row();
14753 } else if above {
14754 row.0 -= 1;
14755 } else {
14756 row.0 += 1;
14757 }
14758
14759 if let Some(new_selection) = self.selections.build_columnar_selection(
14760 &display_map,
14761 row,
14762 &positions,
14763 selection.reversed,
14764 &text_layout_details,
14765 ) {
14766 maybe_new_selection = Some(new_selection);
14767 break;
14768 }
14769 }
14770
14771 if let Some(new_selection) = maybe_new_selection {
14772 group.stack.push(new_selection.id);
14773 if above {
14774 final_selections.push(new_selection);
14775 final_selections.push(selection);
14776 } else {
14777 final_selections.push(selection);
14778 final_selections.push(new_selection);
14779 }
14780 } else {
14781 final_selections.push(selection);
14782 }
14783 } else {
14784 group.stack.pop();
14785 }
14786 } else {
14787 final_selections.push(selection);
14788 }
14789 }
14790
14791 self.change_selections(Default::default(), window, cx, |s| {
14792 s.select(final_selections);
14793 });
14794
14795 let final_selection_ids: HashSet<_> = self
14796 .selections
14797 .all::<Point>(&display_map)
14798 .iter()
14799 .map(|s| s.id)
14800 .collect();
14801 state.groups.retain_mut(|group| {
14802 // selections might get merged above so we remove invalid items from stacks
14803 group.stack.retain(|id| final_selection_ids.contains(id));
14804
14805 // single selection in stack can be treated as initial state
14806 group.stack.len() > 1
14807 });
14808
14809 if !state.groups.is_empty() {
14810 self.add_selections_state = Some(state);
14811 }
14812 }
14813
14814 pub fn insert_snippet_at_selections(
14815 &mut self,
14816 action: &InsertSnippet,
14817 window: &mut Window,
14818 cx: &mut Context<Self>,
14819 ) {
14820 self.try_insert_snippet_at_selections(action, window, cx)
14821 .log_err();
14822 }
14823
14824 fn try_insert_snippet_at_selections(
14825 &mut self,
14826 action: &InsertSnippet,
14827 window: &mut Window,
14828 cx: &mut Context<Self>,
14829 ) -> Result<()> {
14830 let insertion_ranges = self
14831 .selections
14832 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
14833 .into_iter()
14834 .map(|selection| selection.range())
14835 .collect_vec();
14836
14837 let snippet = if let Some(snippet_body) = &action.snippet {
14838 if action.language.is_none() && action.name.is_none() {
14839 Snippet::parse(snippet_body)?
14840 } else {
14841 bail!("`snippet` is mutually exclusive with `language` and `name`")
14842 }
14843 } else if let Some(name) = &action.name {
14844 let project = self.project().context("no project")?;
14845 let snippet_store = project.read(cx).snippets().read(cx);
14846 let snippet = snippet_store
14847 .snippets_for(action.language.clone(), cx)
14848 .into_iter()
14849 .find(|snippet| snippet.name == *name)
14850 .context("snippet not found")?;
14851 Snippet::parse(&snippet.body)?
14852 } else {
14853 // todo(andrew): open modal to select snippet
14854 bail!("`name` or `snippet` is required")
14855 };
14856
14857 self.insert_snippet(&insertion_ranges, snippet, window, cx)
14858 }
14859
14860 fn select_match_ranges(
14861 &mut self,
14862 range: Range<MultiBufferOffset>,
14863 reversed: bool,
14864 replace_newest: bool,
14865 auto_scroll: Option<Autoscroll>,
14866 window: &mut Window,
14867 cx: &mut Context<Editor>,
14868 ) {
14869 self.unfold_ranges(
14870 std::slice::from_ref(&range),
14871 false,
14872 auto_scroll.is_some(),
14873 cx,
14874 );
14875 let effects = if let Some(scroll) = auto_scroll {
14876 SelectionEffects::scroll(scroll)
14877 } else {
14878 SelectionEffects::no_scroll()
14879 };
14880 self.change_selections(effects, window, cx, |s| {
14881 if replace_newest {
14882 s.delete(s.newest_anchor().id);
14883 }
14884 if reversed {
14885 s.insert_range(range.end..range.start);
14886 } else {
14887 s.insert_range(range);
14888 }
14889 });
14890 }
14891
14892 pub fn select_next_match_internal(
14893 &mut self,
14894 display_map: &DisplaySnapshot,
14895 replace_newest: bool,
14896 autoscroll: Option<Autoscroll>,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) -> Result<()> {
14900 let buffer = display_map.buffer_snapshot();
14901 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14902 if let Some(mut select_next_state) = self.select_next_state.take() {
14903 let query = &select_next_state.query;
14904 if !select_next_state.done {
14905 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14906 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14907 let mut next_selected_range = None;
14908
14909 let bytes_after_last_selection =
14910 buffer.bytes_in_range(last_selection.end..buffer.len());
14911 let bytes_before_first_selection =
14912 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14913 let query_matches = query
14914 .stream_find_iter(bytes_after_last_selection)
14915 .map(|result| (last_selection.end, result))
14916 .chain(
14917 query
14918 .stream_find_iter(bytes_before_first_selection)
14919 .map(|result| (MultiBufferOffset(0), result)),
14920 );
14921
14922 for (start_offset, query_match) in query_matches {
14923 let query_match = query_match.unwrap(); // can only fail due to I/O
14924 let offset_range =
14925 start_offset + query_match.start()..start_offset + query_match.end();
14926
14927 if !select_next_state.wordwise
14928 || (!buffer.is_inside_word(offset_range.start, None)
14929 && !buffer.is_inside_word(offset_range.end, None))
14930 {
14931 let idx = selections
14932 .partition_point(|selection| selection.end <= offset_range.start);
14933 let overlaps = selections
14934 .get(idx)
14935 .map_or(false, |selection| selection.start < offset_range.end);
14936
14937 if !overlaps {
14938 next_selected_range = Some(offset_range);
14939 break;
14940 }
14941 }
14942 }
14943
14944 if let Some(next_selected_range) = next_selected_range {
14945 self.select_match_ranges(
14946 next_selected_range,
14947 last_selection.reversed,
14948 replace_newest,
14949 autoscroll,
14950 window,
14951 cx,
14952 );
14953 } else {
14954 select_next_state.done = true;
14955 }
14956 }
14957
14958 self.select_next_state = Some(select_next_state);
14959 } else {
14960 let mut only_carets = true;
14961 let mut same_text_selected = true;
14962 let mut selected_text = None;
14963
14964 let mut selections_iter = selections.iter().peekable();
14965 while let Some(selection) = selections_iter.next() {
14966 if selection.start != selection.end {
14967 only_carets = false;
14968 }
14969
14970 if same_text_selected {
14971 if selected_text.is_none() {
14972 selected_text =
14973 Some(buffer.text_for_range(selection.range()).collect::<String>());
14974 }
14975
14976 if let Some(next_selection) = selections_iter.peek() {
14977 if next_selection.len() == selection.len() {
14978 let next_selected_text = buffer
14979 .text_for_range(next_selection.range())
14980 .collect::<String>();
14981 if Some(next_selected_text) != selected_text {
14982 same_text_selected = false;
14983 selected_text = None;
14984 }
14985 } else {
14986 same_text_selected = false;
14987 selected_text = None;
14988 }
14989 }
14990 }
14991 }
14992
14993 if only_carets {
14994 for selection in &mut selections {
14995 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14996 selection.start = word_range.start;
14997 selection.end = word_range.end;
14998 selection.goal = SelectionGoal::None;
14999 selection.reversed = false;
15000 self.select_match_ranges(
15001 selection.start..selection.end,
15002 selection.reversed,
15003 replace_newest,
15004 autoscroll,
15005 window,
15006 cx,
15007 );
15008 }
15009
15010 if selections.len() == 1 {
15011 let selection = selections
15012 .last()
15013 .expect("ensured that there's only one selection");
15014 let query = buffer
15015 .text_for_range(selection.start..selection.end)
15016 .collect::<String>();
15017 let is_empty = query.is_empty();
15018 let select_state = SelectNextState {
15019 query: self.build_query(&[query], cx)?,
15020 wordwise: true,
15021 done: is_empty,
15022 };
15023 self.select_next_state = Some(select_state);
15024 } else {
15025 self.select_next_state = None;
15026 }
15027 } else if let Some(selected_text) = selected_text {
15028 self.select_next_state = Some(SelectNextState {
15029 query: self.build_query(&[selected_text], cx)?,
15030 wordwise: false,
15031 done: false,
15032 });
15033 self.select_next_match_internal(
15034 display_map,
15035 replace_newest,
15036 autoscroll,
15037 window,
15038 cx,
15039 )?;
15040 }
15041 }
15042 Ok(())
15043 }
15044
15045 pub fn select_all_matches(
15046 &mut self,
15047 _action: &SelectAllMatches,
15048 window: &mut Window,
15049 cx: &mut Context<Self>,
15050 ) -> Result<()> {
15051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15052
15053 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15054
15055 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15056 let Some(select_next_state) = self.select_next_state.as_mut() else {
15057 return Ok(());
15058 };
15059 if select_next_state.done {
15060 return Ok(());
15061 }
15062
15063 let mut new_selections = Vec::new();
15064
15065 let reversed = self
15066 .selections
15067 .oldest::<MultiBufferOffset>(&display_map)
15068 .reversed;
15069 let buffer = display_map.buffer_snapshot();
15070 let query_matches = select_next_state
15071 .query
15072 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15073
15074 for query_match in query_matches.into_iter() {
15075 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15076 let offset_range = if reversed {
15077 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15078 } else {
15079 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15080 };
15081
15082 if !select_next_state.wordwise
15083 || (!buffer.is_inside_word(offset_range.start, None)
15084 && !buffer.is_inside_word(offset_range.end, None))
15085 {
15086 new_selections.push(offset_range.start..offset_range.end);
15087 }
15088 }
15089
15090 select_next_state.done = true;
15091
15092 if new_selections.is_empty() {
15093 log::error!("bug: new_selections is empty in select_all_matches");
15094 return Ok(());
15095 }
15096
15097 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15099 selections.select_ranges(new_selections)
15100 });
15101
15102 Ok(())
15103 }
15104
15105 pub fn select_next(
15106 &mut self,
15107 action: &SelectNext,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) -> Result<()> {
15111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15113 self.select_next_match_internal(
15114 &display_map,
15115 action.replace_newest,
15116 Some(Autoscroll::newest()),
15117 window,
15118 cx,
15119 )?;
15120 Ok(())
15121 }
15122
15123 pub fn select_previous(
15124 &mut self,
15125 action: &SelectPrevious,
15126 window: &mut Window,
15127 cx: &mut Context<Self>,
15128 ) -> Result<()> {
15129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15131 let buffer = display_map.buffer_snapshot();
15132 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15133 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15134 let query = &select_prev_state.query;
15135 if !select_prev_state.done {
15136 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15137 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15138 let mut next_selected_range = None;
15139 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15140 let bytes_before_last_selection =
15141 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15142 let bytes_after_first_selection =
15143 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15144 let query_matches = query
15145 .stream_find_iter(bytes_before_last_selection)
15146 .map(|result| (last_selection.start, result))
15147 .chain(
15148 query
15149 .stream_find_iter(bytes_after_first_selection)
15150 .map(|result| (buffer.len(), result)),
15151 );
15152 for (end_offset, query_match) in query_matches {
15153 let query_match = query_match.unwrap(); // can only fail due to I/O
15154 let offset_range =
15155 end_offset - query_match.end()..end_offset - query_match.start();
15156
15157 if !select_prev_state.wordwise
15158 || (!buffer.is_inside_word(offset_range.start, None)
15159 && !buffer.is_inside_word(offset_range.end, None))
15160 {
15161 next_selected_range = Some(offset_range);
15162 break;
15163 }
15164 }
15165
15166 if let Some(next_selected_range) = next_selected_range {
15167 self.select_match_ranges(
15168 next_selected_range,
15169 last_selection.reversed,
15170 action.replace_newest,
15171 Some(Autoscroll::newest()),
15172 window,
15173 cx,
15174 );
15175 } else {
15176 select_prev_state.done = true;
15177 }
15178 }
15179
15180 self.select_prev_state = Some(select_prev_state);
15181 } else {
15182 let mut only_carets = true;
15183 let mut same_text_selected = true;
15184 let mut selected_text = None;
15185
15186 let mut selections_iter = selections.iter().peekable();
15187 while let Some(selection) = selections_iter.next() {
15188 if selection.start != selection.end {
15189 only_carets = false;
15190 }
15191
15192 if same_text_selected {
15193 if selected_text.is_none() {
15194 selected_text =
15195 Some(buffer.text_for_range(selection.range()).collect::<String>());
15196 }
15197
15198 if let Some(next_selection) = selections_iter.peek() {
15199 if next_selection.len() == selection.len() {
15200 let next_selected_text = buffer
15201 .text_for_range(next_selection.range())
15202 .collect::<String>();
15203 if Some(next_selected_text) != selected_text {
15204 same_text_selected = false;
15205 selected_text = None;
15206 }
15207 } else {
15208 same_text_selected = false;
15209 selected_text = None;
15210 }
15211 }
15212 }
15213 }
15214
15215 if only_carets {
15216 for selection in &mut selections {
15217 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15218 selection.start = word_range.start;
15219 selection.end = word_range.end;
15220 selection.goal = SelectionGoal::None;
15221 selection.reversed = false;
15222 self.select_match_ranges(
15223 selection.start..selection.end,
15224 selection.reversed,
15225 action.replace_newest,
15226 Some(Autoscroll::newest()),
15227 window,
15228 cx,
15229 );
15230 }
15231 if selections.len() == 1 {
15232 let selection = selections
15233 .last()
15234 .expect("ensured that there's only one selection");
15235 let query = buffer
15236 .text_for_range(selection.start..selection.end)
15237 .collect::<String>();
15238 let is_empty = query.is_empty();
15239 let select_state = SelectNextState {
15240 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15241 wordwise: true,
15242 done: is_empty,
15243 };
15244 self.select_prev_state = Some(select_state);
15245 } else {
15246 self.select_prev_state = None;
15247 }
15248 } else if let Some(selected_text) = selected_text {
15249 self.select_prev_state = Some(SelectNextState {
15250 query: self
15251 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15252 wordwise: false,
15253 done: false,
15254 });
15255 self.select_previous(action, window, cx)?;
15256 }
15257 }
15258 Ok(())
15259 }
15260
15261 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15262 /// setting the case sensitivity based on the global
15263 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15264 /// editor's settings.
15265 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15266 where
15267 I: IntoIterator<Item = P>,
15268 P: AsRef<[u8]>,
15269 {
15270 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15271 || EditorSettings::get_global(cx).search.case_sensitive,
15272 |value| value,
15273 );
15274
15275 let mut builder = AhoCorasickBuilder::new();
15276 builder.ascii_case_insensitive(!case_sensitive);
15277 builder.build(patterns)
15278 }
15279
15280 pub fn find_next_match(
15281 &mut self,
15282 _: &FindNextMatch,
15283 window: &mut Window,
15284 cx: &mut Context<Self>,
15285 ) -> Result<()> {
15286 let selections = self.selections.disjoint_anchors_arc();
15287 match selections.first() {
15288 Some(first) if selections.len() >= 2 => {
15289 self.change_selections(Default::default(), window, cx, |s| {
15290 s.select_ranges([first.range()]);
15291 });
15292 }
15293 _ => self.select_next(
15294 &SelectNext {
15295 replace_newest: true,
15296 },
15297 window,
15298 cx,
15299 )?,
15300 }
15301 Ok(())
15302 }
15303
15304 pub fn find_previous_match(
15305 &mut self,
15306 _: &FindPreviousMatch,
15307 window: &mut Window,
15308 cx: &mut Context<Self>,
15309 ) -> Result<()> {
15310 let selections = self.selections.disjoint_anchors_arc();
15311 match selections.last() {
15312 Some(last) if selections.len() >= 2 => {
15313 self.change_selections(Default::default(), window, cx, |s| {
15314 s.select_ranges([last.range()]);
15315 });
15316 }
15317 _ => self.select_previous(
15318 &SelectPrevious {
15319 replace_newest: true,
15320 },
15321 window,
15322 cx,
15323 )?,
15324 }
15325 Ok(())
15326 }
15327
15328 pub fn toggle_comments(
15329 &mut self,
15330 action: &ToggleComments,
15331 window: &mut Window,
15332 cx: &mut Context<Self>,
15333 ) {
15334 if self.read_only(cx) {
15335 return;
15336 }
15337 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15338 let text_layout_details = &self.text_layout_details(window);
15339 self.transact(window, cx, |this, window, cx| {
15340 let mut selections = this
15341 .selections
15342 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15343 let mut edits = Vec::new();
15344 let mut selection_edit_ranges = Vec::new();
15345 let mut last_toggled_row = None;
15346 let snapshot = this.buffer.read(cx).read(cx);
15347 let empty_str: Arc<str> = Arc::default();
15348 let mut suffixes_inserted = Vec::new();
15349 let ignore_indent = action.ignore_indent;
15350
15351 fn comment_prefix_range(
15352 snapshot: &MultiBufferSnapshot,
15353 row: MultiBufferRow,
15354 comment_prefix: &str,
15355 comment_prefix_whitespace: &str,
15356 ignore_indent: bool,
15357 ) -> Range<Point> {
15358 let indent_size = if ignore_indent {
15359 0
15360 } else {
15361 snapshot.indent_size_for_line(row).len
15362 };
15363
15364 let start = Point::new(row.0, indent_size);
15365
15366 let mut line_bytes = snapshot
15367 .bytes_in_range(start..snapshot.max_point())
15368 .flatten()
15369 .copied();
15370
15371 // If this line currently begins with the line comment prefix, then record
15372 // the range containing the prefix.
15373 if line_bytes
15374 .by_ref()
15375 .take(comment_prefix.len())
15376 .eq(comment_prefix.bytes())
15377 {
15378 // Include any whitespace that matches the comment prefix.
15379 let matching_whitespace_len = line_bytes
15380 .zip(comment_prefix_whitespace.bytes())
15381 .take_while(|(a, b)| a == b)
15382 .count() as u32;
15383 let end = Point::new(
15384 start.row,
15385 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15386 );
15387 start..end
15388 } else {
15389 start..start
15390 }
15391 }
15392
15393 fn comment_suffix_range(
15394 snapshot: &MultiBufferSnapshot,
15395 row: MultiBufferRow,
15396 comment_suffix: &str,
15397 comment_suffix_has_leading_space: bool,
15398 ) -> Range<Point> {
15399 let end = Point::new(row.0, snapshot.line_len(row));
15400 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15401
15402 let mut line_end_bytes = snapshot
15403 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15404 .flatten()
15405 .copied();
15406
15407 let leading_space_len = if suffix_start_column > 0
15408 && line_end_bytes.next() == Some(b' ')
15409 && comment_suffix_has_leading_space
15410 {
15411 1
15412 } else {
15413 0
15414 };
15415
15416 // If this line currently begins with the line comment prefix, then record
15417 // the range containing the prefix.
15418 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15419 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15420 start..end
15421 } else {
15422 end..end
15423 }
15424 }
15425
15426 // TODO: Handle selections that cross excerpts
15427 for selection in &mut selections {
15428 let start_column = snapshot
15429 .indent_size_for_line(MultiBufferRow(selection.start.row))
15430 .len;
15431 let language = if let Some(language) =
15432 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15433 {
15434 language
15435 } else {
15436 continue;
15437 };
15438
15439 selection_edit_ranges.clear();
15440
15441 // If multiple selections contain a given row, avoid processing that
15442 // row more than once.
15443 let mut start_row = MultiBufferRow(selection.start.row);
15444 if last_toggled_row == Some(start_row) {
15445 start_row = start_row.next_row();
15446 }
15447 let end_row =
15448 if selection.end.row > selection.start.row && selection.end.column == 0 {
15449 MultiBufferRow(selection.end.row - 1)
15450 } else {
15451 MultiBufferRow(selection.end.row)
15452 };
15453 last_toggled_row = Some(end_row);
15454
15455 if start_row > end_row {
15456 continue;
15457 }
15458
15459 // If the language has line comments, toggle those.
15460 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15461
15462 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15463 if ignore_indent {
15464 full_comment_prefixes = full_comment_prefixes
15465 .into_iter()
15466 .map(|s| Arc::from(s.trim_end()))
15467 .collect();
15468 }
15469
15470 if !full_comment_prefixes.is_empty() {
15471 let first_prefix = full_comment_prefixes
15472 .first()
15473 .expect("prefixes is non-empty");
15474 let prefix_trimmed_lengths = full_comment_prefixes
15475 .iter()
15476 .map(|p| p.trim_end_matches(' ').len())
15477 .collect::<SmallVec<[usize; 4]>>();
15478
15479 let mut all_selection_lines_are_comments = true;
15480
15481 for row in start_row.0..=end_row.0 {
15482 let row = MultiBufferRow(row);
15483 if start_row < end_row && snapshot.is_line_blank(row) {
15484 continue;
15485 }
15486
15487 let prefix_range = full_comment_prefixes
15488 .iter()
15489 .zip(prefix_trimmed_lengths.iter().copied())
15490 .map(|(prefix, trimmed_prefix_len)| {
15491 comment_prefix_range(
15492 snapshot.deref(),
15493 row,
15494 &prefix[..trimmed_prefix_len],
15495 &prefix[trimmed_prefix_len..],
15496 ignore_indent,
15497 )
15498 })
15499 .max_by_key(|range| range.end.column - range.start.column)
15500 .expect("prefixes is non-empty");
15501
15502 if prefix_range.is_empty() {
15503 all_selection_lines_are_comments = false;
15504 }
15505
15506 selection_edit_ranges.push(prefix_range);
15507 }
15508
15509 if all_selection_lines_are_comments {
15510 edits.extend(
15511 selection_edit_ranges
15512 .iter()
15513 .cloned()
15514 .map(|range| (range, empty_str.clone())),
15515 );
15516 } else {
15517 let min_column = selection_edit_ranges
15518 .iter()
15519 .map(|range| range.start.column)
15520 .min()
15521 .unwrap_or(0);
15522 edits.extend(selection_edit_ranges.iter().map(|range| {
15523 let position = Point::new(range.start.row, min_column);
15524 (position..position, first_prefix.clone())
15525 }));
15526 }
15527 } else if let Some(BlockCommentConfig {
15528 start: full_comment_prefix,
15529 end: comment_suffix,
15530 ..
15531 }) = language.block_comment()
15532 {
15533 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15534 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15535 let prefix_range = comment_prefix_range(
15536 snapshot.deref(),
15537 start_row,
15538 comment_prefix,
15539 comment_prefix_whitespace,
15540 ignore_indent,
15541 );
15542 let suffix_range = comment_suffix_range(
15543 snapshot.deref(),
15544 end_row,
15545 comment_suffix.trim_start_matches(' '),
15546 comment_suffix.starts_with(' '),
15547 );
15548
15549 if prefix_range.is_empty() || suffix_range.is_empty() {
15550 edits.push((
15551 prefix_range.start..prefix_range.start,
15552 full_comment_prefix.clone(),
15553 ));
15554 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15555 suffixes_inserted.push((end_row, comment_suffix.len()));
15556 } else {
15557 edits.push((prefix_range, empty_str.clone()));
15558 edits.push((suffix_range, empty_str.clone()));
15559 }
15560 } else {
15561 continue;
15562 }
15563 }
15564
15565 drop(snapshot);
15566 this.buffer.update(cx, |buffer, cx| {
15567 buffer.edit(edits, None, cx);
15568 });
15569
15570 // Adjust selections so that they end before any comment suffixes that
15571 // were inserted.
15572 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15573 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15574 let snapshot = this.buffer.read(cx).read(cx);
15575 for selection in &mut selections {
15576 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15577 match row.cmp(&MultiBufferRow(selection.end.row)) {
15578 Ordering::Less => {
15579 suffixes_inserted.next();
15580 continue;
15581 }
15582 Ordering::Greater => break,
15583 Ordering::Equal => {
15584 if selection.end.column == snapshot.line_len(row) {
15585 if selection.is_empty() {
15586 selection.start.column -= suffix_len as u32;
15587 }
15588 selection.end.column -= suffix_len as u32;
15589 }
15590 break;
15591 }
15592 }
15593 }
15594 }
15595
15596 drop(snapshot);
15597 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15598
15599 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15600 let selections_on_single_row = selections.windows(2).all(|selections| {
15601 selections[0].start.row == selections[1].start.row
15602 && selections[0].end.row == selections[1].end.row
15603 && selections[0].start.row == selections[0].end.row
15604 });
15605 let selections_selecting = selections
15606 .iter()
15607 .any(|selection| selection.start != selection.end);
15608 let advance_downwards = action.advance_downwards
15609 && selections_on_single_row
15610 && !selections_selecting
15611 && !matches!(this.mode, EditorMode::SingleLine);
15612
15613 if advance_downwards {
15614 let snapshot = this.buffer.read(cx).snapshot(cx);
15615
15616 this.change_selections(Default::default(), window, cx, |s| {
15617 s.move_cursors_with(|display_snapshot, display_point, _| {
15618 let mut point = display_point.to_point(display_snapshot);
15619 point.row += 1;
15620 point = snapshot.clip_point(point, Bias::Left);
15621 let display_point = point.to_display_point(display_snapshot);
15622 let goal = SelectionGoal::HorizontalPosition(
15623 display_snapshot
15624 .x_for_display_point(display_point, text_layout_details)
15625 .into(),
15626 );
15627 (display_point, goal)
15628 })
15629 });
15630 }
15631 });
15632 }
15633
15634 pub fn select_enclosing_symbol(
15635 &mut self,
15636 _: &SelectEnclosingSymbol,
15637 window: &mut Window,
15638 cx: &mut Context<Self>,
15639 ) {
15640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15641
15642 let buffer = self.buffer.read(cx).snapshot(cx);
15643 let old_selections = self
15644 .selections
15645 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15646 .into_boxed_slice();
15647
15648 fn update_selection(
15649 selection: &Selection<MultiBufferOffset>,
15650 buffer_snap: &MultiBufferSnapshot,
15651 ) -> Option<Selection<MultiBufferOffset>> {
15652 let cursor = selection.head();
15653 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15654 for symbol in symbols.iter().rev() {
15655 let start = symbol.range.start.to_offset(buffer_snap);
15656 let end = symbol.range.end.to_offset(buffer_snap);
15657 let new_range = start..end;
15658 if start < selection.start || end > selection.end {
15659 return Some(Selection {
15660 id: selection.id,
15661 start: new_range.start,
15662 end: new_range.end,
15663 goal: SelectionGoal::None,
15664 reversed: selection.reversed,
15665 });
15666 }
15667 }
15668 None
15669 }
15670
15671 let mut selected_larger_symbol = false;
15672 let new_selections = old_selections
15673 .iter()
15674 .map(|selection| match update_selection(selection, &buffer) {
15675 Some(new_selection) => {
15676 if new_selection.range() != selection.range() {
15677 selected_larger_symbol = true;
15678 }
15679 new_selection
15680 }
15681 None => selection.clone(),
15682 })
15683 .collect::<Vec<_>>();
15684
15685 if selected_larger_symbol {
15686 self.change_selections(Default::default(), window, cx, |s| {
15687 s.select(new_selections);
15688 });
15689 }
15690 }
15691
15692 pub fn select_larger_syntax_node(
15693 &mut self,
15694 _: &SelectLargerSyntaxNode,
15695 window: &mut Window,
15696 cx: &mut Context<Self>,
15697 ) {
15698 let Some(visible_row_count) = self.visible_row_count() else {
15699 return;
15700 };
15701 let old_selections: Box<[_]> = self
15702 .selections
15703 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15704 .into();
15705 if old_selections.is_empty() {
15706 return;
15707 }
15708
15709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15710
15711 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15712 let buffer = self.buffer.read(cx).snapshot(cx);
15713
15714 let mut selected_larger_node = false;
15715 let mut new_selections = old_selections
15716 .iter()
15717 .map(|selection| {
15718 let old_range = selection.start..selection.end;
15719
15720 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15721 // manually select word at selection
15722 if ["string_content", "inline"].contains(&node.kind()) {
15723 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15724 // ignore if word is already selected
15725 if !word_range.is_empty() && old_range != word_range {
15726 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15727 // only select word if start and end point belongs to same word
15728 if word_range == last_word_range {
15729 selected_larger_node = true;
15730 return Selection {
15731 id: selection.id,
15732 start: word_range.start,
15733 end: word_range.end,
15734 goal: SelectionGoal::None,
15735 reversed: selection.reversed,
15736 };
15737 }
15738 }
15739 }
15740 }
15741
15742 let mut new_range = old_range.clone();
15743 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15744 new_range = range;
15745 if !node.is_named() {
15746 continue;
15747 }
15748 if !display_map.intersects_fold(new_range.start)
15749 && !display_map.intersects_fold(new_range.end)
15750 {
15751 break;
15752 }
15753 }
15754
15755 selected_larger_node |= new_range != old_range;
15756 Selection {
15757 id: selection.id,
15758 start: new_range.start,
15759 end: new_range.end,
15760 goal: SelectionGoal::None,
15761 reversed: selection.reversed,
15762 }
15763 })
15764 .collect::<Vec<_>>();
15765
15766 if !selected_larger_node {
15767 return; // don't put this call in the history
15768 }
15769
15770 // scroll based on transformation done to the last selection created by the user
15771 let (last_old, last_new) = old_selections
15772 .last()
15773 .zip(new_selections.last().cloned())
15774 .expect("old_selections isn't empty");
15775
15776 // revert selection
15777 let is_selection_reversed = {
15778 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15779 new_selections.last_mut().expect("checked above").reversed =
15780 should_newest_selection_be_reversed;
15781 should_newest_selection_be_reversed
15782 };
15783
15784 if selected_larger_node {
15785 self.select_syntax_node_history.disable_clearing = true;
15786 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15787 s.select(new_selections.clone());
15788 });
15789 self.select_syntax_node_history.disable_clearing = false;
15790 }
15791
15792 let start_row = last_new.start.to_display_point(&display_map).row().0;
15793 let end_row = last_new.end.to_display_point(&display_map).row().0;
15794 let selection_height = end_row - start_row + 1;
15795 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15796
15797 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15798 let scroll_behavior = if fits_on_the_screen {
15799 self.request_autoscroll(Autoscroll::fit(), cx);
15800 SelectSyntaxNodeScrollBehavior::FitSelection
15801 } else if is_selection_reversed {
15802 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15803 SelectSyntaxNodeScrollBehavior::CursorTop
15804 } else {
15805 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15806 SelectSyntaxNodeScrollBehavior::CursorBottom
15807 };
15808
15809 self.select_syntax_node_history.push((
15810 old_selections,
15811 scroll_behavior,
15812 is_selection_reversed,
15813 ));
15814 }
15815
15816 pub fn select_smaller_syntax_node(
15817 &mut self,
15818 _: &SelectSmallerSyntaxNode,
15819 window: &mut Window,
15820 cx: &mut Context<Self>,
15821 ) {
15822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15823
15824 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15825 self.select_syntax_node_history.pop()
15826 {
15827 if let Some(selection) = selections.last_mut() {
15828 selection.reversed = is_selection_reversed;
15829 }
15830
15831 self.select_syntax_node_history.disable_clearing = true;
15832 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15833 s.select(selections.to_vec());
15834 });
15835 self.select_syntax_node_history.disable_clearing = false;
15836
15837 match scroll_behavior {
15838 SelectSyntaxNodeScrollBehavior::CursorTop => {
15839 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15840 }
15841 SelectSyntaxNodeScrollBehavior::FitSelection => {
15842 self.request_autoscroll(Autoscroll::fit(), cx);
15843 }
15844 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15845 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15846 }
15847 }
15848 }
15849 }
15850
15851 pub fn unwrap_syntax_node(
15852 &mut self,
15853 _: &UnwrapSyntaxNode,
15854 window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15858
15859 let buffer = self.buffer.read(cx).snapshot(cx);
15860 let selections = self
15861 .selections
15862 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15863 .into_iter()
15864 // subtracting the offset requires sorting
15865 .sorted_by_key(|i| i.start);
15866
15867 let full_edits = selections
15868 .into_iter()
15869 .filter_map(|selection| {
15870 let child = if selection.is_empty()
15871 && let Some((_, ancestor_range)) =
15872 buffer.syntax_ancestor(selection.start..selection.end)
15873 {
15874 ancestor_range
15875 } else {
15876 selection.range()
15877 };
15878
15879 let mut parent = child.clone();
15880 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15881 parent = ancestor_range;
15882 if parent.start < child.start || parent.end > child.end {
15883 break;
15884 }
15885 }
15886
15887 if parent == child {
15888 return None;
15889 }
15890 let text = buffer.text_for_range(child).collect::<String>();
15891 Some((selection.id, parent, text))
15892 })
15893 .collect::<Vec<_>>();
15894 if full_edits.is_empty() {
15895 return;
15896 }
15897
15898 self.transact(window, cx, |this, window, cx| {
15899 this.buffer.update(cx, |buffer, cx| {
15900 buffer.edit(
15901 full_edits
15902 .iter()
15903 .map(|(_, p, t)| (p.clone(), t.clone()))
15904 .collect::<Vec<_>>(),
15905 None,
15906 cx,
15907 );
15908 });
15909 this.change_selections(Default::default(), window, cx, |s| {
15910 let mut offset = 0;
15911 let mut selections = vec![];
15912 for (id, parent, text) in full_edits {
15913 let start = parent.start - offset;
15914 offset += (parent.end - parent.start) - text.len();
15915 selections.push(Selection {
15916 id,
15917 start,
15918 end: start + text.len(),
15919 reversed: false,
15920 goal: Default::default(),
15921 });
15922 }
15923 s.select(selections);
15924 });
15925 });
15926 }
15927
15928 pub fn select_next_syntax_node(
15929 &mut self,
15930 _: &SelectNextSyntaxNode,
15931 window: &mut Window,
15932 cx: &mut Context<Self>,
15933 ) {
15934 let old_selections: Box<[_]> = self
15935 .selections
15936 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15937 .into();
15938 if old_selections.is_empty() {
15939 return;
15940 }
15941
15942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15943
15944 let buffer = self.buffer.read(cx).snapshot(cx);
15945 let mut selected_sibling = false;
15946
15947 let new_selections = old_selections
15948 .iter()
15949 .map(|selection| {
15950 let old_range = selection.start..selection.end;
15951
15952 let old_range =
15953 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15954 let excerpt = buffer.excerpt_containing(old_range.clone());
15955
15956 if let Some(mut excerpt) = excerpt
15957 && let Some(node) = excerpt
15958 .buffer()
15959 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15960 {
15961 let new_range = excerpt.map_range_from_buffer(
15962 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15963 );
15964 selected_sibling = true;
15965 Selection {
15966 id: selection.id,
15967 start: new_range.start,
15968 end: new_range.end,
15969 goal: SelectionGoal::None,
15970 reversed: selection.reversed,
15971 }
15972 } else {
15973 selection.clone()
15974 }
15975 })
15976 .collect::<Vec<_>>();
15977
15978 if selected_sibling {
15979 self.change_selections(
15980 SelectionEffects::scroll(Autoscroll::fit()),
15981 window,
15982 cx,
15983 |s| {
15984 s.select(new_selections);
15985 },
15986 );
15987 }
15988 }
15989
15990 pub fn select_prev_syntax_node(
15991 &mut self,
15992 _: &SelectPreviousSyntaxNode,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 let old_selections: Box<[_]> = self
15997 .selections
15998 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15999 .into();
16000 if old_selections.is_empty() {
16001 return;
16002 }
16003
16004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16005
16006 let buffer = self.buffer.read(cx).snapshot(cx);
16007 let mut selected_sibling = false;
16008
16009 let new_selections = old_selections
16010 .iter()
16011 .map(|selection| {
16012 let old_range = selection.start..selection.end;
16013 let old_range =
16014 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16015 let excerpt = buffer.excerpt_containing(old_range.clone());
16016
16017 if let Some(mut excerpt) = excerpt
16018 && let Some(node) = excerpt
16019 .buffer()
16020 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16021 {
16022 let new_range = excerpt.map_range_from_buffer(
16023 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16024 );
16025 selected_sibling = true;
16026 Selection {
16027 id: selection.id,
16028 start: new_range.start,
16029 end: new_range.end,
16030 goal: SelectionGoal::None,
16031 reversed: selection.reversed,
16032 }
16033 } else {
16034 selection.clone()
16035 }
16036 })
16037 .collect::<Vec<_>>();
16038
16039 if selected_sibling {
16040 self.change_selections(
16041 SelectionEffects::scroll(Autoscroll::fit()),
16042 window,
16043 cx,
16044 |s| {
16045 s.select(new_selections);
16046 },
16047 );
16048 }
16049 }
16050
16051 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16052 if !EditorSettings::get_global(cx).gutter.runnables {
16053 self.clear_tasks();
16054 return Task::ready(());
16055 }
16056 let project = self.project().map(Entity::downgrade);
16057 let task_sources = self.lsp_task_sources(cx);
16058 let multi_buffer = self.buffer.downgrade();
16059 cx.spawn_in(window, async move |editor, cx| {
16060 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16061 let Some(project) = project.and_then(|p| p.upgrade()) else {
16062 return;
16063 };
16064 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16065 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16066 }) else {
16067 return;
16068 };
16069
16070 let hide_runnables = project
16071 .update(cx, |project, _| project.is_via_collab())
16072 .unwrap_or(true);
16073 if hide_runnables {
16074 return;
16075 }
16076 let new_rows =
16077 cx.background_spawn({
16078 let snapshot = display_snapshot.clone();
16079 async move {
16080 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16081 }
16082 })
16083 .await;
16084 let Ok(lsp_tasks) =
16085 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16086 else {
16087 return;
16088 };
16089 let lsp_tasks = lsp_tasks.await;
16090
16091 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16092 lsp_tasks
16093 .into_iter()
16094 .flat_map(|(kind, tasks)| {
16095 tasks.into_iter().filter_map(move |(location, task)| {
16096 Some((kind.clone(), location?, task))
16097 })
16098 })
16099 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16100 let buffer = location.target.buffer;
16101 let buffer_snapshot = buffer.read(cx).snapshot();
16102 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16103 |(excerpt_id, snapshot, _)| {
16104 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16105 display_snapshot
16106 .buffer_snapshot()
16107 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16108 } else {
16109 None
16110 }
16111 },
16112 );
16113 if let Some(offset) = offset {
16114 let task_buffer_range =
16115 location.target.range.to_point(&buffer_snapshot);
16116 let context_buffer_range =
16117 task_buffer_range.to_offset(&buffer_snapshot);
16118 let context_range = BufferOffset(context_buffer_range.start)
16119 ..BufferOffset(context_buffer_range.end);
16120
16121 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16122 .or_insert_with(|| RunnableTasks {
16123 templates: Vec::new(),
16124 offset,
16125 column: task_buffer_range.start.column,
16126 extra_variables: HashMap::default(),
16127 context_range,
16128 })
16129 .templates
16130 .push((kind, task.original_task().clone()));
16131 }
16132
16133 acc
16134 })
16135 }) else {
16136 return;
16137 };
16138
16139 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16140 buffer.language_settings(cx).tasks.prefer_lsp
16141 }) else {
16142 return;
16143 };
16144
16145 let rows = Self::runnable_rows(
16146 project,
16147 display_snapshot,
16148 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16149 new_rows,
16150 cx.clone(),
16151 )
16152 .await;
16153 editor
16154 .update(cx, |editor, _| {
16155 editor.clear_tasks();
16156 for (key, mut value) in rows {
16157 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16158 value.templates.extend(lsp_tasks.templates);
16159 }
16160
16161 editor.insert_tasks(key, value);
16162 }
16163 for (key, value) in lsp_tasks_by_rows {
16164 editor.insert_tasks(key, value);
16165 }
16166 })
16167 .ok();
16168 })
16169 }
16170 fn fetch_runnable_ranges(
16171 snapshot: &DisplaySnapshot,
16172 range: Range<Anchor>,
16173 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16174 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16175 }
16176
16177 fn runnable_rows(
16178 project: Entity<Project>,
16179 snapshot: DisplaySnapshot,
16180 prefer_lsp: bool,
16181 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16182 cx: AsyncWindowContext,
16183 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16184 cx.spawn(async move |cx| {
16185 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16186 for (run_range, mut runnable) in runnable_ranges {
16187 let Some(tasks) = cx
16188 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16189 .ok()
16190 else {
16191 continue;
16192 };
16193 let mut tasks = tasks.await;
16194
16195 if prefer_lsp {
16196 tasks.retain(|(task_kind, _)| {
16197 !matches!(task_kind, TaskSourceKind::Language { .. })
16198 });
16199 }
16200 if tasks.is_empty() {
16201 continue;
16202 }
16203
16204 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16205 let Some(row) = snapshot
16206 .buffer_snapshot()
16207 .buffer_line_for_row(MultiBufferRow(point.row))
16208 .map(|(_, range)| range.start.row)
16209 else {
16210 continue;
16211 };
16212
16213 let context_range =
16214 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16215 runnable_rows.push((
16216 (runnable.buffer_id, row),
16217 RunnableTasks {
16218 templates: tasks,
16219 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16220 context_range,
16221 column: point.column,
16222 extra_variables: runnable.extra_captures,
16223 },
16224 ));
16225 }
16226 runnable_rows
16227 })
16228 }
16229
16230 fn templates_with_tags(
16231 project: &Entity<Project>,
16232 runnable: &mut Runnable,
16233 cx: &mut App,
16234 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16235 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16236 let (worktree_id, file) = project
16237 .buffer_for_id(runnable.buffer, cx)
16238 .and_then(|buffer| buffer.read(cx).file())
16239 .map(|file| (file.worktree_id(cx), file.clone()))
16240 .unzip();
16241
16242 (
16243 project.task_store().read(cx).task_inventory().cloned(),
16244 worktree_id,
16245 file,
16246 )
16247 });
16248
16249 let tags = mem::take(&mut runnable.tags);
16250 let language = runnable.language.clone();
16251 cx.spawn(async move |cx| {
16252 let mut templates_with_tags = Vec::new();
16253 if let Some(inventory) = inventory {
16254 for RunnableTag(tag) in tags {
16255 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16256 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16257 }) else {
16258 return templates_with_tags;
16259 };
16260 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16261 move |(_, template)| {
16262 template.tags.iter().any(|source_tag| source_tag == &tag)
16263 },
16264 ));
16265 }
16266 }
16267 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16268
16269 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16270 // Strongest source wins; if we have worktree tag binding, prefer that to
16271 // global and language bindings;
16272 // if we have a global binding, prefer that to language binding.
16273 let first_mismatch = templates_with_tags
16274 .iter()
16275 .position(|(tag_source, _)| tag_source != leading_tag_source);
16276 if let Some(index) = first_mismatch {
16277 templates_with_tags.truncate(index);
16278 }
16279 }
16280
16281 templates_with_tags
16282 })
16283 }
16284
16285 pub fn move_to_enclosing_bracket(
16286 &mut self,
16287 _: &MoveToEnclosingBracket,
16288 window: &mut Window,
16289 cx: &mut Context<Self>,
16290 ) {
16291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16292 self.change_selections(Default::default(), window, cx, |s| {
16293 s.move_offsets_with(|snapshot, selection| {
16294 let Some(enclosing_bracket_ranges) =
16295 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16296 else {
16297 return;
16298 };
16299
16300 let mut best_length = usize::MAX;
16301 let mut best_inside = false;
16302 let mut best_in_bracket_range = false;
16303 let mut best_destination = None;
16304 for (open, close) in enclosing_bracket_ranges {
16305 let close = close.to_inclusive();
16306 let length = *close.end() - open.start;
16307 let inside = selection.start >= open.end && selection.end <= *close.start();
16308 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16309 || close.contains(&selection.head());
16310
16311 // If best is next to a bracket and current isn't, skip
16312 if !in_bracket_range && best_in_bracket_range {
16313 continue;
16314 }
16315
16316 // Prefer smaller lengths unless best is inside and current isn't
16317 if length > best_length && (best_inside || !inside) {
16318 continue;
16319 }
16320
16321 best_length = length;
16322 best_inside = inside;
16323 best_in_bracket_range = in_bracket_range;
16324 best_destination = Some(
16325 if close.contains(&selection.start) && close.contains(&selection.end) {
16326 if inside { open.end } else { open.start }
16327 } else if inside {
16328 *close.start()
16329 } else {
16330 *close.end()
16331 },
16332 );
16333 }
16334
16335 if let Some(destination) = best_destination {
16336 selection.collapse_to(destination, SelectionGoal::None);
16337 }
16338 })
16339 });
16340 }
16341
16342 pub fn undo_selection(
16343 &mut self,
16344 _: &UndoSelection,
16345 window: &mut Window,
16346 cx: &mut Context<Self>,
16347 ) {
16348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16349 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16350 self.selection_history.mode = SelectionHistoryMode::Undoing;
16351 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16352 this.end_selection(window, cx);
16353 this.change_selections(
16354 SelectionEffects::scroll(Autoscroll::newest()),
16355 window,
16356 cx,
16357 |s| s.select_anchors(entry.selections.to_vec()),
16358 );
16359 });
16360 self.selection_history.mode = SelectionHistoryMode::Normal;
16361
16362 self.select_next_state = entry.select_next_state;
16363 self.select_prev_state = entry.select_prev_state;
16364 self.add_selections_state = entry.add_selections_state;
16365 }
16366 }
16367
16368 pub fn redo_selection(
16369 &mut self,
16370 _: &RedoSelection,
16371 window: &mut Window,
16372 cx: &mut Context<Self>,
16373 ) {
16374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16375 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16376 self.selection_history.mode = SelectionHistoryMode::Redoing;
16377 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16378 this.end_selection(window, cx);
16379 this.change_selections(
16380 SelectionEffects::scroll(Autoscroll::newest()),
16381 window,
16382 cx,
16383 |s| s.select_anchors(entry.selections.to_vec()),
16384 );
16385 });
16386 self.selection_history.mode = SelectionHistoryMode::Normal;
16387
16388 self.select_next_state = entry.select_next_state;
16389 self.select_prev_state = entry.select_prev_state;
16390 self.add_selections_state = entry.add_selections_state;
16391 }
16392 }
16393
16394 pub fn expand_excerpts(
16395 &mut self,
16396 action: &ExpandExcerpts,
16397 _: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) {
16400 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16401 }
16402
16403 pub fn expand_excerpts_down(
16404 &mut self,
16405 action: &ExpandExcerptsDown,
16406 _: &mut Window,
16407 cx: &mut Context<Self>,
16408 ) {
16409 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16410 }
16411
16412 pub fn expand_excerpts_up(
16413 &mut self,
16414 action: &ExpandExcerptsUp,
16415 _: &mut Window,
16416 cx: &mut Context<Self>,
16417 ) {
16418 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16419 }
16420
16421 pub fn expand_excerpts_for_direction(
16422 &mut self,
16423 lines: u32,
16424 direction: ExpandExcerptDirection,
16425
16426 cx: &mut Context<Self>,
16427 ) {
16428 let selections = self.selections.disjoint_anchors_arc();
16429
16430 let lines = if lines == 0 {
16431 EditorSettings::get_global(cx).expand_excerpt_lines
16432 } else {
16433 lines
16434 };
16435
16436 self.buffer.update(cx, |buffer, cx| {
16437 let snapshot = buffer.snapshot(cx);
16438 let mut excerpt_ids = selections
16439 .iter()
16440 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16441 .collect::<Vec<_>>();
16442 excerpt_ids.sort();
16443 excerpt_ids.dedup();
16444 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16445 })
16446 }
16447
16448 pub fn expand_excerpt(
16449 &mut self,
16450 excerpt: ExcerptId,
16451 direction: ExpandExcerptDirection,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) {
16455 let current_scroll_position = self.scroll_position(cx);
16456 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16457 let mut scroll = None;
16458
16459 if direction == ExpandExcerptDirection::Down {
16460 let multi_buffer = self.buffer.read(cx);
16461 let snapshot = multi_buffer.snapshot(cx);
16462 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16463 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16464 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16465 {
16466 let buffer_snapshot = buffer.read(cx).snapshot();
16467 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16468 let last_row = buffer_snapshot.max_point().row;
16469 let lines_below = last_row.saturating_sub(excerpt_end_row);
16470 if lines_below >= lines_to_expand {
16471 scroll = Some(
16472 current_scroll_position
16473 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16474 );
16475 }
16476 }
16477 }
16478 if direction == ExpandExcerptDirection::Up
16479 && self
16480 .buffer
16481 .read(cx)
16482 .snapshot(cx)
16483 .excerpt_before(excerpt)
16484 .is_none()
16485 {
16486 scroll = Some(current_scroll_position);
16487 }
16488
16489 self.buffer.update(cx, |buffer, cx| {
16490 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16491 });
16492
16493 if let Some(new_scroll_position) = scroll {
16494 self.set_scroll_position(new_scroll_position, window, cx);
16495 }
16496 }
16497
16498 pub fn go_to_singleton_buffer_point(
16499 &mut self,
16500 point: Point,
16501 window: &mut Window,
16502 cx: &mut Context<Self>,
16503 ) {
16504 self.go_to_singleton_buffer_range(point..point, window, cx);
16505 }
16506
16507 pub fn go_to_singleton_buffer_range(
16508 &mut self,
16509 range: Range<Point>,
16510 window: &mut Window,
16511 cx: &mut Context<Self>,
16512 ) {
16513 let multibuffer = self.buffer().read(cx);
16514 let Some(buffer) = multibuffer.as_singleton() else {
16515 return;
16516 };
16517 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16518 return;
16519 };
16520 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16521 return;
16522 };
16523 self.change_selections(
16524 SelectionEffects::default().nav_history(true),
16525 window,
16526 cx,
16527 |s| s.select_anchor_ranges([start..end]),
16528 );
16529 }
16530
16531 pub fn go_to_diagnostic(
16532 &mut self,
16533 action: &GoToDiagnostic,
16534 window: &mut Window,
16535 cx: &mut Context<Self>,
16536 ) {
16537 if !self.diagnostics_enabled() {
16538 return;
16539 }
16540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16541 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16542 }
16543
16544 pub fn go_to_prev_diagnostic(
16545 &mut self,
16546 action: &GoToPreviousDiagnostic,
16547 window: &mut Window,
16548 cx: &mut Context<Self>,
16549 ) {
16550 if !self.diagnostics_enabled() {
16551 return;
16552 }
16553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16554 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16555 }
16556
16557 pub fn go_to_diagnostic_impl(
16558 &mut self,
16559 direction: Direction,
16560 severity: GoToDiagnosticSeverityFilter,
16561 window: &mut Window,
16562 cx: &mut Context<Self>,
16563 ) {
16564 let buffer = self.buffer.read(cx).snapshot(cx);
16565 let selection = self
16566 .selections
16567 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16568
16569 let mut active_group_id = None;
16570 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16571 && active_group.active_range.start.to_offset(&buffer) == selection.start
16572 {
16573 active_group_id = Some(active_group.group_id);
16574 }
16575
16576 fn filtered<'a>(
16577 severity: GoToDiagnosticSeverityFilter,
16578 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16579 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16580 diagnostics
16581 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16582 .filter(|entry| entry.range.start != entry.range.end)
16583 .filter(|entry| !entry.diagnostic.is_unnecessary)
16584 }
16585
16586 let before = filtered(
16587 severity,
16588 buffer
16589 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16590 .filter(|entry| entry.range.start <= selection.start),
16591 );
16592 let after = filtered(
16593 severity,
16594 buffer
16595 .diagnostics_in_range(selection.start..buffer.len())
16596 .filter(|entry| entry.range.start >= selection.start),
16597 );
16598
16599 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16600 if direction == Direction::Prev {
16601 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16602 {
16603 for diagnostic in prev_diagnostics.into_iter().rev() {
16604 if diagnostic.range.start != selection.start
16605 || active_group_id
16606 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16607 {
16608 found = Some(diagnostic);
16609 break 'outer;
16610 }
16611 }
16612 }
16613 } else {
16614 for diagnostic in after.chain(before) {
16615 if diagnostic.range.start != selection.start
16616 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16617 {
16618 found = Some(diagnostic);
16619 break;
16620 }
16621 }
16622 }
16623 let Some(next_diagnostic) = found else {
16624 return;
16625 };
16626
16627 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16628 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16629 return;
16630 };
16631 let snapshot = self.snapshot(window, cx);
16632 if snapshot.intersects_fold(next_diagnostic.range.start) {
16633 self.unfold_ranges(
16634 std::slice::from_ref(&next_diagnostic.range),
16635 true,
16636 false,
16637 cx,
16638 );
16639 }
16640 self.change_selections(Default::default(), window, cx, |s| {
16641 s.select_ranges(vec![
16642 next_diagnostic.range.start..next_diagnostic.range.start,
16643 ])
16644 });
16645 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16646 self.refresh_edit_prediction(false, true, window, cx);
16647 }
16648
16649 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16651 let snapshot = self.snapshot(window, cx);
16652 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16653 self.go_to_hunk_before_or_after_position(
16654 &snapshot,
16655 selection.head(),
16656 Direction::Next,
16657 window,
16658 cx,
16659 );
16660 }
16661
16662 pub fn go_to_hunk_before_or_after_position(
16663 &mut self,
16664 snapshot: &EditorSnapshot,
16665 position: Point,
16666 direction: Direction,
16667 window: &mut Window,
16668 cx: &mut Context<Editor>,
16669 ) {
16670 let row = if direction == Direction::Next {
16671 self.hunk_after_position(snapshot, position)
16672 .map(|hunk| hunk.row_range.start)
16673 } else {
16674 self.hunk_before_position(snapshot, position)
16675 };
16676
16677 if let Some(row) = row {
16678 let destination = Point::new(row.0, 0);
16679 let autoscroll = Autoscroll::center();
16680
16681 self.unfold_ranges(&[destination..destination], false, false, cx);
16682 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16683 s.select_ranges([destination..destination]);
16684 });
16685 }
16686 }
16687
16688 fn hunk_after_position(
16689 &mut self,
16690 snapshot: &EditorSnapshot,
16691 position: Point,
16692 ) -> Option<MultiBufferDiffHunk> {
16693 snapshot
16694 .buffer_snapshot()
16695 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16696 .find(|hunk| hunk.row_range.start.0 > position.row)
16697 .or_else(|| {
16698 snapshot
16699 .buffer_snapshot()
16700 .diff_hunks_in_range(Point::zero()..position)
16701 .find(|hunk| hunk.row_range.end.0 < position.row)
16702 })
16703 }
16704
16705 fn go_to_prev_hunk(
16706 &mut self,
16707 _: &GoToPreviousHunk,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) {
16711 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16712 let snapshot = self.snapshot(window, cx);
16713 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16714 self.go_to_hunk_before_or_after_position(
16715 &snapshot,
16716 selection.head(),
16717 Direction::Prev,
16718 window,
16719 cx,
16720 );
16721 }
16722
16723 fn hunk_before_position(
16724 &mut self,
16725 snapshot: &EditorSnapshot,
16726 position: Point,
16727 ) -> Option<MultiBufferRow> {
16728 snapshot
16729 .buffer_snapshot()
16730 .diff_hunk_before(position)
16731 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16732 }
16733
16734 fn go_to_next_change(
16735 &mut self,
16736 _: &GoToNextChange,
16737 window: &mut Window,
16738 cx: &mut Context<Self>,
16739 ) {
16740 if let Some(selections) = self
16741 .change_list
16742 .next_change(1, Direction::Next)
16743 .map(|s| s.to_vec())
16744 {
16745 self.change_selections(Default::default(), window, cx, |s| {
16746 let map = s.display_snapshot();
16747 s.select_display_ranges(selections.iter().map(|a| {
16748 let point = a.to_display_point(&map);
16749 point..point
16750 }))
16751 })
16752 }
16753 }
16754
16755 fn go_to_previous_change(
16756 &mut self,
16757 _: &GoToPreviousChange,
16758 window: &mut Window,
16759 cx: &mut Context<Self>,
16760 ) {
16761 if let Some(selections) = self
16762 .change_list
16763 .next_change(1, Direction::Prev)
16764 .map(|s| s.to_vec())
16765 {
16766 self.change_selections(Default::default(), window, cx, |s| {
16767 let map = s.display_snapshot();
16768 s.select_display_ranges(selections.iter().map(|a| {
16769 let point = a.to_display_point(&map);
16770 point..point
16771 }))
16772 })
16773 }
16774 }
16775
16776 pub fn go_to_next_document_highlight(
16777 &mut self,
16778 _: &GoToNextDocumentHighlight,
16779 window: &mut Window,
16780 cx: &mut Context<Self>,
16781 ) {
16782 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16783 }
16784
16785 pub fn go_to_prev_document_highlight(
16786 &mut self,
16787 _: &GoToPreviousDocumentHighlight,
16788 window: &mut Window,
16789 cx: &mut Context<Self>,
16790 ) {
16791 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16792 }
16793
16794 pub fn go_to_document_highlight_before_or_after_position(
16795 &mut self,
16796 direction: Direction,
16797 window: &mut Window,
16798 cx: &mut Context<Editor>,
16799 ) {
16800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16801 let snapshot = self.snapshot(window, cx);
16802 let buffer = &snapshot.buffer_snapshot();
16803 let position = self
16804 .selections
16805 .newest::<Point>(&snapshot.display_snapshot)
16806 .head();
16807 let anchor_position = buffer.anchor_after(position);
16808
16809 // Get all document highlights (both read and write)
16810 let mut all_highlights = Vec::new();
16811
16812 if let Some((_, read_highlights)) = self
16813 .background_highlights
16814 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16815 {
16816 all_highlights.extend(read_highlights.iter());
16817 }
16818
16819 if let Some((_, write_highlights)) = self
16820 .background_highlights
16821 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16822 {
16823 all_highlights.extend(write_highlights.iter());
16824 }
16825
16826 if all_highlights.is_empty() {
16827 return;
16828 }
16829
16830 // Sort highlights by position
16831 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16832
16833 let target_highlight = match direction {
16834 Direction::Next => {
16835 // Find the first highlight after the current position
16836 all_highlights
16837 .iter()
16838 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16839 }
16840 Direction::Prev => {
16841 // Find the last highlight before the current position
16842 all_highlights
16843 .iter()
16844 .rev()
16845 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16846 }
16847 };
16848
16849 if let Some(highlight) = target_highlight {
16850 let destination = highlight.start.to_point(buffer);
16851 let autoscroll = Autoscroll::center();
16852
16853 self.unfold_ranges(&[destination..destination], false, false, cx);
16854 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16855 s.select_ranges([destination..destination]);
16856 });
16857 }
16858 }
16859
16860 fn go_to_line<T: 'static>(
16861 &mut self,
16862 position: Anchor,
16863 highlight_color: Option<Hsla>,
16864 window: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) {
16867 let snapshot = self.snapshot(window, cx).display_snapshot;
16868 let position = position.to_point(&snapshot.buffer_snapshot());
16869 let start = snapshot
16870 .buffer_snapshot()
16871 .clip_point(Point::new(position.row, 0), Bias::Left);
16872 let end = start + Point::new(1, 0);
16873 let start = snapshot.buffer_snapshot().anchor_before(start);
16874 let end = snapshot.buffer_snapshot().anchor_before(end);
16875
16876 self.highlight_rows::<T>(
16877 start..end,
16878 highlight_color
16879 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16880 Default::default(),
16881 cx,
16882 );
16883
16884 if self.buffer.read(cx).is_singleton() {
16885 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16886 }
16887 }
16888
16889 pub fn go_to_definition(
16890 &mut self,
16891 _: &GoToDefinition,
16892 window: &mut Window,
16893 cx: &mut Context<Self>,
16894 ) -> Task<Result<Navigated>> {
16895 let definition =
16896 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16897 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16898 cx.spawn_in(window, async move |editor, cx| {
16899 if definition.await? == Navigated::Yes {
16900 return Ok(Navigated::Yes);
16901 }
16902 match fallback_strategy {
16903 GoToDefinitionFallback::None => Ok(Navigated::No),
16904 GoToDefinitionFallback::FindAllReferences => {
16905 match editor.update_in(cx, |editor, window, cx| {
16906 editor.find_all_references(&FindAllReferences::default(), window, cx)
16907 })? {
16908 Some(references) => references.await,
16909 None => Ok(Navigated::No),
16910 }
16911 }
16912 }
16913 })
16914 }
16915
16916 pub fn go_to_declaration(
16917 &mut self,
16918 _: &GoToDeclaration,
16919 window: &mut Window,
16920 cx: &mut Context<Self>,
16921 ) -> Task<Result<Navigated>> {
16922 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16923 }
16924
16925 pub fn go_to_declaration_split(
16926 &mut self,
16927 _: &GoToDeclaration,
16928 window: &mut Window,
16929 cx: &mut Context<Self>,
16930 ) -> Task<Result<Navigated>> {
16931 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16932 }
16933
16934 pub fn go_to_implementation(
16935 &mut self,
16936 _: &GoToImplementation,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 ) -> Task<Result<Navigated>> {
16940 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16941 }
16942
16943 pub fn go_to_implementation_split(
16944 &mut self,
16945 _: &GoToImplementationSplit,
16946 window: &mut Window,
16947 cx: &mut Context<Self>,
16948 ) -> Task<Result<Navigated>> {
16949 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16950 }
16951
16952 pub fn go_to_type_definition(
16953 &mut self,
16954 _: &GoToTypeDefinition,
16955 window: &mut Window,
16956 cx: &mut Context<Self>,
16957 ) -> Task<Result<Navigated>> {
16958 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16959 }
16960
16961 pub fn go_to_definition_split(
16962 &mut self,
16963 _: &GoToDefinitionSplit,
16964 window: &mut Window,
16965 cx: &mut Context<Self>,
16966 ) -> Task<Result<Navigated>> {
16967 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16968 }
16969
16970 pub fn go_to_type_definition_split(
16971 &mut self,
16972 _: &GoToTypeDefinitionSplit,
16973 window: &mut Window,
16974 cx: &mut Context<Self>,
16975 ) -> Task<Result<Navigated>> {
16976 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16977 }
16978
16979 fn go_to_definition_of_kind(
16980 &mut self,
16981 kind: GotoDefinitionKind,
16982 split: bool,
16983 window: &mut Window,
16984 cx: &mut Context<Self>,
16985 ) -> Task<Result<Navigated>> {
16986 let Some(provider) = self.semantics_provider.clone() else {
16987 return Task::ready(Ok(Navigated::No));
16988 };
16989 let head = self
16990 .selections
16991 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16992 .head();
16993 let buffer = self.buffer.read(cx);
16994 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16995 return Task::ready(Ok(Navigated::No));
16996 };
16997 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16998 return Task::ready(Ok(Navigated::No));
16999 };
17000
17001 cx.spawn_in(window, async move |editor, cx| {
17002 let Some(definitions) = definitions.await? else {
17003 return Ok(Navigated::No);
17004 };
17005 let navigated = editor
17006 .update_in(cx, |editor, window, cx| {
17007 editor.navigate_to_hover_links(
17008 Some(kind),
17009 definitions
17010 .into_iter()
17011 .filter(|location| {
17012 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17013 })
17014 .map(HoverLink::Text)
17015 .collect::<Vec<_>>(),
17016 split,
17017 window,
17018 cx,
17019 )
17020 })?
17021 .await?;
17022 anyhow::Ok(navigated)
17023 })
17024 }
17025
17026 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17027 let selection = self.selections.newest_anchor();
17028 let head = selection.head();
17029 let tail = selection.tail();
17030
17031 let Some((buffer, start_position)) =
17032 self.buffer.read(cx).text_anchor_for_position(head, cx)
17033 else {
17034 return;
17035 };
17036
17037 let end_position = if head != tail {
17038 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17039 return;
17040 };
17041 Some(pos)
17042 } else {
17043 None
17044 };
17045
17046 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17047 let url = if let Some(end_pos) = end_position {
17048 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17049 } else {
17050 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17051 };
17052
17053 if let Some(url) = url {
17054 cx.update(|window, cx| {
17055 if parse_zed_link(&url, cx).is_some() {
17056 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17057 } else {
17058 cx.open_url(&url);
17059 }
17060 })?;
17061 }
17062
17063 anyhow::Ok(())
17064 });
17065
17066 url_finder.detach();
17067 }
17068
17069 pub fn open_selected_filename(
17070 &mut self,
17071 _: &OpenSelectedFilename,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 let Some(workspace) = self.workspace() else {
17076 return;
17077 };
17078
17079 let position = self.selections.newest_anchor().head();
17080
17081 let Some((buffer, buffer_position)) =
17082 self.buffer.read(cx).text_anchor_for_position(position, cx)
17083 else {
17084 return;
17085 };
17086
17087 let project = self.project.clone();
17088
17089 cx.spawn_in(window, async move |_, cx| {
17090 let result = find_file(&buffer, project, buffer_position, cx).await;
17091
17092 if let Some((_, path)) = result {
17093 workspace
17094 .update_in(cx, |workspace, window, cx| {
17095 workspace.open_resolved_path(path, window, cx)
17096 })?
17097 .await?;
17098 }
17099 anyhow::Ok(())
17100 })
17101 .detach();
17102 }
17103
17104 pub(crate) fn navigate_to_hover_links(
17105 &mut self,
17106 kind: Option<GotoDefinitionKind>,
17107 definitions: Vec<HoverLink>,
17108 split: bool,
17109 window: &mut Window,
17110 cx: &mut Context<Editor>,
17111 ) -> Task<Result<Navigated>> {
17112 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17113 let mut first_url_or_file = None;
17114 let definitions: Vec<_> = definitions
17115 .into_iter()
17116 .filter_map(|def| match def {
17117 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17118 HoverLink::InlayHint(lsp_location, server_id) => {
17119 let computation =
17120 self.compute_target_location(lsp_location, server_id, window, cx);
17121 Some(cx.background_spawn(computation))
17122 }
17123 HoverLink::Url(url) => {
17124 first_url_or_file = Some(Either::Left(url));
17125 None
17126 }
17127 HoverLink::File(path) => {
17128 first_url_or_file = Some(Either::Right(path));
17129 None
17130 }
17131 })
17132 .collect();
17133
17134 let workspace = self.workspace();
17135
17136 cx.spawn_in(window, async move |editor, cx| {
17137 let locations: Vec<Location> = future::join_all(definitions)
17138 .await
17139 .into_iter()
17140 .filter_map(|location| location.transpose())
17141 .collect::<Result<_>>()
17142 .context("location tasks")?;
17143 let mut locations = cx.update(|_, cx| {
17144 locations
17145 .into_iter()
17146 .map(|location| {
17147 let buffer = location.buffer.read(cx);
17148 (location.buffer, location.range.to_point(buffer))
17149 })
17150 .into_group_map()
17151 })?;
17152 let mut num_locations = 0;
17153 for ranges in locations.values_mut() {
17154 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17155 ranges.dedup();
17156 num_locations += ranges.len();
17157 }
17158
17159 if num_locations > 1 {
17160 let tab_kind = match kind {
17161 Some(GotoDefinitionKind::Implementation) => "Implementations",
17162 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17163 Some(GotoDefinitionKind::Declaration) => "Declarations",
17164 Some(GotoDefinitionKind::Type) => "Types",
17165 };
17166 let title = editor
17167 .update_in(cx, |_, _, cx| {
17168 let target = locations
17169 .iter()
17170 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17171 .map(|(buffer, location)| {
17172 buffer
17173 .read(cx)
17174 .text_for_range(location.clone())
17175 .collect::<String>()
17176 })
17177 .filter(|text| !text.contains('\n'))
17178 .unique()
17179 .take(3)
17180 .join(", ");
17181 if target.is_empty() {
17182 tab_kind.to_owned()
17183 } else {
17184 format!("{tab_kind} for {target}")
17185 }
17186 })
17187 .context("buffer title")?;
17188
17189 let Some(workspace) = workspace else {
17190 return Ok(Navigated::No);
17191 };
17192
17193 let opened = workspace
17194 .update_in(cx, |workspace, window, cx| {
17195 let allow_preview = PreviewTabsSettings::get_global(cx)
17196 .enable_preview_multibuffer_from_code_navigation;
17197 Self::open_locations_in_multibuffer(
17198 workspace,
17199 locations,
17200 title,
17201 split,
17202 allow_preview,
17203 MultibufferSelectionMode::First,
17204 window,
17205 cx,
17206 )
17207 })
17208 .is_ok();
17209
17210 anyhow::Ok(Navigated::from_bool(opened))
17211 } else if num_locations == 0 {
17212 // If there is one url or file, open it directly
17213 match first_url_or_file {
17214 Some(Either::Left(url)) => {
17215 cx.update(|_, cx| cx.open_url(&url))?;
17216 Ok(Navigated::Yes)
17217 }
17218 Some(Either::Right(path)) => {
17219 // TODO(andrew): respect preview tab settings
17220 // `enable_keep_preview_on_code_navigation` and
17221 // `enable_preview_file_from_code_navigation`
17222 let Some(workspace) = workspace else {
17223 return Ok(Navigated::No);
17224 };
17225 workspace
17226 .update_in(cx, |workspace, window, cx| {
17227 workspace.open_resolved_path(path, window, cx)
17228 })?
17229 .await?;
17230 Ok(Navigated::Yes)
17231 }
17232 None => Ok(Navigated::No),
17233 }
17234 } else {
17235 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17236 let target_range = target_ranges.first().unwrap().clone();
17237
17238 editor.update_in(cx, |editor, window, cx| {
17239 let range = target_range.to_point(target_buffer.read(cx));
17240 let range = editor.range_for_match(&range);
17241 let range = collapse_multiline_range(range);
17242
17243 if !split
17244 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17245 {
17246 editor.go_to_singleton_buffer_range(range, window, cx);
17247 } else {
17248 let Some(workspace) = workspace else {
17249 return Navigated::No;
17250 };
17251 let pane = workspace.read(cx).active_pane().clone();
17252 window.defer(cx, move |window, cx| {
17253 let target_editor: Entity<Self> =
17254 workspace.update(cx, |workspace, cx| {
17255 let pane = if split {
17256 workspace.adjacent_pane(window, cx)
17257 } else {
17258 workspace.active_pane().clone()
17259 };
17260
17261 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17262 let keep_old_preview = preview_tabs_settings
17263 .enable_keep_preview_on_code_navigation;
17264 let allow_new_preview = preview_tabs_settings
17265 .enable_preview_file_from_code_navigation;
17266
17267 workspace.open_project_item(
17268 pane,
17269 target_buffer.clone(),
17270 true,
17271 true,
17272 keep_old_preview,
17273 allow_new_preview,
17274 window,
17275 cx,
17276 )
17277 });
17278 target_editor.update(cx, |target_editor, cx| {
17279 // When selecting a definition in a different buffer, disable the nav history
17280 // to avoid creating a history entry at the previous cursor location.
17281 pane.update(cx, |pane, _| pane.disable_history());
17282 target_editor.go_to_singleton_buffer_range(range, window, cx);
17283 pane.update(cx, |pane, _| pane.enable_history());
17284 });
17285 });
17286 }
17287 Navigated::Yes
17288 })
17289 }
17290 })
17291 }
17292
17293 fn compute_target_location(
17294 &self,
17295 lsp_location: lsp::Location,
17296 server_id: LanguageServerId,
17297 window: &mut Window,
17298 cx: &mut Context<Self>,
17299 ) -> Task<anyhow::Result<Option<Location>>> {
17300 let Some(project) = self.project.clone() else {
17301 return Task::ready(Ok(None));
17302 };
17303
17304 cx.spawn_in(window, async move |editor, cx| {
17305 let location_task = editor.update(cx, |_, cx| {
17306 project.update(cx, |project, cx| {
17307 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17308 })
17309 })?;
17310 let location = Some({
17311 let target_buffer_handle = location_task.await.context("open local buffer")?;
17312 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17313 let target_start = target_buffer
17314 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17315 let target_end = target_buffer
17316 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17317 target_buffer.anchor_after(target_start)
17318 ..target_buffer.anchor_before(target_end)
17319 })?;
17320 Location {
17321 buffer: target_buffer_handle,
17322 range,
17323 }
17324 });
17325 Ok(location)
17326 })
17327 }
17328
17329 fn go_to_next_reference(
17330 &mut self,
17331 _: &GoToNextReference,
17332 window: &mut Window,
17333 cx: &mut Context<Self>,
17334 ) {
17335 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17336 if let Some(task) = task {
17337 task.detach();
17338 };
17339 }
17340
17341 fn go_to_prev_reference(
17342 &mut self,
17343 _: &GoToPreviousReference,
17344 window: &mut Window,
17345 cx: &mut Context<Self>,
17346 ) {
17347 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17348 if let Some(task) = task {
17349 task.detach();
17350 };
17351 }
17352
17353 pub fn go_to_reference_before_or_after_position(
17354 &mut self,
17355 direction: Direction,
17356 count: usize,
17357 window: &mut Window,
17358 cx: &mut Context<Self>,
17359 ) -> Option<Task<Result<()>>> {
17360 let selection = self.selections.newest_anchor();
17361 let head = selection.head();
17362
17363 let multi_buffer = self.buffer.read(cx);
17364
17365 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17366 let workspace = self.workspace()?;
17367 let project = workspace.read(cx).project().clone();
17368 let references =
17369 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17370 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17371 let Some(locations) = references.await? else {
17372 return Ok(());
17373 };
17374
17375 if locations.is_empty() {
17376 // totally normal - the cursor may be on something which is not
17377 // a symbol (e.g. a keyword)
17378 log::info!("no references found under cursor");
17379 return Ok(());
17380 }
17381
17382 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17383
17384 let (locations, current_location_index) =
17385 multi_buffer.update(cx, |multi_buffer, cx| {
17386 let mut locations = locations
17387 .into_iter()
17388 .filter_map(|loc| {
17389 let start = multi_buffer.buffer_anchor_to_anchor(
17390 &loc.buffer,
17391 loc.range.start,
17392 cx,
17393 )?;
17394 let end = multi_buffer.buffer_anchor_to_anchor(
17395 &loc.buffer,
17396 loc.range.end,
17397 cx,
17398 )?;
17399 Some(start..end)
17400 })
17401 .collect::<Vec<_>>();
17402
17403 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17404 // There is an O(n) implementation, but given this list will be
17405 // small (usually <100 items), the extra O(log(n)) factor isn't
17406 // worth the (surprisingly large amount of) extra complexity.
17407 locations
17408 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17409
17410 let head_offset = head.to_offset(&multi_buffer_snapshot);
17411
17412 let current_location_index = locations.iter().position(|loc| {
17413 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17414 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17415 });
17416
17417 (locations, current_location_index)
17418 })?;
17419
17420 let Some(current_location_index) = current_location_index else {
17421 // This indicates something has gone wrong, because we already
17422 // handle the "no references" case above
17423 log::error!(
17424 "failed to find current reference under cursor. Total references: {}",
17425 locations.len()
17426 );
17427 return Ok(());
17428 };
17429
17430 let destination_location_index = match direction {
17431 Direction::Next => (current_location_index + count) % locations.len(),
17432 Direction::Prev => {
17433 (current_location_index + locations.len() - count % locations.len())
17434 % locations.len()
17435 }
17436 };
17437
17438 // TODO(cameron): is this needed?
17439 // the thinking is to avoid "jumping to the current location" (avoid
17440 // polluting "jumplist" in vim terms)
17441 if current_location_index == destination_location_index {
17442 return Ok(());
17443 }
17444
17445 let Range { start, end } = locations[destination_location_index];
17446
17447 editor.update_in(cx, |editor, window, cx| {
17448 let effects = SelectionEffects::default();
17449
17450 editor.unfold_ranges(&[start..end], false, false, cx);
17451 editor.change_selections(effects, window, cx, |s| {
17452 s.select_ranges([start..start]);
17453 });
17454 })?;
17455
17456 Ok(())
17457 }))
17458 }
17459
17460 pub fn find_all_references(
17461 &mut self,
17462 action: &FindAllReferences,
17463 window: &mut Window,
17464 cx: &mut Context<Self>,
17465 ) -> Option<Task<Result<Navigated>>> {
17466 let always_open_multibuffer = action.always_open_multibuffer;
17467 let selection = self.selections.newest_anchor();
17468 let multi_buffer = self.buffer.read(cx);
17469 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17470 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17471 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17472 let head = selection_offset.head();
17473
17474 let head_anchor = multi_buffer_snapshot.anchor_at(
17475 head,
17476 if head < selection_offset.tail() {
17477 Bias::Right
17478 } else {
17479 Bias::Left
17480 },
17481 );
17482
17483 match self
17484 .find_all_references_task_sources
17485 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17486 {
17487 Ok(_) => {
17488 log::info!(
17489 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17490 );
17491 return None;
17492 }
17493 Err(i) => {
17494 self.find_all_references_task_sources.insert(i, head_anchor);
17495 }
17496 }
17497
17498 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17499 let workspace = self.workspace()?;
17500 let project = workspace.read(cx).project().clone();
17501 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17502 Some(cx.spawn_in(window, async move |editor, cx| {
17503 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17504 if let Ok(i) = editor
17505 .find_all_references_task_sources
17506 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17507 {
17508 editor.find_all_references_task_sources.remove(i);
17509 }
17510 });
17511
17512 let Some(locations) = references.await? else {
17513 return anyhow::Ok(Navigated::No);
17514 };
17515 let mut locations = cx.update(|_, cx| {
17516 locations
17517 .into_iter()
17518 .map(|location| {
17519 let buffer = location.buffer.read(cx);
17520 (location.buffer, location.range.to_point(buffer))
17521 })
17522 // if special-casing the single-match case, remove ranges
17523 // that intersect current selection
17524 .filter(|(location_buffer, location)| {
17525 if always_open_multibuffer || &buffer != location_buffer {
17526 return true;
17527 }
17528
17529 !location.contains_inclusive(&selection_point.range())
17530 })
17531 .into_group_map()
17532 })?;
17533 if locations.is_empty() {
17534 return anyhow::Ok(Navigated::No);
17535 }
17536 for ranges in locations.values_mut() {
17537 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17538 ranges.dedup();
17539 }
17540 let mut num_locations = 0;
17541 for ranges in locations.values_mut() {
17542 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17543 ranges.dedup();
17544 num_locations += ranges.len();
17545 }
17546
17547 if num_locations == 1 && !always_open_multibuffer {
17548 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17549 let target_range = target_ranges.first().unwrap().clone();
17550
17551 return editor.update_in(cx, |editor, window, cx| {
17552 let range = target_range.to_point(target_buffer.read(cx));
17553 let range = editor.range_for_match(&range);
17554 let range = range.start..range.start;
17555
17556 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17557 editor.go_to_singleton_buffer_range(range, window, cx);
17558 } else {
17559 let pane = workspace.read(cx).active_pane().clone();
17560 window.defer(cx, move |window, cx| {
17561 let target_editor: Entity<Self> =
17562 workspace.update(cx, |workspace, cx| {
17563 let pane = workspace.active_pane().clone();
17564
17565 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17566 let keep_old_preview = preview_tabs_settings
17567 .enable_keep_preview_on_code_navigation;
17568 let allow_new_preview = preview_tabs_settings
17569 .enable_preview_file_from_code_navigation;
17570
17571 workspace.open_project_item(
17572 pane,
17573 target_buffer.clone(),
17574 true,
17575 true,
17576 keep_old_preview,
17577 allow_new_preview,
17578 window,
17579 cx,
17580 )
17581 });
17582 target_editor.update(cx, |target_editor, cx| {
17583 // When selecting a definition in a different buffer, disable the nav history
17584 // to avoid creating a history entry at the previous cursor location.
17585 pane.update(cx, |pane, _| pane.disable_history());
17586 target_editor.go_to_singleton_buffer_range(range, window, cx);
17587 pane.update(cx, |pane, _| pane.enable_history());
17588 });
17589 });
17590 }
17591 Navigated::No
17592 });
17593 }
17594
17595 workspace.update_in(cx, |workspace, window, cx| {
17596 let target = locations
17597 .iter()
17598 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17599 .map(|(buffer, location)| {
17600 buffer
17601 .read(cx)
17602 .text_for_range(location.clone())
17603 .collect::<String>()
17604 })
17605 .filter(|text| !text.contains('\n'))
17606 .unique()
17607 .take(3)
17608 .join(", ");
17609 let title = if target.is_empty() {
17610 "References".to_owned()
17611 } else {
17612 format!("References to {target}")
17613 };
17614 let allow_preview = PreviewTabsSettings::get_global(cx)
17615 .enable_preview_multibuffer_from_code_navigation;
17616 Self::open_locations_in_multibuffer(
17617 workspace,
17618 locations,
17619 title,
17620 false,
17621 allow_preview,
17622 MultibufferSelectionMode::First,
17623 window,
17624 cx,
17625 );
17626 Navigated::Yes
17627 })
17628 }))
17629 }
17630
17631 /// Opens a multibuffer with the given project locations in it.
17632 pub fn open_locations_in_multibuffer(
17633 workspace: &mut Workspace,
17634 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17635 title: String,
17636 split: bool,
17637 allow_preview: bool,
17638 multibuffer_selection_mode: MultibufferSelectionMode,
17639 window: &mut Window,
17640 cx: &mut Context<Workspace>,
17641 ) {
17642 if locations.is_empty() {
17643 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17644 return;
17645 }
17646
17647 let capability = workspace.project().read(cx).capability();
17648 let mut ranges = <Vec<Range<Anchor>>>::new();
17649
17650 // a key to find existing multibuffer editors with the same set of locations
17651 // to prevent us from opening more and more multibuffer tabs for searches and the like
17652 let mut key = (title.clone(), vec![]);
17653 let excerpt_buffer = cx.new(|cx| {
17654 let key = &mut key.1;
17655 let mut multibuffer = MultiBuffer::new(capability);
17656 for (buffer, mut ranges_for_buffer) in locations {
17657 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17658 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17659 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17660 PathKey::for_buffer(&buffer, cx),
17661 buffer.clone(),
17662 ranges_for_buffer,
17663 multibuffer_context_lines(cx),
17664 cx,
17665 );
17666 ranges.extend(new_ranges)
17667 }
17668
17669 multibuffer.with_title(title)
17670 });
17671 let existing = workspace.active_pane().update(cx, |pane, cx| {
17672 pane.items()
17673 .filter_map(|item| item.downcast::<Editor>())
17674 .find(|editor| {
17675 editor
17676 .read(cx)
17677 .lookup_key
17678 .as_ref()
17679 .and_then(|it| {
17680 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17681 })
17682 .is_some_and(|it| *it == key)
17683 })
17684 });
17685 let was_existing = existing.is_some();
17686 let editor = existing.unwrap_or_else(|| {
17687 cx.new(|cx| {
17688 let mut editor = Editor::for_multibuffer(
17689 excerpt_buffer,
17690 Some(workspace.project().clone()),
17691 window,
17692 cx,
17693 );
17694 editor.lookup_key = Some(Box::new(key));
17695 editor
17696 })
17697 });
17698 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17699 MultibufferSelectionMode::First => {
17700 if let Some(first_range) = ranges.first() {
17701 editor.change_selections(
17702 SelectionEffects::no_scroll(),
17703 window,
17704 cx,
17705 |selections| {
17706 selections.clear_disjoint();
17707 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17708 },
17709 );
17710 }
17711 editor.highlight_background::<Self>(
17712 &ranges,
17713 |_, theme| theme.colors().editor_highlighted_line_background,
17714 cx,
17715 );
17716 }
17717 MultibufferSelectionMode::All => {
17718 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17719 selections.clear_disjoint();
17720 selections.select_anchor_ranges(ranges);
17721 });
17722 }
17723 });
17724
17725 let item = Box::new(editor);
17726
17727 let pane = if split {
17728 workspace.adjacent_pane(window, cx)
17729 } else {
17730 workspace.active_pane().clone()
17731 };
17732 let activate_pane = split;
17733
17734 let mut destination_index = None;
17735 pane.update(cx, |pane, cx| {
17736 if allow_preview && !was_existing {
17737 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17738 }
17739 if was_existing && !allow_preview {
17740 pane.unpreview_item_if_preview(item.item_id());
17741 }
17742 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17743 });
17744 }
17745
17746 pub fn rename(
17747 &mut self,
17748 _: &Rename,
17749 window: &mut Window,
17750 cx: &mut Context<Self>,
17751 ) -> Option<Task<Result<()>>> {
17752 use language::ToOffset as _;
17753
17754 let provider = self.semantics_provider.clone()?;
17755 let selection = self.selections.newest_anchor().clone();
17756 let (cursor_buffer, cursor_buffer_position) = self
17757 .buffer
17758 .read(cx)
17759 .text_anchor_for_position(selection.head(), cx)?;
17760 let (tail_buffer, cursor_buffer_position_end) = self
17761 .buffer
17762 .read(cx)
17763 .text_anchor_for_position(selection.tail(), cx)?;
17764 if tail_buffer != cursor_buffer {
17765 return None;
17766 }
17767
17768 let snapshot = cursor_buffer.read(cx).snapshot();
17769 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17770 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17771 let prepare_rename = provider
17772 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17773 .unwrap_or_else(|| Task::ready(Ok(None)));
17774 drop(snapshot);
17775
17776 Some(cx.spawn_in(window, async move |this, cx| {
17777 let rename_range = if let Some(range) = prepare_rename.await? {
17778 Some(range)
17779 } else {
17780 this.update(cx, |this, cx| {
17781 let buffer = this.buffer.read(cx).snapshot(cx);
17782 let mut buffer_highlights = this
17783 .document_highlights_for_position(selection.head(), &buffer)
17784 .filter(|highlight| {
17785 highlight.start.excerpt_id == selection.head().excerpt_id
17786 && highlight.end.excerpt_id == selection.head().excerpt_id
17787 });
17788 buffer_highlights
17789 .next()
17790 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17791 })?
17792 };
17793 if let Some(rename_range) = rename_range {
17794 this.update_in(cx, |this, window, cx| {
17795 let snapshot = cursor_buffer.read(cx).snapshot();
17796 let rename_buffer_range = rename_range.to_offset(&snapshot);
17797 let cursor_offset_in_rename_range =
17798 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17799 let cursor_offset_in_rename_range_end =
17800 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17801
17802 this.take_rename(false, window, cx);
17803 let buffer = this.buffer.read(cx).read(cx);
17804 let cursor_offset = selection.head().to_offset(&buffer);
17805 let rename_start =
17806 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17807 let rename_end = rename_start + rename_buffer_range.len();
17808 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17809 let mut old_highlight_id = None;
17810 let old_name: Arc<str> = buffer
17811 .chunks(rename_start..rename_end, true)
17812 .map(|chunk| {
17813 if old_highlight_id.is_none() {
17814 old_highlight_id = chunk.syntax_highlight_id;
17815 }
17816 chunk.text
17817 })
17818 .collect::<String>()
17819 .into();
17820
17821 drop(buffer);
17822
17823 // Position the selection in the rename editor so that it matches the current selection.
17824 this.show_local_selections = false;
17825 let rename_editor = cx.new(|cx| {
17826 let mut editor = Editor::single_line(window, cx);
17827 editor.buffer.update(cx, |buffer, cx| {
17828 buffer.edit(
17829 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17830 None,
17831 cx,
17832 )
17833 });
17834 let cursor_offset_in_rename_range =
17835 MultiBufferOffset(cursor_offset_in_rename_range);
17836 let cursor_offset_in_rename_range_end =
17837 MultiBufferOffset(cursor_offset_in_rename_range_end);
17838 let rename_selection_range = match cursor_offset_in_rename_range
17839 .cmp(&cursor_offset_in_rename_range_end)
17840 {
17841 Ordering::Equal => {
17842 editor.select_all(&SelectAll, window, cx);
17843 return editor;
17844 }
17845 Ordering::Less => {
17846 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17847 }
17848 Ordering::Greater => {
17849 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17850 }
17851 };
17852 if rename_selection_range.end.0 > old_name.len() {
17853 editor.select_all(&SelectAll, window, cx);
17854 } else {
17855 editor.change_selections(Default::default(), window, cx, |s| {
17856 s.select_ranges([rename_selection_range]);
17857 });
17858 }
17859 editor
17860 });
17861 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17862 if e == &EditorEvent::Focused {
17863 cx.emit(EditorEvent::FocusedIn)
17864 }
17865 })
17866 .detach();
17867
17868 let write_highlights =
17869 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17870 let read_highlights =
17871 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17872 let ranges = write_highlights
17873 .iter()
17874 .flat_map(|(_, ranges)| ranges.iter())
17875 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17876 .cloned()
17877 .collect();
17878
17879 this.highlight_text::<Rename>(
17880 ranges,
17881 HighlightStyle {
17882 fade_out: Some(0.6),
17883 ..Default::default()
17884 },
17885 cx,
17886 );
17887 let rename_focus_handle = rename_editor.focus_handle(cx);
17888 window.focus(&rename_focus_handle);
17889 let block_id = this.insert_blocks(
17890 [BlockProperties {
17891 style: BlockStyle::Flex,
17892 placement: BlockPlacement::Below(range.start),
17893 height: Some(1),
17894 render: Arc::new({
17895 let rename_editor = rename_editor.clone();
17896 move |cx: &mut BlockContext| {
17897 let mut text_style = cx.editor_style.text.clone();
17898 if let Some(highlight_style) = old_highlight_id
17899 .and_then(|h| h.style(&cx.editor_style.syntax))
17900 {
17901 text_style = text_style.highlight(highlight_style);
17902 }
17903 div()
17904 .block_mouse_except_scroll()
17905 .pl(cx.anchor_x)
17906 .child(EditorElement::new(
17907 &rename_editor,
17908 EditorStyle {
17909 background: cx.theme().system().transparent,
17910 local_player: cx.editor_style.local_player,
17911 text: text_style,
17912 scrollbar_width: cx.editor_style.scrollbar_width,
17913 syntax: cx.editor_style.syntax.clone(),
17914 status: cx.editor_style.status.clone(),
17915 inlay_hints_style: HighlightStyle {
17916 font_weight: Some(FontWeight::BOLD),
17917 ..make_inlay_hints_style(cx.app)
17918 },
17919 edit_prediction_styles: make_suggestion_styles(
17920 cx.app,
17921 ),
17922 ..EditorStyle::default()
17923 },
17924 ))
17925 .into_any_element()
17926 }
17927 }),
17928 priority: 0,
17929 }],
17930 Some(Autoscroll::fit()),
17931 cx,
17932 )[0];
17933 this.pending_rename = Some(RenameState {
17934 range,
17935 old_name,
17936 editor: rename_editor,
17937 block_id,
17938 });
17939 })?;
17940 }
17941
17942 Ok(())
17943 }))
17944 }
17945
17946 pub fn confirm_rename(
17947 &mut self,
17948 _: &ConfirmRename,
17949 window: &mut Window,
17950 cx: &mut Context<Self>,
17951 ) -> Option<Task<Result<()>>> {
17952 let rename = self.take_rename(false, window, cx)?;
17953 let workspace = self.workspace()?.downgrade();
17954 let (buffer, start) = self
17955 .buffer
17956 .read(cx)
17957 .text_anchor_for_position(rename.range.start, cx)?;
17958 let (end_buffer, _) = self
17959 .buffer
17960 .read(cx)
17961 .text_anchor_for_position(rename.range.end, cx)?;
17962 if buffer != end_buffer {
17963 return None;
17964 }
17965
17966 let old_name = rename.old_name;
17967 let new_name = rename.editor.read(cx).text(cx);
17968
17969 let rename = self.semantics_provider.as_ref()?.perform_rename(
17970 &buffer,
17971 start,
17972 new_name.clone(),
17973 cx,
17974 )?;
17975
17976 Some(cx.spawn_in(window, async move |editor, cx| {
17977 let project_transaction = rename.await?;
17978 Self::open_project_transaction(
17979 &editor,
17980 workspace,
17981 project_transaction,
17982 format!("Rename: {} → {}", old_name, new_name),
17983 cx,
17984 )
17985 .await?;
17986
17987 editor.update(cx, |editor, cx| {
17988 editor.refresh_document_highlights(cx);
17989 })?;
17990 Ok(())
17991 }))
17992 }
17993
17994 fn take_rename(
17995 &mut self,
17996 moving_cursor: bool,
17997 window: &mut Window,
17998 cx: &mut Context<Self>,
17999 ) -> Option<RenameState> {
18000 let rename = self.pending_rename.take()?;
18001 if rename.editor.focus_handle(cx).is_focused(window) {
18002 window.focus(&self.focus_handle);
18003 }
18004
18005 self.remove_blocks(
18006 [rename.block_id].into_iter().collect(),
18007 Some(Autoscroll::fit()),
18008 cx,
18009 );
18010 self.clear_highlights::<Rename>(cx);
18011 self.show_local_selections = true;
18012
18013 if moving_cursor {
18014 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18015 editor
18016 .selections
18017 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18018 .head()
18019 });
18020
18021 // Update the selection to match the position of the selection inside
18022 // the rename editor.
18023 let snapshot = self.buffer.read(cx).read(cx);
18024 let rename_range = rename.range.to_offset(&snapshot);
18025 let cursor_in_editor = snapshot
18026 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18027 .min(rename_range.end);
18028 drop(snapshot);
18029
18030 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18031 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18032 });
18033 } else {
18034 self.refresh_document_highlights(cx);
18035 }
18036
18037 Some(rename)
18038 }
18039
18040 pub fn pending_rename(&self) -> Option<&RenameState> {
18041 self.pending_rename.as_ref()
18042 }
18043
18044 fn format(
18045 &mut self,
18046 _: &Format,
18047 window: &mut Window,
18048 cx: &mut Context<Self>,
18049 ) -> Option<Task<Result<()>>> {
18050 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18051
18052 let project = match &self.project {
18053 Some(project) => project.clone(),
18054 None => return None,
18055 };
18056
18057 Some(self.perform_format(
18058 project,
18059 FormatTrigger::Manual,
18060 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18061 window,
18062 cx,
18063 ))
18064 }
18065
18066 fn format_selections(
18067 &mut self,
18068 _: &FormatSelections,
18069 window: &mut Window,
18070 cx: &mut Context<Self>,
18071 ) -> Option<Task<Result<()>>> {
18072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18073
18074 let project = match &self.project {
18075 Some(project) => project.clone(),
18076 None => return None,
18077 };
18078
18079 let ranges = self
18080 .selections
18081 .all_adjusted(&self.display_snapshot(cx))
18082 .into_iter()
18083 .map(|selection| selection.range())
18084 .collect_vec();
18085
18086 Some(self.perform_format(
18087 project,
18088 FormatTrigger::Manual,
18089 FormatTarget::Ranges(ranges),
18090 window,
18091 cx,
18092 ))
18093 }
18094
18095 fn perform_format(
18096 &mut self,
18097 project: Entity<Project>,
18098 trigger: FormatTrigger,
18099 target: FormatTarget,
18100 window: &mut Window,
18101 cx: &mut Context<Self>,
18102 ) -> Task<Result<()>> {
18103 let buffer = self.buffer.clone();
18104 let (buffers, target) = match target {
18105 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18106 FormatTarget::Ranges(selection_ranges) => {
18107 let multi_buffer = buffer.read(cx);
18108 let snapshot = multi_buffer.read(cx);
18109 let mut buffers = HashSet::default();
18110 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18111 BTreeMap::new();
18112 for selection_range in selection_ranges {
18113 for (buffer, buffer_range, _) in
18114 snapshot.range_to_buffer_ranges(selection_range)
18115 {
18116 let buffer_id = buffer.remote_id();
18117 let start = buffer.anchor_before(buffer_range.start);
18118 let end = buffer.anchor_after(buffer_range.end);
18119 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18120 buffer_id_to_ranges
18121 .entry(buffer_id)
18122 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18123 .or_insert_with(|| vec![start..end]);
18124 }
18125 }
18126 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18127 }
18128 };
18129
18130 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18131 let selections_prev = transaction_id_prev
18132 .and_then(|transaction_id_prev| {
18133 // default to selections as they were after the last edit, if we have them,
18134 // instead of how they are now.
18135 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18136 // will take you back to where you made the last edit, instead of staying where you scrolled
18137 self.selection_history
18138 .transaction(transaction_id_prev)
18139 .map(|t| t.0.clone())
18140 })
18141 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18142
18143 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18144 let format = project.update(cx, |project, cx| {
18145 project.format(buffers, target, true, trigger, cx)
18146 });
18147
18148 cx.spawn_in(window, async move |editor, cx| {
18149 let transaction = futures::select_biased! {
18150 transaction = format.log_err().fuse() => transaction,
18151 () = timeout => {
18152 log::warn!("timed out waiting for formatting");
18153 None
18154 }
18155 };
18156
18157 buffer
18158 .update(cx, |buffer, cx| {
18159 if let Some(transaction) = transaction
18160 && !buffer.is_singleton()
18161 {
18162 buffer.push_transaction(&transaction.0, cx);
18163 }
18164 cx.notify();
18165 })
18166 .ok();
18167
18168 if let Some(transaction_id_now) =
18169 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18170 {
18171 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18172 if has_new_transaction {
18173 _ = editor.update(cx, |editor, _| {
18174 editor
18175 .selection_history
18176 .insert_transaction(transaction_id_now, selections_prev);
18177 });
18178 }
18179 }
18180
18181 Ok(())
18182 })
18183 }
18184
18185 fn organize_imports(
18186 &mut self,
18187 _: &OrganizeImports,
18188 window: &mut Window,
18189 cx: &mut Context<Self>,
18190 ) -> Option<Task<Result<()>>> {
18191 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18192 let project = match &self.project {
18193 Some(project) => project.clone(),
18194 None => return None,
18195 };
18196 Some(self.perform_code_action_kind(
18197 project,
18198 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18199 window,
18200 cx,
18201 ))
18202 }
18203
18204 fn perform_code_action_kind(
18205 &mut self,
18206 project: Entity<Project>,
18207 kind: CodeActionKind,
18208 window: &mut Window,
18209 cx: &mut Context<Self>,
18210 ) -> Task<Result<()>> {
18211 let buffer = self.buffer.clone();
18212 let buffers = buffer.read(cx).all_buffers();
18213 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18214 let apply_action = project.update(cx, |project, cx| {
18215 project.apply_code_action_kind(buffers, kind, true, cx)
18216 });
18217 cx.spawn_in(window, async move |_, cx| {
18218 let transaction = futures::select_biased! {
18219 () = timeout => {
18220 log::warn!("timed out waiting for executing code action");
18221 None
18222 }
18223 transaction = apply_action.log_err().fuse() => transaction,
18224 };
18225 buffer
18226 .update(cx, |buffer, cx| {
18227 // check if we need this
18228 if let Some(transaction) = transaction
18229 && !buffer.is_singleton()
18230 {
18231 buffer.push_transaction(&transaction.0, cx);
18232 }
18233 cx.notify();
18234 })
18235 .ok();
18236 Ok(())
18237 })
18238 }
18239
18240 pub fn restart_language_server(
18241 &mut self,
18242 _: &RestartLanguageServer,
18243 _: &mut Window,
18244 cx: &mut Context<Self>,
18245 ) {
18246 if let Some(project) = self.project.clone() {
18247 self.buffer.update(cx, |multi_buffer, cx| {
18248 project.update(cx, |project, cx| {
18249 project.restart_language_servers_for_buffers(
18250 multi_buffer.all_buffers().into_iter().collect(),
18251 HashSet::default(),
18252 cx,
18253 );
18254 });
18255 })
18256 }
18257 }
18258
18259 pub fn stop_language_server(
18260 &mut self,
18261 _: &StopLanguageServer,
18262 _: &mut Window,
18263 cx: &mut Context<Self>,
18264 ) {
18265 if let Some(project) = self.project.clone() {
18266 self.buffer.update(cx, |multi_buffer, cx| {
18267 project.update(cx, |project, cx| {
18268 project.stop_language_servers_for_buffers(
18269 multi_buffer.all_buffers().into_iter().collect(),
18270 HashSet::default(),
18271 cx,
18272 );
18273 });
18274 });
18275 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18276 }
18277 }
18278
18279 fn cancel_language_server_work(
18280 workspace: &mut Workspace,
18281 _: &actions::CancelLanguageServerWork,
18282 _: &mut Window,
18283 cx: &mut Context<Workspace>,
18284 ) {
18285 let project = workspace.project();
18286 let buffers = workspace
18287 .active_item(cx)
18288 .and_then(|item| item.act_as::<Editor>(cx))
18289 .map_or(HashSet::default(), |editor| {
18290 editor.read(cx).buffer.read(cx).all_buffers()
18291 });
18292 project.update(cx, |project, cx| {
18293 project.cancel_language_server_work_for_buffers(buffers, cx);
18294 });
18295 }
18296
18297 fn show_character_palette(
18298 &mut self,
18299 _: &ShowCharacterPalette,
18300 window: &mut Window,
18301 _: &mut Context<Self>,
18302 ) {
18303 window.show_character_palette();
18304 }
18305
18306 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18307 if !self.diagnostics_enabled() {
18308 return;
18309 }
18310
18311 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18312 let buffer = self.buffer.read(cx).snapshot(cx);
18313 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18314 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18315 let is_valid = buffer
18316 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18317 .any(|entry| {
18318 entry.diagnostic.is_primary
18319 && !entry.range.is_empty()
18320 && entry.range.start == primary_range_start
18321 && entry.diagnostic.message == active_diagnostics.active_message
18322 });
18323
18324 if !is_valid {
18325 self.dismiss_diagnostics(cx);
18326 }
18327 }
18328 }
18329
18330 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18331 match &self.active_diagnostics {
18332 ActiveDiagnostic::Group(group) => Some(group),
18333 _ => None,
18334 }
18335 }
18336
18337 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18338 if !self.diagnostics_enabled() {
18339 return;
18340 }
18341 self.dismiss_diagnostics(cx);
18342 self.active_diagnostics = ActiveDiagnostic::All;
18343 }
18344
18345 fn activate_diagnostics(
18346 &mut self,
18347 buffer_id: BufferId,
18348 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18349 window: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18353 return;
18354 }
18355 self.dismiss_diagnostics(cx);
18356 let snapshot = self.snapshot(window, cx);
18357 let buffer = self.buffer.read(cx).snapshot(cx);
18358 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18359 return;
18360 };
18361
18362 let diagnostic_group = buffer
18363 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18364 .collect::<Vec<_>>();
18365
18366 let language_registry = self
18367 .project()
18368 .map(|project| project.read(cx).languages().clone());
18369
18370 let blocks = renderer.render_group(
18371 diagnostic_group,
18372 buffer_id,
18373 snapshot,
18374 cx.weak_entity(),
18375 language_registry,
18376 cx,
18377 );
18378
18379 let blocks = self.display_map.update(cx, |display_map, cx| {
18380 display_map.insert_blocks(blocks, cx).into_iter().collect()
18381 });
18382 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18383 active_range: buffer.anchor_before(diagnostic.range.start)
18384 ..buffer.anchor_after(diagnostic.range.end),
18385 active_message: diagnostic.diagnostic.message.clone(),
18386 group_id: diagnostic.diagnostic.group_id,
18387 blocks,
18388 });
18389 cx.notify();
18390 }
18391
18392 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18393 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18394 return;
18395 };
18396
18397 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18398 if let ActiveDiagnostic::Group(group) = prev {
18399 self.display_map.update(cx, |display_map, cx| {
18400 display_map.remove_blocks(group.blocks, cx);
18401 });
18402 cx.notify();
18403 }
18404 }
18405
18406 /// Disable inline diagnostics rendering for this editor.
18407 pub fn disable_inline_diagnostics(&mut self) {
18408 self.inline_diagnostics_enabled = false;
18409 self.inline_diagnostics_update = Task::ready(());
18410 self.inline_diagnostics.clear();
18411 }
18412
18413 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18414 self.diagnostics_enabled = false;
18415 self.dismiss_diagnostics(cx);
18416 self.inline_diagnostics_update = Task::ready(());
18417 self.inline_diagnostics.clear();
18418 }
18419
18420 pub fn disable_word_completions(&mut self) {
18421 self.word_completions_enabled = false;
18422 }
18423
18424 pub fn diagnostics_enabled(&self) -> bool {
18425 self.diagnostics_enabled && self.mode.is_full()
18426 }
18427
18428 pub fn inline_diagnostics_enabled(&self) -> bool {
18429 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18430 }
18431
18432 pub fn show_inline_diagnostics(&self) -> bool {
18433 self.show_inline_diagnostics
18434 }
18435
18436 pub fn toggle_inline_diagnostics(
18437 &mut self,
18438 _: &ToggleInlineDiagnostics,
18439 window: &mut Window,
18440 cx: &mut Context<Editor>,
18441 ) {
18442 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18443 self.refresh_inline_diagnostics(false, window, cx);
18444 }
18445
18446 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18447 self.diagnostics_max_severity = severity;
18448 self.display_map.update(cx, |display_map, _| {
18449 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18450 });
18451 }
18452
18453 pub fn toggle_diagnostics(
18454 &mut self,
18455 _: &ToggleDiagnostics,
18456 window: &mut Window,
18457 cx: &mut Context<Editor>,
18458 ) {
18459 if !self.diagnostics_enabled() {
18460 return;
18461 }
18462
18463 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18464 EditorSettings::get_global(cx)
18465 .diagnostics_max_severity
18466 .filter(|severity| severity != &DiagnosticSeverity::Off)
18467 .unwrap_or(DiagnosticSeverity::Hint)
18468 } else {
18469 DiagnosticSeverity::Off
18470 };
18471 self.set_max_diagnostics_severity(new_severity, cx);
18472 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18473 self.active_diagnostics = ActiveDiagnostic::None;
18474 self.inline_diagnostics_update = Task::ready(());
18475 self.inline_diagnostics.clear();
18476 } else {
18477 self.refresh_inline_diagnostics(false, window, cx);
18478 }
18479
18480 cx.notify();
18481 }
18482
18483 pub fn toggle_minimap(
18484 &mut self,
18485 _: &ToggleMinimap,
18486 window: &mut Window,
18487 cx: &mut Context<Editor>,
18488 ) {
18489 if self.supports_minimap(cx) {
18490 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18491 }
18492 }
18493
18494 fn refresh_inline_diagnostics(
18495 &mut self,
18496 debounce: bool,
18497 window: &mut Window,
18498 cx: &mut Context<Self>,
18499 ) {
18500 let max_severity = ProjectSettings::get_global(cx)
18501 .diagnostics
18502 .inline
18503 .max_severity
18504 .unwrap_or(self.diagnostics_max_severity);
18505
18506 if !self.inline_diagnostics_enabled()
18507 || !self.diagnostics_enabled()
18508 || !self.show_inline_diagnostics
18509 || max_severity == DiagnosticSeverity::Off
18510 {
18511 self.inline_diagnostics_update = Task::ready(());
18512 self.inline_diagnostics.clear();
18513 return;
18514 }
18515
18516 let debounce_ms = ProjectSettings::get_global(cx)
18517 .diagnostics
18518 .inline
18519 .update_debounce_ms;
18520 let debounce = if debounce && debounce_ms > 0 {
18521 Some(Duration::from_millis(debounce_ms))
18522 } else {
18523 None
18524 };
18525 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18526 if let Some(debounce) = debounce {
18527 cx.background_executor().timer(debounce).await;
18528 }
18529 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18530 editor
18531 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18532 .ok()
18533 }) else {
18534 return;
18535 };
18536
18537 let new_inline_diagnostics = cx
18538 .background_spawn(async move {
18539 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18540 for diagnostic_entry in
18541 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18542 {
18543 let message = diagnostic_entry
18544 .diagnostic
18545 .message
18546 .split_once('\n')
18547 .map(|(line, _)| line)
18548 .map(SharedString::new)
18549 .unwrap_or_else(|| {
18550 SharedString::new(&*diagnostic_entry.diagnostic.message)
18551 });
18552 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18553 let (Ok(i) | Err(i)) = inline_diagnostics
18554 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18555 inline_diagnostics.insert(
18556 i,
18557 (
18558 start_anchor,
18559 InlineDiagnostic {
18560 message,
18561 group_id: diagnostic_entry.diagnostic.group_id,
18562 start: diagnostic_entry.range.start.to_point(&snapshot),
18563 is_primary: diagnostic_entry.diagnostic.is_primary,
18564 severity: diagnostic_entry.diagnostic.severity,
18565 },
18566 ),
18567 );
18568 }
18569 inline_diagnostics
18570 })
18571 .await;
18572
18573 editor
18574 .update(cx, |editor, cx| {
18575 editor.inline_diagnostics = new_inline_diagnostics;
18576 cx.notify();
18577 })
18578 .ok();
18579 });
18580 }
18581
18582 fn pull_diagnostics(
18583 &mut self,
18584 buffer_id: Option<BufferId>,
18585 window: &Window,
18586 cx: &mut Context<Self>,
18587 ) -> Option<()> {
18588 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18589 return None;
18590 }
18591 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18592 .diagnostics
18593 .lsp_pull_diagnostics;
18594 if !pull_diagnostics_settings.enabled {
18595 return None;
18596 }
18597 let project = self.project()?.downgrade();
18598
18599 let mut edited_buffer_ids = HashSet::default();
18600 let mut edited_worktree_ids = HashSet::default();
18601 let edited_buffers = match buffer_id {
18602 Some(buffer_id) => {
18603 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18604 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18605 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18606 edited_worktree_ids.insert(worktree_id);
18607 vec![buffer]
18608 }
18609 None => self
18610 .buffer()
18611 .read(cx)
18612 .all_buffers()
18613 .into_iter()
18614 .filter(|buffer| {
18615 let buffer = buffer.read(cx);
18616 match buffer.file().map(|f| f.worktree_id(cx)) {
18617 Some(worktree_id) => {
18618 edited_buffer_ids.insert(buffer.remote_id());
18619 edited_worktree_ids.insert(worktree_id);
18620 true
18621 }
18622 None => false,
18623 }
18624 })
18625 .collect::<Vec<_>>(),
18626 };
18627
18628 if edited_buffers.is_empty() {
18629 self.pull_diagnostics_task = Task::ready(());
18630 self.pull_diagnostics_background_task = Task::ready(());
18631 return None;
18632 }
18633
18634 let mut already_used_buffers = HashSet::default();
18635 let related_open_buffers = self
18636 .workspace
18637 .as_ref()
18638 .and_then(|(workspace, _)| workspace.upgrade())
18639 .into_iter()
18640 .flat_map(|workspace| workspace.read(cx).panes())
18641 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18642 .filter(|editor| editor != &cx.entity())
18643 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18644 .filter(|buffer| {
18645 let buffer = buffer.read(cx);
18646 let buffer_id = buffer.remote_id();
18647 if already_used_buffers.insert(buffer_id) {
18648 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18649 return !edited_buffer_ids.contains(&buffer_id)
18650 && !edited_worktree_ids.contains(&worktree_id);
18651 }
18652 }
18653 false
18654 })
18655 .collect::<Vec<_>>();
18656
18657 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18658 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18659 if buffers.is_empty() {
18660 return Task::ready(());
18661 }
18662 let project_weak = project.clone();
18663 cx.spawn_in(window, async move |_, cx| {
18664 cx.background_executor().timer(delay).await;
18665
18666 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18667 buffers
18668 .into_iter()
18669 .filter_map(|buffer| {
18670 project_weak
18671 .update(cx, |project, cx| {
18672 project.lsp_store().update(cx, |lsp_store, cx| {
18673 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18674 })
18675 })
18676 .ok()
18677 })
18678 .collect::<FuturesUnordered<_>>()
18679 }) else {
18680 return;
18681 };
18682
18683 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18684 if let Err(e) = pull_task {
18685 log::error!("Failed to update project diagnostics: {e:#}");
18686 }
18687 }
18688 })
18689 };
18690
18691 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18692 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18693
18694 Some(())
18695 }
18696
18697 pub fn set_selections_from_remote(
18698 &mut self,
18699 selections: Vec<Selection<Anchor>>,
18700 pending_selection: Option<Selection<Anchor>>,
18701 window: &mut Window,
18702 cx: &mut Context<Self>,
18703 ) {
18704 let old_cursor_position = self.selections.newest_anchor().head();
18705 self.selections
18706 .change_with(&self.display_snapshot(cx), |s| {
18707 s.select_anchors(selections);
18708 if let Some(pending_selection) = pending_selection {
18709 s.set_pending(pending_selection, SelectMode::Character);
18710 } else {
18711 s.clear_pending();
18712 }
18713 });
18714 self.selections_did_change(
18715 false,
18716 &old_cursor_position,
18717 SelectionEffects::default(),
18718 window,
18719 cx,
18720 );
18721 }
18722
18723 pub fn transact(
18724 &mut self,
18725 window: &mut Window,
18726 cx: &mut Context<Self>,
18727 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18728 ) -> Option<TransactionId> {
18729 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18730 this.start_transaction_at(Instant::now(), window, cx);
18731 update(this, window, cx);
18732 this.end_transaction_at(Instant::now(), cx)
18733 })
18734 }
18735
18736 pub fn start_transaction_at(
18737 &mut self,
18738 now: Instant,
18739 window: &mut Window,
18740 cx: &mut Context<Self>,
18741 ) -> Option<TransactionId> {
18742 self.end_selection(window, cx);
18743 if let Some(tx_id) = self
18744 .buffer
18745 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18746 {
18747 self.selection_history
18748 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18749 cx.emit(EditorEvent::TransactionBegun {
18750 transaction_id: tx_id,
18751 });
18752 Some(tx_id)
18753 } else {
18754 None
18755 }
18756 }
18757
18758 pub fn end_transaction_at(
18759 &mut self,
18760 now: Instant,
18761 cx: &mut Context<Self>,
18762 ) -> Option<TransactionId> {
18763 if let Some(transaction_id) = self
18764 .buffer
18765 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18766 {
18767 if let Some((_, end_selections)) =
18768 self.selection_history.transaction_mut(transaction_id)
18769 {
18770 *end_selections = Some(self.selections.disjoint_anchors_arc());
18771 } else {
18772 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18773 }
18774
18775 cx.emit(EditorEvent::Edited { transaction_id });
18776 Some(transaction_id)
18777 } else {
18778 None
18779 }
18780 }
18781
18782 pub fn modify_transaction_selection_history(
18783 &mut self,
18784 transaction_id: TransactionId,
18785 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18786 ) -> bool {
18787 self.selection_history
18788 .transaction_mut(transaction_id)
18789 .map(modify)
18790 .is_some()
18791 }
18792
18793 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18794 if self.selection_mark_mode {
18795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18796 s.move_with(|_, sel| {
18797 sel.collapse_to(sel.head(), SelectionGoal::None);
18798 });
18799 })
18800 }
18801 self.selection_mark_mode = true;
18802 cx.notify();
18803 }
18804
18805 pub fn swap_selection_ends(
18806 &mut self,
18807 _: &actions::SwapSelectionEnds,
18808 window: &mut Window,
18809 cx: &mut Context<Self>,
18810 ) {
18811 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18812 s.move_with(|_, sel| {
18813 if sel.start != sel.end {
18814 sel.reversed = !sel.reversed
18815 }
18816 });
18817 });
18818 self.request_autoscroll(Autoscroll::newest(), cx);
18819 cx.notify();
18820 }
18821
18822 pub fn toggle_focus(
18823 workspace: &mut Workspace,
18824 _: &actions::ToggleFocus,
18825 window: &mut Window,
18826 cx: &mut Context<Workspace>,
18827 ) {
18828 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18829 return;
18830 };
18831 workspace.activate_item(&item, true, true, window, cx);
18832 }
18833
18834 pub fn toggle_fold(
18835 &mut self,
18836 _: &actions::ToggleFold,
18837 window: &mut Window,
18838 cx: &mut Context<Self>,
18839 ) {
18840 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18842 let selection = self.selections.newest::<Point>(&display_map);
18843
18844 let range = if selection.is_empty() {
18845 let point = selection.head().to_display_point(&display_map);
18846 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18847 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18848 .to_point(&display_map);
18849 start..end
18850 } else {
18851 selection.range()
18852 };
18853 if display_map.folds_in_range(range).next().is_some() {
18854 self.unfold_lines(&Default::default(), window, cx)
18855 } else {
18856 self.fold(&Default::default(), window, cx)
18857 }
18858 } else {
18859 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18860 let buffer_ids: HashSet<_> = self
18861 .selections
18862 .disjoint_anchor_ranges()
18863 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18864 .collect();
18865
18866 let should_unfold = buffer_ids
18867 .iter()
18868 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18869
18870 for buffer_id in buffer_ids {
18871 if should_unfold {
18872 self.unfold_buffer(buffer_id, cx);
18873 } else {
18874 self.fold_buffer(buffer_id, cx);
18875 }
18876 }
18877 }
18878 }
18879
18880 pub fn toggle_fold_recursive(
18881 &mut self,
18882 _: &actions::ToggleFoldRecursive,
18883 window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18887
18888 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18889 let range = if selection.is_empty() {
18890 let point = selection.head().to_display_point(&display_map);
18891 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18892 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18893 .to_point(&display_map);
18894 start..end
18895 } else {
18896 selection.range()
18897 };
18898 if display_map.folds_in_range(range).next().is_some() {
18899 self.unfold_recursive(&Default::default(), window, cx)
18900 } else {
18901 self.fold_recursive(&Default::default(), window, cx)
18902 }
18903 }
18904
18905 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18906 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18907 let mut to_fold = Vec::new();
18908 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18909 let selections = self.selections.all_adjusted(&display_map);
18910
18911 for selection in selections {
18912 let range = selection.range().sorted();
18913 let buffer_start_row = range.start.row;
18914
18915 if range.start.row != range.end.row {
18916 let mut found = false;
18917 let mut row = range.start.row;
18918 while row <= range.end.row {
18919 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18920 {
18921 found = true;
18922 row = crease.range().end.row + 1;
18923 to_fold.push(crease);
18924 } else {
18925 row += 1
18926 }
18927 }
18928 if found {
18929 continue;
18930 }
18931 }
18932
18933 for row in (0..=range.start.row).rev() {
18934 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18935 && crease.range().end.row >= buffer_start_row
18936 {
18937 to_fold.push(crease);
18938 if row <= range.start.row {
18939 break;
18940 }
18941 }
18942 }
18943 }
18944
18945 self.fold_creases(to_fold, true, window, cx);
18946 } else {
18947 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18948 let buffer_ids = self
18949 .selections
18950 .disjoint_anchor_ranges()
18951 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18952 .collect::<HashSet<_>>();
18953 for buffer_id in buffer_ids {
18954 self.fold_buffer(buffer_id, cx);
18955 }
18956 }
18957 }
18958
18959 pub fn toggle_fold_all(
18960 &mut self,
18961 _: &actions::ToggleFoldAll,
18962 window: &mut Window,
18963 cx: &mut Context<Self>,
18964 ) {
18965 if self.buffer.read(cx).is_singleton() {
18966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18967 let has_folds = display_map
18968 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18969 .next()
18970 .is_some();
18971
18972 if has_folds {
18973 self.unfold_all(&actions::UnfoldAll, window, cx);
18974 } else {
18975 self.fold_all(&actions::FoldAll, window, cx);
18976 }
18977 } else {
18978 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18979 let should_unfold = buffer_ids
18980 .iter()
18981 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18982
18983 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18984 editor
18985 .update_in(cx, |editor, _, cx| {
18986 for buffer_id in buffer_ids {
18987 if should_unfold {
18988 editor.unfold_buffer(buffer_id, cx);
18989 } else {
18990 editor.fold_buffer(buffer_id, cx);
18991 }
18992 }
18993 })
18994 .ok();
18995 });
18996 }
18997 }
18998
18999 fn fold_at_level(
19000 &mut self,
19001 fold_at: &FoldAtLevel,
19002 window: &mut Window,
19003 cx: &mut Context<Self>,
19004 ) {
19005 if !self.buffer.read(cx).is_singleton() {
19006 return;
19007 }
19008
19009 let fold_at_level = fold_at.0;
19010 let snapshot = self.buffer.read(cx).snapshot(cx);
19011 let mut to_fold = Vec::new();
19012 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19013
19014 let row_ranges_to_keep: Vec<Range<u32>> = self
19015 .selections
19016 .all::<Point>(&self.display_snapshot(cx))
19017 .into_iter()
19018 .map(|sel| sel.start.row..sel.end.row)
19019 .collect();
19020
19021 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19022 while start_row < end_row {
19023 match self
19024 .snapshot(window, cx)
19025 .crease_for_buffer_row(MultiBufferRow(start_row))
19026 {
19027 Some(crease) => {
19028 let nested_start_row = crease.range().start.row + 1;
19029 let nested_end_row = crease.range().end.row;
19030
19031 if current_level < fold_at_level {
19032 stack.push((nested_start_row, nested_end_row, current_level + 1));
19033 } else if current_level == fold_at_level {
19034 // Fold iff there is no selection completely contained within the fold region
19035 if !row_ranges_to_keep.iter().any(|selection| {
19036 selection.end >= nested_start_row
19037 && selection.start <= nested_end_row
19038 }) {
19039 to_fold.push(crease);
19040 }
19041 }
19042
19043 start_row = nested_end_row + 1;
19044 }
19045 None => start_row += 1,
19046 }
19047 }
19048 }
19049
19050 self.fold_creases(to_fold, true, window, cx);
19051 }
19052
19053 pub fn fold_at_level_1(
19054 &mut self,
19055 _: &actions::FoldAtLevel1,
19056 window: &mut Window,
19057 cx: &mut Context<Self>,
19058 ) {
19059 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19060 }
19061
19062 pub fn fold_at_level_2(
19063 &mut self,
19064 _: &actions::FoldAtLevel2,
19065 window: &mut Window,
19066 cx: &mut Context<Self>,
19067 ) {
19068 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19069 }
19070
19071 pub fn fold_at_level_3(
19072 &mut self,
19073 _: &actions::FoldAtLevel3,
19074 window: &mut Window,
19075 cx: &mut Context<Self>,
19076 ) {
19077 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19078 }
19079
19080 pub fn fold_at_level_4(
19081 &mut self,
19082 _: &actions::FoldAtLevel4,
19083 window: &mut Window,
19084 cx: &mut Context<Self>,
19085 ) {
19086 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19087 }
19088
19089 pub fn fold_at_level_5(
19090 &mut self,
19091 _: &actions::FoldAtLevel5,
19092 window: &mut Window,
19093 cx: &mut Context<Self>,
19094 ) {
19095 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19096 }
19097
19098 pub fn fold_at_level_6(
19099 &mut self,
19100 _: &actions::FoldAtLevel6,
19101 window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19105 }
19106
19107 pub fn fold_at_level_7(
19108 &mut self,
19109 _: &actions::FoldAtLevel7,
19110 window: &mut Window,
19111 cx: &mut Context<Self>,
19112 ) {
19113 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19114 }
19115
19116 pub fn fold_at_level_8(
19117 &mut self,
19118 _: &actions::FoldAtLevel8,
19119 window: &mut Window,
19120 cx: &mut Context<Self>,
19121 ) {
19122 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19123 }
19124
19125 pub fn fold_at_level_9(
19126 &mut self,
19127 _: &actions::FoldAtLevel9,
19128 window: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) {
19131 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19132 }
19133
19134 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19135 if self.buffer.read(cx).is_singleton() {
19136 let mut fold_ranges = Vec::new();
19137 let snapshot = self.buffer.read(cx).snapshot(cx);
19138
19139 for row in 0..snapshot.max_row().0 {
19140 if let Some(foldable_range) = self
19141 .snapshot(window, cx)
19142 .crease_for_buffer_row(MultiBufferRow(row))
19143 {
19144 fold_ranges.push(foldable_range);
19145 }
19146 }
19147
19148 self.fold_creases(fold_ranges, true, window, cx);
19149 } else {
19150 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19151 editor
19152 .update_in(cx, |editor, _, cx| {
19153 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19154 editor.fold_buffer(buffer_id, cx);
19155 }
19156 })
19157 .ok();
19158 });
19159 }
19160 }
19161
19162 pub fn fold_function_bodies(
19163 &mut self,
19164 _: &actions::FoldFunctionBodies,
19165 window: &mut Window,
19166 cx: &mut Context<Self>,
19167 ) {
19168 let snapshot = self.buffer.read(cx).snapshot(cx);
19169
19170 let ranges = snapshot
19171 .text_object_ranges(
19172 MultiBufferOffset(0)..snapshot.len(),
19173 TreeSitterOptions::default(),
19174 )
19175 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19176 .collect::<Vec<_>>();
19177
19178 let creases = ranges
19179 .into_iter()
19180 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19181 .collect();
19182
19183 self.fold_creases(creases, true, window, cx);
19184 }
19185
19186 pub fn fold_recursive(
19187 &mut self,
19188 _: &actions::FoldRecursive,
19189 window: &mut Window,
19190 cx: &mut Context<Self>,
19191 ) {
19192 let mut to_fold = Vec::new();
19193 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19194 let selections = self.selections.all_adjusted(&display_map);
19195
19196 for selection in selections {
19197 let range = selection.range().sorted();
19198 let buffer_start_row = range.start.row;
19199
19200 if range.start.row != range.end.row {
19201 let mut found = false;
19202 for row in range.start.row..=range.end.row {
19203 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19204 found = true;
19205 to_fold.push(crease);
19206 }
19207 }
19208 if found {
19209 continue;
19210 }
19211 }
19212
19213 for row in (0..=range.start.row).rev() {
19214 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19215 if crease.range().end.row >= buffer_start_row {
19216 to_fold.push(crease);
19217 } else {
19218 break;
19219 }
19220 }
19221 }
19222 }
19223
19224 self.fold_creases(to_fold, true, window, cx);
19225 }
19226
19227 pub fn fold_at(
19228 &mut self,
19229 buffer_row: MultiBufferRow,
19230 window: &mut Window,
19231 cx: &mut Context<Self>,
19232 ) {
19233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19234
19235 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19236 let autoscroll = self
19237 .selections
19238 .all::<Point>(&display_map)
19239 .iter()
19240 .any(|selection| crease.range().overlaps(&selection.range()));
19241
19242 self.fold_creases(vec![crease], autoscroll, window, cx);
19243 }
19244 }
19245
19246 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19247 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19248 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19249 let buffer = display_map.buffer_snapshot();
19250 let selections = self.selections.all::<Point>(&display_map);
19251 let ranges = selections
19252 .iter()
19253 .map(|s| {
19254 let range = s.display_range(&display_map).sorted();
19255 let mut start = range.start.to_point(&display_map);
19256 let mut end = range.end.to_point(&display_map);
19257 start.column = 0;
19258 end.column = buffer.line_len(MultiBufferRow(end.row));
19259 start..end
19260 })
19261 .collect::<Vec<_>>();
19262
19263 self.unfold_ranges(&ranges, true, true, cx);
19264 } else {
19265 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19266 let buffer_ids = self
19267 .selections
19268 .disjoint_anchor_ranges()
19269 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19270 .collect::<HashSet<_>>();
19271 for buffer_id in buffer_ids {
19272 self.unfold_buffer(buffer_id, cx);
19273 }
19274 }
19275 }
19276
19277 pub fn unfold_recursive(
19278 &mut self,
19279 _: &UnfoldRecursive,
19280 _window: &mut Window,
19281 cx: &mut Context<Self>,
19282 ) {
19283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19284 let selections = self.selections.all::<Point>(&display_map);
19285 let ranges = selections
19286 .iter()
19287 .map(|s| {
19288 let mut range = s.display_range(&display_map).sorted();
19289 *range.start.column_mut() = 0;
19290 *range.end.column_mut() = display_map.line_len(range.end.row());
19291 let start = range.start.to_point(&display_map);
19292 let end = range.end.to_point(&display_map);
19293 start..end
19294 })
19295 .collect::<Vec<_>>();
19296
19297 self.unfold_ranges(&ranges, true, true, cx);
19298 }
19299
19300 pub fn unfold_at(
19301 &mut self,
19302 buffer_row: MultiBufferRow,
19303 _window: &mut Window,
19304 cx: &mut Context<Self>,
19305 ) {
19306 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19307
19308 let intersection_range = Point::new(buffer_row.0, 0)
19309 ..Point::new(
19310 buffer_row.0,
19311 display_map.buffer_snapshot().line_len(buffer_row),
19312 );
19313
19314 let autoscroll = self
19315 .selections
19316 .all::<Point>(&display_map)
19317 .iter()
19318 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19319
19320 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19321 }
19322
19323 pub fn unfold_all(
19324 &mut self,
19325 _: &actions::UnfoldAll,
19326 _window: &mut Window,
19327 cx: &mut Context<Self>,
19328 ) {
19329 if self.buffer.read(cx).is_singleton() {
19330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19331 self.unfold_ranges(
19332 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19333 true,
19334 true,
19335 cx,
19336 );
19337 } else {
19338 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19339 editor
19340 .update(cx, |editor, cx| {
19341 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19342 editor.unfold_buffer(buffer_id, cx);
19343 }
19344 })
19345 .ok();
19346 });
19347 }
19348 }
19349
19350 pub fn fold_selected_ranges(
19351 &mut self,
19352 _: &FoldSelectedRanges,
19353 window: &mut Window,
19354 cx: &mut Context<Self>,
19355 ) {
19356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19357 let selections = self.selections.all_adjusted(&display_map);
19358 let ranges = selections
19359 .into_iter()
19360 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19361 .collect::<Vec<_>>();
19362 self.fold_creases(ranges, true, window, cx);
19363 }
19364
19365 pub fn fold_ranges<T: ToOffset + Clone>(
19366 &mut self,
19367 ranges: Vec<Range<T>>,
19368 auto_scroll: bool,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19373 let ranges = ranges
19374 .into_iter()
19375 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19376 .collect::<Vec<_>>();
19377 self.fold_creases(ranges, auto_scroll, window, cx);
19378 }
19379
19380 pub fn fold_creases<T: ToOffset + Clone>(
19381 &mut self,
19382 creases: Vec<Crease<T>>,
19383 auto_scroll: bool,
19384 _window: &mut Window,
19385 cx: &mut Context<Self>,
19386 ) {
19387 if creases.is_empty() {
19388 return;
19389 }
19390
19391 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19392
19393 if auto_scroll {
19394 self.request_autoscroll(Autoscroll::fit(), cx);
19395 }
19396
19397 cx.notify();
19398
19399 self.scrollbar_marker_state.dirty = true;
19400 self.folds_did_change(cx);
19401 }
19402
19403 /// Removes any folds whose ranges intersect any of the given ranges.
19404 pub fn unfold_ranges<T: ToOffset + Clone>(
19405 &mut self,
19406 ranges: &[Range<T>],
19407 inclusive: bool,
19408 auto_scroll: bool,
19409 cx: &mut Context<Self>,
19410 ) {
19411 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19412 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19413 });
19414 self.folds_did_change(cx);
19415 }
19416
19417 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19418 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19419 return;
19420 }
19421
19422 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19423 self.display_map.update(cx, |display_map, cx| {
19424 display_map.fold_buffers([buffer_id], cx)
19425 });
19426
19427 let snapshot = self.display_snapshot(cx);
19428 self.selections.change_with(&snapshot, |selections| {
19429 selections.remove_selections_from_buffer(buffer_id);
19430 });
19431
19432 cx.emit(EditorEvent::BufferFoldToggled {
19433 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19434 folded: true,
19435 });
19436 cx.notify();
19437 }
19438
19439 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19440 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19441 return;
19442 }
19443 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19444 self.display_map.update(cx, |display_map, cx| {
19445 display_map.unfold_buffers([buffer_id], cx);
19446 });
19447 cx.emit(EditorEvent::BufferFoldToggled {
19448 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19449 folded: false,
19450 });
19451 cx.notify();
19452 }
19453
19454 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19455 self.display_map.read(cx).is_buffer_folded(buffer)
19456 }
19457
19458 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19459 self.display_map.read(cx).folded_buffers()
19460 }
19461
19462 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19463 self.display_map.update(cx, |display_map, cx| {
19464 display_map.disable_header_for_buffer(buffer_id, cx);
19465 });
19466 cx.notify();
19467 }
19468
19469 /// Removes any folds with the given ranges.
19470 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19471 &mut self,
19472 ranges: &[Range<T>],
19473 type_id: TypeId,
19474 auto_scroll: bool,
19475 cx: &mut Context<Self>,
19476 ) {
19477 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19478 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19479 });
19480 self.folds_did_change(cx);
19481 }
19482
19483 fn remove_folds_with<T: ToOffset + Clone>(
19484 &mut self,
19485 ranges: &[Range<T>],
19486 auto_scroll: bool,
19487 cx: &mut Context<Self>,
19488 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19489 ) {
19490 if ranges.is_empty() {
19491 return;
19492 }
19493
19494 let mut buffers_affected = HashSet::default();
19495 let multi_buffer = self.buffer().read(cx);
19496 for range in ranges {
19497 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19498 buffers_affected.insert(buffer.read(cx).remote_id());
19499 };
19500 }
19501
19502 self.display_map.update(cx, update);
19503
19504 if auto_scroll {
19505 self.request_autoscroll(Autoscroll::fit(), cx);
19506 }
19507
19508 cx.notify();
19509 self.scrollbar_marker_state.dirty = true;
19510 self.active_indent_guides_state.dirty = true;
19511 }
19512
19513 pub fn update_renderer_widths(
19514 &mut self,
19515 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19516 cx: &mut Context<Self>,
19517 ) -> bool {
19518 self.display_map
19519 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19520 }
19521
19522 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19523 self.display_map.read(cx).fold_placeholder.clone()
19524 }
19525
19526 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19527 self.use_base_text_line_numbers = show;
19528 }
19529
19530 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19531 self.buffer.update(cx, |buffer, cx| {
19532 buffer.set_all_diff_hunks_expanded(cx);
19533 });
19534 }
19535
19536 pub fn expand_all_diff_hunks(
19537 &mut self,
19538 _: &ExpandAllDiffHunks,
19539 _window: &mut Window,
19540 cx: &mut Context<Self>,
19541 ) {
19542 self.buffer.update(cx, |buffer, cx| {
19543 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19544 });
19545 }
19546
19547 pub fn collapse_all_diff_hunks(
19548 &mut self,
19549 _: &CollapseAllDiffHunks,
19550 _window: &mut Window,
19551 cx: &mut Context<Self>,
19552 ) {
19553 self.buffer.update(cx, |buffer, cx| {
19554 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19555 });
19556 }
19557
19558 pub fn toggle_selected_diff_hunks(
19559 &mut self,
19560 _: &ToggleSelectedDiffHunks,
19561 _window: &mut Window,
19562 cx: &mut Context<Self>,
19563 ) {
19564 let ranges: Vec<_> = self
19565 .selections
19566 .disjoint_anchors()
19567 .iter()
19568 .map(|s| s.range())
19569 .collect();
19570 self.toggle_diff_hunks_in_ranges(ranges, cx);
19571 }
19572
19573 pub fn diff_hunks_in_ranges<'a>(
19574 &'a self,
19575 ranges: &'a [Range<Anchor>],
19576 buffer: &'a MultiBufferSnapshot,
19577 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19578 ranges.iter().flat_map(move |range| {
19579 let end_excerpt_id = range.end.excerpt_id;
19580 let range = range.to_point(buffer);
19581 let mut peek_end = range.end;
19582 if range.end.row < buffer.max_row().0 {
19583 peek_end = Point::new(range.end.row + 1, 0);
19584 }
19585 buffer
19586 .diff_hunks_in_range(range.start..peek_end)
19587 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19588 })
19589 }
19590
19591 pub fn has_stageable_diff_hunks_in_ranges(
19592 &self,
19593 ranges: &[Range<Anchor>],
19594 snapshot: &MultiBufferSnapshot,
19595 ) -> bool {
19596 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19597 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19598 }
19599
19600 pub fn toggle_staged_selected_diff_hunks(
19601 &mut self,
19602 _: &::git::ToggleStaged,
19603 _: &mut Window,
19604 cx: &mut Context<Self>,
19605 ) {
19606 let snapshot = self.buffer.read(cx).snapshot(cx);
19607 let ranges: Vec<_> = self
19608 .selections
19609 .disjoint_anchors()
19610 .iter()
19611 .map(|s| s.range())
19612 .collect();
19613 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19614 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19615 }
19616
19617 pub fn set_render_diff_hunk_controls(
19618 &mut self,
19619 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19620 cx: &mut Context<Self>,
19621 ) {
19622 self.render_diff_hunk_controls = render_diff_hunk_controls;
19623 cx.notify();
19624 }
19625
19626 pub fn stage_and_next(
19627 &mut self,
19628 _: &::git::StageAndNext,
19629 window: &mut Window,
19630 cx: &mut Context<Self>,
19631 ) {
19632 self.do_stage_or_unstage_and_next(true, window, cx);
19633 }
19634
19635 pub fn unstage_and_next(
19636 &mut self,
19637 _: &::git::UnstageAndNext,
19638 window: &mut Window,
19639 cx: &mut Context<Self>,
19640 ) {
19641 self.do_stage_or_unstage_and_next(false, window, cx);
19642 }
19643
19644 pub fn stage_or_unstage_diff_hunks(
19645 &mut self,
19646 stage: bool,
19647 ranges: Vec<Range<Anchor>>,
19648 cx: &mut Context<Self>,
19649 ) {
19650 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19651 cx.spawn(async move |this, cx| {
19652 task.await?;
19653 this.update(cx, |this, cx| {
19654 let snapshot = this.buffer.read(cx).snapshot(cx);
19655 let chunk_by = this
19656 .diff_hunks_in_ranges(&ranges, &snapshot)
19657 .chunk_by(|hunk| hunk.buffer_id);
19658 for (buffer_id, hunks) in &chunk_by {
19659 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19660 }
19661 })
19662 })
19663 .detach_and_log_err(cx);
19664 }
19665
19666 fn save_buffers_for_ranges_if_needed(
19667 &mut self,
19668 ranges: &[Range<Anchor>],
19669 cx: &mut Context<Editor>,
19670 ) -> Task<Result<()>> {
19671 let multibuffer = self.buffer.read(cx);
19672 let snapshot = multibuffer.read(cx);
19673 let buffer_ids: HashSet<_> = ranges
19674 .iter()
19675 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19676 .collect();
19677 drop(snapshot);
19678
19679 let mut buffers = HashSet::default();
19680 for buffer_id in buffer_ids {
19681 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19682 let buffer = buffer_entity.read(cx);
19683 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19684 {
19685 buffers.insert(buffer_entity);
19686 }
19687 }
19688 }
19689
19690 if let Some(project) = &self.project {
19691 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19692 } else {
19693 Task::ready(Ok(()))
19694 }
19695 }
19696
19697 fn do_stage_or_unstage_and_next(
19698 &mut self,
19699 stage: bool,
19700 window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19704
19705 if ranges.iter().any(|range| range.start != range.end) {
19706 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19707 return;
19708 }
19709
19710 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19711 let snapshot = self.snapshot(window, cx);
19712 let position = self
19713 .selections
19714 .newest::<Point>(&snapshot.display_snapshot)
19715 .head();
19716 let mut row = snapshot
19717 .buffer_snapshot()
19718 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19719 .find(|hunk| hunk.row_range.start.0 > position.row)
19720 .map(|hunk| hunk.row_range.start);
19721
19722 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19723 // Outside of the project diff editor, wrap around to the beginning.
19724 if !all_diff_hunks_expanded {
19725 row = row.or_else(|| {
19726 snapshot
19727 .buffer_snapshot()
19728 .diff_hunks_in_range(Point::zero()..position)
19729 .find(|hunk| hunk.row_range.end.0 < position.row)
19730 .map(|hunk| hunk.row_range.start)
19731 });
19732 }
19733
19734 if let Some(row) = row {
19735 let destination = Point::new(row.0, 0);
19736 let autoscroll = Autoscroll::center();
19737
19738 self.unfold_ranges(&[destination..destination], false, false, cx);
19739 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19740 s.select_ranges([destination..destination]);
19741 });
19742 }
19743 }
19744
19745 fn do_stage_or_unstage(
19746 &self,
19747 stage: bool,
19748 buffer_id: BufferId,
19749 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19750 cx: &mut App,
19751 ) -> Option<()> {
19752 let project = self.project()?;
19753 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19754 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19755 let buffer_snapshot = buffer.read(cx).snapshot();
19756 let file_exists = buffer_snapshot
19757 .file()
19758 .is_some_and(|file| file.disk_state().exists());
19759 diff.update(cx, |diff, cx| {
19760 diff.stage_or_unstage_hunks(
19761 stage,
19762 &hunks
19763 .map(|hunk| buffer_diff::DiffHunk {
19764 buffer_range: hunk.buffer_range,
19765 // We don't need to pass in word diffs here because they're only used for rendering and
19766 // this function changes internal state
19767 base_word_diffs: Vec::default(),
19768 buffer_word_diffs: Vec::default(),
19769 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19770 ..hunk.diff_base_byte_range.end.0,
19771 secondary_status: hunk.secondary_status,
19772 range: Point::zero()..Point::zero(), // unused
19773 })
19774 .collect::<Vec<_>>(),
19775 &buffer_snapshot,
19776 file_exists,
19777 cx,
19778 )
19779 });
19780 None
19781 }
19782
19783 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19784 let ranges: Vec<_> = self
19785 .selections
19786 .disjoint_anchors()
19787 .iter()
19788 .map(|s| s.range())
19789 .collect();
19790 self.buffer
19791 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19792 }
19793
19794 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19795 self.buffer.update(cx, |buffer, cx| {
19796 let ranges = vec![Anchor::min()..Anchor::max()];
19797 if !buffer.all_diff_hunks_expanded()
19798 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19799 {
19800 buffer.collapse_diff_hunks(ranges, cx);
19801 true
19802 } else {
19803 false
19804 }
19805 })
19806 }
19807
19808 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19809 if self.buffer.read(cx).all_diff_hunks_expanded() {
19810 return true;
19811 }
19812 let ranges = vec![Anchor::min()..Anchor::max()];
19813 self.buffer
19814 .read(cx)
19815 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19816 }
19817
19818 fn toggle_diff_hunks_in_ranges(
19819 &mut self,
19820 ranges: Vec<Range<Anchor>>,
19821 cx: &mut Context<Editor>,
19822 ) {
19823 self.buffer.update(cx, |buffer, cx| {
19824 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19825 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19826 })
19827 }
19828
19829 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19830 self.buffer.update(cx, |buffer, cx| {
19831 let snapshot = buffer.snapshot(cx);
19832 let excerpt_id = range.end.excerpt_id;
19833 let point_range = range.to_point(&snapshot);
19834 let expand = !buffer.single_hunk_is_expanded(range, cx);
19835 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19836 })
19837 }
19838
19839 pub(crate) fn apply_all_diff_hunks(
19840 &mut self,
19841 _: &ApplyAllDiffHunks,
19842 window: &mut Window,
19843 cx: &mut Context<Self>,
19844 ) {
19845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19846
19847 let buffers = self.buffer.read(cx).all_buffers();
19848 for branch_buffer in buffers {
19849 branch_buffer.update(cx, |branch_buffer, cx| {
19850 branch_buffer.merge_into_base(Vec::new(), cx);
19851 });
19852 }
19853
19854 if let Some(project) = self.project.clone() {
19855 self.save(
19856 SaveOptions {
19857 format: true,
19858 autosave: false,
19859 },
19860 project,
19861 window,
19862 cx,
19863 )
19864 .detach_and_log_err(cx);
19865 }
19866 }
19867
19868 pub(crate) fn apply_selected_diff_hunks(
19869 &mut self,
19870 _: &ApplyDiffHunk,
19871 window: &mut Window,
19872 cx: &mut Context<Self>,
19873 ) {
19874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19875 let snapshot = self.snapshot(window, cx);
19876 let hunks = snapshot.hunks_for_ranges(
19877 self.selections
19878 .all(&snapshot.display_snapshot)
19879 .into_iter()
19880 .map(|selection| selection.range()),
19881 );
19882 let mut ranges_by_buffer = HashMap::default();
19883 self.transact(window, cx, |editor, _window, cx| {
19884 for hunk in hunks {
19885 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19886 ranges_by_buffer
19887 .entry(buffer.clone())
19888 .or_insert_with(Vec::new)
19889 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19890 }
19891 }
19892
19893 for (buffer, ranges) in ranges_by_buffer {
19894 buffer.update(cx, |buffer, cx| {
19895 buffer.merge_into_base(ranges, cx);
19896 });
19897 }
19898 });
19899
19900 if let Some(project) = self.project.clone() {
19901 self.save(
19902 SaveOptions {
19903 format: true,
19904 autosave: false,
19905 },
19906 project,
19907 window,
19908 cx,
19909 )
19910 .detach_and_log_err(cx);
19911 }
19912 }
19913
19914 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19915 if hovered != self.gutter_hovered {
19916 self.gutter_hovered = hovered;
19917 cx.notify();
19918 }
19919 }
19920
19921 pub fn insert_blocks(
19922 &mut self,
19923 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19924 autoscroll: Option<Autoscroll>,
19925 cx: &mut Context<Self>,
19926 ) -> Vec<CustomBlockId> {
19927 let blocks = self
19928 .display_map
19929 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19930 if let Some(autoscroll) = autoscroll {
19931 self.request_autoscroll(autoscroll, cx);
19932 }
19933 cx.notify();
19934 blocks
19935 }
19936
19937 pub fn resize_blocks(
19938 &mut self,
19939 heights: HashMap<CustomBlockId, u32>,
19940 autoscroll: Option<Autoscroll>,
19941 cx: &mut Context<Self>,
19942 ) {
19943 self.display_map
19944 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19945 if let Some(autoscroll) = autoscroll {
19946 self.request_autoscroll(autoscroll, cx);
19947 }
19948 cx.notify();
19949 }
19950
19951 pub fn replace_blocks(
19952 &mut self,
19953 renderers: HashMap<CustomBlockId, RenderBlock>,
19954 autoscroll: Option<Autoscroll>,
19955 cx: &mut Context<Self>,
19956 ) {
19957 self.display_map
19958 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19959 if let Some(autoscroll) = autoscroll {
19960 self.request_autoscroll(autoscroll, cx);
19961 }
19962 cx.notify();
19963 }
19964
19965 pub fn remove_blocks(
19966 &mut self,
19967 block_ids: HashSet<CustomBlockId>,
19968 autoscroll: Option<Autoscroll>,
19969 cx: &mut Context<Self>,
19970 ) {
19971 self.display_map.update(cx, |display_map, cx| {
19972 display_map.remove_blocks(block_ids, cx)
19973 });
19974 if let Some(autoscroll) = autoscroll {
19975 self.request_autoscroll(autoscroll, cx);
19976 }
19977 cx.notify();
19978 }
19979
19980 pub fn row_for_block(
19981 &self,
19982 block_id: CustomBlockId,
19983 cx: &mut Context<Self>,
19984 ) -> Option<DisplayRow> {
19985 self.display_map
19986 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19987 }
19988
19989 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19990 self.focused_block = Some(focused_block);
19991 }
19992
19993 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19994 self.focused_block.take()
19995 }
19996
19997 pub fn insert_creases(
19998 &mut self,
19999 creases: impl IntoIterator<Item = Crease<Anchor>>,
20000 cx: &mut Context<Self>,
20001 ) -> Vec<CreaseId> {
20002 self.display_map
20003 .update(cx, |map, cx| map.insert_creases(creases, cx))
20004 }
20005
20006 pub fn remove_creases(
20007 &mut self,
20008 ids: impl IntoIterator<Item = CreaseId>,
20009 cx: &mut Context<Self>,
20010 ) -> Vec<(CreaseId, Range<Anchor>)> {
20011 self.display_map
20012 .update(cx, |map, cx| map.remove_creases(ids, cx))
20013 }
20014
20015 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20016 self.display_map
20017 .update(cx, |map, cx| map.snapshot(cx))
20018 .longest_row()
20019 }
20020
20021 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20022 self.display_map
20023 .update(cx, |map, cx| map.snapshot(cx))
20024 .max_point()
20025 }
20026
20027 pub fn text(&self, cx: &App) -> String {
20028 self.buffer.read(cx).read(cx).text()
20029 }
20030
20031 pub fn is_empty(&self, cx: &App) -> bool {
20032 self.buffer.read(cx).read(cx).is_empty()
20033 }
20034
20035 pub fn text_option(&self, cx: &App) -> Option<String> {
20036 let text = self.text(cx);
20037 let text = text.trim();
20038
20039 if text.is_empty() {
20040 return None;
20041 }
20042
20043 Some(text.to_string())
20044 }
20045
20046 pub fn set_text(
20047 &mut self,
20048 text: impl Into<Arc<str>>,
20049 window: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 self.transact(window, cx, |this, _, cx| {
20053 this.buffer
20054 .read(cx)
20055 .as_singleton()
20056 .expect("you can only call set_text on editors for singleton buffers")
20057 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20058 });
20059 }
20060
20061 pub fn display_text(&self, cx: &mut App) -> String {
20062 self.display_map
20063 .update(cx, |map, cx| map.snapshot(cx))
20064 .text()
20065 }
20066
20067 fn create_minimap(
20068 &self,
20069 minimap_settings: MinimapSettings,
20070 window: &mut Window,
20071 cx: &mut Context<Self>,
20072 ) -> Option<Entity<Self>> {
20073 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20074 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20075 }
20076
20077 fn initialize_new_minimap(
20078 &self,
20079 minimap_settings: MinimapSettings,
20080 window: &mut Window,
20081 cx: &mut Context<Self>,
20082 ) -> Entity<Self> {
20083 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20084
20085 let mut minimap = Editor::new_internal(
20086 EditorMode::Minimap {
20087 parent: cx.weak_entity(),
20088 },
20089 self.buffer.clone(),
20090 None,
20091 Some(self.display_map.clone()),
20092 window,
20093 cx,
20094 );
20095 minimap.scroll_manager.clone_state(&self.scroll_manager);
20096 minimap.set_text_style_refinement(TextStyleRefinement {
20097 font_size: Some(MINIMAP_FONT_SIZE),
20098 font_weight: Some(MINIMAP_FONT_WEIGHT),
20099 ..Default::default()
20100 });
20101 minimap.update_minimap_configuration(minimap_settings, cx);
20102 cx.new(|_| minimap)
20103 }
20104
20105 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20106 let current_line_highlight = minimap_settings
20107 .current_line_highlight
20108 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20109 self.set_current_line_highlight(Some(current_line_highlight));
20110 }
20111
20112 pub fn minimap(&self) -> Option<&Entity<Self>> {
20113 self.minimap
20114 .as_ref()
20115 .filter(|_| self.minimap_visibility.visible())
20116 }
20117
20118 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20119 let mut wrap_guides = smallvec![];
20120
20121 if self.show_wrap_guides == Some(false) {
20122 return wrap_guides;
20123 }
20124
20125 let settings = self.buffer.read(cx).language_settings(cx);
20126 if settings.show_wrap_guides {
20127 match self.soft_wrap_mode(cx) {
20128 SoftWrap::Column(soft_wrap) => {
20129 wrap_guides.push((soft_wrap as usize, true));
20130 }
20131 SoftWrap::Bounded(soft_wrap) => {
20132 wrap_guides.push((soft_wrap as usize, true));
20133 }
20134 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20135 }
20136 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20137 }
20138
20139 wrap_guides
20140 }
20141
20142 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20143 let settings = self.buffer.read(cx).language_settings(cx);
20144 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20145 match mode {
20146 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20147 SoftWrap::None
20148 }
20149 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20150 language_settings::SoftWrap::PreferredLineLength => {
20151 SoftWrap::Column(settings.preferred_line_length)
20152 }
20153 language_settings::SoftWrap::Bounded => {
20154 SoftWrap::Bounded(settings.preferred_line_length)
20155 }
20156 }
20157 }
20158
20159 pub fn set_soft_wrap_mode(
20160 &mut self,
20161 mode: language_settings::SoftWrap,
20162
20163 cx: &mut Context<Self>,
20164 ) {
20165 self.soft_wrap_mode_override = Some(mode);
20166 cx.notify();
20167 }
20168
20169 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20170 self.hard_wrap = hard_wrap;
20171 cx.notify();
20172 }
20173
20174 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20175 self.text_style_refinement = Some(style);
20176 }
20177
20178 /// called by the Element so we know what style we were most recently rendered with.
20179 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20180 // We intentionally do not inform the display map about the minimap style
20181 // so that wrapping is not recalculated and stays consistent for the editor
20182 // and its linked minimap.
20183 if !self.mode.is_minimap() {
20184 let font = style.text.font();
20185 let font_size = style.text.font_size.to_pixels(window.rem_size());
20186 let display_map = self
20187 .placeholder_display_map
20188 .as_ref()
20189 .filter(|_| self.is_empty(cx))
20190 .unwrap_or(&self.display_map);
20191
20192 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20193 }
20194 self.style = Some(style);
20195 }
20196
20197 pub fn style(&self) -> Option<&EditorStyle> {
20198 self.style.as_ref()
20199 }
20200
20201 // Called by the element. This method is not designed to be called outside of the editor
20202 // element's layout code because it does not notify when rewrapping is computed synchronously.
20203 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20204 if self.is_empty(cx) {
20205 self.placeholder_display_map
20206 .as_ref()
20207 .map_or(false, |display_map| {
20208 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20209 })
20210 } else {
20211 self.display_map
20212 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20213 }
20214 }
20215
20216 pub fn set_soft_wrap(&mut self) {
20217 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20218 }
20219
20220 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20221 if self.soft_wrap_mode_override.is_some() {
20222 self.soft_wrap_mode_override.take();
20223 } else {
20224 let soft_wrap = match self.soft_wrap_mode(cx) {
20225 SoftWrap::GitDiff => return,
20226 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20227 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20228 language_settings::SoftWrap::None
20229 }
20230 };
20231 self.soft_wrap_mode_override = Some(soft_wrap);
20232 }
20233 cx.notify();
20234 }
20235
20236 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20237 let Some(workspace) = self.workspace() else {
20238 return;
20239 };
20240 let fs = workspace.read(cx).app_state().fs.clone();
20241 let current_show = TabBarSettings::get_global(cx).show;
20242 update_settings_file(fs, cx, move |setting, _| {
20243 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20244 });
20245 }
20246
20247 pub fn toggle_indent_guides(
20248 &mut self,
20249 _: &ToggleIndentGuides,
20250 _: &mut Window,
20251 cx: &mut Context<Self>,
20252 ) {
20253 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20254 self.buffer
20255 .read(cx)
20256 .language_settings(cx)
20257 .indent_guides
20258 .enabled
20259 });
20260 self.show_indent_guides = Some(!currently_enabled);
20261 cx.notify();
20262 }
20263
20264 fn should_show_indent_guides(&self) -> Option<bool> {
20265 self.show_indent_guides
20266 }
20267
20268 pub fn disable_indent_guides_for_buffer(
20269 &mut self,
20270 buffer_id: BufferId,
20271 cx: &mut Context<Self>,
20272 ) {
20273 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20274 cx.notify();
20275 }
20276
20277 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20278 self.buffers_with_disabled_indent_guides
20279 .contains(&buffer_id)
20280 }
20281
20282 pub fn toggle_line_numbers(
20283 &mut self,
20284 _: &ToggleLineNumbers,
20285 _: &mut Window,
20286 cx: &mut Context<Self>,
20287 ) {
20288 let mut editor_settings = EditorSettings::get_global(cx).clone();
20289 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20290 EditorSettings::override_global(editor_settings, cx);
20291 }
20292
20293 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20294 if let Some(show_line_numbers) = self.show_line_numbers {
20295 return show_line_numbers;
20296 }
20297 EditorSettings::get_global(cx).gutter.line_numbers
20298 }
20299
20300 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20301 match (
20302 self.use_relative_line_numbers,
20303 EditorSettings::get_global(cx).relative_line_numbers,
20304 ) {
20305 (None, setting) => setting,
20306 (Some(false), _) => RelativeLineNumbers::Disabled,
20307 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20308 (Some(true), _) => RelativeLineNumbers::Enabled,
20309 }
20310 }
20311
20312 pub fn toggle_relative_line_numbers(
20313 &mut self,
20314 _: &ToggleRelativeLineNumbers,
20315 _: &mut Window,
20316 cx: &mut Context<Self>,
20317 ) {
20318 let is_relative = self.relative_line_numbers(cx);
20319 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20320 }
20321
20322 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20323 self.use_relative_line_numbers = is_relative;
20324 cx.notify();
20325 }
20326
20327 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20328 self.show_gutter = show_gutter;
20329 cx.notify();
20330 }
20331
20332 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20333 self.show_scrollbars = ScrollbarAxes {
20334 horizontal: show,
20335 vertical: show,
20336 };
20337 cx.notify();
20338 }
20339
20340 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20341 self.show_scrollbars.vertical = show;
20342 cx.notify();
20343 }
20344
20345 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20346 self.show_scrollbars.horizontal = show;
20347 cx.notify();
20348 }
20349
20350 pub fn set_minimap_visibility(
20351 &mut self,
20352 minimap_visibility: MinimapVisibility,
20353 window: &mut Window,
20354 cx: &mut Context<Self>,
20355 ) {
20356 if self.minimap_visibility != minimap_visibility {
20357 if minimap_visibility.visible() && self.minimap.is_none() {
20358 let minimap_settings = EditorSettings::get_global(cx).minimap;
20359 self.minimap =
20360 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20361 }
20362 self.minimap_visibility = minimap_visibility;
20363 cx.notify();
20364 }
20365 }
20366
20367 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20368 self.set_show_scrollbars(false, cx);
20369 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20370 }
20371
20372 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20373 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20374 }
20375
20376 /// Normally the text in full mode and auto height editors is padded on the
20377 /// left side by roughly half a character width for improved hit testing.
20378 ///
20379 /// Use this method to disable this for cases where this is not wanted (e.g.
20380 /// if you want to align the editor text with some other text above or below)
20381 /// or if you want to add this padding to single-line editors.
20382 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20383 self.offset_content = offset_content;
20384 cx.notify();
20385 }
20386
20387 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20388 self.show_line_numbers = Some(show_line_numbers);
20389 cx.notify();
20390 }
20391
20392 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20393 self.disable_expand_excerpt_buttons = true;
20394 cx.notify();
20395 }
20396
20397 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20398 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20399 cx.notify();
20400 }
20401
20402 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20403 self.show_code_actions = Some(show_code_actions);
20404 cx.notify();
20405 }
20406
20407 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20408 self.show_runnables = Some(show_runnables);
20409 cx.notify();
20410 }
20411
20412 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20413 self.show_breakpoints = Some(show_breakpoints);
20414 cx.notify();
20415 }
20416
20417 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20418 if self.display_map.read(cx).masked != masked {
20419 self.display_map.update(cx, |map, _| map.masked = masked);
20420 }
20421 cx.notify()
20422 }
20423
20424 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20425 self.show_wrap_guides = Some(show_wrap_guides);
20426 cx.notify();
20427 }
20428
20429 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20430 self.show_indent_guides = Some(show_indent_guides);
20431 cx.notify();
20432 }
20433
20434 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20435 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20436 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20437 && let Some(dir) = file.abs_path(cx).parent()
20438 {
20439 return Some(dir.to_owned());
20440 }
20441 }
20442
20443 None
20444 }
20445
20446 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20447 self.active_excerpt(cx)?
20448 .1
20449 .read(cx)
20450 .file()
20451 .and_then(|f| f.as_local())
20452 }
20453
20454 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20455 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20456 let buffer = buffer.read(cx);
20457 if let Some(project_path) = buffer.project_path(cx) {
20458 let project = self.project()?.read(cx);
20459 project.absolute_path(&project_path, cx)
20460 } else {
20461 buffer
20462 .file()
20463 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20464 }
20465 })
20466 }
20467
20468 pub fn reveal_in_finder(
20469 &mut self,
20470 _: &RevealInFileManager,
20471 _window: &mut Window,
20472 cx: &mut Context<Self>,
20473 ) {
20474 if let Some(target) = self.target_file(cx) {
20475 cx.reveal_path(&target.abs_path(cx));
20476 }
20477 }
20478
20479 pub fn copy_path(
20480 &mut self,
20481 _: &zed_actions::workspace::CopyPath,
20482 _window: &mut Window,
20483 cx: &mut Context<Self>,
20484 ) {
20485 if let Some(path) = self.target_file_abs_path(cx)
20486 && let Some(path) = path.to_str()
20487 {
20488 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20489 } else {
20490 cx.propagate();
20491 }
20492 }
20493
20494 pub fn copy_relative_path(
20495 &mut self,
20496 _: &zed_actions::workspace::CopyRelativePath,
20497 _window: &mut Window,
20498 cx: &mut Context<Self>,
20499 ) {
20500 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20501 let project = self.project()?.read(cx);
20502 let path = buffer.read(cx).file()?.path();
20503 let path = path.display(project.path_style(cx));
20504 Some(path)
20505 }) {
20506 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20507 } else {
20508 cx.propagate();
20509 }
20510 }
20511
20512 /// Returns the project path for the editor's buffer, if any buffer is
20513 /// opened in the editor.
20514 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20515 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20516 buffer.read(cx).project_path(cx)
20517 } else {
20518 None
20519 }
20520 }
20521
20522 // Returns true if the editor handled a go-to-line request
20523 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20524 maybe!({
20525 let breakpoint_store = self.breakpoint_store.as_ref()?;
20526
20527 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20528 else {
20529 self.clear_row_highlights::<ActiveDebugLine>();
20530 return None;
20531 };
20532
20533 let position = active_stack_frame.position;
20534 let buffer_id = position.buffer_id?;
20535 let snapshot = self
20536 .project
20537 .as_ref()?
20538 .read(cx)
20539 .buffer_for_id(buffer_id, cx)?
20540 .read(cx)
20541 .snapshot();
20542
20543 let mut handled = false;
20544 for (id, ExcerptRange { context, .. }) in
20545 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20546 {
20547 if context.start.cmp(&position, &snapshot).is_ge()
20548 || context.end.cmp(&position, &snapshot).is_lt()
20549 {
20550 continue;
20551 }
20552 let snapshot = self.buffer.read(cx).snapshot(cx);
20553 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20554
20555 handled = true;
20556 self.clear_row_highlights::<ActiveDebugLine>();
20557
20558 self.go_to_line::<ActiveDebugLine>(
20559 multibuffer_anchor,
20560 Some(cx.theme().colors().editor_debugger_active_line_background),
20561 window,
20562 cx,
20563 );
20564
20565 cx.notify();
20566 }
20567
20568 handled.then_some(())
20569 })
20570 .is_some()
20571 }
20572
20573 pub fn copy_file_name_without_extension(
20574 &mut self,
20575 _: &CopyFileNameWithoutExtension,
20576 _: &mut Window,
20577 cx: &mut Context<Self>,
20578 ) {
20579 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20580 let file = buffer.read(cx).file()?;
20581 file.path().file_stem()
20582 }) {
20583 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20584 }
20585 }
20586
20587 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20588 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20589 let file = buffer.read(cx).file()?;
20590 Some(file.file_name(cx))
20591 }) {
20592 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20593 }
20594 }
20595
20596 pub fn toggle_git_blame(
20597 &mut self,
20598 _: &::git::Blame,
20599 window: &mut Window,
20600 cx: &mut Context<Self>,
20601 ) {
20602 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20603
20604 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20605 self.start_git_blame(true, window, cx);
20606 }
20607
20608 cx.notify();
20609 }
20610
20611 pub fn toggle_git_blame_inline(
20612 &mut self,
20613 _: &ToggleGitBlameInline,
20614 window: &mut Window,
20615 cx: &mut Context<Self>,
20616 ) {
20617 self.toggle_git_blame_inline_internal(true, window, cx);
20618 cx.notify();
20619 }
20620
20621 pub fn open_git_blame_commit(
20622 &mut self,
20623 _: &OpenGitBlameCommit,
20624 window: &mut Window,
20625 cx: &mut Context<Self>,
20626 ) {
20627 self.open_git_blame_commit_internal(window, cx);
20628 }
20629
20630 fn open_git_blame_commit_internal(
20631 &mut self,
20632 window: &mut Window,
20633 cx: &mut Context<Self>,
20634 ) -> Option<()> {
20635 let blame = self.blame.as_ref()?;
20636 let snapshot = self.snapshot(window, cx);
20637 let cursor = self
20638 .selections
20639 .newest::<Point>(&snapshot.display_snapshot)
20640 .head();
20641 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20642 let (_, blame_entry) = blame
20643 .update(cx, |blame, cx| {
20644 blame
20645 .blame_for_rows(
20646 &[RowInfo {
20647 buffer_id: Some(buffer.remote_id()),
20648 buffer_row: Some(point.row),
20649 ..Default::default()
20650 }],
20651 cx,
20652 )
20653 .next()
20654 })
20655 .flatten()?;
20656 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20657 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20658 let workspace = self.workspace()?.downgrade();
20659 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20660 None
20661 }
20662
20663 pub fn git_blame_inline_enabled(&self) -> bool {
20664 self.git_blame_inline_enabled
20665 }
20666
20667 pub fn toggle_selection_menu(
20668 &mut self,
20669 _: &ToggleSelectionMenu,
20670 _: &mut Window,
20671 cx: &mut Context<Self>,
20672 ) {
20673 self.show_selection_menu = self
20674 .show_selection_menu
20675 .map(|show_selections_menu| !show_selections_menu)
20676 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20677
20678 cx.notify();
20679 }
20680
20681 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20682 self.show_selection_menu
20683 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20684 }
20685
20686 fn start_git_blame(
20687 &mut self,
20688 user_triggered: bool,
20689 window: &mut Window,
20690 cx: &mut Context<Self>,
20691 ) {
20692 if let Some(project) = self.project() {
20693 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20694 && buffer.read(cx).file().is_none()
20695 {
20696 return;
20697 }
20698
20699 let focused = self.focus_handle(cx).contains_focused(window, cx);
20700
20701 let project = project.clone();
20702 let blame = cx
20703 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20704 self.blame_subscription =
20705 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20706 self.blame = Some(blame);
20707 }
20708 }
20709
20710 fn toggle_git_blame_inline_internal(
20711 &mut self,
20712 user_triggered: bool,
20713 window: &mut Window,
20714 cx: &mut Context<Self>,
20715 ) {
20716 if self.git_blame_inline_enabled {
20717 self.git_blame_inline_enabled = false;
20718 self.show_git_blame_inline = false;
20719 self.show_git_blame_inline_delay_task.take();
20720 } else {
20721 self.git_blame_inline_enabled = true;
20722 self.start_git_blame_inline(user_triggered, window, cx);
20723 }
20724
20725 cx.notify();
20726 }
20727
20728 fn start_git_blame_inline(
20729 &mut self,
20730 user_triggered: bool,
20731 window: &mut Window,
20732 cx: &mut Context<Self>,
20733 ) {
20734 self.start_git_blame(user_triggered, window, cx);
20735
20736 if ProjectSettings::get_global(cx)
20737 .git
20738 .inline_blame_delay()
20739 .is_some()
20740 {
20741 self.start_inline_blame_timer(window, cx);
20742 } else {
20743 self.show_git_blame_inline = true
20744 }
20745 }
20746
20747 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20748 self.blame.as_ref()
20749 }
20750
20751 pub fn show_git_blame_gutter(&self) -> bool {
20752 self.show_git_blame_gutter
20753 }
20754
20755 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20756 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20757 }
20758
20759 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20760 self.show_git_blame_inline
20761 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20762 && !self.newest_selection_head_on_empty_line(cx)
20763 && self.has_blame_entries(cx)
20764 }
20765
20766 fn has_blame_entries(&self, cx: &App) -> bool {
20767 self.blame()
20768 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20769 }
20770
20771 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20772 let cursor_anchor = self.selections.newest_anchor().head();
20773
20774 let snapshot = self.buffer.read(cx).snapshot(cx);
20775 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20776
20777 snapshot.line_len(buffer_row) == 0
20778 }
20779
20780 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20781 let buffer_and_selection = maybe!({
20782 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20783 let selection_range = selection.range();
20784
20785 let multi_buffer = self.buffer().read(cx);
20786 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20787 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20788
20789 let (buffer, range, _) = if selection.reversed {
20790 buffer_ranges.first()
20791 } else {
20792 buffer_ranges.last()
20793 }?;
20794
20795 let selection = text::ToPoint::to_point(&range.start, buffer).row
20796 ..text::ToPoint::to_point(&range.end, buffer).row;
20797 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20798 });
20799
20800 let Some((buffer, selection)) = buffer_and_selection else {
20801 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20802 };
20803
20804 let Some(project) = self.project() else {
20805 return Task::ready(Err(anyhow!("editor does not have project")));
20806 };
20807
20808 project.update(cx, |project, cx| {
20809 project.get_permalink_to_line(&buffer, selection, cx)
20810 })
20811 }
20812
20813 pub fn copy_permalink_to_line(
20814 &mut self,
20815 _: &CopyPermalinkToLine,
20816 window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 let permalink_task = self.get_permalink_to_line(cx);
20820 let workspace = self.workspace();
20821
20822 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20823 Ok(permalink) => {
20824 cx.update(|_, cx| {
20825 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20826 })
20827 .ok();
20828 }
20829 Err(err) => {
20830 let message = format!("Failed to copy permalink: {err}");
20831
20832 anyhow::Result::<()>::Err(err).log_err();
20833
20834 if let Some(workspace) = workspace {
20835 workspace
20836 .update_in(cx, |workspace, _, cx| {
20837 struct CopyPermalinkToLine;
20838
20839 workspace.show_toast(
20840 Toast::new(
20841 NotificationId::unique::<CopyPermalinkToLine>(),
20842 message,
20843 ),
20844 cx,
20845 )
20846 })
20847 .ok();
20848 }
20849 }
20850 })
20851 .detach();
20852 }
20853
20854 pub fn copy_file_location(
20855 &mut self,
20856 _: &CopyFileLocation,
20857 _: &mut Window,
20858 cx: &mut Context<Self>,
20859 ) {
20860 let selection = self
20861 .selections
20862 .newest::<Point>(&self.display_snapshot(cx))
20863 .start
20864 .row
20865 + 1;
20866 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20867 let project = self.project()?.read(cx);
20868 let file = buffer.read(cx).file()?;
20869 let path = file.path().display(project.path_style(cx));
20870
20871 Some(format!("{path}:{selection}"))
20872 }) {
20873 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20874 }
20875 }
20876
20877 pub fn open_permalink_to_line(
20878 &mut self,
20879 _: &OpenPermalinkToLine,
20880 window: &mut Window,
20881 cx: &mut Context<Self>,
20882 ) {
20883 let permalink_task = self.get_permalink_to_line(cx);
20884 let workspace = self.workspace();
20885
20886 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20887 Ok(permalink) => {
20888 cx.update(|_, cx| {
20889 cx.open_url(permalink.as_ref());
20890 })
20891 .ok();
20892 }
20893 Err(err) => {
20894 let message = format!("Failed to open permalink: {err}");
20895
20896 anyhow::Result::<()>::Err(err).log_err();
20897
20898 if let Some(workspace) = workspace {
20899 workspace
20900 .update(cx, |workspace, cx| {
20901 struct OpenPermalinkToLine;
20902
20903 workspace.show_toast(
20904 Toast::new(
20905 NotificationId::unique::<OpenPermalinkToLine>(),
20906 message,
20907 ),
20908 cx,
20909 )
20910 })
20911 .ok();
20912 }
20913 }
20914 })
20915 .detach();
20916 }
20917
20918 pub fn insert_uuid_v4(
20919 &mut self,
20920 _: &InsertUuidV4,
20921 window: &mut Window,
20922 cx: &mut Context<Self>,
20923 ) {
20924 self.insert_uuid(UuidVersion::V4, window, cx);
20925 }
20926
20927 pub fn insert_uuid_v7(
20928 &mut self,
20929 _: &InsertUuidV7,
20930 window: &mut Window,
20931 cx: &mut Context<Self>,
20932 ) {
20933 self.insert_uuid(UuidVersion::V7, window, cx);
20934 }
20935
20936 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20938 self.transact(window, cx, |this, window, cx| {
20939 let edits = this
20940 .selections
20941 .all::<Point>(&this.display_snapshot(cx))
20942 .into_iter()
20943 .map(|selection| {
20944 let uuid = match version {
20945 UuidVersion::V4 => uuid::Uuid::new_v4(),
20946 UuidVersion::V7 => uuid::Uuid::now_v7(),
20947 };
20948
20949 (selection.range(), uuid.to_string())
20950 });
20951 this.edit(edits, cx);
20952 this.refresh_edit_prediction(true, false, window, cx);
20953 });
20954 }
20955
20956 pub fn open_selections_in_multibuffer(
20957 &mut self,
20958 _: &OpenSelectionsInMultibuffer,
20959 window: &mut Window,
20960 cx: &mut Context<Self>,
20961 ) {
20962 let multibuffer = self.buffer.read(cx);
20963
20964 let Some(buffer) = multibuffer.as_singleton() else {
20965 return;
20966 };
20967
20968 let Some(workspace) = self.workspace() else {
20969 return;
20970 };
20971
20972 let title = multibuffer.title(cx).to_string();
20973
20974 let locations = self
20975 .selections
20976 .all_anchors(&self.display_snapshot(cx))
20977 .iter()
20978 .map(|selection| {
20979 (
20980 buffer.clone(),
20981 (selection.start.text_anchor..selection.end.text_anchor)
20982 .to_point(buffer.read(cx)),
20983 )
20984 })
20985 .into_group_map();
20986
20987 cx.spawn_in(window, async move |_, cx| {
20988 workspace.update_in(cx, |workspace, window, cx| {
20989 Self::open_locations_in_multibuffer(
20990 workspace,
20991 locations,
20992 format!("Selections for '{title}'"),
20993 false,
20994 false,
20995 MultibufferSelectionMode::All,
20996 window,
20997 cx,
20998 );
20999 })
21000 })
21001 .detach();
21002 }
21003
21004 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21005 /// last highlight added will be used.
21006 ///
21007 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21008 pub fn highlight_rows<T: 'static>(
21009 &mut self,
21010 range: Range<Anchor>,
21011 color: Hsla,
21012 options: RowHighlightOptions,
21013 cx: &mut Context<Self>,
21014 ) {
21015 let snapshot = self.buffer().read(cx).snapshot(cx);
21016 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21017 let ix = row_highlights.binary_search_by(|highlight| {
21018 Ordering::Equal
21019 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21020 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21021 });
21022
21023 if let Err(mut ix) = ix {
21024 let index = post_inc(&mut self.highlight_order);
21025
21026 // If this range intersects with the preceding highlight, then merge it with
21027 // the preceding highlight. Otherwise insert a new highlight.
21028 let mut merged = false;
21029 if ix > 0 {
21030 let prev_highlight = &mut row_highlights[ix - 1];
21031 if prev_highlight
21032 .range
21033 .end
21034 .cmp(&range.start, &snapshot)
21035 .is_ge()
21036 {
21037 ix -= 1;
21038 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21039 prev_highlight.range.end = range.end;
21040 }
21041 merged = true;
21042 prev_highlight.index = index;
21043 prev_highlight.color = color;
21044 prev_highlight.options = options;
21045 }
21046 }
21047
21048 if !merged {
21049 row_highlights.insert(
21050 ix,
21051 RowHighlight {
21052 range,
21053 index,
21054 color,
21055 options,
21056 type_id: TypeId::of::<T>(),
21057 },
21058 );
21059 }
21060
21061 // If any of the following highlights intersect with this one, merge them.
21062 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21063 let highlight = &row_highlights[ix];
21064 if next_highlight
21065 .range
21066 .start
21067 .cmp(&highlight.range.end, &snapshot)
21068 .is_le()
21069 {
21070 if next_highlight
21071 .range
21072 .end
21073 .cmp(&highlight.range.end, &snapshot)
21074 .is_gt()
21075 {
21076 row_highlights[ix].range.end = next_highlight.range.end;
21077 }
21078 row_highlights.remove(ix + 1);
21079 } else {
21080 break;
21081 }
21082 }
21083 }
21084 }
21085
21086 /// Remove any highlighted row ranges of the given type that intersect the
21087 /// given ranges.
21088 pub fn remove_highlighted_rows<T: 'static>(
21089 &mut self,
21090 ranges_to_remove: Vec<Range<Anchor>>,
21091 cx: &mut Context<Self>,
21092 ) {
21093 let snapshot = self.buffer().read(cx).snapshot(cx);
21094 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21095 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21096 row_highlights.retain(|highlight| {
21097 while let Some(range_to_remove) = ranges_to_remove.peek() {
21098 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21099 Ordering::Less | Ordering::Equal => {
21100 ranges_to_remove.next();
21101 }
21102 Ordering::Greater => {
21103 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21104 Ordering::Less | Ordering::Equal => {
21105 return false;
21106 }
21107 Ordering::Greater => break,
21108 }
21109 }
21110 }
21111 }
21112
21113 true
21114 })
21115 }
21116
21117 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21118 pub fn clear_row_highlights<T: 'static>(&mut self) {
21119 self.highlighted_rows.remove(&TypeId::of::<T>());
21120 }
21121
21122 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21123 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21124 self.highlighted_rows
21125 .get(&TypeId::of::<T>())
21126 .map_or(&[] as &[_], |vec| vec.as_slice())
21127 .iter()
21128 .map(|highlight| (highlight.range.clone(), highlight.color))
21129 }
21130
21131 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21132 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21133 /// Allows to ignore certain kinds of highlights.
21134 pub fn highlighted_display_rows(
21135 &self,
21136 window: &mut Window,
21137 cx: &mut App,
21138 ) -> BTreeMap<DisplayRow, LineHighlight> {
21139 let snapshot = self.snapshot(window, cx);
21140 let mut used_highlight_orders = HashMap::default();
21141 self.highlighted_rows
21142 .iter()
21143 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21144 .fold(
21145 BTreeMap::<DisplayRow, LineHighlight>::new(),
21146 |mut unique_rows, highlight| {
21147 let start = highlight.range.start.to_display_point(&snapshot);
21148 let end = highlight.range.end.to_display_point(&snapshot);
21149 let start_row = start.row().0;
21150 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21151 {
21152 end.row().0.saturating_sub(1)
21153 } else {
21154 end.row().0
21155 };
21156 for row in start_row..=end_row {
21157 let used_index =
21158 used_highlight_orders.entry(row).or_insert(highlight.index);
21159 if highlight.index >= *used_index {
21160 *used_index = highlight.index;
21161 unique_rows.insert(
21162 DisplayRow(row),
21163 LineHighlight {
21164 include_gutter: highlight.options.include_gutter,
21165 border: None,
21166 background: highlight.color.into(),
21167 type_id: Some(highlight.type_id),
21168 },
21169 );
21170 }
21171 }
21172 unique_rows
21173 },
21174 )
21175 }
21176
21177 pub fn highlighted_display_row_for_autoscroll(
21178 &self,
21179 snapshot: &DisplaySnapshot,
21180 ) -> Option<DisplayRow> {
21181 self.highlighted_rows
21182 .values()
21183 .flat_map(|highlighted_rows| highlighted_rows.iter())
21184 .filter_map(|highlight| {
21185 if highlight.options.autoscroll {
21186 Some(highlight.range.start.to_display_point(snapshot).row())
21187 } else {
21188 None
21189 }
21190 })
21191 .min()
21192 }
21193
21194 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21195 self.highlight_background::<SearchWithinRange>(
21196 ranges,
21197 |_, colors| colors.colors().editor_document_highlight_read_background,
21198 cx,
21199 )
21200 }
21201
21202 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21203 self.breadcrumb_header = Some(new_header);
21204 }
21205
21206 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21207 self.clear_background_highlights::<SearchWithinRange>(cx);
21208 }
21209
21210 pub fn highlight_background<T: 'static>(
21211 &mut self,
21212 ranges: &[Range<Anchor>],
21213 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21214 cx: &mut Context<Self>,
21215 ) {
21216 self.background_highlights.insert(
21217 HighlightKey::Type(TypeId::of::<T>()),
21218 (Arc::new(color_fetcher), Arc::from(ranges)),
21219 );
21220 self.scrollbar_marker_state.dirty = true;
21221 cx.notify();
21222 }
21223
21224 pub fn highlight_background_key<T: 'static>(
21225 &mut self,
21226 key: usize,
21227 ranges: &[Range<Anchor>],
21228 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21229 cx: &mut Context<Self>,
21230 ) {
21231 self.background_highlights.insert(
21232 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21233 (Arc::new(color_fetcher), Arc::from(ranges)),
21234 );
21235 self.scrollbar_marker_state.dirty = true;
21236 cx.notify();
21237 }
21238
21239 pub fn clear_background_highlights<T: 'static>(
21240 &mut self,
21241 cx: &mut Context<Self>,
21242 ) -> Option<BackgroundHighlight> {
21243 let text_highlights = self
21244 .background_highlights
21245 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21246 if !text_highlights.1.is_empty() {
21247 self.scrollbar_marker_state.dirty = true;
21248 cx.notify();
21249 }
21250 Some(text_highlights)
21251 }
21252
21253 pub fn highlight_gutter<T: 'static>(
21254 &mut self,
21255 ranges: impl Into<Vec<Range<Anchor>>>,
21256 color_fetcher: fn(&App) -> Hsla,
21257 cx: &mut Context<Self>,
21258 ) {
21259 self.gutter_highlights
21260 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21261 cx.notify();
21262 }
21263
21264 pub fn clear_gutter_highlights<T: 'static>(
21265 &mut self,
21266 cx: &mut Context<Self>,
21267 ) -> Option<GutterHighlight> {
21268 cx.notify();
21269 self.gutter_highlights.remove(&TypeId::of::<T>())
21270 }
21271
21272 pub fn insert_gutter_highlight<T: 'static>(
21273 &mut self,
21274 range: Range<Anchor>,
21275 color_fetcher: fn(&App) -> Hsla,
21276 cx: &mut Context<Self>,
21277 ) {
21278 let snapshot = self.buffer().read(cx).snapshot(cx);
21279 let mut highlights = self
21280 .gutter_highlights
21281 .remove(&TypeId::of::<T>())
21282 .map(|(_, highlights)| highlights)
21283 .unwrap_or_default();
21284 let ix = highlights.binary_search_by(|highlight| {
21285 Ordering::Equal
21286 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21287 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21288 });
21289 if let Err(ix) = ix {
21290 highlights.insert(ix, range);
21291 }
21292 self.gutter_highlights
21293 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21294 }
21295
21296 pub fn remove_gutter_highlights<T: 'static>(
21297 &mut self,
21298 ranges_to_remove: Vec<Range<Anchor>>,
21299 cx: &mut Context<Self>,
21300 ) {
21301 let snapshot = self.buffer().read(cx).snapshot(cx);
21302 let Some((color_fetcher, mut gutter_highlights)) =
21303 self.gutter_highlights.remove(&TypeId::of::<T>())
21304 else {
21305 return;
21306 };
21307 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21308 gutter_highlights.retain(|highlight| {
21309 while let Some(range_to_remove) = ranges_to_remove.peek() {
21310 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21311 Ordering::Less | Ordering::Equal => {
21312 ranges_to_remove.next();
21313 }
21314 Ordering::Greater => {
21315 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21316 Ordering::Less | Ordering::Equal => {
21317 return false;
21318 }
21319 Ordering::Greater => break,
21320 }
21321 }
21322 }
21323 }
21324
21325 true
21326 });
21327 self.gutter_highlights
21328 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21329 }
21330
21331 #[cfg(feature = "test-support")]
21332 pub fn all_text_highlights(
21333 &self,
21334 window: &mut Window,
21335 cx: &mut Context<Self>,
21336 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21337 let snapshot = self.snapshot(window, cx);
21338 self.display_map.update(cx, |display_map, _| {
21339 display_map
21340 .all_text_highlights()
21341 .map(|highlight| {
21342 let (style, ranges) = highlight.as_ref();
21343 (
21344 *style,
21345 ranges
21346 .iter()
21347 .map(|range| range.clone().to_display_points(&snapshot))
21348 .collect(),
21349 )
21350 })
21351 .collect()
21352 })
21353 }
21354
21355 #[cfg(feature = "test-support")]
21356 pub fn all_text_background_highlights(
21357 &self,
21358 window: &mut Window,
21359 cx: &mut Context<Self>,
21360 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21361 let snapshot = self.snapshot(window, cx);
21362 let buffer = &snapshot.buffer_snapshot();
21363 let start = buffer.anchor_before(MultiBufferOffset(0));
21364 let end = buffer.anchor_after(buffer.len());
21365 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21366 }
21367
21368 #[cfg(any(test, feature = "test-support"))]
21369 pub fn sorted_background_highlights_in_range(
21370 &self,
21371 search_range: Range<Anchor>,
21372 display_snapshot: &DisplaySnapshot,
21373 theme: &Theme,
21374 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21375 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21376 res.sort_by(|a, b| {
21377 a.0.start
21378 .cmp(&b.0.start)
21379 .then_with(|| a.0.end.cmp(&b.0.end))
21380 .then_with(|| a.1.cmp(&b.1))
21381 });
21382 res
21383 }
21384
21385 #[cfg(feature = "test-support")]
21386 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21387 let snapshot = self.buffer().read(cx).snapshot(cx);
21388
21389 let highlights = self
21390 .background_highlights
21391 .get(&HighlightKey::Type(TypeId::of::<
21392 items::BufferSearchHighlights,
21393 >()));
21394
21395 if let Some((_color, ranges)) = highlights {
21396 ranges
21397 .iter()
21398 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21399 .collect_vec()
21400 } else {
21401 vec![]
21402 }
21403 }
21404
21405 fn document_highlights_for_position<'a>(
21406 &'a self,
21407 position: Anchor,
21408 buffer: &'a MultiBufferSnapshot,
21409 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21410 let read_highlights = self
21411 .background_highlights
21412 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21413 .map(|h| &h.1);
21414 let write_highlights = self
21415 .background_highlights
21416 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21417 .map(|h| &h.1);
21418 let left_position = position.bias_left(buffer);
21419 let right_position = position.bias_right(buffer);
21420 read_highlights
21421 .into_iter()
21422 .chain(write_highlights)
21423 .flat_map(move |ranges| {
21424 let start_ix = match ranges.binary_search_by(|probe| {
21425 let cmp = probe.end.cmp(&left_position, buffer);
21426 if cmp.is_ge() {
21427 Ordering::Greater
21428 } else {
21429 Ordering::Less
21430 }
21431 }) {
21432 Ok(i) | Err(i) => i,
21433 };
21434
21435 ranges[start_ix..]
21436 .iter()
21437 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21438 })
21439 }
21440
21441 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21442 self.background_highlights
21443 .get(&HighlightKey::Type(TypeId::of::<T>()))
21444 .is_some_and(|(_, highlights)| !highlights.is_empty())
21445 }
21446
21447 /// Returns all background highlights for a given range.
21448 ///
21449 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21450 pub fn background_highlights_in_range(
21451 &self,
21452 search_range: Range<Anchor>,
21453 display_snapshot: &DisplaySnapshot,
21454 theme: &Theme,
21455 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21456 let mut results = Vec::new();
21457 for (color_fetcher, ranges) in self.background_highlights.values() {
21458 let start_ix = match ranges.binary_search_by(|probe| {
21459 let cmp = probe
21460 .end
21461 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21462 if cmp.is_gt() {
21463 Ordering::Greater
21464 } else {
21465 Ordering::Less
21466 }
21467 }) {
21468 Ok(i) | Err(i) => i,
21469 };
21470 for (index, range) in ranges[start_ix..].iter().enumerate() {
21471 if range
21472 .start
21473 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21474 .is_ge()
21475 {
21476 break;
21477 }
21478
21479 let color = color_fetcher(&(start_ix + index), theme);
21480 let start = range.start.to_display_point(display_snapshot);
21481 let end = range.end.to_display_point(display_snapshot);
21482 results.push((start..end, color))
21483 }
21484 }
21485 results
21486 }
21487
21488 pub fn gutter_highlights_in_range(
21489 &self,
21490 search_range: Range<Anchor>,
21491 display_snapshot: &DisplaySnapshot,
21492 cx: &App,
21493 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21494 let mut results = Vec::new();
21495 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21496 let color = color_fetcher(cx);
21497 let start_ix = match ranges.binary_search_by(|probe| {
21498 let cmp = probe
21499 .end
21500 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21501 if cmp.is_gt() {
21502 Ordering::Greater
21503 } else {
21504 Ordering::Less
21505 }
21506 }) {
21507 Ok(i) | Err(i) => i,
21508 };
21509 for range in &ranges[start_ix..] {
21510 if range
21511 .start
21512 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21513 .is_ge()
21514 {
21515 break;
21516 }
21517
21518 let start = range.start.to_display_point(display_snapshot);
21519 let end = range.end.to_display_point(display_snapshot);
21520 results.push((start..end, color))
21521 }
21522 }
21523 results
21524 }
21525
21526 /// Get the text ranges corresponding to the redaction query
21527 pub fn redacted_ranges(
21528 &self,
21529 search_range: Range<Anchor>,
21530 display_snapshot: &DisplaySnapshot,
21531 cx: &App,
21532 ) -> Vec<Range<DisplayPoint>> {
21533 display_snapshot
21534 .buffer_snapshot()
21535 .redacted_ranges(search_range, |file| {
21536 if let Some(file) = file {
21537 file.is_private()
21538 && EditorSettings::get(
21539 Some(SettingsLocation {
21540 worktree_id: file.worktree_id(cx),
21541 path: file.path().as_ref(),
21542 }),
21543 cx,
21544 )
21545 .redact_private_values
21546 } else {
21547 false
21548 }
21549 })
21550 .map(|range| {
21551 range.start.to_display_point(display_snapshot)
21552 ..range.end.to_display_point(display_snapshot)
21553 })
21554 .collect()
21555 }
21556
21557 pub fn highlight_text_key<T: 'static>(
21558 &mut self,
21559 key: usize,
21560 ranges: Vec<Range<Anchor>>,
21561 style: HighlightStyle,
21562 merge: bool,
21563 cx: &mut Context<Self>,
21564 ) {
21565 self.display_map.update(cx, |map, cx| {
21566 map.highlight_text(
21567 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21568 ranges,
21569 style,
21570 merge,
21571 cx,
21572 );
21573 });
21574 cx.notify();
21575 }
21576
21577 pub fn highlight_text<T: 'static>(
21578 &mut self,
21579 ranges: Vec<Range<Anchor>>,
21580 style: HighlightStyle,
21581 cx: &mut Context<Self>,
21582 ) {
21583 self.display_map.update(cx, |map, cx| {
21584 map.highlight_text(
21585 HighlightKey::Type(TypeId::of::<T>()),
21586 ranges,
21587 style,
21588 false,
21589 cx,
21590 )
21591 });
21592 cx.notify();
21593 }
21594
21595 pub fn text_highlights<'a, T: 'static>(
21596 &'a self,
21597 cx: &'a App,
21598 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21599 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21600 }
21601
21602 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21603 let cleared = self
21604 .display_map
21605 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21606 if cleared {
21607 cx.notify();
21608 }
21609 }
21610
21611 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21612 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21613 && self.focus_handle.is_focused(window)
21614 }
21615
21616 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21617 self.show_cursor_when_unfocused = is_enabled;
21618 cx.notify();
21619 }
21620
21621 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21622 cx.notify();
21623 }
21624
21625 fn on_debug_session_event(
21626 &mut self,
21627 _session: Entity<Session>,
21628 event: &SessionEvent,
21629 cx: &mut Context<Self>,
21630 ) {
21631 if let SessionEvent::InvalidateInlineValue = event {
21632 self.refresh_inline_values(cx);
21633 }
21634 }
21635
21636 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21637 let Some(project) = self.project.clone() else {
21638 return;
21639 };
21640
21641 if !self.inline_value_cache.enabled {
21642 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21643 self.splice_inlays(&inlays, Vec::new(), cx);
21644 return;
21645 }
21646
21647 let current_execution_position = self
21648 .highlighted_rows
21649 .get(&TypeId::of::<ActiveDebugLine>())
21650 .and_then(|lines| lines.last().map(|line| line.range.end));
21651
21652 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21653 let inline_values = editor
21654 .update(cx, |editor, cx| {
21655 let Some(current_execution_position) = current_execution_position else {
21656 return Some(Task::ready(Ok(Vec::new())));
21657 };
21658
21659 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21660 let snapshot = buffer.snapshot(cx);
21661
21662 let excerpt = snapshot.excerpt_containing(
21663 current_execution_position..current_execution_position,
21664 )?;
21665
21666 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21667 })?;
21668
21669 let range =
21670 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21671
21672 project.inline_values(buffer, range, cx)
21673 })
21674 .ok()
21675 .flatten()?
21676 .await
21677 .context("refreshing debugger inlays")
21678 .log_err()?;
21679
21680 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21681
21682 for (buffer_id, inline_value) in inline_values
21683 .into_iter()
21684 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21685 {
21686 buffer_inline_values
21687 .entry(buffer_id)
21688 .or_default()
21689 .push(inline_value);
21690 }
21691
21692 editor
21693 .update(cx, |editor, cx| {
21694 let snapshot = editor.buffer.read(cx).snapshot(cx);
21695 let mut new_inlays = Vec::default();
21696
21697 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21698 let buffer_id = buffer_snapshot.remote_id();
21699 buffer_inline_values
21700 .get(&buffer_id)
21701 .into_iter()
21702 .flatten()
21703 .for_each(|hint| {
21704 let inlay = Inlay::debugger(
21705 post_inc(&mut editor.next_inlay_id),
21706 Anchor::in_buffer(excerpt_id, hint.position),
21707 hint.text(),
21708 );
21709 if !inlay.text().chars().contains(&'\n') {
21710 new_inlays.push(inlay);
21711 }
21712 });
21713 }
21714
21715 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21716 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21717
21718 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21719 })
21720 .ok()?;
21721 Some(())
21722 });
21723 }
21724
21725 fn on_buffer_event(
21726 &mut self,
21727 multibuffer: &Entity<MultiBuffer>,
21728 event: &multi_buffer::Event,
21729 window: &mut Window,
21730 cx: &mut Context<Self>,
21731 ) {
21732 match event {
21733 multi_buffer::Event::Edited { edited_buffer } => {
21734 self.scrollbar_marker_state.dirty = true;
21735 self.active_indent_guides_state.dirty = true;
21736 self.refresh_active_diagnostics(cx);
21737 self.refresh_code_actions(window, cx);
21738 self.refresh_single_line_folds(window, cx);
21739 self.refresh_matching_bracket_highlights(window, cx);
21740 if self.has_active_edit_prediction() {
21741 self.update_visible_edit_prediction(window, cx);
21742 }
21743
21744 if let Some(buffer) = edited_buffer {
21745 if buffer.read(cx).file().is_none() {
21746 cx.emit(EditorEvent::TitleChanged);
21747 }
21748
21749 if self.project.is_some() {
21750 let buffer_id = buffer.read(cx).remote_id();
21751 self.register_buffer(buffer_id, cx);
21752 self.update_lsp_data(Some(buffer_id), window, cx);
21753 self.refresh_inlay_hints(
21754 InlayHintRefreshReason::BufferEdited(buffer_id),
21755 cx,
21756 );
21757 }
21758 }
21759
21760 cx.emit(EditorEvent::BufferEdited);
21761 cx.emit(SearchEvent::MatchesInvalidated);
21762
21763 let Some(project) = &self.project else { return };
21764 let (telemetry, is_via_ssh) = {
21765 let project = project.read(cx);
21766 let telemetry = project.client().telemetry().clone();
21767 let is_via_ssh = project.is_via_remote_server();
21768 (telemetry, is_via_ssh)
21769 };
21770 telemetry.log_edit_event("editor", is_via_ssh);
21771 }
21772 multi_buffer::Event::ExcerptsAdded {
21773 buffer,
21774 predecessor,
21775 excerpts,
21776 } => {
21777 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21778 let buffer_id = buffer.read(cx).remote_id();
21779 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21780 && let Some(project) = &self.project
21781 {
21782 update_uncommitted_diff_for_buffer(
21783 cx.entity(),
21784 project,
21785 [buffer.clone()],
21786 self.buffer.clone(),
21787 cx,
21788 )
21789 .detach();
21790 }
21791 self.update_lsp_data(Some(buffer_id), window, cx);
21792 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21793 self.colorize_brackets(false, cx);
21794 cx.emit(EditorEvent::ExcerptsAdded {
21795 buffer: buffer.clone(),
21796 predecessor: *predecessor,
21797 excerpts: excerpts.clone(),
21798 });
21799 }
21800 multi_buffer::Event::ExcerptsRemoved {
21801 ids,
21802 removed_buffer_ids,
21803 } => {
21804 if let Some(inlay_hints) = &mut self.inlay_hints {
21805 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21806 }
21807 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21808 for buffer_id in removed_buffer_ids {
21809 self.registered_buffers.remove(buffer_id);
21810 }
21811 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21812 cx.emit(EditorEvent::ExcerptsRemoved {
21813 ids: ids.clone(),
21814 removed_buffer_ids: removed_buffer_ids.clone(),
21815 });
21816 }
21817 multi_buffer::Event::ExcerptsEdited {
21818 excerpt_ids,
21819 buffer_ids,
21820 } => {
21821 self.display_map.update(cx, |map, cx| {
21822 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21823 });
21824 cx.emit(EditorEvent::ExcerptsEdited {
21825 ids: excerpt_ids.clone(),
21826 });
21827 }
21828 multi_buffer::Event::ExcerptsExpanded { ids } => {
21829 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21830 self.refresh_document_highlights(cx);
21831 for id in ids {
21832 self.fetched_tree_sitter_chunks.remove(id);
21833 }
21834 self.colorize_brackets(false, cx);
21835 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21836 }
21837 multi_buffer::Event::Reparsed(buffer_id) => {
21838 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21839 self.refresh_selected_text_highlights(true, window, cx);
21840 self.colorize_brackets(true, cx);
21841 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21842
21843 cx.emit(EditorEvent::Reparsed(*buffer_id));
21844 }
21845 multi_buffer::Event::DiffHunksToggled => {
21846 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21847 }
21848 multi_buffer::Event::LanguageChanged(buffer_id) => {
21849 self.registered_buffers.remove(&buffer_id);
21850 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21851 cx.emit(EditorEvent::Reparsed(*buffer_id));
21852 cx.notify();
21853 }
21854 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21855 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21856 multi_buffer::Event::FileHandleChanged
21857 | multi_buffer::Event::Reloaded
21858 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21859 multi_buffer::Event::DiagnosticsUpdated => {
21860 self.update_diagnostics_state(window, cx);
21861 }
21862 _ => {}
21863 };
21864 }
21865
21866 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21867 if !self.diagnostics_enabled() {
21868 return;
21869 }
21870 self.refresh_active_diagnostics(cx);
21871 self.refresh_inline_diagnostics(true, window, cx);
21872 self.scrollbar_marker_state.dirty = true;
21873 cx.notify();
21874 }
21875
21876 pub fn start_temporary_diff_override(&mut self) {
21877 self.load_diff_task.take();
21878 self.temporary_diff_override = true;
21879 }
21880
21881 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21882 self.temporary_diff_override = false;
21883 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21884 self.buffer.update(cx, |buffer, cx| {
21885 buffer.set_all_diff_hunks_collapsed(cx);
21886 });
21887
21888 if let Some(project) = self.project.clone() {
21889 self.load_diff_task = Some(
21890 update_uncommitted_diff_for_buffer(
21891 cx.entity(),
21892 &project,
21893 self.buffer.read(cx).all_buffers(),
21894 self.buffer.clone(),
21895 cx,
21896 )
21897 .shared(),
21898 );
21899 }
21900 }
21901
21902 fn on_display_map_changed(
21903 &mut self,
21904 _: Entity<DisplayMap>,
21905 _: &mut Window,
21906 cx: &mut Context<Self>,
21907 ) {
21908 cx.notify();
21909 }
21910
21911 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
21912 if !self.mode.is_full() {
21913 return None;
21914 }
21915
21916 let theme_settings = theme::ThemeSettings::get_global(cx);
21917 let theme = cx.theme();
21918 let accent_colors = theme.accents().clone();
21919
21920 let accent_overrides = theme_settings
21921 .theme_overrides
21922 .get(theme.name.as_ref())
21923 .map(|theme_style| &theme_style.accents)
21924 .into_iter()
21925 .flatten()
21926 .chain(
21927 theme_settings
21928 .experimental_theme_overrides
21929 .as_ref()
21930 .map(|overrides| &overrides.accents)
21931 .into_iter()
21932 .flatten(),
21933 )
21934 .flat_map(|accent| accent.0.clone())
21935 .collect();
21936
21937 Some(AccentData {
21938 colors: accent_colors,
21939 overrides: accent_overrides,
21940 })
21941 }
21942
21943 fn fetch_applicable_language_settings(
21944 &self,
21945 cx: &App,
21946 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21947 if !self.mode.is_full() {
21948 return HashMap::default();
21949 }
21950
21951 self.buffer().read(cx).all_buffers().into_iter().fold(
21952 HashMap::default(),
21953 |mut acc, buffer| {
21954 let buffer = buffer.read(cx);
21955 let language = buffer.language().map(|language| language.name());
21956 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21957 let file = buffer.file();
21958 v.insert(language_settings(language, file, cx).into_owned());
21959 }
21960 acc
21961 },
21962 )
21963 }
21964
21965 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21966 let new_language_settings = self.fetch_applicable_language_settings(cx);
21967 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21968 self.applicable_language_settings = new_language_settings;
21969
21970 let new_accents = self.fetch_accent_data(cx);
21971 let accents_changed = new_accents != self.accent_data;
21972 self.accent_data = new_accents;
21973
21974 if self.diagnostics_enabled() {
21975 let new_severity = EditorSettings::get_global(cx)
21976 .diagnostics_max_severity
21977 .unwrap_or(DiagnosticSeverity::Hint);
21978 self.set_max_diagnostics_severity(new_severity, cx);
21979 }
21980 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21981 self.update_edit_prediction_settings(cx);
21982 self.refresh_edit_prediction(true, false, window, cx);
21983 self.refresh_inline_values(cx);
21984 self.refresh_inlay_hints(
21985 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21986 self.selections.newest_anchor().head(),
21987 &self.buffer.read(cx).snapshot(cx),
21988 cx,
21989 )),
21990 cx,
21991 );
21992
21993 let old_cursor_shape = self.cursor_shape;
21994 let old_show_breadcrumbs = self.show_breadcrumbs;
21995
21996 {
21997 let editor_settings = EditorSettings::get_global(cx);
21998 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21999 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22000 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22001 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22002 }
22003
22004 if old_cursor_shape != self.cursor_shape {
22005 cx.emit(EditorEvent::CursorShapeChanged);
22006 }
22007
22008 if old_show_breadcrumbs != self.show_breadcrumbs {
22009 cx.emit(EditorEvent::BreadcrumbsChanged);
22010 }
22011
22012 let project_settings = ProjectSettings::get_global(cx);
22013 self.buffer_serialization = self
22014 .should_serialize_buffer()
22015 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22016
22017 if self.mode.is_full() {
22018 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22019 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22020 if self.show_inline_diagnostics != show_inline_diagnostics {
22021 self.show_inline_diagnostics = show_inline_diagnostics;
22022 self.refresh_inline_diagnostics(false, window, cx);
22023 }
22024
22025 if self.git_blame_inline_enabled != inline_blame_enabled {
22026 self.toggle_git_blame_inline_internal(false, window, cx);
22027 }
22028
22029 let minimap_settings = EditorSettings::get_global(cx).minimap;
22030 if self.minimap_visibility != MinimapVisibility::Disabled {
22031 if self.minimap_visibility.settings_visibility()
22032 != minimap_settings.minimap_enabled()
22033 {
22034 self.set_minimap_visibility(
22035 MinimapVisibility::for_mode(self.mode(), cx),
22036 window,
22037 cx,
22038 );
22039 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22040 minimap_entity.update(cx, |minimap_editor, cx| {
22041 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22042 })
22043 }
22044 }
22045
22046 if language_settings_changed || accents_changed {
22047 self.colorize_brackets(true, cx);
22048 }
22049
22050 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22051 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22052 }) {
22053 if !inlay_splice.is_empty() {
22054 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22055 }
22056 self.refresh_colors_for_visible_range(None, window, cx);
22057 }
22058 }
22059
22060 cx.notify();
22061 }
22062
22063 pub fn set_searchable(&mut self, searchable: bool) {
22064 self.searchable = searchable;
22065 }
22066
22067 pub fn searchable(&self) -> bool {
22068 self.searchable
22069 }
22070
22071 pub fn open_excerpts_in_split(
22072 &mut self,
22073 _: &OpenExcerptsSplit,
22074 window: &mut Window,
22075 cx: &mut Context<Self>,
22076 ) {
22077 self.open_excerpts_common(None, true, window, cx)
22078 }
22079
22080 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22081 self.open_excerpts_common(None, false, window, cx)
22082 }
22083
22084 fn open_excerpts_common(
22085 &mut self,
22086 jump_data: Option<JumpData>,
22087 split: bool,
22088 window: &mut Window,
22089 cx: &mut Context<Self>,
22090 ) {
22091 let Some(workspace) = self.workspace() else {
22092 cx.propagate();
22093 return;
22094 };
22095
22096 if self.buffer.read(cx).is_singleton() {
22097 cx.propagate();
22098 return;
22099 }
22100
22101 let mut new_selections_by_buffer = HashMap::default();
22102 match &jump_data {
22103 Some(JumpData::MultiBufferPoint {
22104 excerpt_id,
22105 position,
22106 anchor,
22107 line_offset_from_top,
22108 }) => {
22109 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22110 if let Some(buffer) = multi_buffer_snapshot
22111 .buffer_id_for_excerpt(*excerpt_id)
22112 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22113 {
22114 let buffer_snapshot = buffer.read(cx).snapshot();
22115 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22116 language::ToPoint::to_point(anchor, &buffer_snapshot)
22117 } else {
22118 buffer_snapshot.clip_point(*position, Bias::Left)
22119 };
22120 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22121 new_selections_by_buffer.insert(
22122 buffer,
22123 (
22124 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22125 Some(*line_offset_from_top),
22126 ),
22127 );
22128 }
22129 }
22130 Some(JumpData::MultiBufferRow {
22131 row,
22132 line_offset_from_top,
22133 }) => {
22134 let point = MultiBufferPoint::new(row.0, 0);
22135 if let Some((buffer, buffer_point, _)) =
22136 self.buffer.read(cx).point_to_buffer_point(point, cx)
22137 {
22138 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22139 new_selections_by_buffer
22140 .entry(buffer)
22141 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22142 .0
22143 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22144 }
22145 }
22146 None => {
22147 let selections = self
22148 .selections
22149 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22150 let multi_buffer = self.buffer.read(cx);
22151 for selection in selections {
22152 for (snapshot, range, _, anchor) in multi_buffer
22153 .snapshot(cx)
22154 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22155 {
22156 if let Some(anchor) = anchor {
22157 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22158 else {
22159 continue;
22160 };
22161 let offset = text::ToOffset::to_offset(
22162 &anchor.text_anchor,
22163 &buffer_handle.read(cx).snapshot(),
22164 );
22165 let range = BufferOffset(offset)..BufferOffset(offset);
22166 new_selections_by_buffer
22167 .entry(buffer_handle)
22168 .or_insert((Vec::new(), None))
22169 .0
22170 .push(range)
22171 } else {
22172 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22173 else {
22174 continue;
22175 };
22176 new_selections_by_buffer
22177 .entry(buffer_handle)
22178 .or_insert((Vec::new(), None))
22179 .0
22180 .push(range)
22181 }
22182 }
22183 }
22184 }
22185 }
22186
22187 new_selections_by_buffer
22188 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22189
22190 if new_selections_by_buffer.is_empty() {
22191 return;
22192 }
22193
22194 // We defer the pane interaction because we ourselves are a workspace item
22195 // and activating a new item causes the pane to call a method on us reentrantly,
22196 // which panics if we're on the stack.
22197 window.defer(cx, move |window, cx| {
22198 workspace.update(cx, |workspace, cx| {
22199 let pane = if split {
22200 workspace.adjacent_pane(window, cx)
22201 } else {
22202 workspace.active_pane().clone()
22203 };
22204
22205 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22206 let buffer_read = buffer.read(cx);
22207 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22208 (true, project::File::from_dyn(Some(file)).is_some())
22209 } else {
22210 (false, false)
22211 };
22212
22213 // If project file is none workspace.open_project_item will fail to open the excerpt
22214 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22215 // so we check if there's a tab match in that case first
22216 let editor = (!has_file || !is_project_file)
22217 .then(|| {
22218 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22219 // so `workspace.open_project_item` will never find them, always opening a new editor.
22220 // Instead, we try to activate the existing editor in the pane first.
22221 let (editor, pane_item_index, pane_item_id) =
22222 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22223 let editor = item.downcast::<Editor>()?;
22224 let singleton_buffer =
22225 editor.read(cx).buffer().read(cx).as_singleton()?;
22226 if singleton_buffer == buffer {
22227 Some((editor, i, item.item_id()))
22228 } else {
22229 None
22230 }
22231 })?;
22232 pane.update(cx, |pane, cx| {
22233 pane.activate_item(pane_item_index, true, true, window, cx);
22234 if !PreviewTabsSettings::get_global(cx)
22235 .enable_preview_from_multibuffer
22236 {
22237 pane.unpreview_item_if_preview(pane_item_id);
22238 }
22239 });
22240 Some(editor)
22241 })
22242 .flatten()
22243 .unwrap_or_else(|| {
22244 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22245 .enable_keep_preview_on_code_navigation;
22246 let allow_new_preview =
22247 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22248 workspace.open_project_item::<Self>(
22249 pane.clone(),
22250 buffer,
22251 true,
22252 true,
22253 keep_old_preview,
22254 allow_new_preview,
22255 window,
22256 cx,
22257 )
22258 });
22259
22260 editor.update(cx, |editor, cx| {
22261 if has_file && !is_project_file {
22262 editor.set_read_only(true);
22263 }
22264 let autoscroll = match scroll_offset {
22265 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22266 None => Autoscroll::newest(),
22267 };
22268 let nav_history = editor.nav_history.take();
22269 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22270 let Some((&excerpt_id, _, buffer_snapshot)) =
22271 multibuffer_snapshot.as_singleton()
22272 else {
22273 return;
22274 };
22275 editor.change_selections(
22276 SelectionEffects::scroll(autoscroll),
22277 window,
22278 cx,
22279 |s| {
22280 s.select_ranges(ranges.into_iter().map(|range| {
22281 let range = buffer_snapshot.anchor_before(range.start)
22282 ..buffer_snapshot.anchor_after(range.end);
22283 multibuffer_snapshot
22284 .anchor_range_in_excerpt(excerpt_id, range)
22285 .unwrap()
22286 }));
22287 },
22288 );
22289 editor.nav_history = nav_history;
22290 });
22291 }
22292 })
22293 });
22294 }
22295
22296 // Allow opening excerpts for buffers that either belong to the current project
22297 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22298 // are also supported so tests and other in-memory views keep working.
22299 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22300 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22301 }
22302
22303 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22304 let snapshot = self.buffer.read(cx).read(cx);
22305 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22306 Some(
22307 ranges
22308 .iter()
22309 .map(move |range| {
22310 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22311 })
22312 .collect(),
22313 )
22314 }
22315
22316 fn selection_replacement_ranges(
22317 &self,
22318 range: Range<MultiBufferOffsetUtf16>,
22319 cx: &mut App,
22320 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22321 let selections = self
22322 .selections
22323 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22324 let newest_selection = selections
22325 .iter()
22326 .max_by_key(|selection| selection.id)
22327 .unwrap();
22328 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22329 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22330 let snapshot = self.buffer.read(cx).read(cx);
22331 selections
22332 .into_iter()
22333 .map(|mut selection| {
22334 selection.start.0.0 =
22335 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22336 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22337 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22338 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22339 })
22340 .collect()
22341 }
22342
22343 fn report_editor_event(
22344 &self,
22345 reported_event: ReportEditorEvent,
22346 file_extension: Option<String>,
22347 cx: &App,
22348 ) {
22349 if cfg!(any(test, feature = "test-support")) {
22350 return;
22351 }
22352
22353 let Some(project) = &self.project else { return };
22354
22355 // If None, we are in a file without an extension
22356 let file = self
22357 .buffer
22358 .read(cx)
22359 .as_singleton()
22360 .and_then(|b| b.read(cx).file());
22361 let file_extension = file_extension.or(file
22362 .as_ref()
22363 .and_then(|file| Path::new(file.file_name(cx)).extension())
22364 .and_then(|e| e.to_str())
22365 .map(|a| a.to_string()));
22366
22367 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22368 .map(|vim_mode| vim_mode.0)
22369 .unwrap_or(false);
22370
22371 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22372 let copilot_enabled = edit_predictions_provider
22373 == language::language_settings::EditPredictionProvider::Copilot;
22374 let copilot_enabled_for_language = self
22375 .buffer
22376 .read(cx)
22377 .language_settings(cx)
22378 .show_edit_predictions;
22379
22380 let project = project.read(cx);
22381 let event_type = reported_event.event_type();
22382
22383 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22384 telemetry::event!(
22385 event_type,
22386 type = if auto_saved {"autosave"} else {"manual"},
22387 file_extension,
22388 vim_mode,
22389 copilot_enabled,
22390 copilot_enabled_for_language,
22391 edit_predictions_provider,
22392 is_via_ssh = project.is_via_remote_server(),
22393 );
22394 } else {
22395 telemetry::event!(
22396 event_type,
22397 file_extension,
22398 vim_mode,
22399 copilot_enabled,
22400 copilot_enabled_for_language,
22401 edit_predictions_provider,
22402 is_via_ssh = project.is_via_remote_server(),
22403 );
22404 };
22405 }
22406
22407 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22408 /// with each line being an array of {text, highlight} objects.
22409 fn copy_highlight_json(
22410 &mut self,
22411 _: &CopyHighlightJson,
22412 window: &mut Window,
22413 cx: &mut Context<Self>,
22414 ) {
22415 #[derive(Serialize)]
22416 struct Chunk<'a> {
22417 text: String,
22418 highlight: Option<&'a str>,
22419 }
22420
22421 let snapshot = self.buffer.read(cx).snapshot(cx);
22422 let range = self
22423 .selected_text_range(false, window, cx)
22424 .and_then(|selection| {
22425 if selection.range.is_empty() {
22426 None
22427 } else {
22428 Some(
22429 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22430 selection.range.start,
22431 )))
22432 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22433 selection.range.end,
22434 ))),
22435 )
22436 }
22437 })
22438 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22439
22440 let chunks = snapshot.chunks(range, true);
22441 let mut lines = Vec::new();
22442 let mut line: VecDeque<Chunk> = VecDeque::new();
22443
22444 let Some(style) = self.style.as_ref() else {
22445 return;
22446 };
22447
22448 for chunk in chunks {
22449 let highlight = chunk
22450 .syntax_highlight_id
22451 .and_then(|id| id.name(&style.syntax));
22452 let mut chunk_lines = chunk.text.split('\n').peekable();
22453 while let Some(text) = chunk_lines.next() {
22454 let mut merged_with_last_token = false;
22455 if let Some(last_token) = line.back_mut()
22456 && last_token.highlight == highlight
22457 {
22458 last_token.text.push_str(text);
22459 merged_with_last_token = true;
22460 }
22461
22462 if !merged_with_last_token {
22463 line.push_back(Chunk {
22464 text: text.into(),
22465 highlight,
22466 });
22467 }
22468
22469 if chunk_lines.peek().is_some() {
22470 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22471 line.pop_front();
22472 }
22473 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22474 line.pop_back();
22475 }
22476
22477 lines.push(mem::take(&mut line));
22478 }
22479 }
22480 }
22481
22482 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22483 return;
22484 };
22485 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22486 }
22487
22488 pub fn open_context_menu(
22489 &mut self,
22490 _: &OpenContextMenu,
22491 window: &mut Window,
22492 cx: &mut Context<Self>,
22493 ) {
22494 self.request_autoscroll(Autoscroll::newest(), cx);
22495 let position = self
22496 .selections
22497 .newest_display(&self.display_snapshot(cx))
22498 .start;
22499 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22500 }
22501
22502 pub fn replay_insert_event(
22503 &mut self,
22504 text: &str,
22505 relative_utf16_range: Option<Range<isize>>,
22506 window: &mut Window,
22507 cx: &mut Context<Self>,
22508 ) {
22509 if !self.input_enabled {
22510 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22511 return;
22512 }
22513 if let Some(relative_utf16_range) = relative_utf16_range {
22514 let selections = self
22515 .selections
22516 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22517 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22518 let new_ranges = selections.into_iter().map(|range| {
22519 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22520 range
22521 .head()
22522 .0
22523 .0
22524 .saturating_add_signed(relative_utf16_range.start),
22525 ));
22526 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22527 range
22528 .head()
22529 .0
22530 .0
22531 .saturating_add_signed(relative_utf16_range.end),
22532 ));
22533 start..end
22534 });
22535 s.select_ranges(new_ranges);
22536 });
22537 }
22538
22539 self.handle_input(text, window, cx);
22540 }
22541
22542 pub fn is_focused(&self, window: &Window) -> bool {
22543 self.focus_handle.is_focused(window)
22544 }
22545
22546 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22547 cx.emit(EditorEvent::Focused);
22548
22549 if let Some(descendant) = self
22550 .last_focused_descendant
22551 .take()
22552 .and_then(|descendant| descendant.upgrade())
22553 {
22554 window.focus(&descendant);
22555 } else {
22556 if let Some(blame) = self.blame.as_ref() {
22557 blame.update(cx, GitBlame::focus)
22558 }
22559
22560 self.blink_manager.update(cx, BlinkManager::enable);
22561 self.show_cursor_names(window, cx);
22562 self.buffer.update(cx, |buffer, cx| {
22563 buffer.finalize_last_transaction(cx);
22564 if self.leader_id.is_none() {
22565 buffer.set_active_selections(
22566 &self.selections.disjoint_anchors_arc(),
22567 self.selections.line_mode(),
22568 self.cursor_shape,
22569 cx,
22570 );
22571 }
22572 });
22573
22574 if let Some(position_map) = self.last_position_map.clone() {
22575 EditorElement::mouse_moved(
22576 self,
22577 &MouseMoveEvent {
22578 position: window.mouse_position(),
22579 pressed_button: None,
22580 modifiers: window.modifiers(),
22581 },
22582 &position_map,
22583 window,
22584 cx,
22585 );
22586 }
22587 }
22588 }
22589
22590 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22591 cx.emit(EditorEvent::FocusedIn)
22592 }
22593
22594 fn handle_focus_out(
22595 &mut self,
22596 event: FocusOutEvent,
22597 _window: &mut Window,
22598 cx: &mut Context<Self>,
22599 ) {
22600 if event.blurred != self.focus_handle {
22601 self.last_focused_descendant = Some(event.blurred);
22602 }
22603 self.selection_drag_state = SelectionDragState::None;
22604 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22605 }
22606
22607 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22608 self.blink_manager.update(cx, BlinkManager::disable);
22609 self.buffer
22610 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22611
22612 if let Some(blame) = self.blame.as_ref() {
22613 blame.update(cx, GitBlame::blur)
22614 }
22615 if !self.hover_state.focused(window, cx) {
22616 hide_hover(self, cx);
22617 }
22618 if !self
22619 .context_menu
22620 .borrow()
22621 .as_ref()
22622 .is_some_and(|context_menu| context_menu.focused(window, cx))
22623 {
22624 self.hide_context_menu(window, cx);
22625 }
22626 self.take_active_edit_prediction(cx);
22627 cx.emit(EditorEvent::Blurred);
22628 cx.notify();
22629 }
22630
22631 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22632 let mut pending: String = window
22633 .pending_input_keystrokes()
22634 .into_iter()
22635 .flatten()
22636 .filter_map(|keystroke| keystroke.key_char.clone())
22637 .collect();
22638
22639 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22640 pending = "".to_string();
22641 }
22642
22643 let existing_pending = self
22644 .text_highlights::<PendingInput>(cx)
22645 .map(|(_, ranges)| ranges.to_vec());
22646 if existing_pending.is_none() && pending.is_empty() {
22647 return;
22648 }
22649 let transaction =
22650 self.transact(window, cx, |this, window, cx| {
22651 let selections = this
22652 .selections
22653 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22654 let edits = selections
22655 .iter()
22656 .map(|selection| (selection.end..selection.end, pending.clone()));
22657 this.edit(edits, cx);
22658 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22659 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22660 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22661 }));
22662 });
22663 if let Some(existing_ranges) = existing_pending {
22664 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22665 this.edit(edits, cx);
22666 }
22667 });
22668
22669 let snapshot = self.snapshot(window, cx);
22670 let ranges = self
22671 .selections
22672 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22673 .into_iter()
22674 .map(|selection| {
22675 snapshot.buffer_snapshot().anchor_after(selection.end)
22676 ..snapshot
22677 .buffer_snapshot()
22678 .anchor_before(selection.end + pending.len())
22679 })
22680 .collect();
22681
22682 if pending.is_empty() {
22683 self.clear_highlights::<PendingInput>(cx);
22684 } else {
22685 self.highlight_text::<PendingInput>(
22686 ranges,
22687 HighlightStyle {
22688 underline: Some(UnderlineStyle {
22689 thickness: px(1.),
22690 color: None,
22691 wavy: false,
22692 }),
22693 ..Default::default()
22694 },
22695 cx,
22696 );
22697 }
22698
22699 self.ime_transaction = self.ime_transaction.or(transaction);
22700 if let Some(transaction) = self.ime_transaction {
22701 self.buffer.update(cx, |buffer, cx| {
22702 buffer.group_until_transaction(transaction, cx);
22703 });
22704 }
22705
22706 if self.text_highlights::<PendingInput>(cx).is_none() {
22707 self.ime_transaction.take();
22708 }
22709 }
22710
22711 pub fn register_action_renderer(
22712 &mut self,
22713 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22714 ) -> Subscription {
22715 let id = self.next_editor_action_id.post_inc();
22716 self.editor_actions
22717 .borrow_mut()
22718 .insert(id, Box::new(listener));
22719
22720 let editor_actions = self.editor_actions.clone();
22721 Subscription::new(move || {
22722 editor_actions.borrow_mut().remove(&id);
22723 })
22724 }
22725
22726 pub fn register_action<A: Action>(
22727 &mut self,
22728 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22729 ) -> Subscription {
22730 let id = self.next_editor_action_id.post_inc();
22731 let listener = Arc::new(listener);
22732 self.editor_actions.borrow_mut().insert(
22733 id,
22734 Box::new(move |_, window, _| {
22735 let listener = listener.clone();
22736 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22737 let action = action.downcast_ref().unwrap();
22738 if phase == DispatchPhase::Bubble {
22739 listener(action, window, cx)
22740 }
22741 })
22742 }),
22743 );
22744
22745 let editor_actions = self.editor_actions.clone();
22746 Subscription::new(move || {
22747 editor_actions.borrow_mut().remove(&id);
22748 })
22749 }
22750
22751 pub fn file_header_size(&self) -> u32 {
22752 FILE_HEADER_HEIGHT
22753 }
22754
22755 pub fn restore(
22756 &mut self,
22757 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22758 window: &mut Window,
22759 cx: &mut Context<Self>,
22760 ) {
22761 let workspace = self.workspace();
22762 let project = self.project();
22763 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22764 let mut tasks = Vec::new();
22765 for (buffer_id, changes) in revert_changes {
22766 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22767 buffer.update(cx, |buffer, cx| {
22768 buffer.edit(
22769 changes
22770 .into_iter()
22771 .map(|(range, text)| (range, text.to_string())),
22772 None,
22773 cx,
22774 );
22775 });
22776
22777 if let Some(project) =
22778 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22779 {
22780 project.update(cx, |project, cx| {
22781 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22782 })
22783 }
22784 }
22785 }
22786 tasks
22787 });
22788 cx.spawn_in(window, async move |_, cx| {
22789 for (buffer, task) in save_tasks {
22790 let result = task.await;
22791 if result.is_err() {
22792 let Some(path) = buffer
22793 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22794 .ok()
22795 else {
22796 continue;
22797 };
22798 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22799 let Some(task) = cx
22800 .update_window_entity(workspace, |workspace, window, cx| {
22801 workspace
22802 .open_path_preview(path, None, false, false, false, window, cx)
22803 })
22804 .ok()
22805 else {
22806 continue;
22807 };
22808 task.await.log_err();
22809 }
22810 }
22811 }
22812 })
22813 .detach();
22814 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22815 selections.refresh()
22816 });
22817 }
22818
22819 pub fn to_pixel_point(
22820 &self,
22821 source: multi_buffer::Anchor,
22822 editor_snapshot: &EditorSnapshot,
22823 window: &mut Window,
22824 ) -> Option<gpui::Point<Pixels>> {
22825 let source_point = source.to_display_point(editor_snapshot);
22826 self.display_to_pixel_point(source_point, editor_snapshot, window)
22827 }
22828
22829 pub fn display_to_pixel_point(
22830 &self,
22831 source: DisplayPoint,
22832 editor_snapshot: &EditorSnapshot,
22833 window: &mut Window,
22834 ) -> Option<gpui::Point<Pixels>> {
22835 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22836 let text_layout_details = self.text_layout_details(window);
22837 let scroll_top = text_layout_details
22838 .scroll_anchor
22839 .scroll_position(editor_snapshot)
22840 .y;
22841
22842 if source.row().as_f64() < scroll_top.floor() {
22843 return None;
22844 }
22845 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22846 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22847 Some(gpui::Point::new(source_x, source_y))
22848 }
22849
22850 pub fn has_visible_completions_menu(&self) -> bool {
22851 !self.edit_prediction_preview_is_active()
22852 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22853 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22854 })
22855 }
22856
22857 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22858 if self.mode.is_minimap() {
22859 return;
22860 }
22861 self.addons
22862 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22863 }
22864
22865 pub fn unregister_addon<T: Addon>(&mut self) {
22866 self.addons.remove(&std::any::TypeId::of::<T>());
22867 }
22868
22869 pub fn addon<T: Addon>(&self) -> Option<&T> {
22870 let type_id = std::any::TypeId::of::<T>();
22871 self.addons
22872 .get(&type_id)
22873 .and_then(|item| item.to_any().downcast_ref::<T>())
22874 }
22875
22876 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22877 let type_id = std::any::TypeId::of::<T>();
22878 self.addons
22879 .get_mut(&type_id)
22880 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22881 }
22882
22883 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22884 let text_layout_details = self.text_layout_details(window);
22885 let style = &text_layout_details.editor_style;
22886 let font_id = window.text_system().resolve_font(&style.text.font());
22887 let font_size = style.text.font_size.to_pixels(window.rem_size());
22888 let line_height = style.text.line_height_in_pixels(window.rem_size());
22889 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22890 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22891
22892 CharacterDimensions {
22893 em_width,
22894 em_advance,
22895 line_height,
22896 }
22897 }
22898
22899 pub fn last_gutter_dimensions(&self) -> &GutterDimensions {
22900 &self.gutter_dimensions
22901 }
22902
22903 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22904 self.load_diff_task.clone()
22905 }
22906
22907 fn read_metadata_from_db(
22908 &mut self,
22909 item_id: u64,
22910 workspace_id: WorkspaceId,
22911 window: &mut Window,
22912 cx: &mut Context<Editor>,
22913 ) {
22914 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22915 && !self.mode.is_minimap()
22916 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22917 {
22918 let buffer_snapshot = OnceCell::new();
22919
22920 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22921 && !folds.is_empty()
22922 {
22923 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22924 self.fold_ranges(
22925 folds
22926 .into_iter()
22927 .map(|(start, end)| {
22928 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22929 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22930 })
22931 .collect(),
22932 false,
22933 window,
22934 cx,
22935 );
22936 }
22937
22938 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22939 && !selections.is_empty()
22940 {
22941 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22942 // skip adding the initial selection to selection history
22943 self.selection_history.mode = SelectionHistoryMode::Skipping;
22944 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22945 s.select_ranges(selections.into_iter().map(|(start, end)| {
22946 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22947 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22948 }));
22949 });
22950 self.selection_history.mode = SelectionHistoryMode::Normal;
22951 };
22952 }
22953
22954 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22955 }
22956
22957 fn update_lsp_data(
22958 &mut self,
22959 for_buffer: Option<BufferId>,
22960 window: &mut Window,
22961 cx: &mut Context<'_, Self>,
22962 ) {
22963 self.pull_diagnostics(for_buffer, window, cx);
22964 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22965 }
22966
22967 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22968 if self.ignore_lsp_data() {
22969 return;
22970 }
22971 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22972 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22973 }
22974 }
22975
22976 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22977 if self.ignore_lsp_data() {
22978 return;
22979 }
22980
22981 if !self.registered_buffers.contains_key(&buffer_id)
22982 && let Some(project) = self.project.as_ref()
22983 {
22984 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22985 project.update(cx, |project, cx| {
22986 self.registered_buffers.insert(
22987 buffer_id,
22988 project.register_buffer_with_language_servers(&buffer, cx),
22989 );
22990 });
22991 } else {
22992 self.registered_buffers.remove(&buffer_id);
22993 }
22994 }
22995 }
22996
22997 fn ignore_lsp_data(&self) -> bool {
22998 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22999 // skip any LSP updates for it.
23000 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23001 }
23002}
23003
23004fn edit_for_markdown_paste<'a>(
23005 buffer: &MultiBufferSnapshot,
23006 range: Range<MultiBufferOffset>,
23007 to_insert: &'a str,
23008 url: Option<url::Url>,
23009) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23010 if url.is_none() {
23011 return (range, Cow::Borrowed(to_insert));
23012 };
23013
23014 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23015
23016 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23017 Cow::Borrowed(to_insert)
23018 } else {
23019 Cow::Owned(format!("[{old_text}]({to_insert})"))
23020 };
23021 (range, new_text)
23022}
23023
23024fn process_completion_for_edit(
23025 completion: &Completion,
23026 intent: CompletionIntent,
23027 buffer: &Entity<Buffer>,
23028 cursor_position: &text::Anchor,
23029 cx: &mut Context<Editor>,
23030) -> CompletionEdit {
23031 let buffer = buffer.read(cx);
23032 let buffer_snapshot = buffer.snapshot();
23033 let (snippet, new_text) = if completion.is_snippet() {
23034 let mut snippet_source = completion.new_text.clone();
23035 // Workaround for typescript language server issues so that methods don't expand within
23036 // strings and functions with type expressions. The previous point is used because the query
23037 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23038 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23039 let previous_point = if previous_point.column > 0 {
23040 cursor_position.to_previous_offset(&buffer_snapshot)
23041 } else {
23042 cursor_position.to_offset(&buffer_snapshot)
23043 };
23044 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23045 && scope.prefers_label_for_snippet_in_completion()
23046 && let Some(label) = completion.label()
23047 && matches!(
23048 completion.kind(),
23049 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23050 )
23051 {
23052 snippet_source = label;
23053 }
23054 match Snippet::parse(&snippet_source).log_err() {
23055 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23056 None => (None, completion.new_text.clone()),
23057 }
23058 } else {
23059 (None, completion.new_text.clone())
23060 };
23061
23062 let mut range_to_replace = {
23063 let replace_range = &completion.replace_range;
23064 if let CompletionSource::Lsp {
23065 insert_range: Some(insert_range),
23066 ..
23067 } = &completion.source
23068 {
23069 debug_assert_eq!(
23070 insert_range.start, replace_range.start,
23071 "insert_range and replace_range should start at the same position"
23072 );
23073 debug_assert!(
23074 insert_range
23075 .start
23076 .cmp(cursor_position, &buffer_snapshot)
23077 .is_le(),
23078 "insert_range should start before or at cursor position"
23079 );
23080 debug_assert!(
23081 replace_range
23082 .start
23083 .cmp(cursor_position, &buffer_snapshot)
23084 .is_le(),
23085 "replace_range should start before or at cursor position"
23086 );
23087
23088 let should_replace = match intent {
23089 CompletionIntent::CompleteWithInsert => false,
23090 CompletionIntent::CompleteWithReplace => true,
23091 CompletionIntent::Complete | CompletionIntent::Compose => {
23092 let insert_mode =
23093 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23094 .completions
23095 .lsp_insert_mode;
23096 match insert_mode {
23097 LspInsertMode::Insert => false,
23098 LspInsertMode::Replace => true,
23099 LspInsertMode::ReplaceSubsequence => {
23100 let mut text_to_replace = buffer.chars_for_range(
23101 buffer.anchor_before(replace_range.start)
23102 ..buffer.anchor_after(replace_range.end),
23103 );
23104 let mut current_needle = text_to_replace.next();
23105 for haystack_ch in completion.label.text.chars() {
23106 if let Some(needle_ch) = current_needle
23107 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23108 {
23109 current_needle = text_to_replace.next();
23110 }
23111 }
23112 current_needle.is_none()
23113 }
23114 LspInsertMode::ReplaceSuffix => {
23115 if replace_range
23116 .end
23117 .cmp(cursor_position, &buffer_snapshot)
23118 .is_gt()
23119 {
23120 let range_after_cursor = *cursor_position..replace_range.end;
23121 let text_after_cursor = buffer
23122 .text_for_range(
23123 buffer.anchor_before(range_after_cursor.start)
23124 ..buffer.anchor_after(range_after_cursor.end),
23125 )
23126 .collect::<String>()
23127 .to_ascii_lowercase();
23128 completion
23129 .label
23130 .text
23131 .to_ascii_lowercase()
23132 .ends_with(&text_after_cursor)
23133 } else {
23134 true
23135 }
23136 }
23137 }
23138 }
23139 };
23140
23141 if should_replace {
23142 replace_range.clone()
23143 } else {
23144 insert_range.clone()
23145 }
23146 } else {
23147 replace_range.clone()
23148 }
23149 };
23150
23151 if range_to_replace
23152 .end
23153 .cmp(cursor_position, &buffer_snapshot)
23154 .is_lt()
23155 {
23156 range_to_replace.end = *cursor_position;
23157 }
23158
23159 let replace_range = range_to_replace.to_offset(buffer);
23160 CompletionEdit {
23161 new_text,
23162 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23163 snippet,
23164 }
23165}
23166
23167struct CompletionEdit {
23168 new_text: String,
23169 replace_range: Range<BufferOffset>,
23170 snippet: Option<Snippet>,
23171}
23172
23173fn insert_extra_newline_brackets(
23174 buffer: &MultiBufferSnapshot,
23175 range: Range<MultiBufferOffset>,
23176 language: &language::LanguageScope,
23177) -> bool {
23178 let leading_whitespace_len = buffer
23179 .reversed_chars_at(range.start)
23180 .take_while(|c| c.is_whitespace() && *c != '\n')
23181 .map(|c| c.len_utf8())
23182 .sum::<usize>();
23183 let trailing_whitespace_len = buffer
23184 .chars_at(range.end)
23185 .take_while(|c| c.is_whitespace() && *c != '\n')
23186 .map(|c| c.len_utf8())
23187 .sum::<usize>();
23188 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23189
23190 language.brackets().any(|(pair, enabled)| {
23191 let pair_start = pair.start.trim_end();
23192 let pair_end = pair.end.trim_start();
23193
23194 enabled
23195 && pair.newline
23196 && buffer.contains_str_at(range.end, pair_end)
23197 && buffer.contains_str_at(
23198 range.start.saturating_sub_usize(pair_start.len()),
23199 pair_start,
23200 )
23201 })
23202}
23203
23204fn insert_extra_newline_tree_sitter(
23205 buffer: &MultiBufferSnapshot,
23206 range: Range<MultiBufferOffset>,
23207) -> bool {
23208 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23209 [(buffer, range, _)] => (*buffer, range.clone()),
23210 _ => return false,
23211 };
23212 let pair = {
23213 let mut result: Option<BracketMatch<usize>> = None;
23214
23215 for pair in buffer
23216 .all_bracket_ranges(range.start.0..range.end.0)
23217 .filter(move |pair| {
23218 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23219 })
23220 {
23221 let len = pair.close_range.end - pair.open_range.start;
23222
23223 if let Some(existing) = &result {
23224 let existing_len = existing.close_range.end - existing.open_range.start;
23225 if len > existing_len {
23226 continue;
23227 }
23228 }
23229
23230 result = Some(pair);
23231 }
23232
23233 result
23234 };
23235 let Some(pair) = pair else {
23236 return false;
23237 };
23238 pair.newline_only
23239 && buffer
23240 .chars_for_range(pair.open_range.end..range.start.0)
23241 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23242 .all(|c| c.is_whitespace() && c != '\n')
23243}
23244
23245fn update_uncommitted_diff_for_buffer(
23246 editor: Entity<Editor>,
23247 project: &Entity<Project>,
23248 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23249 buffer: Entity<MultiBuffer>,
23250 cx: &mut App,
23251) -> Task<()> {
23252 let mut tasks = Vec::new();
23253 project.update(cx, |project, cx| {
23254 for buffer in buffers {
23255 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23256 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23257 }
23258 }
23259 });
23260 cx.spawn(async move |cx| {
23261 let diffs = future::join_all(tasks).await;
23262 if editor
23263 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23264 .unwrap_or(false)
23265 {
23266 return;
23267 }
23268
23269 buffer
23270 .update(cx, |buffer, cx| {
23271 for diff in diffs.into_iter().flatten() {
23272 buffer.add_diff(diff, cx);
23273 }
23274 })
23275 .ok();
23276 })
23277}
23278
23279fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23280 let tab_size = tab_size.get() as usize;
23281 let mut width = offset;
23282
23283 for ch in text.chars() {
23284 width += if ch == '\t' {
23285 tab_size - (width % tab_size)
23286 } else {
23287 1
23288 };
23289 }
23290
23291 width - offset
23292}
23293
23294#[cfg(test)]
23295mod tests {
23296 use super::*;
23297
23298 #[test]
23299 fn test_string_size_with_expanded_tabs() {
23300 let nz = |val| NonZeroU32::new(val).unwrap();
23301 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23302 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23303 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23304 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23305 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23306 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23307 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23308 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23309 }
23310}
23311
23312/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23313struct WordBreakingTokenizer<'a> {
23314 input: &'a str,
23315}
23316
23317impl<'a> WordBreakingTokenizer<'a> {
23318 fn new(input: &'a str) -> Self {
23319 Self { input }
23320 }
23321}
23322
23323fn is_char_ideographic(ch: char) -> bool {
23324 use unicode_script::Script::*;
23325 use unicode_script::UnicodeScript;
23326 matches!(ch.script(), Han | Tangut | Yi)
23327}
23328
23329fn is_grapheme_ideographic(text: &str) -> bool {
23330 text.chars().any(is_char_ideographic)
23331}
23332
23333fn is_grapheme_whitespace(text: &str) -> bool {
23334 text.chars().any(|x| x.is_whitespace())
23335}
23336
23337fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23338 text.chars()
23339 .next()
23340 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23341}
23342
23343#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23344enum WordBreakToken<'a> {
23345 Word { token: &'a str, grapheme_len: usize },
23346 InlineWhitespace { token: &'a str, grapheme_len: usize },
23347 Newline,
23348}
23349
23350impl<'a> Iterator for WordBreakingTokenizer<'a> {
23351 /// Yields a span, the count of graphemes in the token, and whether it was
23352 /// whitespace. Note that it also breaks at word boundaries.
23353 type Item = WordBreakToken<'a>;
23354
23355 fn next(&mut self) -> Option<Self::Item> {
23356 use unicode_segmentation::UnicodeSegmentation;
23357 if self.input.is_empty() {
23358 return None;
23359 }
23360
23361 let mut iter = self.input.graphemes(true).peekable();
23362 let mut offset = 0;
23363 let mut grapheme_len = 0;
23364 if let Some(first_grapheme) = iter.next() {
23365 let is_newline = first_grapheme == "\n";
23366 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23367 offset += first_grapheme.len();
23368 grapheme_len += 1;
23369 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23370 if let Some(grapheme) = iter.peek().copied()
23371 && should_stay_with_preceding_ideograph(grapheme)
23372 {
23373 offset += grapheme.len();
23374 grapheme_len += 1;
23375 }
23376 } else {
23377 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23378 let mut next_word_bound = words.peek().copied();
23379 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23380 next_word_bound = words.next();
23381 }
23382 while let Some(grapheme) = iter.peek().copied() {
23383 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23384 break;
23385 };
23386 if is_grapheme_whitespace(grapheme) != is_whitespace
23387 || (grapheme == "\n") != is_newline
23388 {
23389 break;
23390 };
23391 offset += grapheme.len();
23392 grapheme_len += 1;
23393 iter.next();
23394 }
23395 }
23396 let token = &self.input[..offset];
23397 self.input = &self.input[offset..];
23398 if token == "\n" {
23399 Some(WordBreakToken::Newline)
23400 } else if is_whitespace {
23401 Some(WordBreakToken::InlineWhitespace {
23402 token,
23403 grapheme_len,
23404 })
23405 } else {
23406 Some(WordBreakToken::Word {
23407 token,
23408 grapheme_len,
23409 })
23410 }
23411 } else {
23412 None
23413 }
23414 }
23415}
23416
23417#[test]
23418fn test_word_breaking_tokenizer() {
23419 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23420 ("", &[]),
23421 (" ", &[whitespace(" ", 2)]),
23422 ("Ʒ", &[word("Ʒ", 1)]),
23423 ("Ǽ", &[word("Ǽ", 1)]),
23424 ("⋑", &[word("⋑", 1)]),
23425 ("⋑⋑", &[word("⋑⋑", 2)]),
23426 (
23427 "原理,进而",
23428 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23429 ),
23430 (
23431 "hello world",
23432 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23433 ),
23434 (
23435 "hello, world",
23436 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23437 ),
23438 (
23439 " hello world",
23440 &[
23441 whitespace(" ", 2),
23442 word("hello", 5),
23443 whitespace(" ", 1),
23444 word("world", 5),
23445 ],
23446 ),
23447 (
23448 "这是什么 \n 钢笔",
23449 &[
23450 word("这", 1),
23451 word("是", 1),
23452 word("什", 1),
23453 word("么", 1),
23454 whitespace(" ", 1),
23455 newline(),
23456 whitespace(" ", 1),
23457 word("钢", 1),
23458 word("笔", 1),
23459 ],
23460 ),
23461 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23462 ];
23463
23464 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23465 WordBreakToken::Word {
23466 token,
23467 grapheme_len,
23468 }
23469 }
23470
23471 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23472 WordBreakToken::InlineWhitespace {
23473 token,
23474 grapheme_len,
23475 }
23476 }
23477
23478 fn newline() -> WordBreakToken<'static> {
23479 WordBreakToken::Newline
23480 }
23481
23482 for (input, result) in tests {
23483 assert_eq!(
23484 WordBreakingTokenizer::new(input)
23485 .collect::<Vec<_>>()
23486 .as_slice(),
23487 *result,
23488 );
23489 }
23490}
23491
23492fn wrap_with_prefix(
23493 first_line_prefix: String,
23494 subsequent_lines_prefix: String,
23495 unwrapped_text: String,
23496 wrap_column: usize,
23497 tab_size: NonZeroU32,
23498 preserve_existing_whitespace: bool,
23499) -> String {
23500 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23501 let subsequent_lines_prefix_len =
23502 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23503 let mut wrapped_text = String::new();
23504 let mut current_line = first_line_prefix;
23505 let mut is_first_line = true;
23506
23507 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23508 let mut current_line_len = first_line_prefix_len;
23509 let mut in_whitespace = false;
23510 for token in tokenizer {
23511 let have_preceding_whitespace = in_whitespace;
23512 match token {
23513 WordBreakToken::Word {
23514 token,
23515 grapheme_len,
23516 } => {
23517 in_whitespace = false;
23518 let current_prefix_len = if is_first_line {
23519 first_line_prefix_len
23520 } else {
23521 subsequent_lines_prefix_len
23522 };
23523 if current_line_len + grapheme_len > wrap_column
23524 && current_line_len != current_prefix_len
23525 {
23526 wrapped_text.push_str(current_line.trim_end());
23527 wrapped_text.push('\n');
23528 is_first_line = false;
23529 current_line = subsequent_lines_prefix.clone();
23530 current_line_len = subsequent_lines_prefix_len;
23531 }
23532 current_line.push_str(token);
23533 current_line_len += grapheme_len;
23534 }
23535 WordBreakToken::InlineWhitespace {
23536 mut token,
23537 mut grapheme_len,
23538 } => {
23539 in_whitespace = true;
23540 if have_preceding_whitespace && !preserve_existing_whitespace {
23541 continue;
23542 }
23543 if !preserve_existing_whitespace {
23544 // Keep a single whitespace grapheme as-is
23545 if let Some(first) =
23546 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23547 {
23548 token = first;
23549 } else {
23550 token = " ";
23551 }
23552 grapheme_len = 1;
23553 }
23554 let current_prefix_len = if is_first_line {
23555 first_line_prefix_len
23556 } else {
23557 subsequent_lines_prefix_len
23558 };
23559 if current_line_len + grapheme_len > wrap_column {
23560 wrapped_text.push_str(current_line.trim_end());
23561 wrapped_text.push('\n');
23562 is_first_line = false;
23563 current_line = subsequent_lines_prefix.clone();
23564 current_line_len = subsequent_lines_prefix_len;
23565 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23566 current_line.push_str(token);
23567 current_line_len += grapheme_len;
23568 }
23569 }
23570 WordBreakToken::Newline => {
23571 in_whitespace = true;
23572 let current_prefix_len = if is_first_line {
23573 first_line_prefix_len
23574 } else {
23575 subsequent_lines_prefix_len
23576 };
23577 if preserve_existing_whitespace {
23578 wrapped_text.push_str(current_line.trim_end());
23579 wrapped_text.push('\n');
23580 is_first_line = false;
23581 current_line = subsequent_lines_prefix.clone();
23582 current_line_len = subsequent_lines_prefix_len;
23583 } else if have_preceding_whitespace {
23584 continue;
23585 } else if current_line_len + 1 > wrap_column
23586 && current_line_len != current_prefix_len
23587 {
23588 wrapped_text.push_str(current_line.trim_end());
23589 wrapped_text.push('\n');
23590 is_first_line = false;
23591 current_line = subsequent_lines_prefix.clone();
23592 current_line_len = subsequent_lines_prefix_len;
23593 } else if current_line_len != current_prefix_len {
23594 current_line.push(' ');
23595 current_line_len += 1;
23596 }
23597 }
23598 }
23599 }
23600
23601 if !current_line.is_empty() {
23602 wrapped_text.push_str(¤t_line);
23603 }
23604 wrapped_text
23605}
23606
23607#[test]
23608fn test_wrap_with_prefix() {
23609 assert_eq!(
23610 wrap_with_prefix(
23611 "# ".to_string(),
23612 "# ".to_string(),
23613 "abcdefg".to_string(),
23614 4,
23615 NonZeroU32::new(4).unwrap(),
23616 false,
23617 ),
23618 "# abcdefg"
23619 );
23620 assert_eq!(
23621 wrap_with_prefix(
23622 "".to_string(),
23623 "".to_string(),
23624 "\thello world".to_string(),
23625 8,
23626 NonZeroU32::new(4).unwrap(),
23627 false,
23628 ),
23629 "hello\nworld"
23630 );
23631 assert_eq!(
23632 wrap_with_prefix(
23633 "// ".to_string(),
23634 "// ".to_string(),
23635 "xx \nyy zz aa bb cc".to_string(),
23636 12,
23637 NonZeroU32::new(4).unwrap(),
23638 false,
23639 ),
23640 "// xx yy zz\n// aa bb cc"
23641 );
23642 assert_eq!(
23643 wrap_with_prefix(
23644 String::new(),
23645 String::new(),
23646 "这是什么 \n 钢笔".to_string(),
23647 3,
23648 NonZeroU32::new(4).unwrap(),
23649 false,
23650 ),
23651 "这是什\n么 钢\n笔"
23652 );
23653 assert_eq!(
23654 wrap_with_prefix(
23655 String::new(),
23656 String::new(),
23657 format!("foo{}bar", '\u{2009}'), // thin space
23658 80,
23659 NonZeroU32::new(4).unwrap(),
23660 false,
23661 ),
23662 format!("foo{}bar", '\u{2009}')
23663 );
23664}
23665
23666pub trait CollaborationHub {
23667 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23668 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23669 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23670}
23671
23672impl CollaborationHub for Entity<Project> {
23673 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23674 self.read(cx).collaborators()
23675 }
23676
23677 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23678 self.read(cx).user_store().read(cx).participant_indices()
23679 }
23680
23681 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23682 let this = self.read(cx);
23683 let user_ids = this.collaborators().values().map(|c| c.user_id);
23684 this.user_store().read(cx).participant_names(user_ids, cx)
23685 }
23686}
23687
23688pub trait SemanticsProvider {
23689 fn hover(
23690 &self,
23691 buffer: &Entity<Buffer>,
23692 position: text::Anchor,
23693 cx: &mut App,
23694 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23695
23696 fn inline_values(
23697 &self,
23698 buffer_handle: Entity<Buffer>,
23699 range: Range<text::Anchor>,
23700 cx: &mut App,
23701 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23702
23703 fn applicable_inlay_chunks(
23704 &self,
23705 buffer: &Entity<Buffer>,
23706 ranges: &[Range<text::Anchor>],
23707 cx: &mut App,
23708 ) -> Vec<Range<BufferRow>>;
23709
23710 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23711
23712 fn inlay_hints(
23713 &self,
23714 invalidate: InvalidationStrategy,
23715 buffer: Entity<Buffer>,
23716 ranges: Vec<Range<text::Anchor>>,
23717 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23718 cx: &mut App,
23719 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23720
23721 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23722
23723 fn document_highlights(
23724 &self,
23725 buffer: &Entity<Buffer>,
23726 position: text::Anchor,
23727 cx: &mut App,
23728 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23729
23730 fn definitions(
23731 &self,
23732 buffer: &Entity<Buffer>,
23733 position: text::Anchor,
23734 kind: GotoDefinitionKind,
23735 cx: &mut App,
23736 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23737
23738 fn range_for_rename(
23739 &self,
23740 buffer: &Entity<Buffer>,
23741 position: text::Anchor,
23742 cx: &mut App,
23743 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23744
23745 fn perform_rename(
23746 &self,
23747 buffer: &Entity<Buffer>,
23748 position: text::Anchor,
23749 new_name: String,
23750 cx: &mut App,
23751 ) -> Option<Task<Result<ProjectTransaction>>>;
23752}
23753
23754pub trait CompletionProvider {
23755 fn completions(
23756 &self,
23757 excerpt_id: ExcerptId,
23758 buffer: &Entity<Buffer>,
23759 buffer_position: text::Anchor,
23760 trigger: CompletionContext,
23761 window: &mut Window,
23762 cx: &mut Context<Editor>,
23763 ) -> Task<Result<Vec<CompletionResponse>>>;
23764
23765 fn resolve_completions(
23766 &self,
23767 _buffer: Entity<Buffer>,
23768 _completion_indices: Vec<usize>,
23769 _completions: Rc<RefCell<Box<[Completion]>>>,
23770 _cx: &mut Context<Editor>,
23771 ) -> Task<Result<bool>> {
23772 Task::ready(Ok(false))
23773 }
23774
23775 fn apply_additional_edits_for_completion(
23776 &self,
23777 _buffer: Entity<Buffer>,
23778 _completions: Rc<RefCell<Box<[Completion]>>>,
23779 _completion_index: usize,
23780 _push_to_history: bool,
23781 _cx: &mut Context<Editor>,
23782 ) -> Task<Result<Option<language::Transaction>>> {
23783 Task::ready(Ok(None))
23784 }
23785
23786 fn is_completion_trigger(
23787 &self,
23788 buffer: &Entity<Buffer>,
23789 position: language::Anchor,
23790 text: &str,
23791 trigger_in_words: bool,
23792 cx: &mut Context<Editor>,
23793 ) -> bool;
23794
23795 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23796
23797 fn sort_completions(&self) -> bool {
23798 true
23799 }
23800
23801 fn filter_completions(&self) -> bool {
23802 true
23803 }
23804
23805 fn show_snippets(&self) -> bool {
23806 false
23807 }
23808}
23809
23810pub trait CodeActionProvider {
23811 fn id(&self) -> Arc<str>;
23812
23813 fn code_actions(
23814 &self,
23815 buffer: &Entity<Buffer>,
23816 range: Range<text::Anchor>,
23817 window: &mut Window,
23818 cx: &mut App,
23819 ) -> Task<Result<Vec<CodeAction>>>;
23820
23821 fn apply_code_action(
23822 &self,
23823 buffer_handle: Entity<Buffer>,
23824 action: CodeAction,
23825 excerpt_id: ExcerptId,
23826 push_to_history: bool,
23827 window: &mut Window,
23828 cx: &mut App,
23829 ) -> Task<Result<ProjectTransaction>>;
23830}
23831
23832impl CodeActionProvider for Entity<Project> {
23833 fn id(&self) -> Arc<str> {
23834 "project".into()
23835 }
23836
23837 fn code_actions(
23838 &self,
23839 buffer: &Entity<Buffer>,
23840 range: Range<text::Anchor>,
23841 _window: &mut Window,
23842 cx: &mut App,
23843 ) -> Task<Result<Vec<CodeAction>>> {
23844 self.update(cx, |project, cx| {
23845 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23846 let code_actions = project.code_actions(buffer, range, None, cx);
23847 cx.background_spawn(async move {
23848 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23849 Ok(code_lens_actions
23850 .context("code lens fetch")?
23851 .into_iter()
23852 .flatten()
23853 .chain(
23854 code_actions
23855 .context("code action fetch")?
23856 .into_iter()
23857 .flatten(),
23858 )
23859 .collect())
23860 })
23861 })
23862 }
23863
23864 fn apply_code_action(
23865 &self,
23866 buffer_handle: Entity<Buffer>,
23867 action: CodeAction,
23868 _excerpt_id: ExcerptId,
23869 push_to_history: bool,
23870 _window: &mut Window,
23871 cx: &mut App,
23872 ) -> Task<Result<ProjectTransaction>> {
23873 self.update(cx, |project, cx| {
23874 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23875 })
23876 }
23877}
23878
23879fn snippet_completions(
23880 project: &Project,
23881 buffer: &Entity<Buffer>,
23882 buffer_anchor: text::Anchor,
23883 classifier: CharClassifier,
23884 cx: &mut App,
23885) -> Task<Result<CompletionResponse>> {
23886 let languages = buffer.read(cx).languages_at(buffer_anchor);
23887 let snippet_store = project.snippets().read(cx);
23888
23889 let scopes: Vec<_> = languages
23890 .iter()
23891 .filter_map(|language| {
23892 let language_name = language.lsp_id();
23893 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23894
23895 if snippets.is_empty() {
23896 None
23897 } else {
23898 Some((language.default_scope(), snippets))
23899 }
23900 })
23901 .collect();
23902
23903 if scopes.is_empty() {
23904 return Task::ready(Ok(CompletionResponse {
23905 completions: vec![],
23906 display_options: CompletionDisplayOptions::default(),
23907 is_incomplete: false,
23908 }));
23909 }
23910
23911 let snapshot = buffer.read(cx).text_snapshot();
23912 let executor = cx.background_executor().clone();
23913
23914 cx.background_spawn(async move {
23915 let is_word_char = |c| classifier.is_word(c);
23916
23917 let mut is_incomplete = false;
23918 let mut completions: Vec<Completion> = Vec::new();
23919
23920 const MAX_PREFIX_LEN: usize = 128;
23921 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23922 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23923 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23924
23925 let max_buffer_window: String = snapshot
23926 .text_for_range(window_start..buffer_offset)
23927 .collect();
23928
23929 if max_buffer_window.is_empty() {
23930 return Ok(CompletionResponse {
23931 completions: vec![],
23932 display_options: CompletionDisplayOptions::default(),
23933 is_incomplete: true,
23934 });
23935 }
23936
23937 for (_scope, snippets) in scopes.into_iter() {
23938 // Sort snippets by word count to match longer snippet prefixes first.
23939 let mut sorted_snippet_candidates = snippets
23940 .iter()
23941 .enumerate()
23942 .flat_map(|(snippet_ix, snippet)| {
23943 snippet
23944 .prefix
23945 .iter()
23946 .enumerate()
23947 .map(move |(prefix_ix, prefix)| {
23948 let word_count =
23949 snippet_candidate_suffixes(prefix, is_word_char).count();
23950 ((snippet_ix, prefix_ix), prefix, word_count)
23951 })
23952 })
23953 .collect_vec();
23954 sorted_snippet_candidates
23955 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23956
23957 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23958
23959 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23960 .take(
23961 sorted_snippet_candidates
23962 .first()
23963 .map(|(_, _, word_count)| *word_count)
23964 .unwrap_or_default(),
23965 )
23966 .collect_vec();
23967
23968 const MAX_RESULTS: usize = 100;
23969 // Each match also remembers how many characters from the buffer it consumed
23970 let mut matches: Vec<(StringMatch, usize)> = vec![];
23971
23972 let mut snippet_list_cutoff_index = 0;
23973 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23974 let word_count = buffer_index + 1;
23975 // Increase `snippet_list_cutoff_index` until we have all of the
23976 // snippets with sufficiently many words.
23977 while sorted_snippet_candidates
23978 .get(snippet_list_cutoff_index)
23979 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23980 *snippet_word_count >= word_count
23981 })
23982 {
23983 snippet_list_cutoff_index += 1;
23984 }
23985
23986 // Take only the candidates with at least `word_count` many words
23987 let snippet_candidates_at_word_len =
23988 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23989
23990 let candidates = snippet_candidates_at_word_len
23991 .iter()
23992 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23993 .enumerate() // index in `sorted_snippet_candidates`
23994 // First char must match
23995 .filter(|(_ix, prefix)| {
23996 itertools::equal(
23997 prefix
23998 .chars()
23999 .next()
24000 .into_iter()
24001 .flat_map(|c| c.to_lowercase()),
24002 buffer_window
24003 .chars()
24004 .next()
24005 .into_iter()
24006 .flat_map(|c| c.to_lowercase()),
24007 )
24008 })
24009 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24010 .collect::<Vec<StringMatchCandidate>>();
24011
24012 matches.extend(
24013 fuzzy::match_strings(
24014 &candidates,
24015 &buffer_window,
24016 buffer_window.chars().any(|c| c.is_uppercase()),
24017 true,
24018 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24019 &Default::default(),
24020 executor.clone(),
24021 )
24022 .await
24023 .into_iter()
24024 .map(|string_match| (string_match, buffer_window.len())),
24025 );
24026
24027 if matches.len() >= MAX_RESULTS {
24028 break;
24029 }
24030 }
24031
24032 let to_lsp = |point: &text::Anchor| {
24033 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24034 point_to_lsp(end)
24035 };
24036 let lsp_end = to_lsp(&buffer_anchor);
24037
24038 if matches.len() >= MAX_RESULTS {
24039 is_incomplete = true;
24040 }
24041
24042 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24043 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24044 sorted_snippet_candidates[string_match.candidate_id];
24045 let snippet = &snippets[snippet_index];
24046 let start = buffer_offset - buffer_window_len;
24047 let start = snapshot.anchor_before(start);
24048 let range = start..buffer_anchor;
24049 let lsp_start = to_lsp(&start);
24050 let lsp_range = lsp::Range {
24051 start: lsp_start,
24052 end: lsp_end,
24053 };
24054 Completion {
24055 replace_range: range,
24056 new_text: snippet.body.clone(),
24057 source: CompletionSource::Lsp {
24058 insert_range: None,
24059 server_id: LanguageServerId(usize::MAX),
24060 resolved: true,
24061 lsp_completion: Box::new(lsp::CompletionItem {
24062 label: snippet.prefix.first().unwrap().clone(),
24063 kind: Some(CompletionItemKind::SNIPPET),
24064 label_details: snippet.description.as_ref().map(|description| {
24065 lsp::CompletionItemLabelDetails {
24066 detail: Some(description.clone()),
24067 description: None,
24068 }
24069 }),
24070 insert_text_format: Some(InsertTextFormat::SNIPPET),
24071 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24072 lsp::InsertReplaceEdit {
24073 new_text: snippet.body.clone(),
24074 insert: lsp_range,
24075 replace: lsp_range,
24076 },
24077 )),
24078 filter_text: Some(snippet.body.clone()),
24079 sort_text: Some(char::MAX.to_string()),
24080 ..lsp::CompletionItem::default()
24081 }),
24082 lsp_defaults: None,
24083 },
24084 label: CodeLabel {
24085 text: matching_prefix.clone(),
24086 runs: Vec::new(),
24087 filter_range: 0..matching_prefix.len(),
24088 },
24089 icon_path: None,
24090 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24091 single_line: snippet.name.clone().into(),
24092 plain_text: snippet
24093 .description
24094 .clone()
24095 .map(|description| description.into()),
24096 }),
24097 insert_text_mode: None,
24098 confirm: None,
24099 match_start: Some(start),
24100 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24101 }
24102 }));
24103 }
24104
24105 Ok(CompletionResponse {
24106 completions,
24107 display_options: CompletionDisplayOptions::default(),
24108 is_incomplete,
24109 })
24110 })
24111}
24112
24113impl CompletionProvider for Entity<Project> {
24114 fn completions(
24115 &self,
24116 _excerpt_id: ExcerptId,
24117 buffer: &Entity<Buffer>,
24118 buffer_position: text::Anchor,
24119 options: CompletionContext,
24120 _window: &mut Window,
24121 cx: &mut Context<Editor>,
24122 ) -> Task<Result<Vec<CompletionResponse>>> {
24123 self.update(cx, |project, cx| {
24124 let task = project.completions(buffer, buffer_position, options, cx);
24125 cx.background_spawn(task)
24126 })
24127 }
24128
24129 fn resolve_completions(
24130 &self,
24131 buffer: Entity<Buffer>,
24132 completion_indices: Vec<usize>,
24133 completions: Rc<RefCell<Box<[Completion]>>>,
24134 cx: &mut Context<Editor>,
24135 ) -> Task<Result<bool>> {
24136 self.update(cx, |project, cx| {
24137 project.lsp_store().update(cx, |lsp_store, cx| {
24138 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24139 })
24140 })
24141 }
24142
24143 fn apply_additional_edits_for_completion(
24144 &self,
24145 buffer: Entity<Buffer>,
24146 completions: Rc<RefCell<Box<[Completion]>>>,
24147 completion_index: usize,
24148 push_to_history: bool,
24149 cx: &mut Context<Editor>,
24150 ) -> Task<Result<Option<language::Transaction>>> {
24151 self.update(cx, |project, cx| {
24152 project.lsp_store().update(cx, |lsp_store, cx| {
24153 lsp_store.apply_additional_edits_for_completion(
24154 buffer,
24155 completions,
24156 completion_index,
24157 push_to_history,
24158 cx,
24159 )
24160 })
24161 })
24162 }
24163
24164 fn is_completion_trigger(
24165 &self,
24166 buffer: &Entity<Buffer>,
24167 position: language::Anchor,
24168 text: &str,
24169 trigger_in_words: bool,
24170 cx: &mut Context<Editor>,
24171 ) -> bool {
24172 let mut chars = text.chars();
24173 let char = if let Some(char) = chars.next() {
24174 char
24175 } else {
24176 return false;
24177 };
24178 if chars.next().is_some() {
24179 return false;
24180 }
24181
24182 let buffer = buffer.read(cx);
24183 let snapshot = buffer.snapshot();
24184 let classifier = snapshot
24185 .char_classifier_at(position)
24186 .scope_context(Some(CharScopeContext::Completion));
24187 if trigger_in_words && classifier.is_word(char) {
24188 return true;
24189 }
24190
24191 buffer.completion_triggers().contains(text)
24192 }
24193
24194 fn show_snippets(&self) -> bool {
24195 true
24196 }
24197}
24198
24199impl SemanticsProvider for Entity<Project> {
24200 fn hover(
24201 &self,
24202 buffer: &Entity<Buffer>,
24203 position: text::Anchor,
24204 cx: &mut App,
24205 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24206 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24207 }
24208
24209 fn document_highlights(
24210 &self,
24211 buffer: &Entity<Buffer>,
24212 position: text::Anchor,
24213 cx: &mut App,
24214 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24215 Some(self.update(cx, |project, cx| {
24216 project.document_highlights(buffer, position, cx)
24217 }))
24218 }
24219
24220 fn definitions(
24221 &self,
24222 buffer: &Entity<Buffer>,
24223 position: text::Anchor,
24224 kind: GotoDefinitionKind,
24225 cx: &mut App,
24226 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24227 Some(self.update(cx, |project, cx| match kind {
24228 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24229 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24230 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24231 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24232 }))
24233 }
24234
24235 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24236 self.update(cx, |project, cx| {
24237 if project
24238 .active_debug_session(cx)
24239 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24240 {
24241 return true;
24242 }
24243
24244 buffer.update(cx, |buffer, cx| {
24245 project.any_language_server_supports_inlay_hints(buffer, cx)
24246 })
24247 })
24248 }
24249
24250 fn inline_values(
24251 &self,
24252 buffer_handle: Entity<Buffer>,
24253 range: Range<text::Anchor>,
24254 cx: &mut App,
24255 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24256 self.update(cx, |project, cx| {
24257 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24258
24259 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24260 })
24261 }
24262
24263 fn applicable_inlay_chunks(
24264 &self,
24265 buffer: &Entity<Buffer>,
24266 ranges: &[Range<text::Anchor>],
24267 cx: &mut App,
24268 ) -> Vec<Range<BufferRow>> {
24269 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24270 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24271 })
24272 }
24273
24274 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24275 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24276 lsp_store.invalidate_inlay_hints(for_buffers)
24277 });
24278 }
24279
24280 fn inlay_hints(
24281 &self,
24282 invalidate: InvalidationStrategy,
24283 buffer: Entity<Buffer>,
24284 ranges: Vec<Range<text::Anchor>>,
24285 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24286 cx: &mut App,
24287 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24288 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24289 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24290 }))
24291 }
24292
24293 fn range_for_rename(
24294 &self,
24295 buffer: &Entity<Buffer>,
24296 position: text::Anchor,
24297 cx: &mut App,
24298 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24299 Some(self.update(cx, |project, cx| {
24300 let buffer = buffer.clone();
24301 let task = project.prepare_rename(buffer.clone(), position, cx);
24302 cx.spawn(async move |_, cx| {
24303 Ok(match task.await? {
24304 PrepareRenameResponse::Success(range) => Some(range),
24305 PrepareRenameResponse::InvalidPosition => None,
24306 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24307 // Fallback on using TreeSitter info to determine identifier range
24308 buffer.read_with(cx, |buffer, _| {
24309 let snapshot = buffer.snapshot();
24310 let (range, kind) = snapshot.surrounding_word(position, None);
24311 if kind != Some(CharKind::Word) {
24312 return None;
24313 }
24314 Some(
24315 snapshot.anchor_before(range.start)
24316 ..snapshot.anchor_after(range.end),
24317 )
24318 })?
24319 }
24320 })
24321 })
24322 }))
24323 }
24324
24325 fn perform_rename(
24326 &self,
24327 buffer: &Entity<Buffer>,
24328 position: text::Anchor,
24329 new_name: String,
24330 cx: &mut App,
24331 ) -> Option<Task<Result<ProjectTransaction>>> {
24332 Some(self.update(cx, |project, cx| {
24333 project.perform_rename(buffer.clone(), position, new_name, cx)
24334 }))
24335 }
24336}
24337
24338fn consume_contiguous_rows(
24339 contiguous_row_selections: &mut Vec<Selection<Point>>,
24340 selection: &Selection<Point>,
24341 display_map: &DisplaySnapshot,
24342 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24343) -> (MultiBufferRow, MultiBufferRow) {
24344 contiguous_row_selections.push(selection.clone());
24345 let start_row = starting_row(selection, display_map);
24346 let mut end_row = ending_row(selection, display_map);
24347
24348 while let Some(next_selection) = selections.peek() {
24349 if next_selection.start.row <= end_row.0 {
24350 end_row = ending_row(next_selection, display_map);
24351 contiguous_row_selections.push(selections.next().unwrap().clone());
24352 } else {
24353 break;
24354 }
24355 }
24356 (start_row, end_row)
24357}
24358
24359fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24360 if selection.start.column > 0 {
24361 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24362 } else {
24363 MultiBufferRow(selection.start.row)
24364 }
24365}
24366
24367fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24368 if next_selection.end.column > 0 || next_selection.is_empty() {
24369 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24370 } else {
24371 MultiBufferRow(next_selection.end.row)
24372 }
24373}
24374
24375impl EditorSnapshot {
24376 pub fn remote_selections_in_range<'a>(
24377 &'a self,
24378 range: &'a Range<Anchor>,
24379 collaboration_hub: &dyn CollaborationHub,
24380 cx: &'a App,
24381 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24382 let participant_names = collaboration_hub.user_names(cx);
24383 let participant_indices = collaboration_hub.user_participant_indices(cx);
24384 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24385 let collaborators_by_replica_id = collaborators_by_peer_id
24386 .values()
24387 .map(|collaborator| (collaborator.replica_id, collaborator))
24388 .collect::<HashMap<_, _>>();
24389 self.buffer_snapshot()
24390 .selections_in_range(range, false)
24391 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24392 if replica_id == ReplicaId::AGENT {
24393 Some(RemoteSelection {
24394 replica_id,
24395 selection,
24396 cursor_shape,
24397 line_mode,
24398 collaborator_id: CollaboratorId::Agent,
24399 user_name: Some("Agent".into()),
24400 color: cx.theme().players().agent(),
24401 })
24402 } else {
24403 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24404 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24405 let user_name = participant_names.get(&collaborator.user_id).cloned();
24406 Some(RemoteSelection {
24407 replica_id,
24408 selection,
24409 cursor_shape,
24410 line_mode,
24411 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24412 user_name,
24413 color: if let Some(index) = participant_index {
24414 cx.theme().players().color_for_participant(index.0)
24415 } else {
24416 cx.theme().players().absent()
24417 },
24418 })
24419 }
24420 })
24421 }
24422
24423 pub fn hunks_for_ranges(
24424 &self,
24425 ranges: impl IntoIterator<Item = Range<Point>>,
24426 ) -> Vec<MultiBufferDiffHunk> {
24427 let mut hunks = Vec::new();
24428 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24429 HashMap::default();
24430 for query_range in ranges {
24431 let query_rows =
24432 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24433 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24434 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24435 ) {
24436 // Include deleted hunks that are adjacent to the query range, because
24437 // otherwise they would be missed.
24438 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24439 if hunk.status().is_deleted() {
24440 intersects_range |= hunk.row_range.start == query_rows.end;
24441 intersects_range |= hunk.row_range.end == query_rows.start;
24442 }
24443 if intersects_range {
24444 if !processed_buffer_rows
24445 .entry(hunk.buffer_id)
24446 .or_default()
24447 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24448 {
24449 continue;
24450 }
24451 hunks.push(hunk);
24452 }
24453 }
24454 }
24455
24456 hunks
24457 }
24458
24459 fn display_diff_hunks_for_rows<'a>(
24460 &'a self,
24461 display_rows: Range<DisplayRow>,
24462 folded_buffers: &'a HashSet<BufferId>,
24463 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24464 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24465 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24466
24467 self.buffer_snapshot()
24468 .diff_hunks_in_range(buffer_start..buffer_end)
24469 .filter_map(|hunk| {
24470 if folded_buffers.contains(&hunk.buffer_id) {
24471 return None;
24472 }
24473
24474 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24475 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24476
24477 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24478 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24479
24480 let display_hunk = if hunk_display_start.column() != 0 {
24481 DisplayDiffHunk::Folded {
24482 display_row: hunk_display_start.row(),
24483 }
24484 } else {
24485 let mut end_row = hunk_display_end.row();
24486 if hunk_display_end.column() > 0 {
24487 end_row.0 += 1;
24488 }
24489 let is_created_file = hunk.is_created_file();
24490
24491 DisplayDiffHunk::Unfolded {
24492 status: hunk.status(),
24493 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24494 ..hunk.diff_base_byte_range.end.0,
24495 word_diffs: hunk.word_diffs,
24496 display_row_range: hunk_display_start.row()..end_row,
24497 multi_buffer_range: Anchor::range_in_buffer(
24498 hunk.excerpt_id,
24499 hunk.buffer_range,
24500 ),
24501 is_created_file,
24502 }
24503 };
24504
24505 Some(display_hunk)
24506 })
24507 }
24508
24509 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24510 self.display_snapshot
24511 .buffer_snapshot()
24512 .language_at(position)
24513 }
24514
24515 pub fn is_focused(&self) -> bool {
24516 self.is_focused
24517 }
24518
24519 pub fn placeholder_text(&self) -> Option<String> {
24520 self.placeholder_display_snapshot
24521 .as_ref()
24522 .map(|display_map| display_map.text())
24523 }
24524
24525 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24526 self.scroll_anchor.scroll_position(&self.display_snapshot)
24527 }
24528
24529 fn gutter_dimensions(
24530 &self,
24531 font_id: FontId,
24532 font_size: Pixels,
24533 max_line_number_width: Pixels,
24534 cx: &App,
24535 ) -> Option<GutterDimensions> {
24536 if !self.show_gutter {
24537 return None;
24538 }
24539
24540 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24541 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24542
24543 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24544 matches!(
24545 ProjectSettings::get_global(cx).git.git_gutter,
24546 GitGutterSetting::TrackedFiles
24547 )
24548 });
24549 let gutter_settings = EditorSettings::get_global(cx).gutter;
24550 let show_line_numbers = self
24551 .show_line_numbers
24552 .unwrap_or(gutter_settings.line_numbers);
24553 let line_gutter_width = if show_line_numbers {
24554 // Avoid flicker-like gutter resizes when the line number gains another digit by
24555 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24556 let min_width_for_number_on_gutter =
24557 ch_advance * gutter_settings.min_line_number_digits as f32;
24558 max_line_number_width.max(min_width_for_number_on_gutter)
24559 } else {
24560 0.0.into()
24561 };
24562
24563 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24564 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24565
24566 let git_blame_entries_width =
24567 self.git_blame_gutter_max_author_length
24568 .map(|max_author_length| {
24569 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24570 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24571
24572 /// The number of characters to dedicate to gaps and margins.
24573 const SPACING_WIDTH: usize = 4;
24574
24575 let max_char_count = max_author_length.min(renderer.max_author_length())
24576 + ::git::SHORT_SHA_LENGTH
24577 + MAX_RELATIVE_TIMESTAMP.len()
24578 + SPACING_WIDTH;
24579
24580 ch_advance * max_char_count
24581 });
24582
24583 let is_singleton = self.buffer_snapshot().is_singleton();
24584
24585 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24586 left_padding += if !is_singleton {
24587 ch_width * 4.0
24588 } else if show_runnables || show_breakpoints {
24589 ch_width * 3.0
24590 } else if show_git_gutter && show_line_numbers {
24591 ch_width * 2.0
24592 } else if show_git_gutter || show_line_numbers {
24593 ch_width
24594 } else {
24595 px(0.)
24596 };
24597
24598 let shows_folds = is_singleton && gutter_settings.folds;
24599
24600 let right_padding = if shows_folds && show_line_numbers {
24601 ch_width * 4.0
24602 } else if shows_folds || (!is_singleton && show_line_numbers) {
24603 ch_width * 3.0
24604 } else if show_line_numbers {
24605 ch_width
24606 } else {
24607 px(0.)
24608 };
24609
24610 Some(GutterDimensions {
24611 left_padding,
24612 right_padding,
24613 width: line_gutter_width + left_padding + right_padding,
24614 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24615 git_blame_entries_width,
24616 })
24617 }
24618
24619 pub fn render_crease_toggle(
24620 &self,
24621 buffer_row: MultiBufferRow,
24622 row_contains_cursor: bool,
24623 editor: Entity<Editor>,
24624 window: &mut Window,
24625 cx: &mut App,
24626 ) -> Option<AnyElement> {
24627 let folded = self.is_line_folded(buffer_row);
24628 let mut is_foldable = false;
24629
24630 if let Some(crease) = self
24631 .crease_snapshot
24632 .query_row(buffer_row, self.buffer_snapshot())
24633 {
24634 is_foldable = true;
24635 match crease {
24636 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24637 if let Some(render_toggle) = render_toggle {
24638 let toggle_callback =
24639 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24640 if folded {
24641 editor.update(cx, |editor, cx| {
24642 editor.fold_at(buffer_row, window, cx)
24643 });
24644 } else {
24645 editor.update(cx, |editor, cx| {
24646 editor.unfold_at(buffer_row, window, cx)
24647 });
24648 }
24649 });
24650 return Some((render_toggle)(
24651 buffer_row,
24652 folded,
24653 toggle_callback,
24654 window,
24655 cx,
24656 ));
24657 }
24658 }
24659 }
24660 }
24661
24662 is_foldable |= self.starts_indent(buffer_row);
24663
24664 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24665 Some(
24666 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24667 .toggle_state(folded)
24668 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24669 if folded {
24670 this.unfold_at(buffer_row, window, cx);
24671 } else {
24672 this.fold_at(buffer_row, window, cx);
24673 }
24674 }))
24675 .into_any_element(),
24676 )
24677 } else {
24678 None
24679 }
24680 }
24681
24682 pub fn render_crease_trailer(
24683 &self,
24684 buffer_row: MultiBufferRow,
24685 window: &mut Window,
24686 cx: &mut App,
24687 ) -> Option<AnyElement> {
24688 let folded = self.is_line_folded(buffer_row);
24689 if let Crease::Inline { render_trailer, .. } = self
24690 .crease_snapshot
24691 .query_row(buffer_row, self.buffer_snapshot())?
24692 {
24693 let render_trailer = render_trailer.as_ref()?;
24694 Some(render_trailer(buffer_row, folded, window, cx))
24695 } else {
24696 None
24697 }
24698 }
24699}
24700
24701impl Deref for EditorSnapshot {
24702 type Target = DisplaySnapshot;
24703
24704 fn deref(&self) -> &Self::Target {
24705 &self.display_snapshot
24706 }
24707}
24708
24709#[derive(Clone, Debug, PartialEq, Eq)]
24710pub enum EditorEvent {
24711 InputIgnored {
24712 text: Arc<str>,
24713 },
24714 InputHandled {
24715 utf16_range_to_replace: Option<Range<isize>>,
24716 text: Arc<str>,
24717 },
24718 ExcerptsAdded {
24719 buffer: Entity<Buffer>,
24720 predecessor: ExcerptId,
24721 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24722 },
24723 ExcerptsRemoved {
24724 ids: Vec<ExcerptId>,
24725 removed_buffer_ids: Vec<BufferId>,
24726 },
24727 BufferFoldToggled {
24728 ids: Vec<ExcerptId>,
24729 folded: bool,
24730 },
24731 ExcerptsEdited {
24732 ids: Vec<ExcerptId>,
24733 },
24734 ExcerptsExpanded {
24735 ids: Vec<ExcerptId>,
24736 },
24737 BufferEdited,
24738 Edited {
24739 transaction_id: clock::Lamport,
24740 },
24741 Reparsed(BufferId),
24742 Focused,
24743 FocusedIn,
24744 Blurred,
24745 DirtyChanged,
24746 Saved,
24747 TitleChanged,
24748 SelectionsChanged {
24749 local: bool,
24750 },
24751 ScrollPositionChanged {
24752 local: bool,
24753 autoscroll: bool,
24754 },
24755 TransactionUndone {
24756 transaction_id: clock::Lamport,
24757 },
24758 TransactionBegun {
24759 transaction_id: clock::Lamport,
24760 },
24761 CursorShapeChanged,
24762 BreadcrumbsChanged,
24763 PushedToNavHistory {
24764 anchor: Anchor,
24765 is_deactivate: bool,
24766 },
24767}
24768
24769impl EventEmitter<EditorEvent> for Editor {}
24770
24771impl Focusable for Editor {
24772 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24773 self.focus_handle.clone()
24774 }
24775}
24776
24777impl Render for Editor {
24778 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24779 let settings = ThemeSettings::get_global(cx);
24780
24781 let mut text_style = match self.mode {
24782 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24783 color: cx.theme().colors().editor_foreground,
24784 font_family: settings.ui_font.family.clone(),
24785 font_features: settings.ui_font.features.clone(),
24786 font_fallbacks: settings.ui_font.fallbacks.clone(),
24787 font_size: rems(0.875).into(),
24788 font_weight: settings.ui_font.weight,
24789 line_height: relative(settings.buffer_line_height.value()),
24790 ..Default::default()
24791 },
24792 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24793 color: cx.theme().colors().editor_foreground,
24794 font_family: settings.buffer_font.family.clone(),
24795 font_features: settings.buffer_font.features.clone(),
24796 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24797 font_size: settings.buffer_font_size(cx).into(),
24798 font_weight: settings.buffer_font.weight,
24799 line_height: relative(settings.buffer_line_height.value()),
24800 ..Default::default()
24801 },
24802 };
24803 if let Some(text_style_refinement) = &self.text_style_refinement {
24804 text_style.refine(text_style_refinement)
24805 }
24806
24807 let background = match self.mode {
24808 EditorMode::SingleLine => cx.theme().system().transparent,
24809 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24810 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24811 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24812 };
24813
24814 EditorElement::new(
24815 &cx.entity(),
24816 EditorStyle {
24817 background,
24818 border: cx.theme().colors().border,
24819 local_player: cx.theme().players().local(),
24820 text: text_style,
24821 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24822 syntax: cx.theme().syntax().clone(),
24823 status: cx.theme().status().clone(),
24824 inlay_hints_style: make_inlay_hints_style(cx),
24825 edit_prediction_styles: make_suggestion_styles(cx),
24826 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24827 show_underlines: self.diagnostics_enabled(),
24828 },
24829 )
24830 }
24831}
24832
24833impl EntityInputHandler for Editor {
24834 fn text_for_range(
24835 &mut self,
24836 range_utf16: Range<usize>,
24837 adjusted_range: &mut Option<Range<usize>>,
24838 _: &mut Window,
24839 cx: &mut Context<Self>,
24840 ) -> Option<String> {
24841 let snapshot = self.buffer.read(cx).read(cx);
24842 let start = snapshot.clip_offset_utf16(
24843 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24844 Bias::Left,
24845 );
24846 let end = snapshot.clip_offset_utf16(
24847 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24848 Bias::Right,
24849 );
24850 if (start.0.0..end.0.0) != range_utf16 {
24851 adjusted_range.replace(start.0.0..end.0.0);
24852 }
24853 Some(snapshot.text_for_range(start..end).collect())
24854 }
24855
24856 fn selected_text_range(
24857 &mut self,
24858 ignore_disabled_input: bool,
24859 _: &mut Window,
24860 cx: &mut Context<Self>,
24861 ) -> Option<UTF16Selection> {
24862 // Prevent the IME menu from appearing when holding down an alphabetic key
24863 // while input is disabled.
24864 if !ignore_disabled_input && !self.input_enabled {
24865 return None;
24866 }
24867
24868 let selection = self
24869 .selections
24870 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24871 let range = selection.range();
24872
24873 Some(UTF16Selection {
24874 range: range.start.0.0..range.end.0.0,
24875 reversed: selection.reversed,
24876 })
24877 }
24878
24879 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24880 let snapshot = self.buffer.read(cx).read(cx);
24881 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24882 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24883 }
24884
24885 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24886 self.clear_highlights::<InputComposition>(cx);
24887 self.ime_transaction.take();
24888 }
24889
24890 fn replace_text_in_range(
24891 &mut self,
24892 range_utf16: Option<Range<usize>>,
24893 text: &str,
24894 window: &mut Window,
24895 cx: &mut Context<Self>,
24896 ) {
24897 if !self.input_enabled {
24898 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24899 return;
24900 }
24901
24902 self.transact(window, cx, |this, window, cx| {
24903 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24904 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24905 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24906 Some(this.selection_replacement_ranges(range_utf16, cx))
24907 } else {
24908 this.marked_text_ranges(cx)
24909 };
24910
24911 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24912 let newest_selection_id = this.selections.newest_anchor().id;
24913 this.selections
24914 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24915 .iter()
24916 .zip(ranges_to_replace.iter())
24917 .find_map(|(selection, range)| {
24918 if selection.id == newest_selection_id {
24919 Some(
24920 (range.start.0.0 as isize - selection.head().0.0 as isize)
24921 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24922 )
24923 } else {
24924 None
24925 }
24926 })
24927 });
24928
24929 cx.emit(EditorEvent::InputHandled {
24930 utf16_range_to_replace: range_to_replace,
24931 text: text.into(),
24932 });
24933
24934 if let Some(new_selected_ranges) = new_selected_ranges {
24935 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24936 selections.select_ranges(new_selected_ranges)
24937 });
24938 this.backspace(&Default::default(), window, cx);
24939 }
24940
24941 this.handle_input(text, window, cx);
24942 });
24943
24944 if let Some(transaction) = self.ime_transaction {
24945 self.buffer.update(cx, |buffer, cx| {
24946 buffer.group_until_transaction(transaction, cx);
24947 });
24948 }
24949
24950 self.unmark_text(window, cx);
24951 }
24952
24953 fn replace_and_mark_text_in_range(
24954 &mut self,
24955 range_utf16: Option<Range<usize>>,
24956 text: &str,
24957 new_selected_range_utf16: Option<Range<usize>>,
24958 window: &mut Window,
24959 cx: &mut Context<Self>,
24960 ) {
24961 if !self.input_enabled {
24962 return;
24963 }
24964
24965 let transaction = self.transact(window, cx, |this, window, cx| {
24966 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24967 let snapshot = this.buffer.read(cx).read(cx);
24968 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24969 for marked_range in &mut marked_ranges {
24970 marked_range.end = marked_range.start + relative_range_utf16.end;
24971 marked_range.start += relative_range_utf16.start;
24972 marked_range.start =
24973 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24974 marked_range.end =
24975 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24976 }
24977 }
24978 Some(marked_ranges)
24979 } else if let Some(range_utf16) = range_utf16 {
24980 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24981 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24982 Some(this.selection_replacement_ranges(range_utf16, cx))
24983 } else {
24984 None
24985 };
24986
24987 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24988 let newest_selection_id = this.selections.newest_anchor().id;
24989 this.selections
24990 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24991 .iter()
24992 .zip(ranges_to_replace.iter())
24993 .find_map(|(selection, range)| {
24994 if selection.id == newest_selection_id {
24995 Some(
24996 (range.start.0.0 as isize - selection.head().0.0 as isize)
24997 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24998 )
24999 } else {
25000 None
25001 }
25002 })
25003 });
25004
25005 cx.emit(EditorEvent::InputHandled {
25006 utf16_range_to_replace: range_to_replace,
25007 text: text.into(),
25008 });
25009
25010 if let Some(ranges) = ranges_to_replace {
25011 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25012 s.select_ranges(ranges)
25013 });
25014 }
25015
25016 let marked_ranges = {
25017 let snapshot = this.buffer.read(cx).read(cx);
25018 this.selections
25019 .disjoint_anchors_arc()
25020 .iter()
25021 .map(|selection| {
25022 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25023 })
25024 .collect::<Vec<_>>()
25025 };
25026
25027 if text.is_empty() {
25028 this.unmark_text(window, cx);
25029 } else {
25030 this.highlight_text::<InputComposition>(
25031 marked_ranges.clone(),
25032 HighlightStyle {
25033 underline: Some(UnderlineStyle {
25034 thickness: px(1.),
25035 color: None,
25036 wavy: false,
25037 }),
25038 ..Default::default()
25039 },
25040 cx,
25041 );
25042 }
25043
25044 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25045 let use_autoclose = this.use_autoclose;
25046 let use_auto_surround = this.use_auto_surround;
25047 this.set_use_autoclose(false);
25048 this.set_use_auto_surround(false);
25049 this.handle_input(text, window, cx);
25050 this.set_use_autoclose(use_autoclose);
25051 this.set_use_auto_surround(use_auto_surround);
25052
25053 if let Some(new_selected_range) = new_selected_range_utf16 {
25054 let snapshot = this.buffer.read(cx).read(cx);
25055 let new_selected_ranges = marked_ranges
25056 .into_iter()
25057 .map(|marked_range| {
25058 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25059 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25060 insertion_start.0 + new_selected_range.start,
25061 ));
25062 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25063 insertion_start.0 + new_selected_range.end,
25064 ));
25065 snapshot.clip_offset_utf16(new_start, Bias::Left)
25066 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25067 })
25068 .collect::<Vec<_>>();
25069
25070 drop(snapshot);
25071 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25072 selections.select_ranges(new_selected_ranges)
25073 });
25074 }
25075 });
25076
25077 self.ime_transaction = self.ime_transaction.or(transaction);
25078 if let Some(transaction) = self.ime_transaction {
25079 self.buffer.update(cx, |buffer, cx| {
25080 buffer.group_until_transaction(transaction, cx);
25081 });
25082 }
25083
25084 if self.text_highlights::<InputComposition>(cx).is_none() {
25085 self.ime_transaction.take();
25086 }
25087 }
25088
25089 fn bounds_for_range(
25090 &mut self,
25091 range_utf16: Range<usize>,
25092 element_bounds: gpui::Bounds<Pixels>,
25093 window: &mut Window,
25094 cx: &mut Context<Self>,
25095 ) -> Option<gpui::Bounds<Pixels>> {
25096 let text_layout_details = self.text_layout_details(window);
25097 let CharacterDimensions {
25098 em_width,
25099 em_advance,
25100 line_height,
25101 } = self.character_dimensions(window);
25102
25103 let snapshot = self.snapshot(window, cx);
25104 let scroll_position = snapshot.scroll_position();
25105 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25106
25107 let start =
25108 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25109 let x = Pixels::from(
25110 ScrollOffset::from(
25111 snapshot.x_for_display_point(start, &text_layout_details)
25112 + self.gutter_dimensions.full_width(),
25113 ) - scroll_left,
25114 );
25115 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25116
25117 Some(Bounds {
25118 origin: element_bounds.origin + point(x, y),
25119 size: size(em_width, line_height),
25120 })
25121 }
25122
25123 fn character_index_for_point(
25124 &mut self,
25125 point: gpui::Point<Pixels>,
25126 _window: &mut Window,
25127 _cx: &mut Context<Self>,
25128 ) -> Option<usize> {
25129 let position_map = self.last_position_map.as_ref()?;
25130 if !position_map.text_hitbox.contains(&point) {
25131 return None;
25132 }
25133 let display_point = position_map.point_for_position(point).previous_valid;
25134 let anchor = position_map
25135 .snapshot
25136 .display_point_to_anchor(display_point, Bias::Left);
25137 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25138 Some(utf16_offset.0.0)
25139 }
25140
25141 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25142 self.input_enabled
25143 }
25144}
25145
25146trait SelectionExt {
25147 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25148 fn spanned_rows(
25149 &self,
25150 include_end_if_at_line_start: bool,
25151 map: &DisplaySnapshot,
25152 ) -> Range<MultiBufferRow>;
25153}
25154
25155impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25156 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25157 let start = self
25158 .start
25159 .to_point(map.buffer_snapshot())
25160 .to_display_point(map);
25161 let end = self
25162 .end
25163 .to_point(map.buffer_snapshot())
25164 .to_display_point(map);
25165 if self.reversed {
25166 end..start
25167 } else {
25168 start..end
25169 }
25170 }
25171
25172 fn spanned_rows(
25173 &self,
25174 include_end_if_at_line_start: bool,
25175 map: &DisplaySnapshot,
25176 ) -> Range<MultiBufferRow> {
25177 let start = self.start.to_point(map.buffer_snapshot());
25178 let mut end = self.end.to_point(map.buffer_snapshot());
25179 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25180 end.row -= 1;
25181 }
25182
25183 let buffer_start = map.prev_line_boundary(start).0;
25184 let buffer_end = map.next_line_boundary(end).0;
25185 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25186 }
25187}
25188
25189impl<T: InvalidationRegion> InvalidationStack<T> {
25190 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25191 where
25192 S: Clone + ToOffset,
25193 {
25194 while let Some(region) = self.last() {
25195 let all_selections_inside_invalidation_ranges =
25196 if selections.len() == region.ranges().len() {
25197 selections
25198 .iter()
25199 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25200 .all(|(selection, invalidation_range)| {
25201 let head = selection.head().to_offset(buffer);
25202 invalidation_range.start <= head && invalidation_range.end >= head
25203 })
25204 } else {
25205 false
25206 };
25207
25208 if all_selections_inside_invalidation_ranges {
25209 break;
25210 } else {
25211 self.pop();
25212 }
25213 }
25214 }
25215}
25216
25217impl<T> Default for InvalidationStack<T> {
25218 fn default() -> Self {
25219 Self(Default::default())
25220 }
25221}
25222
25223impl<T> Deref for InvalidationStack<T> {
25224 type Target = Vec<T>;
25225
25226 fn deref(&self) -> &Self::Target {
25227 &self.0
25228 }
25229}
25230
25231impl<T> DerefMut for InvalidationStack<T> {
25232 fn deref_mut(&mut self) -> &mut Self::Target {
25233 &mut self.0
25234 }
25235}
25236
25237impl InvalidationRegion for SnippetState {
25238 fn ranges(&self) -> &[Range<Anchor>] {
25239 &self.ranges[self.active_index]
25240 }
25241}
25242
25243fn edit_prediction_edit_text(
25244 current_snapshot: &BufferSnapshot,
25245 edits: &[(Range<Anchor>, impl AsRef<str>)],
25246 edit_preview: &EditPreview,
25247 include_deletions: bool,
25248 cx: &App,
25249) -> HighlightedText {
25250 let edits = edits
25251 .iter()
25252 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25253 .collect::<Vec<_>>();
25254
25255 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25256}
25257
25258fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25259 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25260 // Just show the raw edit text with basic styling
25261 let mut text = String::new();
25262 let mut highlights = Vec::new();
25263
25264 let insertion_highlight_style = HighlightStyle {
25265 color: Some(cx.theme().colors().text),
25266 ..Default::default()
25267 };
25268
25269 for (_, edit_text) in edits {
25270 let start_offset = text.len();
25271 text.push_str(edit_text);
25272 let end_offset = text.len();
25273
25274 if start_offset < end_offset {
25275 highlights.push((start_offset..end_offset, insertion_highlight_style));
25276 }
25277 }
25278
25279 HighlightedText {
25280 text: text.into(),
25281 highlights,
25282 }
25283}
25284
25285pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25286 match severity {
25287 lsp::DiagnosticSeverity::ERROR => colors.error,
25288 lsp::DiagnosticSeverity::WARNING => colors.warning,
25289 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25290 lsp::DiagnosticSeverity::HINT => colors.info,
25291 _ => colors.ignored,
25292 }
25293}
25294
25295pub fn styled_runs_for_code_label<'a>(
25296 label: &'a CodeLabel,
25297 syntax_theme: &'a theme::SyntaxTheme,
25298 local_player: &'a theme::PlayerColor,
25299) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25300 let fade_out = HighlightStyle {
25301 fade_out: Some(0.35),
25302 ..Default::default()
25303 };
25304
25305 let mut prev_end = label.filter_range.end;
25306 label
25307 .runs
25308 .iter()
25309 .enumerate()
25310 .flat_map(move |(ix, (range, highlight_id))| {
25311 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25312 HighlightStyle {
25313 color: Some(local_player.cursor),
25314 ..Default::default()
25315 }
25316 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25317 HighlightStyle {
25318 background_color: Some(local_player.selection),
25319 ..Default::default()
25320 }
25321 } else if let Some(style) = highlight_id.style(syntax_theme) {
25322 style
25323 } else {
25324 return Default::default();
25325 };
25326 let muted_style = style.highlight(fade_out);
25327
25328 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25329 if range.start >= label.filter_range.end {
25330 if range.start > prev_end {
25331 runs.push((prev_end..range.start, fade_out));
25332 }
25333 runs.push((range.clone(), muted_style));
25334 } else if range.end <= label.filter_range.end {
25335 runs.push((range.clone(), style));
25336 } else {
25337 runs.push((range.start..label.filter_range.end, style));
25338 runs.push((label.filter_range.end..range.end, muted_style));
25339 }
25340 prev_end = cmp::max(prev_end, range.end);
25341
25342 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25343 runs.push((prev_end..label.text.len(), fade_out));
25344 }
25345
25346 runs
25347 })
25348}
25349
25350pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25351 let mut prev_index = 0;
25352 let mut prev_codepoint: Option<char> = None;
25353 text.char_indices()
25354 .chain([(text.len(), '\0')])
25355 .filter_map(move |(index, codepoint)| {
25356 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25357 let is_boundary = index == text.len()
25358 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25359 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25360 if is_boundary {
25361 let chunk = &text[prev_index..index];
25362 prev_index = index;
25363 Some(chunk)
25364 } else {
25365 None
25366 }
25367 })
25368}
25369
25370/// Given a string of text immediately before the cursor, iterates over possible
25371/// strings a snippet could match to. More precisely: returns an iterator over
25372/// suffixes of `text` created by splitting at word boundaries (before & after
25373/// every non-word character).
25374///
25375/// Shorter suffixes are returned first.
25376pub(crate) fn snippet_candidate_suffixes(
25377 text: &str,
25378 is_word_char: impl Fn(char) -> bool,
25379) -> impl std::iter::Iterator<Item = &str> {
25380 let mut prev_index = text.len();
25381 let mut prev_codepoint = None;
25382 text.char_indices()
25383 .rev()
25384 .chain([(0, '\0')])
25385 .filter_map(move |(index, codepoint)| {
25386 let prev_index = std::mem::replace(&mut prev_index, index);
25387 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25388 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25389 None
25390 } else {
25391 let chunk = &text[prev_index..]; // go to end of string
25392 Some(chunk)
25393 }
25394 })
25395}
25396
25397pub trait RangeToAnchorExt: Sized {
25398 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25399
25400 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25401 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25402 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25403 }
25404}
25405
25406impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25407 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25408 let start_offset = self.start.to_offset(snapshot);
25409 let end_offset = self.end.to_offset(snapshot);
25410 if start_offset == end_offset {
25411 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25412 } else {
25413 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25414 }
25415 }
25416}
25417
25418pub trait RowExt {
25419 fn as_f64(&self) -> f64;
25420
25421 fn next_row(&self) -> Self;
25422
25423 fn previous_row(&self) -> Self;
25424
25425 fn minus(&self, other: Self) -> u32;
25426}
25427
25428impl RowExt for DisplayRow {
25429 fn as_f64(&self) -> f64 {
25430 self.0 as _
25431 }
25432
25433 fn next_row(&self) -> Self {
25434 Self(self.0 + 1)
25435 }
25436
25437 fn previous_row(&self) -> Self {
25438 Self(self.0.saturating_sub(1))
25439 }
25440
25441 fn minus(&self, other: Self) -> u32 {
25442 self.0 - other.0
25443 }
25444}
25445
25446impl RowExt for MultiBufferRow {
25447 fn as_f64(&self) -> f64 {
25448 self.0 as _
25449 }
25450
25451 fn next_row(&self) -> Self {
25452 Self(self.0 + 1)
25453 }
25454
25455 fn previous_row(&self) -> Self {
25456 Self(self.0.saturating_sub(1))
25457 }
25458
25459 fn minus(&self, other: Self) -> u32 {
25460 self.0 - other.0
25461 }
25462}
25463
25464trait RowRangeExt {
25465 type Row;
25466
25467 fn len(&self) -> usize;
25468
25469 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25470}
25471
25472impl RowRangeExt for Range<MultiBufferRow> {
25473 type Row = MultiBufferRow;
25474
25475 fn len(&self) -> usize {
25476 (self.end.0 - self.start.0) as usize
25477 }
25478
25479 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25480 (self.start.0..self.end.0).map(MultiBufferRow)
25481 }
25482}
25483
25484impl RowRangeExt for Range<DisplayRow> {
25485 type Row = DisplayRow;
25486
25487 fn len(&self) -> usize {
25488 (self.end.0 - self.start.0) as usize
25489 }
25490
25491 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25492 (self.start.0..self.end.0).map(DisplayRow)
25493 }
25494}
25495
25496/// If select range has more than one line, we
25497/// just point the cursor to range.start.
25498fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25499 if range.start.row == range.end.row {
25500 range
25501 } else {
25502 range.start..range.start
25503 }
25504}
25505pub struct KillRing(ClipboardItem);
25506impl Global for KillRing {}
25507
25508const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25509
25510enum BreakpointPromptEditAction {
25511 Log,
25512 Condition,
25513 HitCondition,
25514}
25515
25516struct BreakpointPromptEditor {
25517 pub(crate) prompt: Entity<Editor>,
25518 editor: WeakEntity<Editor>,
25519 breakpoint_anchor: Anchor,
25520 breakpoint: Breakpoint,
25521 edit_action: BreakpointPromptEditAction,
25522 block_ids: HashSet<CustomBlockId>,
25523 editor_margins: Arc<Mutex<EditorMargins>>,
25524 _subscriptions: Vec<Subscription>,
25525}
25526
25527impl BreakpointPromptEditor {
25528 const MAX_LINES: u8 = 4;
25529
25530 fn new(
25531 editor: WeakEntity<Editor>,
25532 breakpoint_anchor: Anchor,
25533 breakpoint: Breakpoint,
25534 edit_action: BreakpointPromptEditAction,
25535 window: &mut Window,
25536 cx: &mut Context<Self>,
25537 ) -> Self {
25538 let base_text = match edit_action {
25539 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25540 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25541 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25542 }
25543 .map(|msg| msg.to_string())
25544 .unwrap_or_default();
25545
25546 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25547 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25548
25549 let prompt = cx.new(|cx| {
25550 let mut prompt = Editor::new(
25551 EditorMode::AutoHeight {
25552 min_lines: 1,
25553 max_lines: Some(Self::MAX_LINES as usize),
25554 },
25555 buffer,
25556 None,
25557 window,
25558 cx,
25559 );
25560 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25561 prompt.set_show_cursor_when_unfocused(false, cx);
25562 prompt.set_placeholder_text(
25563 match edit_action {
25564 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25565 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25566 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25567 },
25568 window,
25569 cx,
25570 );
25571
25572 prompt
25573 });
25574
25575 Self {
25576 prompt,
25577 editor,
25578 breakpoint_anchor,
25579 breakpoint,
25580 edit_action,
25581 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25582 block_ids: Default::default(),
25583 _subscriptions: vec![],
25584 }
25585 }
25586
25587 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25588 self.block_ids.extend(block_ids)
25589 }
25590
25591 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25592 if let Some(editor) = self.editor.upgrade() {
25593 let message = self
25594 .prompt
25595 .read(cx)
25596 .buffer
25597 .read(cx)
25598 .as_singleton()
25599 .expect("A multi buffer in breakpoint prompt isn't possible")
25600 .read(cx)
25601 .as_rope()
25602 .to_string();
25603
25604 editor.update(cx, |editor, cx| {
25605 editor.edit_breakpoint_at_anchor(
25606 self.breakpoint_anchor,
25607 self.breakpoint.clone(),
25608 match self.edit_action {
25609 BreakpointPromptEditAction::Log => {
25610 BreakpointEditAction::EditLogMessage(message.into())
25611 }
25612 BreakpointPromptEditAction::Condition => {
25613 BreakpointEditAction::EditCondition(message.into())
25614 }
25615 BreakpointPromptEditAction::HitCondition => {
25616 BreakpointEditAction::EditHitCondition(message.into())
25617 }
25618 },
25619 cx,
25620 );
25621
25622 editor.remove_blocks(self.block_ids.clone(), None, cx);
25623 cx.focus_self(window);
25624 });
25625 }
25626 }
25627
25628 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25629 self.editor
25630 .update(cx, |editor, cx| {
25631 editor.remove_blocks(self.block_ids.clone(), None, cx);
25632 window.focus(&editor.focus_handle);
25633 })
25634 .log_err();
25635 }
25636
25637 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25638 let settings = ThemeSettings::get_global(cx);
25639 let text_style = TextStyle {
25640 color: if self.prompt.read(cx).read_only(cx) {
25641 cx.theme().colors().text_disabled
25642 } else {
25643 cx.theme().colors().text
25644 },
25645 font_family: settings.buffer_font.family.clone(),
25646 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25647 font_size: settings.buffer_font_size(cx).into(),
25648 font_weight: settings.buffer_font.weight,
25649 line_height: relative(settings.buffer_line_height.value()),
25650 ..Default::default()
25651 };
25652 EditorElement::new(
25653 &self.prompt,
25654 EditorStyle {
25655 background: cx.theme().colors().editor_background,
25656 local_player: cx.theme().players().local(),
25657 text: text_style,
25658 ..Default::default()
25659 },
25660 )
25661 }
25662}
25663
25664impl Render for BreakpointPromptEditor {
25665 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25666 let editor_margins = *self.editor_margins.lock();
25667 let gutter_dimensions = editor_margins.gutter;
25668 h_flex()
25669 .key_context("Editor")
25670 .bg(cx.theme().colors().editor_background)
25671 .border_y_1()
25672 .border_color(cx.theme().status().info_border)
25673 .size_full()
25674 .py(window.line_height() / 2.5)
25675 .on_action(cx.listener(Self::confirm))
25676 .on_action(cx.listener(Self::cancel))
25677 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25678 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25679 }
25680}
25681
25682impl Focusable for BreakpointPromptEditor {
25683 fn focus_handle(&self, cx: &App) -> FocusHandle {
25684 self.prompt.focus_handle(cx)
25685 }
25686}
25687
25688fn all_edits_insertions_or_deletions(
25689 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25690 snapshot: &MultiBufferSnapshot,
25691) -> bool {
25692 let mut all_insertions = true;
25693 let mut all_deletions = true;
25694
25695 for (range, new_text) in edits.iter() {
25696 let range_is_empty = range.to_offset(snapshot).is_empty();
25697 let text_is_empty = new_text.is_empty();
25698
25699 if range_is_empty != text_is_empty {
25700 if range_is_empty {
25701 all_deletions = false;
25702 } else {
25703 all_insertions = false;
25704 }
25705 } else {
25706 return false;
25707 }
25708
25709 if !all_insertions && !all_deletions {
25710 return false;
25711 }
25712 }
25713 all_insertions || all_deletions
25714}
25715
25716struct MissingEditPredictionKeybindingTooltip;
25717
25718impl Render for MissingEditPredictionKeybindingTooltip {
25719 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25720 ui::tooltip_container(cx, |container, cx| {
25721 container
25722 .flex_shrink_0()
25723 .max_w_80()
25724 .min_h(rems_from_px(124.))
25725 .justify_between()
25726 .child(
25727 v_flex()
25728 .flex_1()
25729 .text_ui_sm(cx)
25730 .child(Label::new("Conflict with Accept Keybinding"))
25731 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25732 )
25733 .child(
25734 h_flex()
25735 .pb_1()
25736 .gap_1()
25737 .items_end()
25738 .w_full()
25739 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25740 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25741 }))
25742 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25743 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25744 })),
25745 )
25746 })
25747 }
25748}
25749
25750#[derive(Debug, Clone, Copy, PartialEq)]
25751pub struct LineHighlight {
25752 pub background: Background,
25753 pub border: Option<gpui::Hsla>,
25754 pub include_gutter: bool,
25755 pub type_id: Option<TypeId>,
25756}
25757
25758struct LineManipulationResult {
25759 pub new_text: String,
25760 pub line_count_before: usize,
25761 pub line_count_after: usize,
25762}
25763
25764fn render_diff_hunk_controls(
25765 row: u32,
25766 status: &DiffHunkStatus,
25767 hunk_range: Range<Anchor>,
25768 is_created_file: bool,
25769 line_height: Pixels,
25770 editor: &Entity<Editor>,
25771 _window: &mut Window,
25772 cx: &mut App,
25773) -> AnyElement {
25774 h_flex()
25775 .h(line_height)
25776 .mr_1()
25777 .gap_1()
25778 .px_0p5()
25779 .pb_1()
25780 .border_x_1()
25781 .border_b_1()
25782 .border_color(cx.theme().colors().border_variant)
25783 .rounded_b_lg()
25784 .bg(cx.theme().colors().editor_background)
25785 .gap_1()
25786 .block_mouse_except_scroll()
25787 .shadow_md()
25788 .child(if status.has_secondary_hunk() {
25789 Button::new(("stage", row as u64), "Stage")
25790 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25791 .tooltip({
25792 let focus_handle = editor.focus_handle(cx);
25793 move |_window, cx| {
25794 Tooltip::for_action_in(
25795 "Stage Hunk",
25796 &::git::ToggleStaged,
25797 &focus_handle,
25798 cx,
25799 )
25800 }
25801 })
25802 .on_click({
25803 let editor = editor.clone();
25804 move |_event, _window, cx| {
25805 editor.update(cx, |editor, cx| {
25806 editor.stage_or_unstage_diff_hunks(
25807 true,
25808 vec![hunk_range.start..hunk_range.start],
25809 cx,
25810 );
25811 });
25812 }
25813 })
25814 } else {
25815 Button::new(("unstage", row as u64), "Unstage")
25816 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25817 .tooltip({
25818 let focus_handle = editor.focus_handle(cx);
25819 move |_window, cx| {
25820 Tooltip::for_action_in(
25821 "Unstage Hunk",
25822 &::git::ToggleStaged,
25823 &focus_handle,
25824 cx,
25825 )
25826 }
25827 })
25828 .on_click({
25829 let editor = editor.clone();
25830 move |_event, _window, cx| {
25831 editor.update(cx, |editor, cx| {
25832 editor.stage_or_unstage_diff_hunks(
25833 false,
25834 vec![hunk_range.start..hunk_range.start],
25835 cx,
25836 );
25837 });
25838 }
25839 })
25840 })
25841 .child(
25842 Button::new(("restore", row as u64), "Restore")
25843 .tooltip({
25844 let focus_handle = editor.focus_handle(cx);
25845 move |_window, cx| {
25846 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25847 }
25848 })
25849 .on_click({
25850 let editor = editor.clone();
25851 move |_event, window, cx| {
25852 editor.update(cx, |editor, cx| {
25853 let snapshot = editor.snapshot(window, cx);
25854 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25855 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25856 });
25857 }
25858 })
25859 .disabled(is_created_file),
25860 )
25861 .when(
25862 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25863 |el| {
25864 el.child(
25865 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25866 .shape(IconButtonShape::Square)
25867 .icon_size(IconSize::Small)
25868 // .disabled(!has_multiple_hunks)
25869 .tooltip({
25870 let focus_handle = editor.focus_handle(cx);
25871 move |_window, cx| {
25872 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25873 }
25874 })
25875 .on_click({
25876 let editor = editor.clone();
25877 move |_event, window, cx| {
25878 editor.update(cx, |editor, cx| {
25879 let snapshot = editor.snapshot(window, cx);
25880 let position =
25881 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25882 editor.go_to_hunk_before_or_after_position(
25883 &snapshot,
25884 position,
25885 Direction::Next,
25886 window,
25887 cx,
25888 );
25889 editor.expand_selected_diff_hunks(cx);
25890 });
25891 }
25892 }),
25893 )
25894 .child(
25895 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25896 .shape(IconButtonShape::Square)
25897 .icon_size(IconSize::Small)
25898 // .disabled(!has_multiple_hunks)
25899 .tooltip({
25900 let focus_handle = editor.focus_handle(cx);
25901 move |_window, cx| {
25902 Tooltip::for_action_in(
25903 "Previous Hunk",
25904 &GoToPreviousHunk,
25905 &focus_handle,
25906 cx,
25907 )
25908 }
25909 })
25910 .on_click({
25911 let editor = editor.clone();
25912 move |_event, window, cx| {
25913 editor.update(cx, |editor, cx| {
25914 let snapshot = editor.snapshot(window, cx);
25915 let point =
25916 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25917 editor.go_to_hunk_before_or_after_position(
25918 &snapshot,
25919 point,
25920 Direction::Prev,
25921 window,
25922 cx,
25923 );
25924 editor.expand_selected_diff_hunks(cx);
25925 });
25926 }
25927 }),
25928 )
25929 },
25930 )
25931 .into_any_element()
25932}
25933
25934pub fn multibuffer_context_lines(cx: &App) -> u32 {
25935 EditorSettings::try_get(cx)
25936 .map(|settings| settings.excerpt_context_lines)
25937 .unwrap_or(2)
25938 .min(32)
25939}