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, TextRun, 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: &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: &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 offset_content: bool,
1253 show_line_numbers: Option<bool>,
1254 show_git_diff_gutter: Option<bool>,
1255 show_code_actions: Option<bool>,
1256 show_runnables: Option<bool>,
1257 show_breakpoints: Option<bool>,
1258 git_blame_gutter_max_author_length: Option<usize>,
1259 pub display_snapshot: DisplaySnapshot,
1260 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1261 is_focused: bool,
1262 scroll_anchor: ScrollAnchor,
1263 ongoing_scroll: OngoingScroll,
1264 current_line_highlight: CurrentLineHighlight,
1265 gutter_hovered: bool,
1266}
1267
1268#[derive(Default, Debug, Clone, Copy)]
1269pub struct GutterDimensions {
1270 pub left_padding: Pixels,
1271 pub right_padding: Pixels,
1272 pub width: Pixels,
1273 pub margin: Pixels,
1274 pub git_blame_entries_width: Option<Pixels>,
1275}
1276
1277impl GutterDimensions {
1278 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1279 Self {
1280 margin: Self::default_gutter_margin(font_id, font_size, cx),
1281 ..Default::default()
1282 }
1283 }
1284
1285 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1286 -cx.text_system().descent(font_id, font_size)
1287 }
1288 /// The full width of the space taken up by the gutter.
1289 pub fn full_width(&self) -> Pixels {
1290 self.margin + self.width
1291 }
1292
1293 /// The width of the space reserved for the fold indicators,
1294 /// use alongside 'justify_end' and `gutter_width` to
1295 /// right align content with the line numbers
1296 pub fn fold_area_width(&self) -> Pixels {
1297 self.margin + self.right_padding
1298 }
1299}
1300
1301struct CharacterDimensions {
1302 em_width: Pixels,
1303 em_advance: Pixels,
1304 line_height: Pixels,
1305}
1306
1307#[derive(Debug)]
1308pub struct RemoteSelection {
1309 pub replica_id: ReplicaId,
1310 pub selection: Selection<Anchor>,
1311 pub cursor_shape: CursorShape,
1312 pub collaborator_id: CollaboratorId,
1313 pub line_mode: bool,
1314 pub user_name: Option<SharedString>,
1315 pub color: PlayerColor,
1316}
1317
1318#[derive(Clone, Debug)]
1319struct SelectionHistoryEntry {
1320 selections: Arc<[Selection<Anchor>]>,
1321 select_next_state: Option<SelectNextState>,
1322 select_prev_state: Option<SelectNextState>,
1323 add_selections_state: Option<AddSelectionsState>,
1324}
1325
1326#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1327enum SelectionHistoryMode {
1328 #[default]
1329 Normal,
1330 Undoing,
1331 Redoing,
1332 Skipping,
1333}
1334
1335#[derive(Clone, PartialEq, Eq, Hash)]
1336struct HoveredCursor {
1337 replica_id: ReplicaId,
1338 selection_id: usize,
1339}
1340
1341#[derive(Debug)]
1342/// SelectionEffects controls the side-effects of updating the selection.
1343///
1344/// The default behaviour does "what you mostly want":
1345/// - it pushes to the nav history if the cursor moved by >10 lines
1346/// - it re-triggers completion requests
1347/// - it scrolls to fit
1348///
1349/// You might want to modify these behaviours. For example when doing a "jump"
1350/// like go to definition, we always want to add to nav history; but when scrolling
1351/// in vim mode we never do.
1352///
1353/// Similarly, you might want to disable scrolling if you don't want the viewport to
1354/// move.
1355#[derive(Clone)]
1356pub struct SelectionEffects {
1357 nav_history: Option<bool>,
1358 completions: bool,
1359 scroll: Option<Autoscroll>,
1360}
1361
1362impl Default for SelectionEffects {
1363 fn default() -> Self {
1364 Self {
1365 nav_history: None,
1366 completions: true,
1367 scroll: Some(Autoscroll::fit()),
1368 }
1369 }
1370}
1371impl SelectionEffects {
1372 pub fn scroll(scroll: Autoscroll) -> Self {
1373 Self {
1374 scroll: Some(scroll),
1375 ..Default::default()
1376 }
1377 }
1378
1379 pub fn no_scroll() -> Self {
1380 Self {
1381 scroll: None,
1382 ..Default::default()
1383 }
1384 }
1385
1386 pub fn completions(self, completions: bool) -> Self {
1387 Self {
1388 completions,
1389 ..self
1390 }
1391 }
1392
1393 pub fn nav_history(self, nav_history: bool) -> Self {
1394 Self {
1395 nav_history: Some(nav_history),
1396 ..self
1397 }
1398 }
1399}
1400
1401struct DeferredSelectionEffectsState {
1402 changed: bool,
1403 effects: SelectionEffects,
1404 old_cursor_position: Anchor,
1405 history_entry: SelectionHistoryEntry,
1406}
1407
1408#[derive(Default)]
1409struct SelectionHistory {
1410 #[allow(clippy::type_complexity)]
1411 selections_by_transaction:
1412 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1413 mode: SelectionHistoryMode,
1414 undo_stack: VecDeque<SelectionHistoryEntry>,
1415 redo_stack: VecDeque<SelectionHistoryEntry>,
1416}
1417
1418impl SelectionHistory {
1419 #[track_caller]
1420 fn insert_transaction(
1421 &mut self,
1422 transaction_id: TransactionId,
1423 selections: Arc<[Selection<Anchor>]>,
1424 ) {
1425 if selections.is_empty() {
1426 log::error!(
1427 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1428 std::panic::Location::caller()
1429 );
1430 return;
1431 }
1432 self.selections_by_transaction
1433 .insert(transaction_id, (selections, None));
1434 }
1435
1436 #[allow(clippy::type_complexity)]
1437 fn transaction(
1438 &self,
1439 transaction_id: TransactionId,
1440 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1441 self.selections_by_transaction.get(&transaction_id)
1442 }
1443
1444 #[allow(clippy::type_complexity)]
1445 fn transaction_mut(
1446 &mut self,
1447 transaction_id: TransactionId,
1448 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1449 self.selections_by_transaction.get_mut(&transaction_id)
1450 }
1451
1452 fn push(&mut self, entry: SelectionHistoryEntry) {
1453 if !entry.selections.is_empty() {
1454 match self.mode {
1455 SelectionHistoryMode::Normal => {
1456 self.push_undo(entry);
1457 self.redo_stack.clear();
1458 }
1459 SelectionHistoryMode::Undoing => self.push_redo(entry),
1460 SelectionHistoryMode::Redoing => self.push_undo(entry),
1461 SelectionHistoryMode::Skipping => {}
1462 }
1463 }
1464 }
1465
1466 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1467 if self
1468 .undo_stack
1469 .back()
1470 .is_none_or(|e| e.selections != entry.selections)
1471 {
1472 self.undo_stack.push_back(entry);
1473 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1474 self.undo_stack.pop_front();
1475 }
1476 }
1477 }
1478
1479 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1480 if self
1481 .redo_stack
1482 .back()
1483 .is_none_or(|e| e.selections != entry.selections)
1484 {
1485 self.redo_stack.push_back(entry);
1486 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1487 self.redo_stack.pop_front();
1488 }
1489 }
1490 }
1491}
1492
1493#[derive(Clone, Copy)]
1494pub struct RowHighlightOptions {
1495 pub autoscroll: bool,
1496 pub include_gutter: bool,
1497}
1498
1499impl Default for RowHighlightOptions {
1500 fn default() -> Self {
1501 Self {
1502 autoscroll: Default::default(),
1503 include_gutter: true,
1504 }
1505 }
1506}
1507
1508struct RowHighlight {
1509 index: usize,
1510 range: Range<Anchor>,
1511 color: Hsla,
1512 options: RowHighlightOptions,
1513 type_id: TypeId,
1514}
1515
1516#[derive(Clone, Debug)]
1517struct AddSelectionsState {
1518 groups: Vec<AddSelectionsGroup>,
1519}
1520
1521#[derive(Clone, Debug)]
1522struct AddSelectionsGroup {
1523 above: bool,
1524 stack: Vec<usize>,
1525}
1526
1527#[derive(Clone)]
1528struct SelectNextState {
1529 query: AhoCorasick,
1530 wordwise: bool,
1531 done: bool,
1532}
1533
1534impl std::fmt::Debug for SelectNextState {
1535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1536 f.debug_struct(std::any::type_name::<Self>())
1537 .field("wordwise", &self.wordwise)
1538 .field("done", &self.done)
1539 .finish()
1540 }
1541}
1542
1543#[derive(Debug)]
1544struct AutocloseRegion {
1545 selection_id: usize,
1546 range: Range<Anchor>,
1547 pair: BracketPair,
1548}
1549
1550#[derive(Debug)]
1551struct SnippetState {
1552 ranges: Vec<Vec<Range<Anchor>>>,
1553 active_index: usize,
1554 choices: Vec<Option<Vec<String>>>,
1555}
1556
1557#[doc(hidden)]
1558pub struct RenameState {
1559 pub range: Range<Anchor>,
1560 pub old_name: Arc<str>,
1561 pub editor: Entity<Editor>,
1562 block_id: CustomBlockId,
1563}
1564
1565struct InvalidationStack<T>(Vec<T>);
1566
1567struct RegisteredEditPredictionDelegate {
1568 provider: Arc<dyn EditPredictionDelegateHandle>,
1569 _subscription: Subscription,
1570}
1571
1572#[derive(Debug, PartialEq, Eq)]
1573pub struct ActiveDiagnosticGroup {
1574 pub active_range: Range<Anchor>,
1575 pub active_message: String,
1576 pub group_id: usize,
1577 pub blocks: HashSet<CustomBlockId>,
1578}
1579
1580#[derive(Debug, PartialEq, Eq)]
1581
1582pub(crate) enum ActiveDiagnostic {
1583 None,
1584 All,
1585 Group(ActiveDiagnosticGroup),
1586}
1587
1588#[derive(Serialize, Deserialize, Clone, Debug)]
1589pub struct ClipboardSelection {
1590 /// The number of bytes in this selection.
1591 pub len: usize,
1592 /// Whether this was a full-line selection.
1593 pub is_entire_line: bool,
1594 /// The indentation of the first line when this content was originally copied.
1595 pub first_line_indent: u32,
1596 #[serde(default)]
1597 pub file_path: Option<PathBuf>,
1598 #[serde(default)]
1599 pub line_range: Option<RangeInclusive<u32>>,
1600}
1601
1602impl ClipboardSelection {
1603 pub fn for_buffer(
1604 len: usize,
1605 is_entire_line: bool,
1606 range: Range<Point>,
1607 buffer: &MultiBufferSnapshot,
1608 project: Option<&Entity<Project>>,
1609 cx: &App,
1610 ) -> Self {
1611 let first_line_indent = buffer
1612 .indent_size_for_line(MultiBufferRow(range.start.row))
1613 .len;
1614
1615 let file_path = util::maybe!({
1616 let project = project?.read(cx);
1617 let file = buffer.file_at(range.start)?;
1618 let project_path = ProjectPath {
1619 worktree_id: file.worktree_id(cx),
1620 path: file.path().clone(),
1621 };
1622 project.absolute_path(&project_path, cx)
1623 });
1624
1625 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1626
1627 Self {
1628 len,
1629 is_entire_line,
1630 first_line_indent,
1631 file_path,
1632 line_range,
1633 }
1634 }
1635}
1636
1637// selections, scroll behavior, was newest selection reversed
1638type SelectSyntaxNodeHistoryState = (
1639 Box<[Selection<MultiBufferOffset>]>,
1640 SelectSyntaxNodeScrollBehavior,
1641 bool,
1642);
1643
1644#[derive(Default)]
1645struct SelectSyntaxNodeHistory {
1646 stack: Vec<SelectSyntaxNodeHistoryState>,
1647 // disable temporarily to allow changing selections without losing the stack
1648 pub disable_clearing: bool,
1649}
1650
1651impl SelectSyntaxNodeHistory {
1652 pub fn try_clear(&mut self) {
1653 if !self.disable_clearing {
1654 self.stack.clear();
1655 }
1656 }
1657
1658 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1659 self.stack.push(selection);
1660 }
1661
1662 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1663 self.stack.pop()
1664 }
1665}
1666
1667enum SelectSyntaxNodeScrollBehavior {
1668 CursorTop,
1669 FitSelection,
1670 CursorBottom,
1671}
1672
1673#[derive(Debug)]
1674pub(crate) struct NavigationData {
1675 cursor_anchor: Anchor,
1676 cursor_position: Point,
1677 scroll_anchor: ScrollAnchor,
1678 scroll_top_row: u32,
1679}
1680
1681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1682pub enum GotoDefinitionKind {
1683 Symbol,
1684 Declaration,
1685 Type,
1686 Implementation,
1687}
1688
1689pub enum FormatTarget {
1690 Buffers(HashSet<Entity<Buffer>>),
1691 Ranges(Vec<Range<MultiBufferPoint>>),
1692}
1693
1694pub(crate) struct FocusedBlock {
1695 id: BlockId,
1696 focus_handle: WeakFocusHandle,
1697}
1698
1699#[derive(Clone, Debug)]
1700enum JumpData {
1701 MultiBufferRow {
1702 row: MultiBufferRow,
1703 line_offset_from_top: u32,
1704 },
1705 MultiBufferPoint {
1706 excerpt_id: ExcerptId,
1707 position: Point,
1708 anchor: text::Anchor,
1709 line_offset_from_top: u32,
1710 },
1711}
1712
1713pub enum MultibufferSelectionMode {
1714 First,
1715 All,
1716}
1717
1718#[derive(Clone, Copy, Debug, Default)]
1719pub struct RewrapOptions {
1720 pub override_language_settings: bool,
1721 pub preserve_existing_whitespace: bool,
1722}
1723
1724impl Editor {
1725 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1726 let buffer = cx.new(|cx| Buffer::local("", cx));
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1729 }
1730
1731 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1732 let buffer = cx.new(|cx| Buffer::local("", cx));
1733 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1734 Self::new(EditorMode::full(), buffer, None, window, cx)
1735 }
1736
1737 pub fn auto_height(
1738 min_lines: usize,
1739 max_lines: usize,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 let buffer = cx.new(|cx| Buffer::local("", cx));
1744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(
1746 EditorMode::AutoHeight {
1747 min_lines,
1748 max_lines: Some(max_lines),
1749 },
1750 buffer,
1751 None,
1752 window,
1753 cx,
1754 )
1755 }
1756
1757 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1758 /// The editor grows as tall as needed to fit its content.
1759 pub fn auto_height_unbounded(
1760 min_lines: usize,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 let buffer = cx.new(|cx| Buffer::local("", cx));
1765 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1766 Self::new(
1767 EditorMode::AutoHeight {
1768 min_lines,
1769 max_lines: None,
1770 },
1771 buffer,
1772 None,
1773 window,
1774 cx,
1775 )
1776 }
1777
1778 pub fn for_buffer(
1779 buffer: Entity<Buffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1785 Self::new(EditorMode::full(), buffer, project, window, cx)
1786 }
1787
1788 pub fn for_multibuffer(
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 window: &mut Window,
1792 cx: &mut Context<Self>,
1793 ) -> Self {
1794 Self::new(EditorMode::full(), buffer, project, window, cx)
1795 }
1796
1797 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1798 let mut clone = Self::new(
1799 self.mode.clone(),
1800 self.buffer.clone(),
1801 self.project.clone(),
1802 window,
1803 cx,
1804 );
1805 self.display_map.update(cx, |display_map, cx| {
1806 let snapshot = display_map.snapshot(cx);
1807 clone.display_map.update(cx, |display_map, cx| {
1808 display_map.set_state(&snapshot, cx);
1809 });
1810 });
1811 clone.folds_did_change(cx);
1812 clone.selections.clone_state(&self.selections);
1813 clone.scroll_manager.clone_state(&self.scroll_manager);
1814 clone.searchable = self.searchable;
1815 clone.read_only = self.read_only;
1816 clone
1817 }
1818
1819 pub fn new(
1820 mode: EditorMode,
1821 buffer: Entity<MultiBuffer>,
1822 project: Option<Entity<Project>>,
1823 window: &mut Window,
1824 cx: &mut Context<Self>,
1825 ) -> Self {
1826 Editor::new_internal(mode, buffer, project, None, window, cx)
1827 }
1828
1829 pub fn sticky_headers(
1830 &self,
1831 style: &EditorStyle,
1832 cx: &App,
1833 ) -> Option<Vec<OutlineItem<Anchor>>> {
1834 let multi_buffer = self.buffer().read(cx);
1835 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1836 let multi_buffer_visible_start = self
1837 .scroll_manager
1838 .anchor()
1839 .anchor
1840 .to_point(&multi_buffer_snapshot);
1841 let max_row = multi_buffer_snapshot.max_point().row;
1842
1843 let start_row = (multi_buffer_visible_start.row).min(max_row);
1844 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1845
1846 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1847 let outline_items = buffer
1848 .outline_items_containing(
1849 Point::new(start_row, 0)..Point::new(end_row, 0),
1850 true,
1851 Some(style.syntax.as_ref()),
1852 )
1853 .into_iter()
1854 .map(|outline_item| OutlineItem {
1855 depth: outline_item.depth,
1856 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1857 source_range_for_text: Anchor::range_in_buffer(
1858 *excerpt_id,
1859 outline_item.source_range_for_text,
1860 ),
1861 text: outline_item.text,
1862 highlight_ranges: outline_item.highlight_ranges,
1863 name_ranges: outline_item.name_ranges,
1864 body_range: outline_item
1865 .body_range
1866 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1867 annotation_range: outline_item
1868 .annotation_range
1869 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1870 });
1871 return Some(outline_items.collect());
1872 }
1873
1874 None
1875 }
1876
1877 fn new_internal(
1878 mode: EditorMode,
1879 multi_buffer: Entity<MultiBuffer>,
1880 project: Option<Entity<Project>>,
1881 display_map: Option<Entity<DisplayMap>>,
1882 window: &mut Window,
1883 cx: &mut Context<Self>,
1884 ) -> Self {
1885 debug_assert!(
1886 display_map.is_none() || mode.is_minimap(),
1887 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1888 );
1889
1890 let full_mode = mode.is_full();
1891 let is_minimap = mode.is_minimap();
1892 let diagnostics_max_severity = if full_mode {
1893 EditorSettings::get_global(cx)
1894 .diagnostics_max_severity
1895 .unwrap_or(DiagnosticSeverity::Hint)
1896 } else {
1897 DiagnosticSeverity::Off
1898 };
1899 let style = window.text_style();
1900 let font_size = style.font_size.to_pixels(window.rem_size());
1901 let editor = cx.entity().downgrade();
1902 let fold_placeholder = FoldPlaceholder {
1903 constrain_width: false,
1904 render: Arc::new(move |fold_id, fold_range, cx| {
1905 let editor = editor.clone();
1906 div()
1907 .id(fold_id)
1908 .bg(cx.theme().colors().ghost_element_background)
1909 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1910 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1911 .rounded_xs()
1912 .size_full()
1913 .cursor_pointer()
1914 .child("⋯")
1915 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1916 .on_click(move |_, _window, cx| {
1917 editor
1918 .update(cx, |editor, cx| {
1919 editor.unfold_ranges(
1920 &[fold_range.start..fold_range.end],
1921 true,
1922 false,
1923 cx,
1924 );
1925 cx.stop_propagation();
1926 })
1927 .ok();
1928 })
1929 .into_any()
1930 }),
1931 merge_adjacent: true,
1932 ..FoldPlaceholder::default()
1933 };
1934 let display_map = display_map.unwrap_or_else(|| {
1935 cx.new(|cx| {
1936 DisplayMap::new(
1937 multi_buffer.clone(),
1938 style.font(),
1939 font_size,
1940 None,
1941 FILE_HEADER_HEIGHT,
1942 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1943 fold_placeholder,
1944 diagnostics_max_severity,
1945 cx,
1946 )
1947 })
1948 });
1949
1950 let selections = SelectionsCollection::new();
1951
1952 let blink_manager = cx.new(|cx| {
1953 let mut blink_manager = BlinkManager::new(
1954 CURSOR_BLINK_INTERVAL,
1955 |cx| EditorSettings::get_global(cx).cursor_blink,
1956 cx,
1957 );
1958 if is_minimap {
1959 blink_manager.disable(cx);
1960 }
1961 blink_manager
1962 });
1963
1964 let soft_wrap_mode_override =
1965 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1966
1967 let mut project_subscriptions = Vec::new();
1968 if full_mode && let Some(project) = project.as_ref() {
1969 project_subscriptions.push(cx.subscribe_in(
1970 project,
1971 window,
1972 |editor, _, event, window, cx| match event {
1973 project::Event::RefreshCodeLens => {
1974 // we always query lens with actions, without storing them, always refreshing them
1975 }
1976 project::Event::RefreshInlayHints {
1977 server_id,
1978 request_id,
1979 } => {
1980 editor.refresh_inlay_hints(
1981 InlayHintRefreshReason::RefreshRequested {
1982 server_id: *server_id,
1983 request_id: *request_id,
1984 },
1985 cx,
1986 );
1987 }
1988 project::Event::LanguageServerRemoved(..) => {
1989 if editor.tasks_update_task.is_none() {
1990 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1991 }
1992 editor.registered_buffers.clear();
1993 editor.register_visible_buffers(cx);
1994 }
1995 project::Event::LanguageServerAdded(..) => {
1996 if editor.tasks_update_task.is_none() {
1997 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1998 }
1999 }
2000 project::Event::SnippetEdit(id, snippet_edits) => {
2001 // todo(lw): Non singletons
2002 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2003 let snapshot = buffer.read(cx).snapshot();
2004 let focus_handle = editor.focus_handle(cx);
2005 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2006 for (range, snippet) in snippet_edits {
2007 let buffer_range =
2008 language::range_from_lsp(*range).to_offset(&snapshot);
2009 editor
2010 .insert_snippet(
2011 &[MultiBufferOffset(buffer_range.start)
2012 ..MultiBufferOffset(buffer_range.end)],
2013 snippet.clone(),
2014 window,
2015 cx,
2016 )
2017 .ok();
2018 }
2019 }
2020 }
2021 }
2022 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2023 let buffer_id = *buffer_id;
2024 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2025 editor.register_buffer(buffer_id, cx);
2026 editor.update_lsp_data(Some(buffer_id), window, cx);
2027 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2028 refresh_linked_ranges(editor, window, cx);
2029 editor.refresh_code_actions(window, cx);
2030 editor.refresh_document_highlights(cx);
2031 }
2032 }
2033
2034 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2035 let Some(workspace) = editor.workspace() else {
2036 return;
2037 };
2038 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2039 else {
2040 return;
2041 };
2042
2043 if active_editor.entity_id() == cx.entity_id() {
2044 let entity_id = cx.entity_id();
2045 workspace.update(cx, |this, cx| {
2046 this.panes_mut()
2047 .iter_mut()
2048 .filter(|pane| pane.entity_id() != entity_id)
2049 .for_each(|p| {
2050 p.update(cx, |pane, _| {
2051 pane.nav_history_mut().rename_item(
2052 entity_id,
2053 project_path.clone(),
2054 abs_path.clone().into(),
2055 );
2056 })
2057 });
2058 });
2059 let edited_buffers_already_open = {
2060 let other_editors: Vec<Entity<Editor>> = workspace
2061 .read(cx)
2062 .panes()
2063 .iter()
2064 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2065 .filter(|editor| editor.entity_id() != cx.entity_id())
2066 .collect();
2067
2068 transaction.0.keys().all(|buffer| {
2069 other_editors.iter().any(|editor| {
2070 let multi_buffer = editor.read(cx).buffer();
2071 multi_buffer.read(cx).is_singleton()
2072 && multi_buffer.read(cx).as_singleton().map_or(
2073 false,
2074 |singleton| {
2075 singleton.entity_id() == buffer.entity_id()
2076 },
2077 )
2078 })
2079 })
2080 };
2081 if !edited_buffers_already_open {
2082 let workspace = workspace.downgrade();
2083 let transaction = transaction.clone();
2084 cx.defer_in(window, move |_, window, cx| {
2085 cx.spawn_in(window, async move |editor, cx| {
2086 Self::open_project_transaction(
2087 &editor,
2088 workspace,
2089 transaction,
2090 "Rename".to_string(),
2091 cx,
2092 )
2093 .await
2094 .ok()
2095 })
2096 .detach();
2097 });
2098 }
2099 }
2100 }
2101
2102 _ => {}
2103 },
2104 ));
2105 if let Some(task_inventory) = project
2106 .read(cx)
2107 .task_store()
2108 .read(cx)
2109 .task_inventory()
2110 .cloned()
2111 {
2112 project_subscriptions.push(cx.observe_in(
2113 &task_inventory,
2114 window,
2115 |editor, _, window, cx| {
2116 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2117 },
2118 ));
2119 };
2120
2121 project_subscriptions.push(cx.subscribe_in(
2122 &project.read(cx).breakpoint_store(),
2123 window,
2124 |editor, _, event, window, cx| match event {
2125 BreakpointStoreEvent::ClearDebugLines => {
2126 editor.clear_row_highlights::<ActiveDebugLine>();
2127 editor.refresh_inline_values(cx);
2128 }
2129 BreakpointStoreEvent::SetDebugLine => {
2130 if editor.go_to_active_debug_line(window, cx) {
2131 cx.stop_propagation();
2132 }
2133
2134 editor.refresh_inline_values(cx);
2135 }
2136 _ => {}
2137 },
2138 ));
2139 let git_store = project.read(cx).git_store().clone();
2140 let project = project.clone();
2141 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2142 if let GitStoreEvent::RepositoryAdded = event {
2143 this.load_diff_task = Some(
2144 update_uncommitted_diff_for_buffer(
2145 cx.entity(),
2146 &project,
2147 this.buffer.read(cx).all_buffers(),
2148 this.buffer.clone(),
2149 cx,
2150 )
2151 .shared(),
2152 );
2153 }
2154 }));
2155 }
2156
2157 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2158
2159 let inlay_hint_settings =
2160 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2161 let focus_handle = cx.focus_handle();
2162 if !is_minimap {
2163 cx.on_focus(&focus_handle, window, Self::handle_focus)
2164 .detach();
2165 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2166 .detach();
2167 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2168 .detach();
2169 cx.on_blur(&focus_handle, window, Self::handle_blur)
2170 .detach();
2171 cx.observe_pending_input(window, Self::observe_pending_input)
2172 .detach();
2173 }
2174
2175 let show_indent_guides =
2176 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2177 Some(false)
2178 } else {
2179 None
2180 };
2181
2182 let breakpoint_store = match (&mode, project.as_ref()) {
2183 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2184 _ => None,
2185 };
2186
2187 let mut code_action_providers = Vec::new();
2188 let mut load_uncommitted_diff = None;
2189 if let Some(project) = project.clone() {
2190 load_uncommitted_diff = Some(
2191 update_uncommitted_diff_for_buffer(
2192 cx.entity(),
2193 &project,
2194 multi_buffer.read(cx).all_buffers(),
2195 multi_buffer.clone(),
2196 cx,
2197 )
2198 .shared(),
2199 );
2200 code_action_providers.push(Rc::new(project) as Rc<_>);
2201 }
2202
2203 let mut editor = Self {
2204 focus_handle,
2205 show_cursor_when_unfocused: false,
2206 last_focused_descendant: None,
2207 buffer: multi_buffer.clone(),
2208 display_map: display_map.clone(),
2209 placeholder_display_map: None,
2210 selections,
2211 scroll_manager: ScrollManager::new(cx),
2212 columnar_selection_state: None,
2213 add_selections_state: None,
2214 select_next_state: None,
2215 select_prev_state: None,
2216 selection_history: SelectionHistory::default(),
2217 defer_selection_effects: false,
2218 deferred_selection_effects_state: None,
2219 autoclose_regions: Vec::new(),
2220 snippet_stack: InvalidationStack::default(),
2221 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2222 ime_transaction: None,
2223 active_diagnostics: ActiveDiagnostic::None,
2224 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2225 inline_diagnostics_update: Task::ready(()),
2226 inline_diagnostics: Vec::new(),
2227 soft_wrap_mode_override,
2228 diagnostics_max_severity,
2229 hard_wrap: None,
2230 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2231 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2232 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2233 project,
2234 blink_manager: blink_manager.clone(),
2235 show_local_selections: true,
2236 show_scrollbars: ScrollbarAxes {
2237 horizontal: full_mode,
2238 vertical: full_mode,
2239 },
2240 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2241 offset_content: !matches!(mode, EditorMode::SingleLine),
2242 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2243 show_gutter: full_mode,
2244 show_line_numbers: (!full_mode).then_some(false),
2245 use_relative_line_numbers: None,
2246 disable_expand_excerpt_buttons: !full_mode,
2247 show_git_diff_gutter: None,
2248 show_code_actions: None,
2249 show_runnables: None,
2250 show_breakpoints: None,
2251 show_wrap_guides: None,
2252 show_indent_guides,
2253 buffers_with_disabled_indent_guides: HashSet::default(),
2254 highlight_order: 0,
2255 highlighted_rows: HashMap::default(),
2256 background_highlights: HashMap::default(),
2257 gutter_highlights: HashMap::default(),
2258 scrollbar_marker_state: ScrollbarMarkerState::default(),
2259 active_indent_guides_state: ActiveIndentGuidesState::default(),
2260 nav_history: None,
2261 context_menu: RefCell::new(None),
2262 context_menu_options: None,
2263 mouse_context_menu: None,
2264 completion_tasks: Vec::new(),
2265 inline_blame_popover: None,
2266 inline_blame_popover_show_task: None,
2267 signature_help_state: SignatureHelpState::default(),
2268 auto_signature_help: None,
2269 find_all_references_task_sources: Vec::new(),
2270 next_completion_id: 0,
2271 next_inlay_id: 0,
2272 code_action_providers,
2273 available_code_actions: None,
2274 code_actions_task: None,
2275 quick_selection_highlight_task: None,
2276 debounced_selection_highlight_task: None,
2277 document_highlights_task: None,
2278 linked_editing_range_task: None,
2279 pending_rename: None,
2280 searchable: !is_minimap,
2281 cursor_shape: EditorSettings::get_global(cx)
2282 .cursor_shape
2283 .unwrap_or_default(),
2284 current_line_highlight: None,
2285 autoindent_mode: Some(AutoindentMode::EachLine),
2286 collapse_matches: false,
2287 workspace: None,
2288 input_enabled: !is_minimap,
2289 use_modal_editing: full_mode,
2290 read_only: is_minimap,
2291 use_autoclose: true,
2292 use_auto_surround: true,
2293 auto_replace_emoji_shortcode: false,
2294 jsx_tag_auto_close_enabled_in_any_buffer: false,
2295 leader_id: None,
2296 remote_id: None,
2297 hover_state: HoverState::default(),
2298 pending_mouse_down: None,
2299 hovered_link_state: None,
2300 edit_prediction_provider: None,
2301 active_edit_prediction: None,
2302 stale_edit_prediction_in_menu: None,
2303 edit_prediction_preview: EditPredictionPreview::Inactive {
2304 released_too_fast: false,
2305 },
2306 inline_diagnostics_enabled: full_mode,
2307 diagnostics_enabled: full_mode,
2308 word_completions_enabled: full_mode,
2309 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2310 gutter_hovered: false,
2311 pixel_position_of_newest_cursor: None,
2312 last_bounds: None,
2313 last_position_map: None,
2314 expect_bounds_change: None,
2315 gutter_dimensions: GutterDimensions::default(),
2316 style: None,
2317 show_cursor_names: false,
2318 hovered_cursors: HashMap::default(),
2319 next_editor_action_id: EditorActionId::default(),
2320 editor_actions: Rc::default(),
2321 edit_predictions_hidden_for_vim_mode: false,
2322 show_edit_predictions_override: None,
2323 show_completions_on_input_override: None,
2324 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2325 edit_prediction_settings: EditPredictionSettings::Disabled,
2326 edit_prediction_indent_conflict: false,
2327 edit_prediction_requires_modifier_in_indent_conflict: true,
2328 custom_context_menu: None,
2329 show_git_blame_gutter: false,
2330 show_git_blame_inline: false,
2331 show_selection_menu: None,
2332 show_git_blame_inline_delay_task: None,
2333 git_blame_inline_enabled: full_mode
2334 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2335 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2336 buffer_serialization: is_minimap.not().then(|| {
2337 BufferSerialization::new(
2338 ProjectSettings::get_global(cx)
2339 .session
2340 .restore_unsaved_buffers,
2341 )
2342 }),
2343 blame: None,
2344 blame_subscription: None,
2345 tasks: BTreeMap::default(),
2346
2347 breakpoint_store,
2348 gutter_breakpoint_indicator: (None, None),
2349 hovered_diff_hunk_row: None,
2350 _subscriptions: (!is_minimap)
2351 .then(|| {
2352 vec![
2353 cx.observe(&multi_buffer, Self::on_buffer_changed),
2354 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2355 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2356 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2357 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2358 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2359 cx.observe_window_activation(window, |editor, window, cx| {
2360 let active = window.is_window_active();
2361 editor.blink_manager.update(cx, |blink_manager, cx| {
2362 if active {
2363 blink_manager.enable(cx);
2364 } else {
2365 blink_manager.disable(cx);
2366 }
2367 });
2368 if active {
2369 editor.show_mouse_cursor(cx);
2370 }
2371 }),
2372 ]
2373 })
2374 .unwrap_or_default(),
2375 tasks_update_task: None,
2376 pull_diagnostics_task: Task::ready(()),
2377 pull_diagnostics_background_task: Task::ready(()),
2378 colors: None,
2379 refresh_colors_task: Task::ready(()),
2380 inlay_hints: None,
2381 next_color_inlay_id: 0,
2382 post_scroll_update: Task::ready(()),
2383 linked_edit_ranges: Default::default(),
2384 in_project_search: false,
2385 previous_search_ranges: None,
2386 breadcrumb_header: None,
2387 focused_block: None,
2388 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2389 addons: HashMap::default(),
2390 registered_buffers: HashMap::default(),
2391 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2392 selection_mark_mode: false,
2393 toggle_fold_multiple_buffers: Task::ready(()),
2394 serialize_selections: Task::ready(()),
2395 serialize_folds: Task::ready(()),
2396 text_style_refinement: None,
2397 load_diff_task: load_uncommitted_diff,
2398 temporary_diff_override: false,
2399 mouse_cursor_hidden: false,
2400 minimap: None,
2401 hide_mouse_mode: EditorSettings::get_global(cx)
2402 .hide_mouse
2403 .unwrap_or_default(),
2404 change_list: ChangeList::new(),
2405 mode,
2406 selection_drag_state: SelectionDragState::None,
2407 folding_newlines: Task::ready(()),
2408 lookup_key: None,
2409 select_next_is_case_sensitive: None,
2410 applicable_language_settings: HashMap::default(),
2411 accent_data: None,
2412 fetched_tree_sitter_chunks: HashMap::default(),
2413 use_base_text_line_numbers: false,
2414 };
2415
2416 if is_minimap {
2417 return editor;
2418 }
2419
2420 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2421 editor.accent_data = editor.fetch_accent_data(cx);
2422
2423 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2424 editor
2425 ._subscriptions
2426 .push(cx.observe(breakpoints, |_, _, cx| {
2427 cx.notify();
2428 }));
2429 }
2430 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2431 editor._subscriptions.extend(project_subscriptions);
2432
2433 editor._subscriptions.push(cx.subscribe_in(
2434 &cx.entity(),
2435 window,
2436 |editor, _, e: &EditorEvent, window, cx| match e {
2437 EditorEvent::ScrollPositionChanged { local, .. } => {
2438 if *local {
2439 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2440 editor.inline_blame_popover.take();
2441 let new_anchor = editor.scroll_manager.anchor();
2442 let snapshot = editor.snapshot(window, cx);
2443 editor.update_restoration_data(cx, move |data| {
2444 data.scroll_position = (
2445 new_anchor.top_row(snapshot.buffer_snapshot()),
2446 new_anchor.offset,
2447 );
2448 });
2449
2450 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2451 cx.background_executor()
2452 .timer(Duration::from_millis(50))
2453 .await;
2454 editor
2455 .update_in(cx, |editor, window, cx| {
2456 editor.register_visible_buffers(cx);
2457 editor.refresh_colors_for_visible_range(None, window, cx);
2458 editor.refresh_inlay_hints(
2459 InlayHintRefreshReason::NewLinesShown,
2460 cx,
2461 );
2462 editor.colorize_brackets(false, cx);
2463 })
2464 .ok();
2465 });
2466 }
2467 }
2468 EditorEvent::Edited { .. } => {
2469 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2470 .map(|vim_mode| vim_mode.0)
2471 .unwrap_or(false);
2472 if !vim_mode {
2473 let display_map = editor.display_snapshot(cx);
2474 let selections = editor.selections.all_adjusted_display(&display_map);
2475 let pop_state = editor
2476 .change_list
2477 .last()
2478 .map(|previous| {
2479 previous.len() == selections.len()
2480 && previous.iter().enumerate().all(|(ix, p)| {
2481 p.to_display_point(&display_map).row()
2482 == selections[ix].head().row()
2483 })
2484 })
2485 .unwrap_or(false);
2486 let new_positions = selections
2487 .into_iter()
2488 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2489 .collect();
2490 editor
2491 .change_list
2492 .push_to_change_list(pop_state, new_positions);
2493 }
2494 }
2495 _ => (),
2496 },
2497 ));
2498
2499 if let Some(dap_store) = editor
2500 .project
2501 .as_ref()
2502 .map(|project| project.read(cx).dap_store())
2503 {
2504 let weak_editor = cx.weak_entity();
2505
2506 editor
2507 ._subscriptions
2508 .push(
2509 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2510 let session_entity = cx.entity();
2511 weak_editor
2512 .update(cx, |editor, cx| {
2513 editor._subscriptions.push(
2514 cx.subscribe(&session_entity, Self::on_debug_session_event),
2515 );
2516 })
2517 .ok();
2518 }),
2519 );
2520
2521 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2522 editor
2523 ._subscriptions
2524 .push(cx.subscribe(&session, Self::on_debug_session_event));
2525 }
2526 }
2527
2528 // skip adding the initial selection to selection history
2529 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2530 editor.end_selection(window, cx);
2531 editor.selection_history.mode = SelectionHistoryMode::Normal;
2532
2533 editor.scroll_manager.show_scrollbars(window, cx);
2534 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2535
2536 if full_mode {
2537 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2538 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2539
2540 if editor.git_blame_inline_enabled {
2541 editor.start_git_blame_inline(false, window, cx);
2542 }
2543
2544 editor.go_to_active_debug_line(window, cx);
2545
2546 editor.minimap =
2547 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2548 editor.colors = Some(LspColorData::new(cx));
2549 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2550
2551 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2552 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2553 }
2554 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2555 }
2556
2557 editor
2558 }
2559
2560 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2561 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2562 }
2563
2564 pub fn deploy_mouse_context_menu(
2565 &mut self,
2566 position: gpui::Point<Pixels>,
2567 context_menu: Entity<ContextMenu>,
2568 window: &mut Window,
2569 cx: &mut Context<Self>,
2570 ) {
2571 self.mouse_context_menu = Some(MouseContextMenu::new(
2572 self,
2573 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2574 context_menu,
2575 window,
2576 cx,
2577 ));
2578 }
2579
2580 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2581 self.mouse_context_menu
2582 .as_ref()
2583 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2584 }
2585
2586 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2587 if self
2588 .selections
2589 .pending_anchor()
2590 .is_some_and(|pending_selection| {
2591 let snapshot = self.buffer().read(cx).snapshot(cx);
2592 pending_selection.range().includes(range, &snapshot)
2593 })
2594 {
2595 return true;
2596 }
2597
2598 self.selections
2599 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2600 .into_iter()
2601 .any(|selection| {
2602 // This is needed to cover a corner case, if we just check for an existing
2603 // selection in the fold range, having a cursor at the start of the fold
2604 // marks it as selected. Non-empty selections don't cause this.
2605 let length = selection.end - selection.start;
2606 length > 0
2607 })
2608 }
2609
2610 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2611 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2612 }
2613
2614 fn key_context_internal(
2615 &self,
2616 has_active_edit_prediction: bool,
2617 window: &mut Window,
2618 cx: &mut App,
2619 ) -> KeyContext {
2620 let mut key_context = KeyContext::new_with_defaults();
2621 key_context.add("Editor");
2622 let mode = match self.mode {
2623 EditorMode::SingleLine => "single_line",
2624 EditorMode::AutoHeight { .. } => "auto_height",
2625 EditorMode::Minimap { .. } => "minimap",
2626 EditorMode::Full { .. } => "full",
2627 };
2628
2629 if EditorSettings::jupyter_enabled(cx) {
2630 key_context.add("jupyter");
2631 }
2632
2633 key_context.set("mode", mode);
2634 if self.pending_rename.is_some() {
2635 key_context.add("renaming");
2636 }
2637
2638 if let Some(snippet_stack) = self.snippet_stack.last() {
2639 key_context.add("in_snippet");
2640
2641 if snippet_stack.active_index > 0 {
2642 key_context.add("has_previous_tabstop");
2643 }
2644
2645 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2646 key_context.add("has_next_tabstop");
2647 }
2648 }
2649
2650 match self.context_menu.borrow().as_ref() {
2651 Some(CodeContextMenu::Completions(menu)) => {
2652 if menu.visible() {
2653 key_context.add("menu");
2654 key_context.add("showing_completions");
2655 }
2656 }
2657 Some(CodeContextMenu::CodeActions(menu)) => {
2658 if menu.visible() {
2659 key_context.add("menu");
2660 key_context.add("showing_code_actions")
2661 }
2662 }
2663 None => {}
2664 }
2665
2666 if self.signature_help_state.has_multiple_signatures() {
2667 key_context.add("showing_signature_help");
2668 }
2669
2670 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2671 if !self.focus_handle(cx).contains_focused(window, cx)
2672 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2673 {
2674 for addon in self.addons.values() {
2675 addon.extend_key_context(&mut key_context, cx)
2676 }
2677 }
2678
2679 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2680 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2681 Some(
2682 file.full_path(cx)
2683 .extension()?
2684 .to_string_lossy()
2685 .into_owned(),
2686 )
2687 }) {
2688 key_context.set("extension", extension);
2689 }
2690 } else {
2691 key_context.add("multibuffer");
2692 }
2693
2694 if has_active_edit_prediction {
2695 if self.edit_prediction_in_conflict() {
2696 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2697 } else {
2698 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2699 key_context.add("copilot_suggestion");
2700 }
2701 }
2702
2703 if self.selection_mark_mode {
2704 key_context.add("selection_mode");
2705 }
2706
2707 let disjoint = self.selections.disjoint_anchors();
2708 let snapshot = self.snapshot(window, cx);
2709 let snapshot = snapshot.buffer_snapshot();
2710 if self.mode == EditorMode::SingleLine
2711 && let [selection] = disjoint
2712 && selection.start == selection.end
2713 && selection.end.to_offset(snapshot) == snapshot.len()
2714 {
2715 key_context.add("end_of_input");
2716 }
2717
2718 if self.has_any_expanded_diff_hunks(cx) {
2719 key_context.add("diffs_expanded");
2720 }
2721
2722 key_context
2723 }
2724
2725 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2726 self.last_bounds.as_ref()
2727 }
2728
2729 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2730 if self.mouse_cursor_hidden {
2731 self.mouse_cursor_hidden = false;
2732 cx.notify();
2733 }
2734 }
2735
2736 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2737 let hide_mouse_cursor = match origin {
2738 HideMouseCursorOrigin::TypingAction => {
2739 matches!(
2740 self.hide_mouse_mode,
2741 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2742 )
2743 }
2744 HideMouseCursorOrigin::MovementAction => {
2745 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2746 }
2747 };
2748 if self.mouse_cursor_hidden != hide_mouse_cursor {
2749 self.mouse_cursor_hidden = hide_mouse_cursor;
2750 cx.notify();
2751 }
2752 }
2753
2754 pub fn edit_prediction_in_conflict(&self) -> bool {
2755 if !self.show_edit_predictions_in_menu() {
2756 return false;
2757 }
2758
2759 let showing_completions = self
2760 .context_menu
2761 .borrow()
2762 .as_ref()
2763 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2764
2765 showing_completions
2766 || self.edit_prediction_requires_modifier()
2767 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2768 // bindings to insert tab characters.
2769 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2770 }
2771
2772 pub fn accept_edit_prediction_keybind(
2773 &self,
2774 accept_partial: bool,
2775 window: &mut Window,
2776 cx: &mut App,
2777 ) -> AcceptEditPredictionBinding {
2778 let key_context = self.key_context_internal(true, window, cx);
2779 let in_conflict = self.edit_prediction_in_conflict();
2780
2781 let bindings = if accept_partial {
2782 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2783 } else {
2784 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2785 };
2786
2787 // TODO: if the binding contains multiple keystrokes, display all of them, not
2788 // just the first one.
2789 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2790 !in_conflict
2791 || binding
2792 .keystrokes()
2793 .first()
2794 .is_some_and(|keystroke| keystroke.modifiers().modified())
2795 }))
2796 }
2797
2798 pub fn new_file(
2799 workspace: &mut Workspace,
2800 _: &workspace::NewFile,
2801 window: &mut Window,
2802 cx: &mut Context<Workspace>,
2803 ) {
2804 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2805 "Failed to create buffer",
2806 window,
2807 cx,
2808 |e, _, _| match e.error_code() {
2809 ErrorCode::RemoteUpgradeRequired => Some(format!(
2810 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2811 e.error_tag("required").unwrap_or("the latest version")
2812 )),
2813 _ => None,
2814 },
2815 );
2816 }
2817
2818 pub fn new_in_workspace(
2819 workspace: &mut Workspace,
2820 window: &mut Window,
2821 cx: &mut Context<Workspace>,
2822 ) -> Task<Result<Entity<Editor>>> {
2823 let project = workspace.project().clone();
2824 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2825
2826 cx.spawn_in(window, async move |workspace, cx| {
2827 let buffer = create.await?;
2828 workspace.update_in(cx, |workspace, window, cx| {
2829 let editor =
2830 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2831 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2832 editor
2833 })
2834 })
2835 }
2836
2837 fn new_file_vertical(
2838 workspace: &mut Workspace,
2839 _: &workspace::NewFileSplitVertical,
2840 window: &mut Window,
2841 cx: &mut Context<Workspace>,
2842 ) {
2843 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2844 }
2845
2846 fn new_file_horizontal(
2847 workspace: &mut Workspace,
2848 _: &workspace::NewFileSplitHorizontal,
2849 window: &mut Window,
2850 cx: &mut Context<Workspace>,
2851 ) {
2852 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2853 }
2854
2855 fn new_file_split(
2856 workspace: &mut Workspace,
2857 action: &workspace::NewFileSplit,
2858 window: &mut Window,
2859 cx: &mut Context<Workspace>,
2860 ) {
2861 Self::new_file_in_direction(workspace, action.0, window, cx)
2862 }
2863
2864 fn new_file_in_direction(
2865 workspace: &mut Workspace,
2866 direction: SplitDirection,
2867 window: &mut Window,
2868 cx: &mut Context<Workspace>,
2869 ) {
2870 let project = workspace.project().clone();
2871 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2872
2873 cx.spawn_in(window, async move |workspace, cx| {
2874 let buffer = create.await?;
2875 workspace.update_in(cx, move |workspace, window, cx| {
2876 workspace.split_item(
2877 direction,
2878 Box::new(
2879 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2880 ),
2881 window,
2882 cx,
2883 )
2884 })?;
2885 anyhow::Ok(())
2886 })
2887 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2888 match e.error_code() {
2889 ErrorCode::RemoteUpgradeRequired => Some(format!(
2890 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2891 e.error_tag("required").unwrap_or("the latest version")
2892 )),
2893 _ => None,
2894 }
2895 });
2896 }
2897
2898 pub fn leader_id(&self) -> Option<CollaboratorId> {
2899 self.leader_id
2900 }
2901
2902 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2903 &self.buffer
2904 }
2905
2906 pub fn project(&self) -> Option<&Entity<Project>> {
2907 self.project.as_ref()
2908 }
2909
2910 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2911 self.workspace.as_ref()?.0.upgrade()
2912 }
2913
2914 /// Returns the workspace serialization ID if this editor should be serialized.
2915 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2916 self.workspace
2917 .as_ref()
2918 .filter(|_| self.should_serialize_buffer())
2919 .and_then(|workspace| workspace.1)
2920 }
2921
2922 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2923 self.buffer().read(cx).title(cx)
2924 }
2925
2926 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2927 let git_blame_gutter_max_author_length = self
2928 .render_git_blame_gutter(cx)
2929 .then(|| {
2930 if let Some(blame) = self.blame.as_ref() {
2931 let max_author_length =
2932 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2933 Some(max_author_length)
2934 } else {
2935 None
2936 }
2937 })
2938 .flatten();
2939
2940 EditorSnapshot {
2941 mode: self.mode.clone(),
2942 show_gutter: self.show_gutter,
2943 offset_content: self.offset_content,
2944 show_line_numbers: self.show_line_numbers,
2945 show_git_diff_gutter: self.show_git_diff_gutter,
2946 show_code_actions: self.show_code_actions,
2947 show_runnables: self.show_runnables,
2948 show_breakpoints: self.show_breakpoints,
2949 git_blame_gutter_max_author_length,
2950 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2951 placeholder_display_snapshot: self
2952 .placeholder_display_map
2953 .as_ref()
2954 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2955 scroll_anchor: self.scroll_manager.anchor(),
2956 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2957 is_focused: self.focus_handle.is_focused(window),
2958 current_line_highlight: self
2959 .current_line_highlight
2960 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2961 gutter_hovered: self.gutter_hovered,
2962 }
2963 }
2964
2965 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2966 self.buffer.read(cx).language_at(point, cx)
2967 }
2968
2969 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2970 self.buffer.read(cx).read(cx).file_at(point).cloned()
2971 }
2972
2973 pub fn active_excerpt(
2974 &self,
2975 cx: &App,
2976 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2977 self.buffer
2978 .read(cx)
2979 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2980 }
2981
2982 pub fn mode(&self) -> &EditorMode {
2983 &self.mode
2984 }
2985
2986 pub fn set_mode(&mut self, mode: EditorMode) {
2987 self.mode = mode;
2988 }
2989
2990 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2991 self.collaboration_hub.as_deref()
2992 }
2993
2994 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2995 self.collaboration_hub = Some(hub);
2996 }
2997
2998 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2999 self.in_project_search = in_project_search;
3000 }
3001
3002 pub fn set_custom_context_menu(
3003 &mut self,
3004 f: impl 'static
3005 + Fn(
3006 &mut Self,
3007 DisplayPoint,
3008 &mut Window,
3009 &mut Context<Self>,
3010 ) -> Option<Entity<ui::ContextMenu>>,
3011 ) {
3012 self.custom_context_menu = Some(Box::new(f))
3013 }
3014
3015 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3016 self.completion_provider = provider;
3017 }
3018
3019 #[cfg(any(test, feature = "test-support"))]
3020 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3021 self.completion_provider.clone()
3022 }
3023
3024 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3025 self.semantics_provider.clone()
3026 }
3027
3028 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3029 self.semantics_provider = provider;
3030 }
3031
3032 pub fn set_edit_prediction_provider<T>(
3033 &mut self,
3034 provider: Option<Entity<T>>,
3035 window: &mut Window,
3036 cx: &mut Context<Self>,
3037 ) where
3038 T: EditPredictionDelegate,
3039 {
3040 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3041 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3042 if this.focus_handle.is_focused(window) {
3043 this.update_visible_edit_prediction(window, cx);
3044 }
3045 }),
3046 provider: Arc::new(provider),
3047 });
3048 self.update_edit_prediction_settings(cx);
3049 self.refresh_edit_prediction(false, false, window, cx);
3050 }
3051
3052 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3053 self.placeholder_display_map
3054 .as_ref()
3055 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3056 }
3057
3058 pub fn set_placeholder_text(
3059 &mut self,
3060 placeholder_text: &str,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 ) {
3064 let multibuffer = cx
3065 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3066
3067 let style = window.text_style();
3068
3069 self.placeholder_display_map = Some(cx.new(|cx| {
3070 DisplayMap::new(
3071 multibuffer,
3072 style.font(),
3073 style.font_size.to_pixels(window.rem_size()),
3074 None,
3075 FILE_HEADER_HEIGHT,
3076 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3077 Default::default(),
3078 DiagnosticSeverity::Off,
3079 cx,
3080 )
3081 }));
3082 cx.notify();
3083 }
3084
3085 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3086 self.cursor_shape = cursor_shape;
3087
3088 // Disrupt blink for immediate user feedback that the cursor shape has changed
3089 self.blink_manager.update(cx, BlinkManager::show_cursor);
3090
3091 cx.notify();
3092 }
3093
3094 pub fn cursor_shape(&self) -> CursorShape {
3095 self.cursor_shape
3096 }
3097
3098 pub fn set_current_line_highlight(
3099 &mut self,
3100 current_line_highlight: Option<CurrentLineHighlight>,
3101 ) {
3102 self.current_line_highlight = current_line_highlight;
3103 }
3104
3105 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3106 self.collapse_matches = collapse_matches;
3107 }
3108
3109 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3110 if self.collapse_matches {
3111 return range.start..range.start;
3112 }
3113 range.clone()
3114 }
3115
3116 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3117 self.display_map.read(cx).clip_at_line_ends
3118 }
3119
3120 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3121 if self.display_map.read(cx).clip_at_line_ends != clip {
3122 self.display_map
3123 .update(cx, |map, _| map.clip_at_line_ends = clip);
3124 }
3125 }
3126
3127 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3128 self.input_enabled = input_enabled;
3129 }
3130
3131 pub fn set_edit_predictions_hidden_for_vim_mode(
3132 &mut self,
3133 hidden: bool,
3134 window: &mut Window,
3135 cx: &mut Context<Self>,
3136 ) {
3137 if hidden != self.edit_predictions_hidden_for_vim_mode {
3138 self.edit_predictions_hidden_for_vim_mode = hidden;
3139 if hidden {
3140 self.update_visible_edit_prediction(window, cx);
3141 } else {
3142 self.refresh_edit_prediction(true, false, window, cx);
3143 }
3144 }
3145 }
3146
3147 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3148 self.menu_edit_predictions_policy = value;
3149 }
3150
3151 pub fn set_autoindent(&mut self, autoindent: bool) {
3152 if autoindent {
3153 self.autoindent_mode = Some(AutoindentMode::EachLine);
3154 } else {
3155 self.autoindent_mode = None;
3156 }
3157 }
3158
3159 pub fn read_only(&self, cx: &App) -> bool {
3160 self.read_only || self.buffer.read(cx).read_only()
3161 }
3162
3163 pub fn set_read_only(&mut self, read_only: bool) {
3164 self.read_only = read_only;
3165 }
3166
3167 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3168 self.use_autoclose = autoclose;
3169 }
3170
3171 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3172 self.use_auto_surround = auto_surround;
3173 }
3174
3175 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3176 self.auto_replace_emoji_shortcode = auto_replace;
3177 }
3178
3179 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3180 self.buffer_serialization = should_serialize.then(|| {
3181 BufferSerialization::new(
3182 ProjectSettings::get_global(cx)
3183 .session
3184 .restore_unsaved_buffers,
3185 )
3186 })
3187 }
3188
3189 fn should_serialize_buffer(&self) -> bool {
3190 self.buffer_serialization.is_some()
3191 }
3192
3193 pub fn toggle_edit_predictions(
3194 &mut self,
3195 _: &ToggleEditPrediction,
3196 window: &mut Window,
3197 cx: &mut Context<Self>,
3198 ) {
3199 if self.show_edit_predictions_override.is_some() {
3200 self.set_show_edit_predictions(None, window, cx);
3201 } else {
3202 let show_edit_predictions = !self.edit_predictions_enabled();
3203 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3204 }
3205 }
3206
3207 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3208 self.show_completions_on_input_override = show_completions_on_input;
3209 }
3210
3211 pub fn set_show_edit_predictions(
3212 &mut self,
3213 show_edit_predictions: Option<bool>,
3214 window: &mut Window,
3215 cx: &mut Context<Self>,
3216 ) {
3217 self.show_edit_predictions_override = show_edit_predictions;
3218 self.update_edit_prediction_settings(cx);
3219
3220 if let Some(false) = show_edit_predictions {
3221 self.discard_edit_prediction(false, cx);
3222 } else {
3223 self.refresh_edit_prediction(false, true, window, cx);
3224 }
3225 }
3226
3227 fn edit_predictions_disabled_in_scope(
3228 &self,
3229 buffer: &Entity<Buffer>,
3230 buffer_position: language::Anchor,
3231 cx: &App,
3232 ) -> bool {
3233 let snapshot = buffer.read(cx).snapshot();
3234 let settings = snapshot.settings_at(buffer_position, cx);
3235
3236 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3237 return false;
3238 };
3239
3240 scope.override_name().is_some_and(|scope_name| {
3241 settings
3242 .edit_predictions_disabled_in
3243 .iter()
3244 .any(|s| s == scope_name)
3245 })
3246 }
3247
3248 pub fn set_use_modal_editing(&mut self, to: bool) {
3249 self.use_modal_editing = to;
3250 }
3251
3252 pub fn use_modal_editing(&self) -> bool {
3253 self.use_modal_editing
3254 }
3255
3256 fn selections_did_change(
3257 &mut self,
3258 local: bool,
3259 old_cursor_position: &Anchor,
3260 effects: SelectionEffects,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 ) {
3264 window.invalidate_character_coordinates();
3265
3266 // Copy selections to primary selection buffer
3267 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3268 if local {
3269 let selections = self
3270 .selections
3271 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3272 let buffer_handle = self.buffer.read(cx).read(cx);
3273
3274 let mut text = String::new();
3275 for (index, selection) in selections.iter().enumerate() {
3276 let text_for_selection = buffer_handle
3277 .text_for_range(selection.start..selection.end)
3278 .collect::<String>();
3279
3280 text.push_str(&text_for_selection);
3281 if index != selections.len() - 1 {
3282 text.push('\n');
3283 }
3284 }
3285
3286 if !text.is_empty() {
3287 cx.write_to_primary(ClipboardItem::new_string(text));
3288 }
3289 }
3290
3291 let selection_anchors = self.selections.disjoint_anchors_arc();
3292
3293 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3294 self.buffer.update(cx, |buffer, cx| {
3295 buffer.set_active_selections(
3296 &selection_anchors,
3297 self.selections.line_mode(),
3298 self.cursor_shape,
3299 cx,
3300 )
3301 });
3302 }
3303 let display_map = self
3304 .display_map
3305 .update(cx, |display_map, cx| display_map.snapshot(cx));
3306 let buffer = display_map.buffer_snapshot();
3307 if self.selections.count() == 1 {
3308 self.add_selections_state = None;
3309 }
3310 self.select_next_state = None;
3311 self.select_prev_state = None;
3312 self.select_syntax_node_history.try_clear();
3313 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3314 self.snippet_stack.invalidate(&selection_anchors, buffer);
3315 self.take_rename(false, window, cx);
3316
3317 let newest_selection = self.selections.newest_anchor();
3318 let new_cursor_position = newest_selection.head();
3319 let selection_start = newest_selection.start;
3320
3321 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3322 self.push_to_nav_history(
3323 *old_cursor_position,
3324 Some(new_cursor_position.to_point(buffer)),
3325 false,
3326 effects.nav_history == Some(true),
3327 cx,
3328 );
3329 }
3330
3331 if local {
3332 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3333 self.register_buffer(buffer_id, cx);
3334 }
3335
3336 let mut context_menu = self.context_menu.borrow_mut();
3337 let completion_menu = match context_menu.as_ref() {
3338 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3339 Some(CodeContextMenu::CodeActions(_)) => {
3340 *context_menu = None;
3341 None
3342 }
3343 None => None,
3344 };
3345 let completion_position = completion_menu.map(|menu| menu.initial_position);
3346 drop(context_menu);
3347
3348 if effects.completions
3349 && let Some(completion_position) = completion_position
3350 {
3351 let start_offset = selection_start.to_offset(buffer);
3352 let position_matches = start_offset == completion_position.to_offset(buffer);
3353 let continue_showing = if position_matches {
3354 if self.snippet_stack.is_empty() {
3355 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3356 == Some(CharKind::Word)
3357 } else {
3358 // Snippet choices can be shown even when the cursor is in whitespace.
3359 // Dismissing the menu with actions like backspace is handled by
3360 // invalidation regions.
3361 true
3362 }
3363 } else {
3364 false
3365 };
3366
3367 if continue_showing {
3368 self.open_or_update_completions_menu(None, None, false, window, cx);
3369 } else {
3370 self.hide_context_menu(window, cx);
3371 }
3372 }
3373
3374 hide_hover(self, cx);
3375
3376 if old_cursor_position.to_display_point(&display_map).row()
3377 != new_cursor_position.to_display_point(&display_map).row()
3378 {
3379 self.available_code_actions.take();
3380 }
3381 self.refresh_code_actions(window, cx);
3382 self.refresh_document_highlights(cx);
3383 refresh_linked_ranges(self, window, cx);
3384
3385 self.refresh_selected_text_highlights(false, window, cx);
3386 self.refresh_matching_bracket_highlights(window, cx);
3387 self.update_visible_edit_prediction(window, cx);
3388 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3389 self.inline_blame_popover.take();
3390 if self.git_blame_inline_enabled {
3391 self.start_inline_blame_timer(window, cx);
3392 }
3393 }
3394
3395 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3396 cx.emit(EditorEvent::SelectionsChanged { local });
3397
3398 let selections = &self.selections.disjoint_anchors_arc();
3399 if selections.len() == 1 {
3400 cx.emit(SearchEvent::ActiveMatchChanged)
3401 }
3402 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3403 let inmemory_selections = selections
3404 .iter()
3405 .map(|s| {
3406 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3407 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3408 })
3409 .collect();
3410 self.update_restoration_data(cx, |data| {
3411 data.selections = inmemory_selections;
3412 });
3413
3414 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3415 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3416 {
3417 let snapshot = self.buffer().read(cx).snapshot(cx);
3418 let selections = selections.clone();
3419 let background_executor = cx.background_executor().clone();
3420 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3421 self.serialize_selections = cx.background_spawn(async move {
3422 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3423 let db_selections = selections
3424 .iter()
3425 .map(|selection| {
3426 (
3427 selection.start.to_offset(&snapshot).0,
3428 selection.end.to_offset(&snapshot).0,
3429 )
3430 })
3431 .collect();
3432
3433 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3434 .await
3435 .with_context(|| {
3436 format!(
3437 "persisting editor selections for editor {editor_id}, \
3438 workspace {workspace_id:?}"
3439 )
3440 })
3441 .log_err();
3442 });
3443 }
3444 }
3445
3446 cx.notify();
3447 }
3448
3449 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3450 use text::ToOffset as _;
3451 use text::ToPoint as _;
3452
3453 if self.mode.is_minimap()
3454 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3455 {
3456 return;
3457 }
3458
3459 if !self.buffer().read(cx).is_singleton() {
3460 return;
3461 }
3462
3463 let display_snapshot = self
3464 .display_map
3465 .update(cx, |display_map, cx| display_map.snapshot(cx));
3466 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3467 return;
3468 };
3469 let inmemory_folds = display_snapshot
3470 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3471 .map(|fold| {
3472 fold.range.start.text_anchor.to_point(&snapshot)
3473 ..fold.range.end.text_anchor.to_point(&snapshot)
3474 })
3475 .collect();
3476 self.update_restoration_data(cx, |data| {
3477 data.folds = inmemory_folds;
3478 });
3479
3480 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3481 return;
3482 };
3483 let background_executor = cx.background_executor().clone();
3484 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3485 let db_folds = display_snapshot
3486 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3487 .map(|fold| {
3488 (
3489 fold.range.start.text_anchor.to_offset(&snapshot),
3490 fold.range.end.text_anchor.to_offset(&snapshot),
3491 )
3492 })
3493 .collect();
3494 self.serialize_folds = cx.background_spawn(async move {
3495 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3496 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3497 .await
3498 .with_context(|| {
3499 format!(
3500 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3501 )
3502 })
3503 .log_err();
3504 });
3505 }
3506
3507 pub fn sync_selections(
3508 &mut self,
3509 other: Entity<Editor>,
3510 cx: &mut Context<Self>,
3511 ) -> gpui::Subscription {
3512 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3513 if !other_selections.is_empty() {
3514 self.selections
3515 .change_with(&self.display_snapshot(cx), |selections| {
3516 selections.select_anchors(other_selections);
3517 });
3518 }
3519
3520 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3521 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3522 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3523 if other_selections.is_empty() {
3524 return;
3525 }
3526 let snapshot = this.display_snapshot(cx);
3527 this.selections.change_with(&snapshot, |selections| {
3528 selections.select_anchors(other_selections);
3529 });
3530 }
3531 });
3532
3533 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3534 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3535 let these_selections = this.selections.disjoint_anchors().to_vec();
3536 if these_selections.is_empty() {
3537 return;
3538 }
3539 other.update(cx, |other_editor, cx| {
3540 let snapshot = other_editor.display_snapshot(cx);
3541 other_editor
3542 .selections
3543 .change_with(&snapshot, |selections| {
3544 selections.select_anchors(these_selections);
3545 })
3546 });
3547 }
3548 });
3549
3550 Subscription::join(other_subscription, this_subscription)
3551 }
3552
3553 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3554 if self.buffer().read(cx).is_singleton() {
3555 return;
3556 }
3557 let snapshot = self.buffer.read(cx).snapshot(cx);
3558 let buffer_ids: HashSet<BufferId> = self
3559 .selections
3560 .disjoint_anchor_ranges()
3561 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3562 .collect();
3563 for buffer_id in buffer_ids {
3564 self.unfold_buffer(buffer_id, cx);
3565 }
3566 }
3567
3568 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3569 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3570 /// effects of selection change occur at the end of the transaction.
3571 pub fn change_selections<R>(
3572 &mut self,
3573 effects: SelectionEffects,
3574 window: &mut Window,
3575 cx: &mut Context<Self>,
3576 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3577 ) -> R {
3578 let snapshot = self.display_snapshot(cx);
3579 if let Some(state) = &mut self.deferred_selection_effects_state {
3580 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3581 state.effects.completions = effects.completions;
3582 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3583 let (changed, result) = self.selections.change_with(&snapshot, change);
3584 state.changed |= changed;
3585 return result;
3586 }
3587 let mut state = DeferredSelectionEffectsState {
3588 changed: false,
3589 effects,
3590 old_cursor_position: self.selections.newest_anchor().head(),
3591 history_entry: SelectionHistoryEntry {
3592 selections: self.selections.disjoint_anchors_arc(),
3593 select_next_state: self.select_next_state.clone(),
3594 select_prev_state: self.select_prev_state.clone(),
3595 add_selections_state: self.add_selections_state.clone(),
3596 },
3597 };
3598 let (changed, result) = self.selections.change_with(&snapshot, change);
3599 state.changed = state.changed || changed;
3600 if self.defer_selection_effects {
3601 self.deferred_selection_effects_state = Some(state);
3602 } else {
3603 self.apply_selection_effects(state, window, cx);
3604 }
3605 result
3606 }
3607
3608 /// Defers the effects of selection change, so that the effects of multiple calls to
3609 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3610 /// to selection history and the state of popovers based on selection position aren't
3611 /// erroneously updated.
3612 pub fn with_selection_effects_deferred<R>(
3613 &mut self,
3614 window: &mut Window,
3615 cx: &mut Context<Self>,
3616 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3617 ) -> R {
3618 let already_deferred = self.defer_selection_effects;
3619 self.defer_selection_effects = true;
3620 let result = update(self, window, cx);
3621 if !already_deferred {
3622 self.defer_selection_effects = false;
3623 if let Some(state) = self.deferred_selection_effects_state.take() {
3624 self.apply_selection_effects(state, window, cx);
3625 }
3626 }
3627 result
3628 }
3629
3630 fn apply_selection_effects(
3631 &mut self,
3632 state: DeferredSelectionEffectsState,
3633 window: &mut Window,
3634 cx: &mut Context<Self>,
3635 ) {
3636 if state.changed {
3637 self.selection_history.push(state.history_entry);
3638
3639 if let Some(autoscroll) = state.effects.scroll {
3640 self.request_autoscroll(autoscroll, cx);
3641 }
3642
3643 let old_cursor_position = &state.old_cursor_position;
3644
3645 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3646
3647 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3648 self.show_signature_help(&ShowSignatureHelp, window, cx);
3649 }
3650 }
3651 }
3652
3653 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3654 where
3655 I: IntoIterator<Item = (Range<S>, T)>,
3656 S: ToOffset,
3657 T: Into<Arc<str>>,
3658 {
3659 if self.read_only(cx) {
3660 return;
3661 }
3662
3663 self.buffer
3664 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3665 }
3666
3667 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3668 where
3669 I: IntoIterator<Item = (Range<S>, T)>,
3670 S: ToOffset,
3671 T: Into<Arc<str>>,
3672 {
3673 if self.read_only(cx) {
3674 return;
3675 }
3676
3677 self.buffer.update(cx, |buffer, cx| {
3678 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3679 });
3680 }
3681
3682 pub fn edit_with_block_indent<I, S, T>(
3683 &mut self,
3684 edits: I,
3685 original_indent_columns: Vec<Option<u32>>,
3686 cx: &mut Context<Self>,
3687 ) where
3688 I: IntoIterator<Item = (Range<S>, T)>,
3689 S: ToOffset,
3690 T: Into<Arc<str>>,
3691 {
3692 if self.read_only(cx) {
3693 return;
3694 }
3695
3696 self.buffer.update(cx, |buffer, cx| {
3697 buffer.edit(
3698 edits,
3699 Some(AutoindentMode::Block {
3700 original_indent_columns,
3701 }),
3702 cx,
3703 )
3704 });
3705 }
3706
3707 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3708 self.hide_context_menu(window, cx);
3709
3710 match phase {
3711 SelectPhase::Begin {
3712 position,
3713 add,
3714 click_count,
3715 } => self.begin_selection(position, add, click_count, window, cx),
3716 SelectPhase::BeginColumnar {
3717 position,
3718 goal_column,
3719 reset,
3720 mode,
3721 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3722 SelectPhase::Extend {
3723 position,
3724 click_count,
3725 } => self.extend_selection(position, click_count, window, cx),
3726 SelectPhase::Update {
3727 position,
3728 goal_column,
3729 scroll_delta,
3730 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3731 SelectPhase::End => self.end_selection(window, cx),
3732 }
3733 }
3734
3735 fn extend_selection(
3736 &mut self,
3737 position: DisplayPoint,
3738 click_count: usize,
3739 window: &mut Window,
3740 cx: &mut Context<Self>,
3741 ) {
3742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3743 let tail = self
3744 .selections
3745 .newest::<MultiBufferOffset>(&display_map)
3746 .tail();
3747 let click_count = click_count.max(match self.selections.select_mode() {
3748 SelectMode::Character => 1,
3749 SelectMode::Word(_) => 2,
3750 SelectMode::Line(_) => 3,
3751 SelectMode::All => 4,
3752 });
3753 self.begin_selection(position, false, click_count, window, cx);
3754
3755 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3756
3757 let current_selection = match self.selections.select_mode() {
3758 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3759 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3760 };
3761
3762 let mut pending_selection = self
3763 .selections
3764 .pending_anchor()
3765 .cloned()
3766 .expect("extend_selection not called with pending selection");
3767
3768 if pending_selection
3769 .start
3770 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3771 == Ordering::Greater
3772 {
3773 pending_selection.start = current_selection.start;
3774 }
3775 if pending_selection
3776 .end
3777 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3778 == Ordering::Less
3779 {
3780 pending_selection.end = current_selection.end;
3781 pending_selection.reversed = true;
3782 }
3783
3784 let mut pending_mode = self.selections.pending_mode().unwrap();
3785 match &mut pending_mode {
3786 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3787 _ => {}
3788 }
3789
3790 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3791 SelectionEffects::scroll(Autoscroll::fit())
3792 } else {
3793 SelectionEffects::no_scroll()
3794 };
3795
3796 self.change_selections(effects, window, cx, |s| {
3797 s.set_pending(pending_selection.clone(), pending_mode);
3798 s.set_is_extending(true);
3799 });
3800 }
3801
3802 fn begin_selection(
3803 &mut self,
3804 position: DisplayPoint,
3805 add: bool,
3806 click_count: usize,
3807 window: &mut Window,
3808 cx: &mut Context<Self>,
3809 ) {
3810 if !self.focus_handle.is_focused(window) {
3811 self.last_focused_descendant = None;
3812 window.focus(&self.focus_handle);
3813 }
3814
3815 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3816 let buffer = display_map.buffer_snapshot();
3817 let position = display_map.clip_point(position, Bias::Left);
3818
3819 let start;
3820 let end;
3821 let mode;
3822 let mut auto_scroll;
3823 match click_count {
3824 1 => {
3825 start = buffer.anchor_before(position.to_point(&display_map));
3826 end = start;
3827 mode = SelectMode::Character;
3828 auto_scroll = true;
3829 }
3830 2 => {
3831 let position = display_map
3832 .clip_point(position, Bias::Left)
3833 .to_offset(&display_map, Bias::Left);
3834 let (range, _) = buffer.surrounding_word(position, None);
3835 start = buffer.anchor_before(range.start);
3836 end = buffer.anchor_before(range.end);
3837 mode = SelectMode::Word(start..end);
3838 auto_scroll = true;
3839 }
3840 3 => {
3841 let position = display_map
3842 .clip_point(position, Bias::Left)
3843 .to_point(&display_map);
3844 let line_start = display_map.prev_line_boundary(position).0;
3845 let next_line_start = buffer.clip_point(
3846 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3847 Bias::Left,
3848 );
3849 start = buffer.anchor_before(line_start);
3850 end = buffer.anchor_before(next_line_start);
3851 mode = SelectMode::Line(start..end);
3852 auto_scroll = true;
3853 }
3854 _ => {
3855 start = buffer.anchor_before(MultiBufferOffset(0));
3856 end = buffer.anchor_before(buffer.len());
3857 mode = SelectMode::All;
3858 auto_scroll = false;
3859 }
3860 }
3861 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3862
3863 let point_to_delete: Option<usize> = {
3864 let selected_points: Vec<Selection<Point>> =
3865 self.selections.disjoint_in_range(start..end, &display_map);
3866
3867 if !add || click_count > 1 {
3868 None
3869 } else if !selected_points.is_empty() {
3870 Some(selected_points[0].id)
3871 } else {
3872 let clicked_point_already_selected =
3873 self.selections.disjoint_anchors().iter().find(|selection| {
3874 selection.start.to_point(buffer) == start.to_point(buffer)
3875 || selection.end.to_point(buffer) == end.to_point(buffer)
3876 });
3877
3878 clicked_point_already_selected.map(|selection| selection.id)
3879 }
3880 };
3881
3882 let selections_count = self.selections.count();
3883 let effects = if auto_scroll {
3884 SelectionEffects::default()
3885 } else {
3886 SelectionEffects::no_scroll()
3887 };
3888
3889 self.change_selections(effects, window, cx, |s| {
3890 if let Some(point_to_delete) = point_to_delete {
3891 s.delete(point_to_delete);
3892
3893 if selections_count == 1 {
3894 s.set_pending_anchor_range(start..end, mode);
3895 }
3896 } else {
3897 if !add {
3898 s.clear_disjoint();
3899 }
3900
3901 s.set_pending_anchor_range(start..end, mode);
3902 }
3903 });
3904 }
3905
3906 fn begin_columnar_selection(
3907 &mut self,
3908 position: DisplayPoint,
3909 goal_column: u32,
3910 reset: bool,
3911 mode: ColumnarMode,
3912 window: &mut Window,
3913 cx: &mut Context<Self>,
3914 ) {
3915 if !self.focus_handle.is_focused(window) {
3916 self.last_focused_descendant = None;
3917 window.focus(&self.focus_handle);
3918 }
3919
3920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3921
3922 if reset {
3923 let pointer_position = display_map
3924 .buffer_snapshot()
3925 .anchor_before(position.to_point(&display_map));
3926
3927 self.change_selections(
3928 SelectionEffects::scroll(Autoscroll::newest()),
3929 window,
3930 cx,
3931 |s| {
3932 s.clear_disjoint();
3933 s.set_pending_anchor_range(
3934 pointer_position..pointer_position,
3935 SelectMode::Character,
3936 );
3937 },
3938 );
3939 };
3940
3941 let tail = self.selections.newest::<Point>(&display_map).tail();
3942 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3943 self.columnar_selection_state = match mode {
3944 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3945 selection_tail: selection_anchor,
3946 display_point: if reset {
3947 if position.column() != goal_column {
3948 Some(DisplayPoint::new(position.row(), goal_column))
3949 } else {
3950 None
3951 }
3952 } else {
3953 None
3954 },
3955 }),
3956 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3957 selection_tail: selection_anchor,
3958 }),
3959 };
3960
3961 if !reset {
3962 self.select_columns(position, goal_column, &display_map, window, cx);
3963 }
3964 }
3965
3966 fn update_selection(
3967 &mut self,
3968 position: DisplayPoint,
3969 goal_column: u32,
3970 scroll_delta: gpui::Point<f32>,
3971 window: &mut Window,
3972 cx: &mut Context<Self>,
3973 ) {
3974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3975
3976 if self.columnar_selection_state.is_some() {
3977 self.select_columns(position, goal_column, &display_map, window, cx);
3978 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3979 let buffer = display_map.buffer_snapshot();
3980 let head;
3981 let tail;
3982 let mode = self.selections.pending_mode().unwrap();
3983 match &mode {
3984 SelectMode::Character => {
3985 head = position.to_point(&display_map);
3986 tail = pending.tail().to_point(buffer);
3987 }
3988 SelectMode::Word(original_range) => {
3989 let offset = display_map
3990 .clip_point(position, Bias::Left)
3991 .to_offset(&display_map, Bias::Left);
3992 let original_range = original_range.to_offset(buffer);
3993
3994 let head_offset = if buffer.is_inside_word(offset, None)
3995 || original_range.contains(&offset)
3996 {
3997 let (word_range, _) = buffer.surrounding_word(offset, None);
3998 if word_range.start < original_range.start {
3999 word_range.start
4000 } else {
4001 word_range.end
4002 }
4003 } else {
4004 offset
4005 };
4006
4007 head = head_offset.to_point(buffer);
4008 if head_offset <= original_range.start {
4009 tail = original_range.end.to_point(buffer);
4010 } else {
4011 tail = original_range.start.to_point(buffer);
4012 }
4013 }
4014 SelectMode::Line(original_range) => {
4015 let original_range = original_range.to_point(display_map.buffer_snapshot());
4016
4017 let position = display_map
4018 .clip_point(position, Bias::Left)
4019 .to_point(&display_map);
4020 let line_start = display_map.prev_line_boundary(position).0;
4021 let next_line_start = buffer.clip_point(
4022 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4023 Bias::Left,
4024 );
4025
4026 if line_start < original_range.start {
4027 head = line_start
4028 } else {
4029 head = next_line_start
4030 }
4031
4032 if head <= original_range.start {
4033 tail = original_range.end;
4034 } else {
4035 tail = original_range.start;
4036 }
4037 }
4038 SelectMode::All => {
4039 return;
4040 }
4041 };
4042
4043 if head < tail {
4044 pending.start = buffer.anchor_before(head);
4045 pending.end = buffer.anchor_before(tail);
4046 pending.reversed = true;
4047 } else {
4048 pending.start = buffer.anchor_before(tail);
4049 pending.end = buffer.anchor_before(head);
4050 pending.reversed = false;
4051 }
4052
4053 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4054 s.set_pending(pending.clone(), mode);
4055 });
4056 } else {
4057 log::error!("update_selection dispatched with no pending selection");
4058 return;
4059 }
4060
4061 self.apply_scroll_delta(scroll_delta, window, cx);
4062 cx.notify();
4063 }
4064
4065 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4066 self.columnar_selection_state.take();
4067 if let Some(pending_mode) = self.selections.pending_mode() {
4068 let selections = self
4069 .selections
4070 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4072 s.select(selections);
4073 s.clear_pending();
4074 if s.is_extending() {
4075 s.set_is_extending(false);
4076 } else {
4077 s.set_select_mode(pending_mode);
4078 }
4079 });
4080 }
4081 }
4082
4083 fn select_columns(
4084 &mut self,
4085 head: DisplayPoint,
4086 goal_column: u32,
4087 display_map: &DisplaySnapshot,
4088 window: &mut Window,
4089 cx: &mut Context<Self>,
4090 ) {
4091 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4092 return;
4093 };
4094
4095 let tail = match columnar_state {
4096 ColumnarSelectionState::FromMouse {
4097 selection_tail,
4098 display_point,
4099 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4100 ColumnarSelectionState::FromSelection { selection_tail } => {
4101 selection_tail.to_display_point(display_map)
4102 }
4103 };
4104
4105 let start_row = cmp::min(tail.row(), head.row());
4106 let end_row = cmp::max(tail.row(), head.row());
4107 let start_column = cmp::min(tail.column(), goal_column);
4108 let end_column = cmp::max(tail.column(), goal_column);
4109 let reversed = start_column < tail.column();
4110
4111 let selection_ranges = (start_row.0..=end_row.0)
4112 .map(DisplayRow)
4113 .filter_map(|row| {
4114 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4115 || start_column <= display_map.line_len(row))
4116 && !display_map.is_block_line(row)
4117 {
4118 let start = display_map
4119 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4120 .to_point(display_map);
4121 let end = display_map
4122 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4123 .to_point(display_map);
4124 if reversed {
4125 Some(end..start)
4126 } else {
4127 Some(start..end)
4128 }
4129 } else {
4130 None
4131 }
4132 })
4133 .collect::<Vec<_>>();
4134 if selection_ranges.is_empty() {
4135 return;
4136 }
4137
4138 let ranges = match columnar_state {
4139 ColumnarSelectionState::FromMouse { .. } => {
4140 let mut non_empty_ranges = selection_ranges
4141 .iter()
4142 .filter(|selection_range| selection_range.start != selection_range.end)
4143 .peekable();
4144 if non_empty_ranges.peek().is_some() {
4145 non_empty_ranges.cloned().collect()
4146 } else {
4147 selection_ranges
4148 }
4149 }
4150 _ => selection_ranges,
4151 };
4152
4153 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4154 s.select_ranges(ranges);
4155 });
4156 cx.notify();
4157 }
4158
4159 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4160 self.selections
4161 .all_adjusted(snapshot)
4162 .iter()
4163 .any(|selection| !selection.is_empty())
4164 }
4165
4166 pub fn has_pending_nonempty_selection(&self) -> bool {
4167 let pending_nonempty_selection = match self.selections.pending_anchor() {
4168 Some(Selection { start, end, .. }) => start != end,
4169 None => false,
4170 };
4171
4172 pending_nonempty_selection
4173 || (self.columnar_selection_state.is_some()
4174 && self.selections.disjoint_anchors().len() > 1)
4175 }
4176
4177 pub fn has_pending_selection(&self) -> bool {
4178 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4179 }
4180
4181 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4182 self.selection_mark_mode = false;
4183 self.selection_drag_state = SelectionDragState::None;
4184
4185 if self.dismiss_menus_and_popups(true, window, cx) {
4186 cx.notify();
4187 return;
4188 }
4189 if self.clear_expanded_diff_hunks(cx) {
4190 cx.notify();
4191 return;
4192 }
4193 if self.show_git_blame_gutter {
4194 self.show_git_blame_gutter = false;
4195 cx.notify();
4196 return;
4197 }
4198
4199 if self.mode.is_full()
4200 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4201 {
4202 cx.notify();
4203 return;
4204 }
4205
4206 cx.propagate();
4207 }
4208
4209 pub fn dismiss_menus_and_popups(
4210 &mut self,
4211 is_user_requested: bool,
4212 window: &mut Window,
4213 cx: &mut Context<Self>,
4214 ) -> bool {
4215 let mut dismissed = false;
4216
4217 dismissed |= self.take_rename(false, window, cx).is_some();
4218 dismissed |= self.hide_blame_popover(true, cx);
4219 dismissed |= hide_hover(self, cx);
4220 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4221 dismissed |= self.hide_context_menu(window, cx).is_some();
4222 dismissed |= self.mouse_context_menu.take().is_some();
4223 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4224 dismissed |= self.snippet_stack.pop().is_some();
4225
4226 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4227 self.dismiss_diagnostics(cx);
4228 dismissed = true;
4229 }
4230
4231 dismissed
4232 }
4233
4234 fn linked_editing_ranges_for(
4235 &self,
4236 selection: Range<text::Anchor>,
4237 cx: &App,
4238 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4239 if self.linked_edit_ranges.is_empty() {
4240 return None;
4241 }
4242 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4243 selection.end.buffer_id.and_then(|end_buffer_id| {
4244 if selection.start.buffer_id != Some(end_buffer_id) {
4245 return None;
4246 }
4247 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4248 let snapshot = buffer.read(cx).snapshot();
4249 self.linked_edit_ranges
4250 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4251 .map(|ranges| (ranges, snapshot, buffer))
4252 })?;
4253 use text::ToOffset as TO;
4254 // find offset from the start of current range to current cursor position
4255 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4256
4257 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4258 let start_difference = start_offset - start_byte_offset;
4259 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4260 let end_difference = end_offset - start_byte_offset;
4261 // Current range has associated linked ranges.
4262 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4263 for range in linked_ranges.iter() {
4264 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4265 let end_offset = start_offset + end_difference;
4266 let start_offset = start_offset + start_difference;
4267 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4268 continue;
4269 }
4270 if self.selections.disjoint_anchor_ranges().any(|s| {
4271 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4272 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4273 {
4274 return false;
4275 }
4276 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4277 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4278 }) {
4279 continue;
4280 }
4281 let start = buffer_snapshot.anchor_after(start_offset);
4282 let end = buffer_snapshot.anchor_after(end_offset);
4283 linked_edits
4284 .entry(buffer.clone())
4285 .or_default()
4286 .push(start..end);
4287 }
4288 Some(linked_edits)
4289 }
4290
4291 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4292 let text: Arc<str> = text.into();
4293
4294 if self.read_only(cx) {
4295 return;
4296 }
4297
4298 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4299
4300 self.unfold_buffers_with_selections(cx);
4301
4302 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4303 let mut bracket_inserted = false;
4304 let mut edits = Vec::new();
4305 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4306 let mut new_selections = Vec::with_capacity(selections.len());
4307 let mut new_autoclose_regions = Vec::new();
4308 let snapshot = self.buffer.read(cx).read(cx);
4309 let mut clear_linked_edit_ranges = false;
4310
4311 for (selection, autoclose_region) in
4312 self.selections_with_autoclose_regions(selections, &snapshot)
4313 {
4314 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4315 // Determine if the inserted text matches the opening or closing
4316 // bracket of any of this language's bracket pairs.
4317 let mut bracket_pair = None;
4318 let mut is_bracket_pair_start = false;
4319 let mut is_bracket_pair_end = false;
4320 if !text.is_empty() {
4321 let mut bracket_pair_matching_end = None;
4322 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4323 // and they are removing the character that triggered IME popup.
4324 for (pair, enabled) in scope.brackets() {
4325 if !pair.close && !pair.surround {
4326 continue;
4327 }
4328
4329 if enabled && pair.start.ends_with(text.as_ref()) {
4330 let prefix_len = pair.start.len() - text.len();
4331 let preceding_text_matches_prefix = prefix_len == 0
4332 || (selection.start.column >= (prefix_len as u32)
4333 && snapshot.contains_str_at(
4334 Point::new(
4335 selection.start.row,
4336 selection.start.column - (prefix_len as u32),
4337 ),
4338 &pair.start[..prefix_len],
4339 ));
4340 if preceding_text_matches_prefix {
4341 bracket_pair = Some(pair.clone());
4342 is_bracket_pair_start = true;
4343 break;
4344 }
4345 }
4346 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4347 {
4348 // take first bracket pair matching end, but don't break in case a later bracket
4349 // pair matches start
4350 bracket_pair_matching_end = Some(pair.clone());
4351 }
4352 }
4353 if let Some(end) = bracket_pair_matching_end
4354 && bracket_pair.is_none()
4355 {
4356 bracket_pair = Some(end);
4357 is_bracket_pair_end = true;
4358 }
4359 }
4360
4361 if let Some(bracket_pair) = bracket_pair {
4362 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4363 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4364 let auto_surround =
4365 self.use_auto_surround && snapshot_settings.use_auto_surround;
4366 if selection.is_empty() {
4367 if is_bracket_pair_start {
4368 // If the inserted text is a suffix of an opening bracket and the
4369 // selection is preceded by the rest of the opening bracket, then
4370 // insert the closing bracket.
4371 let following_text_allows_autoclose = snapshot
4372 .chars_at(selection.start)
4373 .next()
4374 .is_none_or(|c| scope.should_autoclose_before(c));
4375
4376 let preceding_text_allows_autoclose = selection.start.column == 0
4377 || snapshot
4378 .reversed_chars_at(selection.start)
4379 .next()
4380 .is_none_or(|c| {
4381 bracket_pair.start != bracket_pair.end
4382 || !snapshot
4383 .char_classifier_at(selection.start)
4384 .is_word(c)
4385 });
4386
4387 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4388 && bracket_pair.start.len() == 1
4389 {
4390 let target = bracket_pair.start.chars().next().unwrap();
4391 let current_line_count = snapshot
4392 .reversed_chars_at(selection.start)
4393 .take_while(|&c| c != '\n')
4394 .filter(|&c| c == target)
4395 .count();
4396 current_line_count % 2 == 1
4397 } else {
4398 false
4399 };
4400
4401 if autoclose
4402 && bracket_pair.close
4403 && following_text_allows_autoclose
4404 && preceding_text_allows_autoclose
4405 && !is_closing_quote
4406 {
4407 let anchor = snapshot.anchor_before(selection.end);
4408 new_selections.push((selection.map(|_| anchor), text.len()));
4409 new_autoclose_regions.push((
4410 anchor,
4411 text.len(),
4412 selection.id,
4413 bracket_pair.clone(),
4414 ));
4415 edits.push((
4416 selection.range(),
4417 format!("{}{}", text, bracket_pair.end).into(),
4418 ));
4419 bracket_inserted = true;
4420 continue;
4421 }
4422 }
4423
4424 if let Some(region) = autoclose_region {
4425 // If the selection is followed by an auto-inserted closing bracket,
4426 // then don't insert that closing bracket again; just move the selection
4427 // past the closing bracket.
4428 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4429 && text.as_ref() == region.pair.end.as_str()
4430 && snapshot.contains_str_at(region.range.end, text.as_ref());
4431 if should_skip {
4432 let anchor = snapshot.anchor_after(selection.end);
4433 new_selections
4434 .push((selection.map(|_| anchor), region.pair.end.len()));
4435 continue;
4436 }
4437 }
4438
4439 let always_treat_brackets_as_autoclosed = snapshot
4440 .language_settings_at(selection.start, cx)
4441 .always_treat_brackets_as_autoclosed;
4442 if always_treat_brackets_as_autoclosed
4443 && is_bracket_pair_end
4444 && snapshot.contains_str_at(selection.end, text.as_ref())
4445 {
4446 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4447 // and the inserted text is a closing bracket and the selection is followed
4448 // by the closing bracket then move the selection past the closing bracket.
4449 let anchor = snapshot.anchor_after(selection.end);
4450 new_selections.push((selection.map(|_| anchor), text.len()));
4451 continue;
4452 }
4453 }
4454 // If an opening bracket is 1 character long and is typed while
4455 // text is selected, then surround that text with the bracket pair.
4456 else if auto_surround
4457 && bracket_pair.surround
4458 && is_bracket_pair_start
4459 && bracket_pair.start.chars().count() == 1
4460 {
4461 edits.push((selection.start..selection.start, text.clone()));
4462 edits.push((
4463 selection.end..selection.end,
4464 bracket_pair.end.as_str().into(),
4465 ));
4466 bracket_inserted = true;
4467 new_selections.push((
4468 Selection {
4469 id: selection.id,
4470 start: snapshot.anchor_after(selection.start),
4471 end: snapshot.anchor_before(selection.end),
4472 reversed: selection.reversed,
4473 goal: selection.goal,
4474 },
4475 0,
4476 ));
4477 continue;
4478 }
4479 }
4480 }
4481
4482 if self.auto_replace_emoji_shortcode
4483 && selection.is_empty()
4484 && text.as_ref().ends_with(':')
4485 && let Some(possible_emoji_short_code) =
4486 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4487 && !possible_emoji_short_code.is_empty()
4488 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4489 {
4490 let emoji_shortcode_start = Point::new(
4491 selection.start.row,
4492 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4493 );
4494
4495 // Remove shortcode from buffer
4496 edits.push((
4497 emoji_shortcode_start..selection.start,
4498 "".to_string().into(),
4499 ));
4500 new_selections.push((
4501 Selection {
4502 id: selection.id,
4503 start: snapshot.anchor_after(emoji_shortcode_start),
4504 end: snapshot.anchor_before(selection.start),
4505 reversed: selection.reversed,
4506 goal: selection.goal,
4507 },
4508 0,
4509 ));
4510
4511 // Insert emoji
4512 let selection_start_anchor = snapshot.anchor_after(selection.start);
4513 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4514 edits.push((selection.start..selection.end, emoji.to_string().into()));
4515
4516 continue;
4517 }
4518
4519 // If not handling any auto-close operation, then just replace the selected
4520 // text with the given input and move the selection to the end of the
4521 // newly inserted text.
4522 let anchor = snapshot.anchor_after(selection.end);
4523 if !self.linked_edit_ranges.is_empty() {
4524 let start_anchor = snapshot.anchor_before(selection.start);
4525
4526 let is_word_char = text.chars().next().is_none_or(|char| {
4527 let classifier = snapshot
4528 .char_classifier_at(start_anchor.to_offset(&snapshot))
4529 .scope_context(Some(CharScopeContext::LinkedEdit));
4530 classifier.is_word(char)
4531 });
4532
4533 if is_word_char {
4534 if let Some(ranges) = self
4535 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4536 {
4537 for (buffer, edits) in ranges {
4538 linked_edits
4539 .entry(buffer.clone())
4540 .or_default()
4541 .extend(edits.into_iter().map(|range| (range, text.clone())));
4542 }
4543 }
4544 } else {
4545 clear_linked_edit_ranges = true;
4546 }
4547 }
4548
4549 new_selections.push((selection.map(|_| anchor), 0));
4550 edits.push((selection.start..selection.end, text.clone()));
4551 }
4552
4553 drop(snapshot);
4554
4555 self.transact(window, cx, |this, window, cx| {
4556 if clear_linked_edit_ranges {
4557 this.linked_edit_ranges.clear();
4558 }
4559 let initial_buffer_versions =
4560 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4561
4562 this.buffer.update(cx, |buffer, cx| {
4563 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4564 });
4565 for (buffer, edits) in linked_edits {
4566 buffer.update(cx, |buffer, cx| {
4567 let snapshot = buffer.snapshot();
4568 let edits = edits
4569 .into_iter()
4570 .map(|(range, text)| {
4571 use text::ToPoint as TP;
4572 let end_point = TP::to_point(&range.end, &snapshot);
4573 let start_point = TP::to_point(&range.start, &snapshot);
4574 (start_point..end_point, text)
4575 })
4576 .sorted_by_key(|(range, _)| range.start);
4577 buffer.edit(edits, None, cx);
4578 })
4579 }
4580 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4581 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4582 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4583 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4584 new_anchor_selections,
4585 &map,
4586 )
4587 .zip(new_selection_deltas)
4588 .map(|(selection, delta)| Selection {
4589 id: selection.id,
4590 start: selection.start + delta,
4591 end: selection.end + delta,
4592 reversed: selection.reversed,
4593 goal: SelectionGoal::None,
4594 })
4595 .collect::<Vec<_>>();
4596
4597 let mut i = 0;
4598 for (position, delta, selection_id, pair) in new_autoclose_regions {
4599 let position = position.to_offset(map.buffer_snapshot()) + delta;
4600 let start = map.buffer_snapshot().anchor_before(position);
4601 let end = map.buffer_snapshot().anchor_after(position);
4602 while let Some(existing_state) = this.autoclose_regions.get(i) {
4603 match existing_state
4604 .range
4605 .start
4606 .cmp(&start, map.buffer_snapshot())
4607 {
4608 Ordering::Less => i += 1,
4609 Ordering::Greater => break,
4610 Ordering::Equal => {
4611 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4612 Ordering::Less => i += 1,
4613 Ordering::Equal => break,
4614 Ordering::Greater => break,
4615 }
4616 }
4617 }
4618 }
4619 this.autoclose_regions.insert(
4620 i,
4621 AutocloseRegion {
4622 selection_id,
4623 range: start..end,
4624 pair,
4625 },
4626 );
4627 }
4628
4629 let had_active_edit_prediction = this.has_active_edit_prediction();
4630 this.change_selections(
4631 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4632 window,
4633 cx,
4634 |s| s.select(new_selections),
4635 );
4636
4637 if !bracket_inserted
4638 && let Some(on_type_format_task) =
4639 this.trigger_on_type_formatting(text.to_string(), window, cx)
4640 {
4641 on_type_format_task.detach_and_log_err(cx);
4642 }
4643
4644 let editor_settings = EditorSettings::get_global(cx);
4645 if bracket_inserted
4646 && (editor_settings.auto_signature_help
4647 || editor_settings.show_signature_help_after_edits)
4648 {
4649 this.show_signature_help(&ShowSignatureHelp, window, cx);
4650 }
4651
4652 let trigger_in_words =
4653 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4654 if this.hard_wrap.is_some() {
4655 let latest: Range<Point> = this.selections.newest(&map).range();
4656 if latest.is_empty()
4657 && this
4658 .buffer()
4659 .read(cx)
4660 .snapshot(cx)
4661 .line_len(MultiBufferRow(latest.start.row))
4662 == latest.start.column
4663 {
4664 this.rewrap_impl(
4665 RewrapOptions {
4666 override_language_settings: true,
4667 preserve_existing_whitespace: true,
4668 },
4669 cx,
4670 )
4671 }
4672 }
4673 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4674 refresh_linked_ranges(this, window, cx);
4675 this.refresh_edit_prediction(true, false, window, cx);
4676 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4677 });
4678 }
4679
4680 fn find_possible_emoji_shortcode_at_position(
4681 snapshot: &MultiBufferSnapshot,
4682 position: Point,
4683 ) -> Option<String> {
4684 let mut chars = Vec::new();
4685 let mut found_colon = false;
4686 for char in snapshot.reversed_chars_at(position).take(100) {
4687 // Found a possible emoji shortcode in the middle of the buffer
4688 if found_colon {
4689 if char.is_whitespace() {
4690 chars.reverse();
4691 return Some(chars.iter().collect());
4692 }
4693 // If the previous character is not a whitespace, we are in the middle of a word
4694 // and we only want to complete the shortcode if the word is made up of other emojis
4695 let mut containing_word = String::new();
4696 for ch in snapshot
4697 .reversed_chars_at(position)
4698 .skip(chars.len() + 1)
4699 .take(100)
4700 {
4701 if ch.is_whitespace() {
4702 break;
4703 }
4704 containing_word.push(ch);
4705 }
4706 let containing_word = containing_word.chars().rev().collect::<String>();
4707 if util::word_consists_of_emojis(containing_word.as_str()) {
4708 chars.reverse();
4709 return Some(chars.iter().collect());
4710 }
4711 }
4712
4713 if char.is_whitespace() || !char.is_ascii() {
4714 return None;
4715 }
4716 if char == ':' {
4717 found_colon = true;
4718 } else {
4719 chars.push(char);
4720 }
4721 }
4722 // Found a possible emoji shortcode at the beginning of the buffer
4723 chars.reverse();
4724 Some(chars.iter().collect())
4725 }
4726
4727 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4729 self.transact(window, cx, |this, window, cx| {
4730 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4731 let selections = this
4732 .selections
4733 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4734 let multi_buffer = this.buffer.read(cx);
4735 let buffer = multi_buffer.snapshot(cx);
4736 selections
4737 .iter()
4738 .map(|selection| {
4739 let start_point = selection.start.to_point(&buffer);
4740 let mut existing_indent =
4741 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4742 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4743 let start = selection.start;
4744 let end = selection.end;
4745 let selection_is_empty = start == end;
4746 let language_scope = buffer.language_scope_at(start);
4747 let (
4748 comment_delimiter,
4749 doc_delimiter,
4750 insert_extra_newline,
4751 indent_on_newline,
4752 indent_on_extra_newline,
4753 ) = if let Some(language) = &language_scope {
4754 let mut insert_extra_newline =
4755 insert_extra_newline_brackets(&buffer, start..end, language)
4756 || insert_extra_newline_tree_sitter(&buffer, start..end);
4757
4758 // Comment extension on newline is allowed only for cursor selections
4759 let comment_delimiter = maybe!({
4760 if !selection_is_empty {
4761 return None;
4762 }
4763
4764 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4765 return None;
4766 }
4767
4768 let delimiters = language.line_comment_prefixes();
4769 let max_len_of_delimiter =
4770 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4771 let (snapshot, range) =
4772 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4773
4774 let num_of_whitespaces = snapshot
4775 .chars_for_range(range.clone())
4776 .take_while(|c| c.is_whitespace())
4777 .count();
4778 let comment_candidate = snapshot
4779 .chars_for_range(range.clone())
4780 .skip(num_of_whitespaces)
4781 .take(max_len_of_delimiter)
4782 .collect::<String>();
4783 let (delimiter, trimmed_len) = delimiters
4784 .iter()
4785 .filter_map(|delimiter| {
4786 let prefix = delimiter.trim_end();
4787 if comment_candidate.starts_with(prefix) {
4788 Some((delimiter, prefix.len()))
4789 } else {
4790 None
4791 }
4792 })
4793 .max_by_key(|(_, len)| *len)?;
4794
4795 if let Some(BlockCommentConfig {
4796 start: block_start, ..
4797 }) = language.block_comment()
4798 {
4799 let block_start_trimmed = block_start.trim_end();
4800 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4801 let line_content = snapshot
4802 .chars_for_range(range)
4803 .skip(num_of_whitespaces)
4804 .take(block_start_trimmed.len())
4805 .collect::<String>();
4806
4807 if line_content.starts_with(block_start_trimmed) {
4808 return None;
4809 }
4810 }
4811 }
4812
4813 let cursor_is_placed_after_comment_marker =
4814 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4815 if cursor_is_placed_after_comment_marker {
4816 Some(delimiter.clone())
4817 } else {
4818 None
4819 }
4820 });
4821
4822 let mut indent_on_newline = IndentSize::spaces(0);
4823 let mut indent_on_extra_newline = IndentSize::spaces(0);
4824
4825 let doc_delimiter = maybe!({
4826 if !selection_is_empty {
4827 return None;
4828 }
4829
4830 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4831 return None;
4832 }
4833
4834 let BlockCommentConfig {
4835 start: start_tag,
4836 end: end_tag,
4837 prefix: delimiter,
4838 tab_size: len,
4839 } = language.documentation_comment()?;
4840 let is_within_block_comment = buffer
4841 .language_scope_at(start_point)
4842 .is_some_and(|scope| scope.override_name() == Some("comment"));
4843 if !is_within_block_comment {
4844 return None;
4845 }
4846
4847 let (snapshot, range) =
4848 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4849
4850 let num_of_whitespaces = snapshot
4851 .chars_for_range(range.clone())
4852 .take_while(|c| c.is_whitespace())
4853 .count();
4854
4855 // 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.
4856 let column = start_point.column;
4857 let cursor_is_after_start_tag = {
4858 let start_tag_len = start_tag.len();
4859 let start_tag_line = snapshot
4860 .chars_for_range(range.clone())
4861 .skip(num_of_whitespaces)
4862 .take(start_tag_len)
4863 .collect::<String>();
4864 if start_tag_line.starts_with(start_tag.as_ref()) {
4865 num_of_whitespaces + start_tag_len <= column as usize
4866 } else {
4867 false
4868 }
4869 };
4870
4871 let cursor_is_after_delimiter = {
4872 let delimiter_trim = delimiter.trim_end();
4873 let delimiter_line = snapshot
4874 .chars_for_range(range.clone())
4875 .skip(num_of_whitespaces)
4876 .take(delimiter_trim.len())
4877 .collect::<String>();
4878 if delimiter_line.starts_with(delimiter_trim) {
4879 num_of_whitespaces + delimiter_trim.len() <= column as usize
4880 } else {
4881 false
4882 }
4883 };
4884
4885 let cursor_is_before_end_tag_if_exists = {
4886 let mut char_position = 0u32;
4887 let mut end_tag_offset = None;
4888
4889 'outer: for chunk in snapshot.text_for_range(range) {
4890 if let Some(byte_pos) = chunk.find(&**end_tag) {
4891 let chars_before_match =
4892 chunk[..byte_pos].chars().count() as u32;
4893 end_tag_offset =
4894 Some(char_position + chars_before_match);
4895 break 'outer;
4896 }
4897 char_position += chunk.chars().count() as u32;
4898 }
4899
4900 if let Some(end_tag_offset) = end_tag_offset {
4901 let cursor_is_before_end_tag = column <= end_tag_offset;
4902 if cursor_is_after_start_tag {
4903 if cursor_is_before_end_tag {
4904 insert_extra_newline = true;
4905 }
4906 let cursor_is_at_start_of_end_tag =
4907 column == end_tag_offset;
4908 if cursor_is_at_start_of_end_tag {
4909 indent_on_extra_newline.len = *len;
4910 }
4911 }
4912 cursor_is_before_end_tag
4913 } else {
4914 true
4915 }
4916 };
4917
4918 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4919 && cursor_is_before_end_tag_if_exists
4920 {
4921 if cursor_is_after_start_tag {
4922 indent_on_newline.len = *len;
4923 }
4924 Some(delimiter.clone())
4925 } else {
4926 None
4927 }
4928 });
4929
4930 (
4931 comment_delimiter,
4932 doc_delimiter,
4933 insert_extra_newline,
4934 indent_on_newline,
4935 indent_on_extra_newline,
4936 )
4937 } else {
4938 (
4939 None,
4940 None,
4941 false,
4942 IndentSize::default(),
4943 IndentSize::default(),
4944 )
4945 };
4946
4947 let prevent_auto_indent = doc_delimiter.is_some();
4948 let delimiter = comment_delimiter.or(doc_delimiter);
4949
4950 let capacity_for_delimiter =
4951 delimiter.as_deref().map(str::len).unwrap_or_default();
4952 let mut new_text = String::with_capacity(
4953 1 + capacity_for_delimiter
4954 + existing_indent.len as usize
4955 + indent_on_newline.len as usize
4956 + indent_on_extra_newline.len as usize,
4957 );
4958 new_text.push('\n');
4959 new_text.extend(existing_indent.chars());
4960 new_text.extend(indent_on_newline.chars());
4961
4962 if let Some(delimiter) = &delimiter {
4963 new_text.push_str(delimiter);
4964 }
4965
4966 if insert_extra_newline {
4967 new_text.push('\n');
4968 new_text.extend(existing_indent.chars());
4969 new_text.extend(indent_on_extra_newline.chars());
4970 }
4971
4972 let anchor = buffer.anchor_after(end);
4973 let new_selection = selection.map(|_| anchor);
4974 (
4975 ((start..end, new_text), prevent_auto_indent),
4976 (insert_extra_newline, new_selection),
4977 )
4978 })
4979 .unzip()
4980 };
4981
4982 let mut auto_indent_edits = Vec::new();
4983 let mut edits = Vec::new();
4984 for (edit, prevent_auto_indent) in edits_with_flags {
4985 if prevent_auto_indent {
4986 edits.push(edit);
4987 } else {
4988 auto_indent_edits.push(edit);
4989 }
4990 }
4991 if !edits.is_empty() {
4992 this.edit(edits, cx);
4993 }
4994 if !auto_indent_edits.is_empty() {
4995 this.edit_with_autoindent(auto_indent_edits, cx);
4996 }
4997
4998 let buffer = this.buffer.read(cx).snapshot(cx);
4999 let new_selections = selection_info
5000 .into_iter()
5001 .map(|(extra_newline_inserted, new_selection)| {
5002 let mut cursor = new_selection.end.to_point(&buffer);
5003 if extra_newline_inserted {
5004 cursor.row -= 1;
5005 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5006 }
5007 new_selection.map(|_| cursor)
5008 })
5009 .collect();
5010
5011 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5012 this.refresh_edit_prediction(true, false, window, cx);
5013 });
5014 }
5015
5016 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5018
5019 let buffer = self.buffer.read(cx);
5020 let snapshot = buffer.snapshot(cx);
5021
5022 let mut edits = Vec::new();
5023 let mut rows = Vec::new();
5024
5025 for (rows_inserted, selection) in self
5026 .selections
5027 .all_adjusted(&self.display_snapshot(cx))
5028 .into_iter()
5029 .enumerate()
5030 {
5031 let cursor = selection.head();
5032 let row = cursor.row;
5033
5034 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5035
5036 let newline = "\n".to_string();
5037 edits.push((start_of_line..start_of_line, newline));
5038
5039 rows.push(row + rows_inserted as u32);
5040 }
5041
5042 self.transact(window, cx, |editor, window, cx| {
5043 editor.edit(edits, cx);
5044
5045 editor.change_selections(Default::default(), window, cx, |s| {
5046 let mut index = 0;
5047 s.move_cursors_with(|map, _, _| {
5048 let row = rows[index];
5049 index += 1;
5050
5051 let point = Point::new(row, 0);
5052 let boundary = map.next_line_boundary(point).1;
5053 let clipped = map.clip_point(boundary, Bias::Left);
5054
5055 (clipped, SelectionGoal::None)
5056 });
5057 });
5058
5059 let mut indent_edits = Vec::new();
5060 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5061 for row in rows {
5062 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5063 for (row, indent) in indents {
5064 if indent.len == 0 {
5065 continue;
5066 }
5067
5068 let text = match indent.kind {
5069 IndentKind::Space => " ".repeat(indent.len as usize),
5070 IndentKind::Tab => "\t".repeat(indent.len as usize),
5071 };
5072 let point = Point::new(row.0, 0);
5073 indent_edits.push((point..point, text));
5074 }
5075 }
5076 editor.edit(indent_edits, cx);
5077 });
5078 }
5079
5080 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5082
5083 let buffer = self.buffer.read(cx);
5084 let snapshot = buffer.snapshot(cx);
5085
5086 let mut edits = Vec::new();
5087 let mut rows = Vec::new();
5088 let mut rows_inserted = 0;
5089
5090 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5091 let cursor = selection.head();
5092 let row = cursor.row;
5093
5094 let point = Point::new(row + 1, 0);
5095 let start_of_line = snapshot.clip_point(point, Bias::Left);
5096
5097 let newline = "\n".to_string();
5098 edits.push((start_of_line..start_of_line, newline));
5099
5100 rows_inserted += 1;
5101 rows.push(row + rows_inserted);
5102 }
5103
5104 self.transact(window, cx, |editor, window, cx| {
5105 editor.edit(edits, cx);
5106
5107 editor.change_selections(Default::default(), window, cx, |s| {
5108 let mut index = 0;
5109 s.move_cursors_with(|map, _, _| {
5110 let row = rows[index];
5111 index += 1;
5112
5113 let point = Point::new(row, 0);
5114 let boundary = map.next_line_boundary(point).1;
5115 let clipped = map.clip_point(boundary, Bias::Left);
5116
5117 (clipped, SelectionGoal::None)
5118 });
5119 });
5120
5121 let mut indent_edits = Vec::new();
5122 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5123 for row in rows {
5124 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5125 for (row, indent) in indents {
5126 if indent.len == 0 {
5127 continue;
5128 }
5129
5130 let text = match indent.kind {
5131 IndentKind::Space => " ".repeat(indent.len as usize),
5132 IndentKind::Tab => "\t".repeat(indent.len as usize),
5133 };
5134 let point = Point::new(row.0, 0);
5135 indent_edits.push((point..point, text));
5136 }
5137 }
5138 editor.edit(indent_edits, cx);
5139 });
5140 }
5141
5142 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5143 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5144 original_indent_columns: Vec::new(),
5145 });
5146 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5147 }
5148
5149 fn insert_with_autoindent_mode(
5150 &mut self,
5151 text: &str,
5152 autoindent_mode: Option<AutoindentMode>,
5153 window: &mut Window,
5154 cx: &mut Context<Self>,
5155 ) {
5156 if self.read_only(cx) {
5157 return;
5158 }
5159
5160 let text: Arc<str> = text.into();
5161 self.transact(window, cx, |this, window, cx| {
5162 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5163 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5164 let anchors = {
5165 let snapshot = buffer.read(cx);
5166 old_selections
5167 .iter()
5168 .map(|s| {
5169 let anchor = snapshot.anchor_after(s.head());
5170 s.map(|_| anchor)
5171 })
5172 .collect::<Vec<_>>()
5173 };
5174 buffer.edit(
5175 old_selections
5176 .iter()
5177 .map(|s| (s.start..s.end, text.clone())),
5178 autoindent_mode,
5179 cx,
5180 );
5181 anchors
5182 });
5183
5184 this.change_selections(Default::default(), window, cx, |s| {
5185 s.select_anchors(selection_anchors);
5186 });
5187
5188 cx.notify();
5189 });
5190 }
5191
5192 fn trigger_completion_on_input(
5193 &mut self,
5194 text: &str,
5195 trigger_in_words: bool,
5196 window: &mut Window,
5197 cx: &mut Context<Self>,
5198 ) {
5199 let completions_source = self
5200 .context_menu
5201 .borrow()
5202 .as_ref()
5203 .and_then(|menu| match menu {
5204 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5205 CodeContextMenu::CodeActions(_) => None,
5206 });
5207
5208 match completions_source {
5209 Some(CompletionsMenuSource::Words { .. }) => {
5210 self.open_or_update_completions_menu(
5211 Some(CompletionsMenuSource::Words {
5212 ignore_threshold: false,
5213 }),
5214 None,
5215 trigger_in_words,
5216 window,
5217 cx,
5218 );
5219 }
5220 _ => self.open_or_update_completions_menu(
5221 None,
5222 Some(text.to_owned()).filter(|x| !x.is_empty()),
5223 true,
5224 window,
5225 cx,
5226 ),
5227 }
5228 }
5229
5230 /// If any empty selections is touching the start of its innermost containing autoclose
5231 /// region, expand it to select the brackets.
5232 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5233 let selections = self
5234 .selections
5235 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5236 let buffer = self.buffer.read(cx).read(cx);
5237 let new_selections = self
5238 .selections_with_autoclose_regions(selections, &buffer)
5239 .map(|(mut selection, region)| {
5240 if !selection.is_empty() {
5241 return selection;
5242 }
5243
5244 if let Some(region) = region {
5245 let mut range = region.range.to_offset(&buffer);
5246 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5247 range.start -= region.pair.start.len();
5248 if buffer.contains_str_at(range.start, ®ion.pair.start)
5249 && buffer.contains_str_at(range.end, ®ion.pair.end)
5250 {
5251 range.end += region.pair.end.len();
5252 selection.start = range.start;
5253 selection.end = range.end;
5254
5255 return selection;
5256 }
5257 }
5258 }
5259
5260 let always_treat_brackets_as_autoclosed = buffer
5261 .language_settings_at(selection.start, cx)
5262 .always_treat_brackets_as_autoclosed;
5263
5264 if !always_treat_brackets_as_autoclosed {
5265 return selection;
5266 }
5267
5268 if let Some(scope) = buffer.language_scope_at(selection.start) {
5269 for (pair, enabled) in scope.brackets() {
5270 if !enabled || !pair.close {
5271 continue;
5272 }
5273
5274 if buffer.contains_str_at(selection.start, &pair.end) {
5275 let pair_start_len = pair.start.len();
5276 if buffer.contains_str_at(
5277 selection.start.saturating_sub_usize(pair_start_len),
5278 &pair.start,
5279 ) {
5280 selection.start -= pair_start_len;
5281 selection.end += pair.end.len();
5282
5283 return selection;
5284 }
5285 }
5286 }
5287 }
5288
5289 selection
5290 })
5291 .collect();
5292
5293 drop(buffer);
5294 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5295 selections.select(new_selections)
5296 });
5297 }
5298
5299 /// Iterate the given selections, and for each one, find the smallest surrounding
5300 /// autoclose region. This uses the ordering of the selections and the autoclose
5301 /// regions to avoid repeated comparisons.
5302 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5303 &'a self,
5304 selections: impl IntoIterator<Item = Selection<D>>,
5305 buffer: &'a MultiBufferSnapshot,
5306 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5307 let mut i = 0;
5308 let mut regions = self.autoclose_regions.as_slice();
5309 selections.into_iter().map(move |selection| {
5310 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5311
5312 let mut enclosing = None;
5313 while let Some(pair_state) = regions.get(i) {
5314 if pair_state.range.end.to_offset(buffer) < range.start {
5315 regions = ®ions[i + 1..];
5316 i = 0;
5317 } else if pair_state.range.start.to_offset(buffer) > range.end {
5318 break;
5319 } else {
5320 if pair_state.selection_id == selection.id {
5321 enclosing = Some(pair_state);
5322 }
5323 i += 1;
5324 }
5325 }
5326
5327 (selection, enclosing)
5328 })
5329 }
5330
5331 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5332 fn invalidate_autoclose_regions(
5333 &mut self,
5334 mut selections: &[Selection<Anchor>],
5335 buffer: &MultiBufferSnapshot,
5336 ) {
5337 self.autoclose_regions.retain(|state| {
5338 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5339 return false;
5340 }
5341
5342 let mut i = 0;
5343 while let Some(selection) = selections.get(i) {
5344 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5345 selections = &selections[1..];
5346 continue;
5347 }
5348 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5349 break;
5350 }
5351 if selection.id == state.selection_id {
5352 return true;
5353 } else {
5354 i += 1;
5355 }
5356 }
5357 false
5358 });
5359 }
5360
5361 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5362 let offset = position.to_offset(buffer);
5363 let (word_range, kind) =
5364 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5365 if offset > word_range.start && kind == Some(CharKind::Word) {
5366 Some(
5367 buffer
5368 .text_for_range(word_range.start..offset)
5369 .collect::<String>(),
5370 )
5371 } else {
5372 None
5373 }
5374 }
5375
5376 pub fn visible_excerpts(
5377 &self,
5378 lsp_related_only: bool,
5379 cx: &mut Context<Editor>,
5380 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5381 let project = self.project().cloned();
5382 let multi_buffer = self.buffer().read(cx);
5383 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5384 let multi_buffer_visible_start = self
5385 .scroll_manager
5386 .anchor()
5387 .anchor
5388 .to_point(&multi_buffer_snapshot);
5389 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5390 multi_buffer_visible_start
5391 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5392 Bias::Left,
5393 );
5394 multi_buffer_snapshot
5395 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5396 .into_iter()
5397 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5398 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5399 if !lsp_related_only {
5400 return Some((
5401 excerpt_id,
5402 (
5403 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5404 buffer.version().clone(),
5405 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5406 ),
5407 ));
5408 }
5409
5410 let project = project.as_ref()?.read(cx);
5411 let buffer_file = project::File::from_dyn(buffer.file())?;
5412 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5413 let worktree_entry = buffer_worktree
5414 .read(cx)
5415 .entry_for_id(buffer_file.project_entry_id()?)?;
5416 if worktree_entry.is_ignored {
5417 None
5418 } else {
5419 Some((
5420 excerpt_id,
5421 (
5422 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5423 buffer.version().clone(),
5424 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5425 ),
5426 ))
5427 }
5428 })
5429 .collect()
5430 }
5431
5432 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5433 TextLayoutDetails {
5434 text_system: window.text_system().clone(),
5435 editor_style: self.style.clone().unwrap(),
5436 rem_size: window.rem_size(),
5437 scroll_anchor: self.scroll_manager.anchor(),
5438 visible_rows: self.visible_line_count(),
5439 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5440 }
5441 }
5442
5443 fn trigger_on_type_formatting(
5444 &self,
5445 input: String,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) -> Option<Task<Result<()>>> {
5449 if input.len() != 1 {
5450 return None;
5451 }
5452
5453 let project = self.project()?;
5454 let position = self.selections.newest_anchor().head();
5455 let (buffer, buffer_position) = self
5456 .buffer
5457 .read(cx)
5458 .text_anchor_for_position(position, cx)?;
5459
5460 let settings = language_settings::language_settings(
5461 buffer
5462 .read(cx)
5463 .language_at(buffer_position)
5464 .map(|l| l.name()),
5465 buffer.read(cx).file(),
5466 cx,
5467 );
5468 if !settings.use_on_type_format {
5469 return None;
5470 }
5471
5472 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5473 // hence we do LSP request & edit on host side only — add formats to host's history.
5474 let push_to_lsp_host_history = true;
5475 // If this is not the host, append its history with new edits.
5476 let push_to_client_history = project.read(cx).is_via_collab();
5477
5478 let on_type_formatting = project.update(cx, |project, cx| {
5479 project.on_type_format(
5480 buffer.clone(),
5481 buffer_position,
5482 input,
5483 push_to_lsp_host_history,
5484 cx,
5485 )
5486 });
5487 Some(cx.spawn_in(window, async move |editor, cx| {
5488 if let Some(transaction) = on_type_formatting.await? {
5489 if push_to_client_history {
5490 buffer
5491 .update(cx, |buffer, _| {
5492 buffer.push_transaction(transaction, Instant::now());
5493 buffer.finalize_last_transaction();
5494 })
5495 .ok();
5496 }
5497 editor.update(cx, |editor, cx| {
5498 editor.refresh_document_highlights(cx);
5499 })?;
5500 }
5501 Ok(())
5502 }))
5503 }
5504
5505 pub fn show_word_completions(
5506 &mut self,
5507 _: &ShowWordCompletions,
5508 window: &mut Window,
5509 cx: &mut Context<Self>,
5510 ) {
5511 self.open_or_update_completions_menu(
5512 Some(CompletionsMenuSource::Words {
5513 ignore_threshold: true,
5514 }),
5515 None,
5516 false,
5517 window,
5518 cx,
5519 );
5520 }
5521
5522 pub fn show_completions(
5523 &mut self,
5524 _: &ShowCompletions,
5525 window: &mut Window,
5526 cx: &mut Context<Self>,
5527 ) {
5528 self.open_or_update_completions_menu(None, None, false, window, cx);
5529 }
5530
5531 fn open_or_update_completions_menu(
5532 &mut self,
5533 requested_source: Option<CompletionsMenuSource>,
5534 trigger: Option<String>,
5535 trigger_in_words: bool,
5536 window: &mut Window,
5537 cx: &mut Context<Self>,
5538 ) {
5539 if self.pending_rename.is_some() {
5540 return;
5541 }
5542
5543 let completions_source = self
5544 .context_menu
5545 .borrow()
5546 .as_ref()
5547 .and_then(|menu| match menu {
5548 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5549 CodeContextMenu::CodeActions(_) => None,
5550 });
5551
5552 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5553
5554 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5555 // inserted and selected. To handle that case, the start of the selection is used so that
5556 // the menu starts with all choices.
5557 let position = self
5558 .selections
5559 .newest_anchor()
5560 .start
5561 .bias_right(&multibuffer_snapshot);
5562 if position.diff_base_anchor.is_some() {
5563 return;
5564 }
5565 let buffer_position = multibuffer_snapshot.anchor_before(position);
5566 let Some(buffer) = buffer_position
5567 .text_anchor
5568 .buffer_id
5569 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5570 else {
5571 return;
5572 };
5573 let buffer_snapshot = buffer.read(cx).snapshot();
5574
5575 let menu_is_open = matches!(
5576 self.context_menu.borrow().as_ref(),
5577 Some(CodeContextMenu::Completions(_))
5578 );
5579
5580 let language = buffer_snapshot
5581 .language_at(buffer_position.text_anchor)
5582 .map(|language| language.name());
5583
5584 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5585 let completion_settings = language_settings.completions.clone();
5586
5587 let show_completions_on_input = self
5588 .show_completions_on_input_override
5589 .unwrap_or(language_settings.show_completions_on_input);
5590 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5591 return;
5592 }
5593
5594 let query: Option<Arc<String>> =
5595 Self::completion_query(&multibuffer_snapshot, buffer_position)
5596 .map(|query| query.into());
5597
5598 drop(multibuffer_snapshot);
5599
5600 // Hide the current completions menu when query is empty. Without this, cached
5601 // completions from before the trigger char may be reused (#32774).
5602 if query.is_none() && menu_is_open {
5603 self.hide_context_menu(window, cx);
5604 }
5605
5606 let mut ignore_word_threshold = false;
5607 let provider = match requested_source {
5608 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5609 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5610 ignore_word_threshold = ignore_threshold;
5611 None
5612 }
5613 Some(CompletionsMenuSource::SnippetChoices)
5614 | Some(CompletionsMenuSource::SnippetsOnly) => {
5615 log::error!("bug: SnippetChoices requested_source is not handled");
5616 None
5617 }
5618 };
5619
5620 let sort_completions = provider
5621 .as_ref()
5622 .is_some_and(|provider| provider.sort_completions());
5623
5624 let filter_completions = provider
5625 .as_ref()
5626 .is_none_or(|provider| provider.filter_completions());
5627
5628 let was_snippets_only = matches!(
5629 completions_source,
5630 Some(CompletionsMenuSource::SnippetsOnly)
5631 );
5632
5633 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5634 if filter_completions {
5635 menu.filter(
5636 query.clone().unwrap_or_default(),
5637 buffer_position.text_anchor,
5638 &buffer,
5639 provider.clone(),
5640 window,
5641 cx,
5642 );
5643 }
5644 // When `is_incomplete` is false, no need to re-query completions when the current query
5645 // is a suffix of the initial query.
5646 let was_complete = !menu.is_incomplete;
5647 if was_complete && !was_snippets_only {
5648 // If the new query is a suffix of the old query (typing more characters) and
5649 // the previous result was complete, the existing completions can be filtered.
5650 //
5651 // Note that snippet completions are always complete.
5652 let query_matches = match (&menu.initial_query, &query) {
5653 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5654 (None, _) => true,
5655 _ => false,
5656 };
5657 if query_matches {
5658 let position_matches = if menu.initial_position == position {
5659 true
5660 } else {
5661 let snapshot = self.buffer.read(cx).read(cx);
5662 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5663 };
5664 if position_matches {
5665 return;
5666 }
5667 }
5668 }
5669 };
5670
5671 let Anchor {
5672 excerpt_id: buffer_excerpt_id,
5673 text_anchor: buffer_position,
5674 ..
5675 } = buffer_position;
5676
5677 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5678 buffer_snapshot.surrounding_word(buffer_position, None)
5679 {
5680 let word_to_exclude = buffer_snapshot
5681 .text_for_range(word_range.clone())
5682 .collect::<String>();
5683 (
5684 buffer_snapshot.anchor_before(word_range.start)
5685 ..buffer_snapshot.anchor_after(buffer_position),
5686 Some(word_to_exclude),
5687 )
5688 } else {
5689 (buffer_position..buffer_position, None)
5690 };
5691
5692 let show_completion_documentation = buffer_snapshot
5693 .settings_at(buffer_position, cx)
5694 .show_completion_documentation;
5695
5696 // The document can be large, so stay in reasonable bounds when searching for words,
5697 // otherwise completion pop-up might be slow to appear.
5698 const WORD_LOOKUP_ROWS: u32 = 5_000;
5699 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5700 let min_word_search = buffer_snapshot.clip_point(
5701 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5702 Bias::Left,
5703 );
5704 let max_word_search = buffer_snapshot.clip_point(
5705 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5706 Bias::Right,
5707 );
5708 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5709 ..buffer_snapshot.point_to_offset(max_word_search);
5710
5711 let skip_digits = query
5712 .as_ref()
5713 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5714
5715 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5716 trigger.as_ref().is_none_or(|trigger| {
5717 provider.is_completion_trigger(
5718 &buffer,
5719 position.text_anchor,
5720 trigger,
5721 trigger_in_words,
5722 cx,
5723 )
5724 })
5725 });
5726
5727 let provider_responses = if let Some(provider) = &provider
5728 && load_provider_completions
5729 {
5730 let trigger_character =
5731 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5732 let completion_context = CompletionContext {
5733 trigger_kind: match &trigger_character {
5734 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5735 None => CompletionTriggerKind::INVOKED,
5736 },
5737 trigger_character,
5738 };
5739
5740 provider.completions(
5741 buffer_excerpt_id,
5742 &buffer,
5743 buffer_position,
5744 completion_context,
5745 window,
5746 cx,
5747 )
5748 } else {
5749 Task::ready(Ok(Vec::new()))
5750 };
5751
5752 let load_word_completions = if !self.word_completions_enabled {
5753 false
5754 } else if requested_source
5755 == Some(CompletionsMenuSource::Words {
5756 ignore_threshold: true,
5757 })
5758 {
5759 true
5760 } else {
5761 load_provider_completions
5762 && completion_settings.words != WordsCompletionMode::Disabled
5763 && (ignore_word_threshold || {
5764 let words_min_length = completion_settings.words_min_length;
5765 // check whether word has at least `words_min_length` characters
5766 let query_chars = query.iter().flat_map(|q| q.chars());
5767 query_chars.take(words_min_length).count() == words_min_length
5768 })
5769 };
5770
5771 let mut words = if load_word_completions {
5772 cx.background_spawn({
5773 let buffer_snapshot = buffer_snapshot.clone();
5774 async move {
5775 buffer_snapshot.words_in_range(WordsQuery {
5776 fuzzy_contents: None,
5777 range: word_search_range,
5778 skip_digits,
5779 })
5780 }
5781 })
5782 } else {
5783 Task::ready(BTreeMap::default())
5784 };
5785
5786 let snippets = if let Some(provider) = &provider
5787 && provider.show_snippets()
5788 && let Some(project) = self.project()
5789 {
5790 let char_classifier = buffer_snapshot
5791 .char_classifier_at(buffer_position)
5792 .scope_context(Some(CharScopeContext::Completion));
5793 project.update(cx, |project, cx| {
5794 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5795 })
5796 } else {
5797 Task::ready(Ok(CompletionResponse {
5798 completions: Vec::new(),
5799 display_options: Default::default(),
5800 is_incomplete: false,
5801 }))
5802 };
5803
5804 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5805
5806 let id = post_inc(&mut self.next_completion_id);
5807 let task = cx.spawn_in(window, async move |editor, cx| {
5808 let Ok(()) = editor.update(cx, |this, _| {
5809 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5810 }) else {
5811 return;
5812 };
5813
5814 // TODO: Ideally completions from different sources would be selectively re-queried, so
5815 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5816 let mut completions = Vec::new();
5817 let mut is_incomplete = false;
5818 let mut display_options: Option<CompletionDisplayOptions> = None;
5819 if let Some(provider_responses) = provider_responses.await.log_err()
5820 && !provider_responses.is_empty()
5821 {
5822 for response in provider_responses {
5823 completions.extend(response.completions);
5824 is_incomplete = is_incomplete || response.is_incomplete;
5825 match display_options.as_mut() {
5826 None => {
5827 display_options = Some(response.display_options);
5828 }
5829 Some(options) => options.merge(&response.display_options),
5830 }
5831 }
5832 if completion_settings.words == WordsCompletionMode::Fallback {
5833 words = Task::ready(BTreeMap::default());
5834 }
5835 }
5836 let display_options = display_options.unwrap_or_default();
5837
5838 let mut words = words.await;
5839 if let Some(word_to_exclude) = &word_to_exclude {
5840 words.remove(word_to_exclude);
5841 }
5842 for lsp_completion in &completions {
5843 words.remove(&lsp_completion.new_text);
5844 }
5845 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5846 replace_range: word_replace_range.clone(),
5847 new_text: word.clone(),
5848 label: CodeLabel::plain(word, None),
5849 match_start: None,
5850 snippet_deduplication_key: None,
5851 icon_path: None,
5852 documentation: None,
5853 source: CompletionSource::BufferWord {
5854 word_range,
5855 resolved: false,
5856 },
5857 insert_text_mode: Some(InsertTextMode::AS_IS),
5858 confirm: None,
5859 }));
5860
5861 completions.extend(
5862 snippets
5863 .await
5864 .into_iter()
5865 .flat_map(|response| response.completions),
5866 );
5867
5868 let menu = if completions.is_empty() {
5869 None
5870 } else {
5871 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5872 let languages = editor
5873 .workspace
5874 .as_ref()
5875 .and_then(|(workspace, _)| workspace.upgrade())
5876 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5877 let menu = CompletionsMenu::new(
5878 id,
5879 requested_source.unwrap_or(if load_provider_completions {
5880 CompletionsMenuSource::Normal
5881 } else {
5882 CompletionsMenuSource::SnippetsOnly
5883 }),
5884 sort_completions,
5885 show_completion_documentation,
5886 position,
5887 query.clone(),
5888 is_incomplete,
5889 buffer.clone(),
5890 completions.into(),
5891 editor
5892 .context_menu()
5893 .borrow_mut()
5894 .as_ref()
5895 .map(|menu| menu.primary_scroll_handle()),
5896 display_options,
5897 snippet_sort_order,
5898 languages,
5899 language,
5900 cx,
5901 );
5902
5903 let query = if filter_completions { query } else { None };
5904 let matches_task = menu.do_async_filtering(
5905 query.unwrap_or_default(),
5906 buffer_position,
5907 &buffer,
5908 cx,
5909 );
5910 (menu, matches_task)
5911 }) else {
5912 return;
5913 };
5914
5915 let matches = matches_task.await;
5916
5917 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5918 // Newer menu already set, so exit.
5919 if let Some(CodeContextMenu::Completions(prev_menu)) =
5920 editor.context_menu.borrow().as_ref()
5921 && prev_menu.id > id
5922 {
5923 return;
5924 };
5925
5926 // Only valid to take prev_menu because either the new menu is immediately set
5927 // below, or the menu is hidden.
5928 if let Some(CodeContextMenu::Completions(prev_menu)) =
5929 editor.context_menu.borrow_mut().take()
5930 {
5931 let position_matches =
5932 if prev_menu.initial_position == menu.initial_position {
5933 true
5934 } else {
5935 let snapshot = editor.buffer.read(cx).read(cx);
5936 prev_menu.initial_position.to_offset(&snapshot)
5937 == menu.initial_position.to_offset(&snapshot)
5938 };
5939 if position_matches {
5940 // Preserve markdown cache before `set_filter_results` because it will
5941 // try to populate the documentation cache.
5942 menu.preserve_markdown_cache(prev_menu);
5943 }
5944 };
5945
5946 menu.set_filter_results(matches, provider, window, cx);
5947 }) else {
5948 return;
5949 };
5950
5951 menu.visible().then_some(menu)
5952 };
5953
5954 editor
5955 .update_in(cx, |editor, window, cx| {
5956 if editor.focus_handle.is_focused(window)
5957 && let Some(menu) = menu
5958 {
5959 *editor.context_menu.borrow_mut() =
5960 Some(CodeContextMenu::Completions(menu));
5961
5962 crate::hover_popover::hide_hover(editor, cx);
5963 if editor.show_edit_predictions_in_menu() {
5964 editor.update_visible_edit_prediction(window, cx);
5965 } else {
5966 editor.discard_edit_prediction(false, cx);
5967 }
5968
5969 cx.notify();
5970 return;
5971 }
5972
5973 if editor.completion_tasks.len() <= 1 {
5974 // If there are no more completion tasks and the last menu was empty, we should hide it.
5975 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5976 // If it was already hidden and we don't show edit predictions in the menu,
5977 // we should also show the edit prediction when available.
5978 if was_hidden && editor.show_edit_predictions_in_menu() {
5979 editor.update_visible_edit_prediction(window, cx);
5980 }
5981 }
5982 })
5983 .ok();
5984 });
5985
5986 self.completion_tasks.push((id, task));
5987 }
5988
5989 #[cfg(feature = "test-support")]
5990 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5991 let menu = self.context_menu.borrow();
5992 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5993 let completions = menu.completions.borrow();
5994 Some(completions.to_vec())
5995 } else {
5996 None
5997 }
5998 }
5999
6000 pub fn with_completions_menu_matching_id<R>(
6001 &self,
6002 id: CompletionId,
6003 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6004 ) -> R {
6005 let mut context_menu = self.context_menu.borrow_mut();
6006 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6007 return f(None);
6008 };
6009 if completions_menu.id != id {
6010 return f(None);
6011 }
6012 f(Some(completions_menu))
6013 }
6014
6015 pub fn confirm_completion(
6016 &mut self,
6017 action: &ConfirmCompletion,
6018 window: &mut Window,
6019 cx: &mut Context<Self>,
6020 ) -> Option<Task<Result<()>>> {
6021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6022 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6023 }
6024
6025 pub fn confirm_completion_insert(
6026 &mut self,
6027 _: &ConfirmCompletionInsert,
6028 window: &mut Window,
6029 cx: &mut Context<Self>,
6030 ) -> Option<Task<Result<()>>> {
6031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6032 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6033 }
6034
6035 pub fn confirm_completion_replace(
6036 &mut self,
6037 _: &ConfirmCompletionReplace,
6038 window: &mut Window,
6039 cx: &mut Context<Self>,
6040 ) -> Option<Task<Result<()>>> {
6041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6042 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6043 }
6044
6045 pub fn compose_completion(
6046 &mut self,
6047 action: &ComposeCompletion,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) -> Option<Task<Result<()>>> {
6051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6052 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6053 }
6054
6055 fn do_completion(
6056 &mut self,
6057 item_ix: Option<usize>,
6058 intent: CompletionIntent,
6059 window: &mut Window,
6060 cx: &mut Context<Editor>,
6061 ) -> Option<Task<Result<()>>> {
6062 use language::ToOffset as _;
6063
6064 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6065 else {
6066 return None;
6067 };
6068
6069 let candidate_id = {
6070 let entries = completions_menu.entries.borrow();
6071 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6072 if self.show_edit_predictions_in_menu() {
6073 self.discard_edit_prediction(true, cx);
6074 }
6075 mat.candidate_id
6076 };
6077
6078 let completion = completions_menu
6079 .completions
6080 .borrow()
6081 .get(candidate_id)?
6082 .clone();
6083 cx.stop_propagation();
6084
6085 let buffer_handle = completions_menu.buffer.clone();
6086
6087 let CompletionEdit {
6088 new_text,
6089 snippet,
6090 replace_range,
6091 } = process_completion_for_edit(
6092 &completion,
6093 intent,
6094 &buffer_handle,
6095 &completions_menu.initial_position.text_anchor,
6096 cx,
6097 );
6098
6099 let buffer = buffer_handle.read(cx);
6100 let snapshot = self.buffer.read(cx).snapshot(cx);
6101 let newest_anchor = self.selections.newest_anchor();
6102 let replace_range_multibuffer = {
6103 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6104 excerpt.map_range_from_buffer(replace_range.clone())
6105 };
6106 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6107 return None;
6108 }
6109
6110 let old_text = buffer
6111 .text_for_range(replace_range.clone())
6112 .collect::<String>();
6113 let lookbehind = newest_anchor
6114 .start
6115 .text_anchor
6116 .to_offset(buffer)
6117 .saturating_sub(replace_range.start.0);
6118 let lookahead = replace_range
6119 .end
6120 .0
6121 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6122 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6123 let suffix = &old_text[lookbehind.min(old_text.len())..];
6124
6125 let selections = self
6126 .selections
6127 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6128 let mut ranges = Vec::new();
6129 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6130
6131 for selection in &selections {
6132 let range = if selection.id == newest_anchor.id {
6133 replace_range_multibuffer.clone()
6134 } else {
6135 let mut range = selection.range();
6136
6137 // if prefix is present, don't duplicate it
6138 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6139 range.start = range.start.saturating_sub_usize(lookbehind);
6140
6141 // if suffix is also present, mimic the newest cursor and replace it
6142 if selection.id != newest_anchor.id
6143 && snapshot.contains_str_at(range.end, suffix)
6144 {
6145 range.end += lookahead;
6146 }
6147 }
6148 range
6149 };
6150
6151 ranges.push(range.clone());
6152
6153 if !self.linked_edit_ranges.is_empty() {
6154 let start_anchor = snapshot.anchor_before(range.start);
6155 let end_anchor = snapshot.anchor_after(range.end);
6156 if let Some(ranges) = self
6157 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6158 {
6159 for (buffer, edits) in ranges {
6160 linked_edits
6161 .entry(buffer.clone())
6162 .or_default()
6163 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6164 }
6165 }
6166 }
6167 }
6168
6169 let common_prefix_len = old_text
6170 .chars()
6171 .zip(new_text.chars())
6172 .take_while(|(a, b)| a == b)
6173 .map(|(a, _)| a.len_utf8())
6174 .sum::<usize>();
6175
6176 cx.emit(EditorEvent::InputHandled {
6177 utf16_range_to_replace: None,
6178 text: new_text[common_prefix_len..].into(),
6179 });
6180
6181 self.transact(window, cx, |editor, window, cx| {
6182 if let Some(mut snippet) = snippet {
6183 snippet.text = new_text.to_string();
6184 editor
6185 .insert_snippet(&ranges, snippet, window, cx)
6186 .log_err();
6187 } else {
6188 editor.buffer.update(cx, |multi_buffer, cx| {
6189 let auto_indent = match completion.insert_text_mode {
6190 Some(InsertTextMode::AS_IS) => None,
6191 _ => editor.autoindent_mode.clone(),
6192 };
6193 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6194 multi_buffer.edit(edits, auto_indent, cx);
6195 });
6196 }
6197 for (buffer, edits) in linked_edits {
6198 buffer.update(cx, |buffer, cx| {
6199 let snapshot = buffer.snapshot();
6200 let edits = edits
6201 .into_iter()
6202 .map(|(range, text)| {
6203 use text::ToPoint as TP;
6204 let end_point = TP::to_point(&range.end, &snapshot);
6205 let start_point = TP::to_point(&range.start, &snapshot);
6206 (start_point..end_point, text)
6207 })
6208 .sorted_by_key(|(range, _)| range.start);
6209 buffer.edit(edits, None, cx);
6210 })
6211 }
6212
6213 editor.refresh_edit_prediction(true, false, window, cx);
6214 });
6215 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6216
6217 let show_new_completions_on_confirm = completion
6218 .confirm
6219 .as_ref()
6220 .is_some_and(|confirm| confirm(intent, window, cx));
6221 if show_new_completions_on_confirm {
6222 self.open_or_update_completions_menu(None, None, false, window, cx);
6223 }
6224
6225 let provider = self.completion_provider.as_ref()?;
6226
6227 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6228 let command = lsp_store.as_ref().and_then(|lsp_store| {
6229 let CompletionSource::Lsp {
6230 lsp_completion,
6231 server_id,
6232 ..
6233 } = &completion.source
6234 else {
6235 return None;
6236 };
6237 let lsp_command = lsp_completion.command.as_ref()?;
6238 let available_commands = lsp_store
6239 .read(cx)
6240 .lsp_server_capabilities
6241 .get(server_id)
6242 .and_then(|server_capabilities| {
6243 server_capabilities
6244 .execute_command_provider
6245 .as_ref()
6246 .map(|options| options.commands.as_slice())
6247 })?;
6248 if available_commands.contains(&lsp_command.command) {
6249 Some(CodeAction {
6250 server_id: *server_id,
6251 range: language::Anchor::MIN..language::Anchor::MIN,
6252 lsp_action: LspAction::Command(lsp_command.clone()),
6253 resolved: false,
6254 })
6255 } else {
6256 None
6257 }
6258 });
6259
6260 drop(completion);
6261 let apply_edits = provider.apply_additional_edits_for_completion(
6262 buffer_handle.clone(),
6263 completions_menu.completions.clone(),
6264 candidate_id,
6265 true,
6266 cx,
6267 );
6268
6269 let editor_settings = EditorSettings::get_global(cx);
6270 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6271 // After the code completion is finished, users often want to know what signatures are needed.
6272 // so we should automatically call signature_help
6273 self.show_signature_help(&ShowSignatureHelp, window, cx);
6274 }
6275
6276 Some(cx.spawn_in(window, async move |editor, cx| {
6277 apply_edits.await?;
6278
6279 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6280 let title = command.lsp_action.title().to_owned();
6281 let project_transaction = lsp_store
6282 .update(cx, |lsp_store, cx| {
6283 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6284 })?
6285 .await
6286 .context("applying post-completion command")?;
6287 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6288 Self::open_project_transaction(
6289 &editor,
6290 workspace.downgrade(),
6291 project_transaction,
6292 title,
6293 cx,
6294 )
6295 .await?;
6296 }
6297 }
6298
6299 Ok(())
6300 }))
6301 }
6302
6303 pub fn toggle_code_actions(
6304 &mut self,
6305 action: &ToggleCodeActions,
6306 window: &mut Window,
6307 cx: &mut Context<Self>,
6308 ) {
6309 let quick_launch = action.quick_launch;
6310 let mut context_menu = self.context_menu.borrow_mut();
6311 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6312 if code_actions.deployed_from == action.deployed_from {
6313 // Toggle if we're selecting the same one
6314 *context_menu = None;
6315 cx.notify();
6316 return;
6317 } else {
6318 // Otherwise, clear it and start a new one
6319 *context_menu = None;
6320 cx.notify();
6321 }
6322 }
6323 drop(context_menu);
6324 let snapshot = self.snapshot(window, cx);
6325 let deployed_from = action.deployed_from.clone();
6326 let action = action.clone();
6327 self.completion_tasks.clear();
6328 self.discard_edit_prediction(false, cx);
6329
6330 let multibuffer_point = match &action.deployed_from {
6331 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6332 DisplayPoint::new(*row, 0).to_point(&snapshot)
6333 }
6334 _ => self
6335 .selections
6336 .newest::<Point>(&snapshot.display_snapshot)
6337 .head(),
6338 };
6339 let Some((buffer, buffer_row)) = snapshot
6340 .buffer_snapshot()
6341 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6342 .and_then(|(buffer_snapshot, range)| {
6343 self.buffer()
6344 .read(cx)
6345 .buffer(buffer_snapshot.remote_id())
6346 .map(|buffer| (buffer, range.start.row))
6347 })
6348 else {
6349 return;
6350 };
6351 let buffer_id = buffer.read(cx).remote_id();
6352 let tasks = self
6353 .tasks
6354 .get(&(buffer_id, buffer_row))
6355 .map(|t| Arc::new(t.to_owned()));
6356
6357 if !self.focus_handle.is_focused(window) {
6358 return;
6359 }
6360 let project = self.project.clone();
6361
6362 let code_actions_task = match deployed_from {
6363 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6364 _ => self.code_actions(buffer_row, window, cx),
6365 };
6366
6367 let runnable_task = match deployed_from {
6368 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6369 _ => {
6370 let mut task_context_task = Task::ready(None);
6371 if let Some(tasks) = &tasks
6372 && let Some(project) = project
6373 {
6374 task_context_task =
6375 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6376 }
6377
6378 cx.spawn_in(window, {
6379 let buffer = buffer.clone();
6380 async move |editor, cx| {
6381 let task_context = task_context_task.await;
6382
6383 let resolved_tasks =
6384 tasks
6385 .zip(task_context.clone())
6386 .map(|(tasks, task_context)| ResolvedTasks {
6387 templates: tasks.resolve(&task_context).collect(),
6388 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6389 multibuffer_point.row,
6390 tasks.column,
6391 )),
6392 });
6393 let debug_scenarios = editor
6394 .update(cx, |editor, cx| {
6395 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6396 })?
6397 .await;
6398 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6399 }
6400 })
6401 }
6402 };
6403
6404 cx.spawn_in(window, async move |editor, cx| {
6405 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6406 let code_actions = code_actions_task.await;
6407 let spawn_straight_away = quick_launch
6408 && resolved_tasks
6409 .as_ref()
6410 .is_some_and(|tasks| tasks.templates.len() == 1)
6411 && code_actions
6412 .as_ref()
6413 .is_none_or(|actions| actions.is_empty())
6414 && debug_scenarios.is_empty();
6415
6416 editor.update_in(cx, |editor, window, cx| {
6417 crate::hover_popover::hide_hover(editor, cx);
6418 let actions = CodeActionContents::new(
6419 resolved_tasks,
6420 code_actions,
6421 debug_scenarios,
6422 task_context.unwrap_or_default(),
6423 );
6424
6425 // Don't show the menu if there are no actions available
6426 if actions.is_empty() {
6427 cx.notify();
6428 return Task::ready(Ok(()));
6429 }
6430
6431 *editor.context_menu.borrow_mut() =
6432 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6433 buffer,
6434 actions,
6435 selected_item: Default::default(),
6436 scroll_handle: UniformListScrollHandle::default(),
6437 deployed_from,
6438 }));
6439 cx.notify();
6440 if spawn_straight_away
6441 && let Some(task) = editor.confirm_code_action(
6442 &ConfirmCodeAction { item_ix: Some(0) },
6443 window,
6444 cx,
6445 )
6446 {
6447 return task;
6448 }
6449
6450 Task::ready(Ok(()))
6451 })
6452 })
6453 .detach_and_log_err(cx);
6454 }
6455
6456 fn debug_scenarios(
6457 &mut self,
6458 resolved_tasks: &Option<ResolvedTasks>,
6459 buffer: &Entity<Buffer>,
6460 cx: &mut App,
6461 ) -> Task<Vec<task::DebugScenario>> {
6462 maybe!({
6463 let project = self.project()?;
6464 let dap_store = project.read(cx).dap_store();
6465 let mut scenarios = vec![];
6466 let resolved_tasks = resolved_tasks.as_ref()?;
6467 let buffer = buffer.read(cx);
6468 let language = buffer.language()?;
6469 let file = buffer.file();
6470 let debug_adapter = language_settings(language.name().into(), file, cx)
6471 .debuggers
6472 .first()
6473 .map(SharedString::from)
6474 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6475
6476 dap_store.update(cx, |dap_store, cx| {
6477 for (_, task) in &resolved_tasks.templates {
6478 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6479 task.original_task().clone(),
6480 debug_adapter.clone().into(),
6481 task.display_label().to_owned().into(),
6482 cx,
6483 );
6484 scenarios.push(maybe_scenario);
6485 }
6486 });
6487 Some(cx.background_spawn(async move {
6488 futures::future::join_all(scenarios)
6489 .await
6490 .into_iter()
6491 .flatten()
6492 .collect::<Vec<_>>()
6493 }))
6494 })
6495 .unwrap_or_else(|| Task::ready(vec![]))
6496 }
6497
6498 fn code_actions(
6499 &mut self,
6500 buffer_row: u32,
6501 window: &mut Window,
6502 cx: &mut Context<Self>,
6503 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6504 let mut task = self.code_actions_task.take();
6505 cx.spawn_in(window, async move |editor, cx| {
6506 while let Some(prev_task) = task {
6507 prev_task.await.log_err();
6508 task = editor
6509 .update(cx, |this, _| this.code_actions_task.take())
6510 .ok()?;
6511 }
6512
6513 editor
6514 .update(cx, |editor, cx| {
6515 editor
6516 .available_code_actions
6517 .clone()
6518 .and_then(|(location, code_actions)| {
6519 let snapshot = location.buffer.read(cx).snapshot();
6520 let point_range = location.range.to_point(&snapshot);
6521 let point_range = point_range.start.row..=point_range.end.row;
6522 if point_range.contains(&buffer_row) {
6523 Some(code_actions)
6524 } else {
6525 None
6526 }
6527 })
6528 })
6529 .ok()
6530 .flatten()
6531 })
6532 }
6533
6534 pub fn confirm_code_action(
6535 &mut self,
6536 action: &ConfirmCodeAction,
6537 window: &mut Window,
6538 cx: &mut Context<Self>,
6539 ) -> Option<Task<Result<()>>> {
6540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6541
6542 let actions_menu =
6543 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6544 menu
6545 } else {
6546 return None;
6547 };
6548
6549 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6550 let action = actions_menu.actions.get(action_ix)?;
6551 let title = action.label();
6552 let buffer = actions_menu.buffer;
6553 let workspace = self.workspace()?;
6554
6555 match action {
6556 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6557 workspace.update(cx, |workspace, cx| {
6558 workspace.schedule_resolved_task(
6559 task_source_kind,
6560 resolved_task,
6561 false,
6562 window,
6563 cx,
6564 );
6565
6566 Some(Task::ready(Ok(())))
6567 })
6568 }
6569 CodeActionsItem::CodeAction {
6570 excerpt_id,
6571 action,
6572 provider,
6573 } => {
6574 let apply_code_action =
6575 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6576 let workspace = workspace.downgrade();
6577 Some(cx.spawn_in(window, async move |editor, cx| {
6578 let project_transaction = apply_code_action.await?;
6579 Self::open_project_transaction(
6580 &editor,
6581 workspace,
6582 project_transaction,
6583 title,
6584 cx,
6585 )
6586 .await
6587 }))
6588 }
6589 CodeActionsItem::DebugScenario(scenario) => {
6590 let context = actions_menu.actions.context;
6591
6592 workspace.update(cx, |workspace, cx| {
6593 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6594 workspace.start_debug_session(
6595 scenario,
6596 context,
6597 Some(buffer),
6598 None,
6599 window,
6600 cx,
6601 );
6602 });
6603 Some(Task::ready(Ok(())))
6604 }
6605 }
6606 }
6607
6608 pub async fn open_project_transaction(
6609 editor: &WeakEntity<Editor>,
6610 workspace: WeakEntity<Workspace>,
6611 transaction: ProjectTransaction,
6612 title: String,
6613 cx: &mut AsyncWindowContext,
6614 ) -> Result<()> {
6615 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6616 cx.update(|_, cx| {
6617 entries.sort_unstable_by_key(|(buffer, _)| {
6618 buffer.read(cx).file().map(|f| f.path().clone())
6619 });
6620 })?;
6621 if entries.is_empty() {
6622 return Ok(());
6623 }
6624
6625 // If the project transaction's edits are all contained within this editor, then
6626 // avoid opening a new editor to display them.
6627
6628 if let [(buffer, transaction)] = &*entries {
6629 let excerpt = editor.update(cx, |editor, cx| {
6630 editor
6631 .buffer()
6632 .read(cx)
6633 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6634 })?;
6635 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6636 && excerpted_buffer == *buffer
6637 {
6638 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6639 let excerpt_range = excerpt_range.to_offset(buffer);
6640 buffer
6641 .edited_ranges_for_transaction::<usize>(transaction)
6642 .all(|range| {
6643 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6644 })
6645 })?;
6646
6647 if all_edits_within_excerpt {
6648 return Ok(());
6649 }
6650 }
6651 }
6652
6653 let mut ranges_to_highlight = Vec::new();
6654 let excerpt_buffer = cx.new(|cx| {
6655 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6656 for (buffer_handle, transaction) in &entries {
6657 let edited_ranges = buffer_handle
6658 .read(cx)
6659 .edited_ranges_for_transaction::<Point>(transaction)
6660 .collect::<Vec<_>>();
6661 let (ranges, _) = multibuffer.set_excerpts_for_path(
6662 PathKey::for_buffer(buffer_handle, cx),
6663 buffer_handle.clone(),
6664 edited_ranges,
6665 multibuffer_context_lines(cx),
6666 cx,
6667 );
6668
6669 ranges_to_highlight.extend(ranges);
6670 }
6671 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6672 multibuffer
6673 })?;
6674
6675 workspace.update_in(cx, |workspace, window, cx| {
6676 let project = workspace.project().clone();
6677 let editor =
6678 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6679 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6680 editor.update(cx, |editor, cx| {
6681 editor.highlight_background::<Self>(
6682 &ranges_to_highlight,
6683 |_, theme| theme.colors().editor_highlighted_line_background,
6684 cx,
6685 );
6686 });
6687 })?;
6688
6689 Ok(())
6690 }
6691
6692 pub fn clear_code_action_providers(&mut self) {
6693 self.code_action_providers.clear();
6694 self.available_code_actions.take();
6695 }
6696
6697 pub fn add_code_action_provider(
6698 &mut self,
6699 provider: Rc<dyn CodeActionProvider>,
6700 window: &mut Window,
6701 cx: &mut Context<Self>,
6702 ) {
6703 if self
6704 .code_action_providers
6705 .iter()
6706 .any(|existing_provider| existing_provider.id() == provider.id())
6707 {
6708 return;
6709 }
6710
6711 self.code_action_providers.push(provider);
6712 self.refresh_code_actions(window, cx);
6713 }
6714
6715 pub fn remove_code_action_provider(
6716 &mut self,
6717 id: Arc<str>,
6718 window: &mut Window,
6719 cx: &mut Context<Self>,
6720 ) {
6721 self.code_action_providers
6722 .retain(|provider| provider.id() != id);
6723 self.refresh_code_actions(window, cx);
6724 }
6725
6726 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6727 !self.code_action_providers.is_empty()
6728 && EditorSettings::get_global(cx).toolbar.code_actions
6729 }
6730
6731 pub fn has_available_code_actions(&self) -> bool {
6732 self.available_code_actions
6733 .as_ref()
6734 .is_some_and(|(_, actions)| !actions.is_empty())
6735 }
6736
6737 fn render_inline_code_actions(
6738 &self,
6739 icon_size: ui::IconSize,
6740 display_row: DisplayRow,
6741 is_active: bool,
6742 cx: &mut Context<Self>,
6743 ) -> AnyElement {
6744 let show_tooltip = !self.context_menu_visible();
6745 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6746 .icon_size(icon_size)
6747 .shape(ui::IconButtonShape::Square)
6748 .icon_color(ui::Color::Hidden)
6749 .toggle_state(is_active)
6750 .when(show_tooltip, |this| {
6751 this.tooltip({
6752 let focus_handle = self.focus_handle.clone();
6753 move |_window, cx| {
6754 Tooltip::for_action_in(
6755 "Toggle Code Actions",
6756 &ToggleCodeActions {
6757 deployed_from: None,
6758 quick_launch: false,
6759 },
6760 &focus_handle,
6761 cx,
6762 )
6763 }
6764 })
6765 })
6766 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6767 window.focus(&editor.focus_handle(cx));
6768 editor.toggle_code_actions(
6769 &crate::actions::ToggleCodeActions {
6770 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6771 display_row,
6772 )),
6773 quick_launch: false,
6774 },
6775 window,
6776 cx,
6777 );
6778 }))
6779 .into_any_element()
6780 }
6781
6782 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6783 &self.context_menu
6784 }
6785
6786 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6787 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6788 cx.background_executor()
6789 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6790 .await;
6791
6792 let (start_buffer, start, _, end, newest_selection) = this
6793 .update(cx, |this, cx| {
6794 let newest_selection = this.selections.newest_anchor().clone();
6795 if newest_selection.head().diff_base_anchor.is_some() {
6796 return None;
6797 }
6798 let display_snapshot = this.display_snapshot(cx);
6799 let newest_selection_adjusted =
6800 this.selections.newest_adjusted(&display_snapshot);
6801 let buffer = this.buffer.read(cx);
6802
6803 let (start_buffer, start) =
6804 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6805 let (end_buffer, end) =
6806 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6807
6808 Some((start_buffer, start, end_buffer, end, newest_selection))
6809 })?
6810 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6811 .context(
6812 "Expected selection to lie in a single buffer when refreshing code actions",
6813 )?;
6814 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6815 let providers = this.code_action_providers.clone();
6816 let tasks = this
6817 .code_action_providers
6818 .iter()
6819 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6820 .collect::<Vec<_>>();
6821 (providers, tasks)
6822 })?;
6823
6824 let mut actions = Vec::new();
6825 for (provider, provider_actions) in
6826 providers.into_iter().zip(future::join_all(tasks).await)
6827 {
6828 if let Some(provider_actions) = provider_actions.log_err() {
6829 actions.extend(provider_actions.into_iter().map(|action| {
6830 AvailableCodeAction {
6831 excerpt_id: newest_selection.start.excerpt_id,
6832 action,
6833 provider: provider.clone(),
6834 }
6835 }));
6836 }
6837 }
6838
6839 this.update(cx, |this, cx| {
6840 this.available_code_actions = if actions.is_empty() {
6841 None
6842 } else {
6843 Some((
6844 Location {
6845 buffer: start_buffer,
6846 range: start..end,
6847 },
6848 actions.into(),
6849 ))
6850 };
6851 cx.notify();
6852 })
6853 }));
6854 }
6855
6856 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6857 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6858 self.show_git_blame_inline = false;
6859
6860 self.show_git_blame_inline_delay_task =
6861 Some(cx.spawn_in(window, async move |this, cx| {
6862 cx.background_executor().timer(delay).await;
6863
6864 this.update(cx, |this, cx| {
6865 this.show_git_blame_inline = true;
6866 cx.notify();
6867 })
6868 .log_err();
6869 }));
6870 }
6871 }
6872
6873 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6874 let snapshot = self.snapshot(window, cx);
6875 let cursor = self
6876 .selections
6877 .newest::<Point>(&snapshot.display_snapshot)
6878 .head();
6879 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6880 else {
6881 return;
6882 };
6883
6884 if self.blame.is_none() {
6885 self.start_git_blame(true, window, cx);
6886 }
6887 let Some(blame) = self.blame.as_ref() else {
6888 return;
6889 };
6890
6891 let row_info = RowInfo {
6892 buffer_id: Some(buffer.remote_id()),
6893 buffer_row: Some(point.row),
6894 ..Default::default()
6895 };
6896 let Some((buffer, blame_entry)) = blame
6897 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6898 .flatten()
6899 else {
6900 return;
6901 };
6902
6903 let anchor = self.selections.newest_anchor().head();
6904 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6905 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6906 self.show_blame_popover(
6907 buffer,
6908 &blame_entry,
6909 position + last_bounds.origin,
6910 true,
6911 cx,
6912 );
6913 };
6914 }
6915
6916 fn show_blame_popover(
6917 &mut self,
6918 buffer: BufferId,
6919 blame_entry: &BlameEntry,
6920 position: gpui::Point<Pixels>,
6921 ignore_timeout: bool,
6922 cx: &mut Context<Self>,
6923 ) {
6924 if let Some(state) = &mut self.inline_blame_popover {
6925 state.hide_task.take();
6926 } else {
6927 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6928 let blame_entry = blame_entry.clone();
6929 let show_task = cx.spawn(async move |editor, cx| {
6930 if !ignore_timeout {
6931 cx.background_executor()
6932 .timer(std::time::Duration::from_millis(blame_popover_delay))
6933 .await;
6934 }
6935 editor
6936 .update(cx, |editor, cx| {
6937 editor.inline_blame_popover_show_task.take();
6938 let Some(blame) = editor.blame.as_ref() else {
6939 return;
6940 };
6941 let blame = blame.read(cx);
6942 let details = blame.details_for_entry(buffer, &blame_entry);
6943 let markdown = cx.new(|cx| {
6944 Markdown::new(
6945 details
6946 .as_ref()
6947 .map(|message| message.message.clone())
6948 .unwrap_or_default(),
6949 None,
6950 None,
6951 cx,
6952 )
6953 });
6954 editor.inline_blame_popover = Some(InlineBlamePopover {
6955 position,
6956 hide_task: None,
6957 popover_bounds: None,
6958 popover_state: InlineBlamePopoverState {
6959 scroll_handle: ScrollHandle::new(),
6960 commit_message: details,
6961 markdown,
6962 },
6963 keyboard_grace: ignore_timeout,
6964 });
6965 cx.notify();
6966 })
6967 .ok();
6968 });
6969 self.inline_blame_popover_show_task = Some(show_task);
6970 }
6971 }
6972
6973 pub fn has_mouse_context_menu(&self) -> bool {
6974 self.mouse_context_menu.is_some()
6975 }
6976
6977 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6978 self.inline_blame_popover_show_task.take();
6979 if let Some(state) = &mut self.inline_blame_popover {
6980 let hide_task = cx.spawn(async move |editor, cx| {
6981 if !ignore_timeout {
6982 cx.background_executor()
6983 .timer(std::time::Duration::from_millis(100))
6984 .await;
6985 }
6986 editor
6987 .update(cx, |editor, cx| {
6988 editor.inline_blame_popover.take();
6989 cx.notify();
6990 })
6991 .ok();
6992 });
6993 state.hide_task = Some(hide_task);
6994 true
6995 } else {
6996 false
6997 }
6998 }
6999
7000 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7001 if self.pending_rename.is_some() {
7002 return None;
7003 }
7004
7005 let provider = self.semantics_provider.clone()?;
7006 let buffer = self.buffer.read(cx);
7007 let newest_selection = self.selections.newest_anchor().clone();
7008 let cursor_position = newest_selection.head();
7009 let (cursor_buffer, cursor_buffer_position) =
7010 buffer.text_anchor_for_position(cursor_position, cx)?;
7011 let (tail_buffer, tail_buffer_position) =
7012 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7013 if cursor_buffer != tail_buffer {
7014 return None;
7015 }
7016
7017 let snapshot = cursor_buffer.read(cx).snapshot();
7018 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7019 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7020 if start_word_range != end_word_range {
7021 self.document_highlights_task.take();
7022 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7023 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7024 return None;
7025 }
7026
7027 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7028 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7029 cx.background_executor()
7030 .timer(Duration::from_millis(debounce))
7031 .await;
7032
7033 let highlights = if let Some(highlights) = cx
7034 .update(|cx| {
7035 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7036 })
7037 .ok()
7038 .flatten()
7039 {
7040 highlights.await.log_err()
7041 } else {
7042 None
7043 };
7044
7045 if let Some(highlights) = highlights {
7046 this.update(cx, |this, cx| {
7047 if this.pending_rename.is_some() {
7048 return;
7049 }
7050
7051 let buffer = this.buffer.read(cx);
7052 if buffer
7053 .text_anchor_for_position(cursor_position, cx)
7054 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7055 {
7056 return;
7057 }
7058
7059 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7060 let mut write_ranges = Vec::new();
7061 let mut read_ranges = Vec::new();
7062 for highlight in highlights {
7063 let buffer_id = cursor_buffer.read(cx).remote_id();
7064 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7065 {
7066 let start = highlight
7067 .range
7068 .start
7069 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7070 let end = highlight
7071 .range
7072 .end
7073 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7074 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7075 continue;
7076 }
7077
7078 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7079 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7080 write_ranges.push(range);
7081 } else {
7082 read_ranges.push(range);
7083 }
7084 }
7085 }
7086
7087 this.highlight_background::<DocumentHighlightRead>(
7088 &read_ranges,
7089 |_, theme| theme.colors().editor_document_highlight_read_background,
7090 cx,
7091 );
7092 this.highlight_background::<DocumentHighlightWrite>(
7093 &write_ranges,
7094 |_, theme| theme.colors().editor_document_highlight_write_background,
7095 cx,
7096 );
7097 cx.notify();
7098 })
7099 .log_err();
7100 }
7101 }));
7102 None
7103 }
7104
7105 fn prepare_highlight_query_from_selection(
7106 &mut self,
7107 window: &Window,
7108 cx: &mut Context<Editor>,
7109 ) -> Option<(String, Range<Anchor>)> {
7110 if matches!(self.mode, EditorMode::SingleLine) {
7111 return None;
7112 }
7113 if !EditorSettings::get_global(cx).selection_highlight {
7114 return None;
7115 }
7116 if self.selections.count() != 1 || self.selections.line_mode() {
7117 return None;
7118 }
7119 let snapshot = self.snapshot(window, cx);
7120 let selection = self.selections.newest::<Point>(&snapshot);
7121 // If the selection spans multiple rows OR it is empty
7122 if selection.start.row != selection.end.row
7123 || selection.start.column == selection.end.column
7124 {
7125 return None;
7126 }
7127 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7128 let query = snapshot
7129 .buffer_snapshot()
7130 .text_for_range(selection_anchor_range.clone())
7131 .collect::<String>();
7132 if query.trim().is_empty() {
7133 return None;
7134 }
7135 Some((query, selection_anchor_range))
7136 }
7137
7138 fn update_selection_occurrence_highlights(
7139 &mut self,
7140 query_text: String,
7141 query_range: Range<Anchor>,
7142 multi_buffer_range_to_query: Range<Point>,
7143 use_debounce: bool,
7144 window: &mut Window,
7145 cx: &mut Context<Editor>,
7146 ) -> Task<()> {
7147 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7148 cx.spawn_in(window, async move |editor, cx| {
7149 if use_debounce {
7150 cx.background_executor()
7151 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7152 .await;
7153 }
7154 let match_task = cx.background_spawn(async move {
7155 let buffer_ranges = multi_buffer_snapshot
7156 .range_to_buffer_ranges(multi_buffer_range_to_query)
7157 .into_iter()
7158 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7159 let mut match_ranges = Vec::new();
7160 let Ok(regex) = project::search::SearchQuery::text(
7161 query_text.clone(),
7162 false,
7163 false,
7164 false,
7165 Default::default(),
7166 Default::default(),
7167 false,
7168 None,
7169 ) else {
7170 return Vec::default();
7171 };
7172 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7173 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7174 match_ranges.extend(
7175 regex
7176 .search(
7177 buffer_snapshot,
7178 Some(search_range.start.0..search_range.end.0),
7179 )
7180 .await
7181 .into_iter()
7182 .filter_map(|match_range| {
7183 let match_start = buffer_snapshot
7184 .anchor_after(search_range.start + match_range.start);
7185 let match_end = buffer_snapshot
7186 .anchor_before(search_range.start + match_range.end);
7187 let match_anchor_range =
7188 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7189 (match_anchor_range != query_range).then_some(match_anchor_range)
7190 }),
7191 );
7192 }
7193 match_ranges
7194 });
7195 let match_ranges = match_task.await;
7196 editor
7197 .update_in(cx, |editor, _, cx| {
7198 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7199 if !match_ranges.is_empty() {
7200 editor.highlight_background::<SelectedTextHighlight>(
7201 &match_ranges,
7202 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7203 cx,
7204 )
7205 }
7206 })
7207 .log_err();
7208 })
7209 }
7210
7211 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7212 struct NewlineFold;
7213 let type_id = std::any::TypeId::of::<NewlineFold>();
7214 if !self.mode.is_single_line() {
7215 return;
7216 }
7217 let snapshot = self.snapshot(window, cx);
7218 if snapshot.buffer_snapshot().max_point().row == 0 {
7219 return;
7220 }
7221 let task = cx.background_spawn(async move {
7222 let new_newlines = snapshot
7223 .buffer_chars_at(MultiBufferOffset(0))
7224 .filter_map(|(c, i)| {
7225 if c == '\n' {
7226 Some(
7227 snapshot.buffer_snapshot().anchor_after(i)
7228 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7229 )
7230 } else {
7231 None
7232 }
7233 })
7234 .collect::<Vec<_>>();
7235 let existing_newlines = snapshot
7236 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7237 .filter_map(|fold| {
7238 if fold.placeholder.type_tag == Some(type_id) {
7239 Some(fold.range.start..fold.range.end)
7240 } else {
7241 None
7242 }
7243 })
7244 .collect::<Vec<_>>();
7245
7246 (new_newlines, existing_newlines)
7247 });
7248 self.folding_newlines = cx.spawn(async move |this, cx| {
7249 let (new_newlines, existing_newlines) = task.await;
7250 if new_newlines == existing_newlines {
7251 return;
7252 }
7253 let placeholder = FoldPlaceholder {
7254 render: Arc::new(move |_, _, cx| {
7255 div()
7256 .bg(cx.theme().status().hint_background)
7257 .border_b_1()
7258 .size_full()
7259 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7260 .border_color(cx.theme().status().hint)
7261 .child("\\n")
7262 .into_any()
7263 }),
7264 constrain_width: false,
7265 merge_adjacent: false,
7266 type_tag: Some(type_id),
7267 };
7268 let creases = new_newlines
7269 .into_iter()
7270 .map(|range| Crease::simple(range, placeholder.clone()))
7271 .collect();
7272 this.update(cx, |this, cx| {
7273 this.display_map.update(cx, |display_map, cx| {
7274 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7275 display_map.fold(creases, cx);
7276 });
7277 })
7278 .ok();
7279 });
7280 }
7281
7282 fn refresh_selected_text_highlights(
7283 &mut self,
7284 on_buffer_edit: bool,
7285 window: &mut Window,
7286 cx: &mut Context<Editor>,
7287 ) {
7288 let Some((query_text, query_range)) =
7289 self.prepare_highlight_query_from_selection(window, cx)
7290 else {
7291 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7292 self.quick_selection_highlight_task.take();
7293 self.debounced_selection_highlight_task.take();
7294 return;
7295 };
7296 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7297 if on_buffer_edit
7298 || self
7299 .quick_selection_highlight_task
7300 .as_ref()
7301 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7302 {
7303 let multi_buffer_visible_start = self
7304 .scroll_manager
7305 .anchor()
7306 .anchor
7307 .to_point(&multi_buffer_snapshot);
7308 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7309 multi_buffer_visible_start
7310 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7311 Bias::Left,
7312 );
7313 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7314 self.quick_selection_highlight_task = Some((
7315 query_range.clone(),
7316 self.update_selection_occurrence_highlights(
7317 query_text.clone(),
7318 query_range.clone(),
7319 multi_buffer_visible_range,
7320 false,
7321 window,
7322 cx,
7323 ),
7324 ));
7325 }
7326 if on_buffer_edit
7327 || self
7328 .debounced_selection_highlight_task
7329 .as_ref()
7330 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7331 {
7332 let multi_buffer_start = multi_buffer_snapshot
7333 .anchor_before(MultiBufferOffset(0))
7334 .to_point(&multi_buffer_snapshot);
7335 let multi_buffer_end = multi_buffer_snapshot
7336 .anchor_after(multi_buffer_snapshot.len())
7337 .to_point(&multi_buffer_snapshot);
7338 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7339 self.debounced_selection_highlight_task = Some((
7340 query_range.clone(),
7341 self.update_selection_occurrence_highlights(
7342 query_text,
7343 query_range,
7344 multi_buffer_full_range,
7345 true,
7346 window,
7347 cx,
7348 ),
7349 ));
7350 }
7351 }
7352
7353 pub fn refresh_edit_prediction(
7354 &mut self,
7355 debounce: bool,
7356 user_requested: bool,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) -> Option<()> {
7360 if DisableAiSettings::get_global(cx).disable_ai {
7361 return None;
7362 }
7363
7364 let provider = self.edit_prediction_provider()?;
7365 let cursor = self.selections.newest_anchor().head();
7366 let (buffer, cursor_buffer_position) =
7367 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7368
7369 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7370 self.discard_edit_prediction(false, cx);
7371 return None;
7372 }
7373
7374 self.update_visible_edit_prediction(window, cx);
7375
7376 if !user_requested
7377 && (!self.should_show_edit_predictions()
7378 || !self.is_focused(window)
7379 || buffer.read(cx).is_empty())
7380 {
7381 self.discard_edit_prediction(false, cx);
7382 return None;
7383 }
7384
7385 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7386 Some(())
7387 }
7388
7389 fn show_edit_predictions_in_menu(&self) -> bool {
7390 match self.edit_prediction_settings {
7391 EditPredictionSettings::Disabled => false,
7392 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7393 }
7394 }
7395
7396 pub fn edit_predictions_enabled(&self) -> bool {
7397 match self.edit_prediction_settings {
7398 EditPredictionSettings::Disabled => false,
7399 EditPredictionSettings::Enabled { .. } => true,
7400 }
7401 }
7402
7403 fn edit_prediction_requires_modifier(&self) -> bool {
7404 match self.edit_prediction_settings {
7405 EditPredictionSettings::Disabled => false,
7406 EditPredictionSettings::Enabled {
7407 preview_requires_modifier,
7408 ..
7409 } => preview_requires_modifier,
7410 }
7411 }
7412
7413 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7414 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7415 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7416 self.discard_edit_prediction(false, cx);
7417 } else {
7418 let selection = self.selections.newest_anchor();
7419 let cursor = selection.head();
7420
7421 if let Some((buffer, cursor_buffer_position)) =
7422 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7423 {
7424 self.edit_prediction_settings =
7425 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7426 }
7427 }
7428 }
7429
7430 fn edit_prediction_settings_at_position(
7431 &self,
7432 buffer: &Entity<Buffer>,
7433 buffer_position: language::Anchor,
7434 cx: &App,
7435 ) -> EditPredictionSettings {
7436 if !self.mode.is_full()
7437 || !self.show_edit_predictions_override.unwrap_or(true)
7438 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7439 {
7440 return EditPredictionSettings::Disabled;
7441 }
7442
7443 let buffer = buffer.read(cx);
7444
7445 let file = buffer.file();
7446
7447 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7448 return EditPredictionSettings::Disabled;
7449 };
7450
7451 let by_provider = matches!(
7452 self.menu_edit_predictions_policy,
7453 MenuEditPredictionsPolicy::ByProvider
7454 );
7455
7456 let show_in_menu = by_provider
7457 && self
7458 .edit_prediction_provider
7459 .as_ref()
7460 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7461
7462 let preview_requires_modifier =
7463 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7464
7465 EditPredictionSettings::Enabled {
7466 show_in_menu,
7467 preview_requires_modifier,
7468 }
7469 }
7470
7471 fn should_show_edit_predictions(&self) -> bool {
7472 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7473 }
7474
7475 pub fn edit_prediction_preview_is_active(&self) -> bool {
7476 matches!(
7477 self.edit_prediction_preview,
7478 EditPredictionPreview::Active { .. }
7479 )
7480 }
7481
7482 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7483 let cursor = self.selections.newest_anchor().head();
7484 if let Some((buffer, cursor_position)) =
7485 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7486 {
7487 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7488 } else {
7489 false
7490 }
7491 }
7492
7493 pub fn supports_minimap(&self, cx: &App) -> bool {
7494 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7495 }
7496
7497 fn edit_predictions_enabled_in_buffer(
7498 &self,
7499 buffer: &Entity<Buffer>,
7500 buffer_position: language::Anchor,
7501 cx: &App,
7502 ) -> bool {
7503 maybe!({
7504 if self.read_only(cx) {
7505 return Some(false);
7506 }
7507 let provider = self.edit_prediction_provider()?;
7508 if !provider.is_enabled(buffer, buffer_position, cx) {
7509 return Some(false);
7510 }
7511 let buffer = buffer.read(cx);
7512 let Some(file) = buffer.file() else {
7513 return Some(true);
7514 };
7515 let settings = all_language_settings(Some(file), cx);
7516 Some(settings.edit_predictions_enabled_for_file(file, cx))
7517 })
7518 .unwrap_or(false)
7519 }
7520
7521 fn cycle_edit_prediction(
7522 &mut self,
7523 direction: Direction,
7524 window: &mut Window,
7525 cx: &mut Context<Self>,
7526 ) -> Option<()> {
7527 let provider = self.edit_prediction_provider()?;
7528 let cursor = self.selections.newest_anchor().head();
7529 let (buffer, cursor_buffer_position) =
7530 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7531 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7532 return None;
7533 }
7534
7535 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7536 self.update_visible_edit_prediction(window, cx);
7537
7538 Some(())
7539 }
7540
7541 pub fn show_edit_prediction(
7542 &mut self,
7543 _: &ShowEditPrediction,
7544 window: &mut Window,
7545 cx: &mut Context<Self>,
7546 ) {
7547 if !self.has_active_edit_prediction() {
7548 self.refresh_edit_prediction(false, true, window, cx);
7549 return;
7550 }
7551
7552 self.update_visible_edit_prediction(window, cx);
7553 }
7554
7555 pub fn display_cursor_names(
7556 &mut self,
7557 _: &DisplayCursorNames,
7558 window: &mut Window,
7559 cx: &mut Context<Self>,
7560 ) {
7561 self.show_cursor_names(window, cx);
7562 }
7563
7564 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7565 self.show_cursor_names = true;
7566 cx.notify();
7567 cx.spawn_in(window, async move |this, cx| {
7568 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7569 this.update(cx, |this, cx| {
7570 this.show_cursor_names = false;
7571 cx.notify()
7572 })
7573 .ok()
7574 })
7575 .detach();
7576 }
7577
7578 pub fn next_edit_prediction(
7579 &mut self,
7580 _: &NextEditPrediction,
7581 window: &mut Window,
7582 cx: &mut Context<Self>,
7583 ) {
7584 if self.has_active_edit_prediction() {
7585 self.cycle_edit_prediction(Direction::Next, window, cx);
7586 } else {
7587 let is_copilot_disabled = self
7588 .refresh_edit_prediction(false, true, window, cx)
7589 .is_none();
7590 if is_copilot_disabled {
7591 cx.propagate();
7592 }
7593 }
7594 }
7595
7596 pub fn previous_edit_prediction(
7597 &mut self,
7598 _: &PreviousEditPrediction,
7599 window: &mut Window,
7600 cx: &mut Context<Self>,
7601 ) {
7602 if self.has_active_edit_prediction() {
7603 self.cycle_edit_prediction(Direction::Prev, window, cx);
7604 } else {
7605 let is_copilot_disabled = self
7606 .refresh_edit_prediction(false, true, window, cx)
7607 .is_none();
7608 if is_copilot_disabled {
7609 cx.propagate();
7610 }
7611 }
7612 }
7613
7614 pub fn accept_edit_prediction(
7615 &mut self,
7616 _: &AcceptEditPrediction,
7617 window: &mut Window,
7618 cx: &mut Context<Self>,
7619 ) {
7620 if self.show_edit_predictions_in_menu() {
7621 self.hide_context_menu(window, cx);
7622 }
7623
7624 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7625 return;
7626 };
7627
7628 match &active_edit_prediction.completion {
7629 EditPrediction::MoveWithin { target, .. } => {
7630 let target = *target;
7631
7632 if let Some(position_map) = &self.last_position_map {
7633 if position_map
7634 .visible_row_range
7635 .contains(&target.to_display_point(&position_map.snapshot).row())
7636 || !self.edit_prediction_requires_modifier()
7637 {
7638 self.unfold_ranges(&[target..target], true, false, cx);
7639 // Note that this is also done in vim's handler of the Tab action.
7640 self.change_selections(
7641 SelectionEffects::scroll(Autoscroll::newest()),
7642 window,
7643 cx,
7644 |selections| {
7645 selections.select_anchor_ranges([target..target]);
7646 },
7647 );
7648 self.clear_row_highlights::<EditPredictionPreview>();
7649
7650 self.edit_prediction_preview
7651 .set_previous_scroll_position(None);
7652 } else {
7653 self.edit_prediction_preview
7654 .set_previous_scroll_position(Some(
7655 position_map.snapshot.scroll_anchor,
7656 ));
7657
7658 self.highlight_rows::<EditPredictionPreview>(
7659 target..target,
7660 cx.theme().colors().editor_highlighted_line_background,
7661 RowHighlightOptions {
7662 autoscroll: true,
7663 ..Default::default()
7664 },
7665 cx,
7666 );
7667 self.request_autoscroll(Autoscroll::fit(), cx);
7668 }
7669 }
7670 }
7671 EditPrediction::MoveOutside { snapshot, target } => {
7672 if let Some(workspace) = self.workspace() {
7673 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7674 .detach_and_log_err(cx);
7675 }
7676 }
7677 EditPrediction::Edit { edits, .. } => {
7678 self.report_edit_prediction_event(
7679 active_edit_prediction.completion_id.clone(),
7680 true,
7681 cx,
7682 );
7683
7684 if let Some(provider) = self.edit_prediction_provider() {
7685 provider.accept(cx);
7686 }
7687
7688 // Store the transaction ID and selections before applying the edit
7689 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7690
7691 let snapshot = self.buffer.read(cx).snapshot(cx);
7692 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7693
7694 self.buffer.update(cx, |buffer, cx| {
7695 buffer.edit(edits.iter().cloned(), None, cx)
7696 });
7697
7698 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7699 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7700 });
7701
7702 let selections = self.selections.disjoint_anchors_arc();
7703 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7704 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7705 if has_new_transaction {
7706 self.selection_history
7707 .insert_transaction(transaction_id_now, selections);
7708 }
7709 }
7710
7711 self.update_visible_edit_prediction(window, cx);
7712 if self.active_edit_prediction.is_none() {
7713 self.refresh_edit_prediction(true, true, window, cx);
7714 }
7715
7716 cx.notify();
7717 }
7718 }
7719
7720 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7721 }
7722
7723 pub fn accept_partial_edit_prediction(
7724 &mut self,
7725 _: &AcceptPartialEditPrediction,
7726 window: &mut Window,
7727 cx: &mut Context<Self>,
7728 ) {
7729 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7730 return;
7731 };
7732 if self.selections.count() != 1 {
7733 return;
7734 }
7735
7736 match &active_edit_prediction.completion {
7737 EditPrediction::MoveWithin { target, .. } => {
7738 let target = *target;
7739 self.change_selections(
7740 SelectionEffects::scroll(Autoscroll::newest()),
7741 window,
7742 cx,
7743 |selections| {
7744 selections.select_anchor_ranges([target..target]);
7745 },
7746 );
7747 }
7748 EditPrediction::MoveOutside { snapshot, target } => {
7749 if let Some(workspace) = self.workspace() {
7750 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7751 .detach_and_log_err(cx);
7752 }
7753 }
7754 EditPrediction::Edit { edits, .. } => {
7755 self.report_edit_prediction_event(
7756 active_edit_prediction.completion_id.clone(),
7757 true,
7758 cx,
7759 );
7760
7761 // Find an insertion that starts at the cursor position.
7762 let snapshot = self.buffer.read(cx).snapshot(cx);
7763 let cursor_offset = self
7764 .selections
7765 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7766 .head();
7767 let insertion = edits.iter().find_map(|(range, text)| {
7768 let range = range.to_offset(&snapshot);
7769 if range.is_empty() && range.start == cursor_offset {
7770 Some(text)
7771 } else {
7772 None
7773 }
7774 });
7775
7776 if let Some(text) = insertion {
7777 let mut partial_completion = text
7778 .chars()
7779 .by_ref()
7780 .take_while(|c| c.is_alphabetic())
7781 .collect::<String>();
7782 if partial_completion.is_empty() {
7783 partial_completion = text
7784 .chars()
7785 .by_ref()
7786 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7787 .collect::<String>();
7788 }
7789
7790 cx.emit(EditorEvent::InputHandled {
7791 utf16_range_to_replace: None,
7792 text: partial_completion.clone().into(),
7793 });
7794
7795 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7796
7797 self.refresh_edit_prediction(true, true, window, cx);
7798 cx.notify();
7799 } else {
7800 self.accept_edit_prediction(&Default::default(), window, cx);
7801 }
7802 }
7803 }
7804 }
7805
7806 fn discard_edit_prediction(
7807 &mut self,
7808 should_report_edit_prediction_event: bool,
7809 cx: &mut Context<Self>,
7810 ) -> bool {
7811 if should_report_edit_prediction_event {
7812 let completion_id = self
7813 .active_edit_prediction
7814 .as_ref()
7815 .and_then(|active_completion| active_completion.completion_id.clone());
7816
7817 self.report_edit_prediction_event(completion_id, false, cx);
7818 }
7819
7820 if let Some(provider) = self.edit_prediction_provider() {
7821 provider.discard(cx);
7822 }
7823
7824 self.take_active_edit_prediction(cx)
7825 }
7826
7827 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7828 let Some(provider) = self.edit_prediction_provider() else {
7829 return;
7830 };
7831
7832 let Some((_, buffer, _)) = self
7833 .buffer
7834 .read(cx)
7835 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7836 else {
7837 return;
7838 };
7839
7840 let extension = buffer
7841 .read(cx)
7842 .file()
7843 .and_then(|file| Some(file.path().extension()?.to_string()));
7844
7845 let event_type = match accepted {
7846 true => "Edit Prediction Accepted",
7847 false => "Edit Prediction Discarded",
7848 };
7849 telemetry::event!(
7850 event_type,
7851 provider = provider.name(),
7852 prediction_id = id,
7853 suggestion_accepted = accepted,
7854 file_extension = extension,
7855 );
7856 }
7857
7858 fn open_editor_at_anchor(
7859 snapshot: &language::BufferSnapshot,
7860 target: language::Anchor,
7861 workspace: &Entity<Workspace>,
7862 window: &mut Window,
7863 cx: &mut App,
7864 ) -> Task<Result<()>> {
7865 workspace.update(cx, |workspace, cx| {
7866 let path = snapshot.file().map(|file| file.full_path(cx));
7867 let Some(path) =
7868 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7869 else {
7870 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7871 };
7872 let target = text::ToPoint::to_point(&target, snapshot);
7873 let item = workspace.open_path(path, None, true, window, cx);
7874 window.spawn(cx, async move |cx| {
7875 let Some(editor) = item.await?.downcast::<Editor>() else {
7876 return Ok(());
7877 };
7878 editor
7879 .update_in(cx, |editor, window, cx| {
7880 editor.go_to_singleton_buffer_point(target, window, cx);
7881 })
7882 .ok();
7883 anyhow::Ok(())
7884 })
7885 })
7886 }
7887
7888 pub fn has_active_edit_prediction(&self) -> bool {
7889 self.active_edit_prediction.is_some()
7890 }
7891
7892 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7893 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7894 return false;
7895 };
7896
7897 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7898 self.clear_highlights::<EditPredictionHighlight>(cx);
7899 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7900 true
7901 }
7902
7903 /// Returns true when we're displaying the edit prediction popover below the cursor
7904 /// like we are not previewing and the LSP autocomplete menu is visible
7905 /// or we are in `when_holding_modifier` mode.
7906 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7907 if self.edit_prediction_preview_is_active()
7908 || !self.show_edit_predictions_in_menu()
7909 || !self.edit_predictions_enabled()
7910 {
7911 return false;
7912 }
7913
7914 if self.has_visible_completions_menu() {
7915 return true;
7916 }
7917
7918 has_completion && self.edit_prediction_requires_modifier()
7919 }
7920
7921 fn handle_modifiers_changed(
7922 &mut self,
7923 modifiers: Modifiers,
7924 position_map: &PositionMap,
7925 window: &mut Window,
7926 cx: &mut Context<Self>,
7927 ) {
7928 // Ensure that the edit prediction preview is updated, even when not
7929 // enabled, if there's an active edit prediction preview.
7930 if self.show_edit_predictions_in_menu()
7931 || matches!(
7932 self.edit_prediction_preview,
7933 EditPredictionPreview::Active { .. }
7934 )
7935 {
7936 self.update_edit_prediction_preview(&modifiers, window, cx);
7937 }
7938
7939 self.update_selection_mode(&modifiers, position_map, window, cx);
7940
7941 let mouse_position = window.mouse_position();
7942 if !position_map.text_hitbox.is_hovered(window) {
7943 return;
7944 }
7945
7946 self.update_hovered_link(
7947 position_map.point_for_position(mouse_position),
7948 &position_map.snapshot,
7949 modifiers,
7950 window,
7951 cx,
7952 )
7953 }
7954
7955 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7956 match EditorSettings::get_global(cx).multi_cursor_modifier {
7957 MultiCursorModifier::Alt => modifiers.secondary(),
7958 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7959 }
7960 }
7961
7962 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7963 match EditorSettings::get_global(cx).multi_cursor_modifier {
7964 MultiCursorModifier::Alt => modifiers.alt,
7965 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7966 }
7967 }
7968
7969 fn columnar_selection_mode(
7970 modifiers: &Modifiers,
7971 cx: &mut Context<Self>,
7972 ) -> Option<ColumnarMode> {
7973 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7974 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7975 Some(ColumnarMode::FromMouse)
7976 } else if Self::is_alt_pressed(modifiers, cx) {
7977 Some(ColumnarMode::FromSelection)
7978 } else {
7979 None
7980 }
7981 } else {
7982 None
7983 }
7984 }
7985
7986 fn update_selection_mode(
7987 &mut self,
7988 modifiers: &Modifiers,
7989 position_map: &PositionMap,
7990 window: &mut Window,
7991 cx: &mut Context<Self>,
7992 ) {
7993 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7994 return;
7995 };
7996 if self.selections.pending_anchor().is_none() {
7997 return;
7998 }
7999
8000 let mouse_position = window.mouse_position();
8001 let point_for_position = position_map.point_for_position(mouse_position);
8002 let position = point_for_position.previous_valid;
8003
8004 self.select(
8005 SelectPhase::BeginColumnar {
8006 position,
8007 reset: false,
8008 mode,
8009 goal_column: point_for_position.exact_unclipped.column(),
8010 },
8011 window,
8012 cx,
8013 );
8014 }
8015
8016 fn update_edit_prediction_preview(
8017 &mut self,
8018 modifiers: &Modifiers,
8019 window: &mut Window,
8020 cx: &mut Context<Self>,
8021 ) {
8022 let mut modifiers_held = false;
8023 if let Some(accept_keystroke) = self
8024 .accept_edit_prediction_keybind(false, window, cx)
8025 .keystroke()
8026 {
8027 modifiers_held = modifiers_held
8028 || (accept_keystroke.modifiers() == modifiers
8029 && accept_keystroke.modifiers().modified());
8030 };
8031 if let Some(accept_partial_keystroke) = self
8032 .accept_edit_prediction_keybind(true, window, cx)
8033 .keystroke()
8034 {
8035 modifiers_held = modifiers_held
8036 || (accept_partial_keystroke.modifiers() == modifiers
8037 && accept_partial_keystroke.modifiers().modified());
8038 }
8039
8040 if modifiers_held {
8041 if matches!(
8042 self.edit_prediction_preview,
8043 EditPredictionPreview::Inactive { .. }
8044 ) {
8045 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8046 provider.provider.did_show(cx)
8047 }
8048
8049 self.edit_prediction_preview = EditPredictionPreview::Active {
8050 previous_scroll_position: None,
8051 since: Instant::now(),
8052 };
8053
8054 self.update_visible_edit_prediction(window, cx);
8055 cx.notify();
8056 }
8057 } else if let EditPredictionPreview::Active {
8058 previous_scroll_position,
8059 since,
8060 } = self.edit_prediction_preview
8061 {
8062 if let (Some(previous_scroll_position), Some(position_map)) =
8063 (previous_scroll_position, self.last_position_map.as_ref())
8064 {
8065 self.set_scroll_position(
8066 previous_scroll_position
8067 .scroll_position(&position_map.snapshot.display_snapshot),
8068 window,
8069 cx,
8070 );
8071 }
8072
8073 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8074 released_too_fast: since.elapsed() < Duration::from_millis(200),
8075 };
8076 self.clear_row_highlights::<EditPredictionPreview>();
8077 self.update_visible_edit_prediction(window, cx);
8078 cx.notify();
8079 }
8080 }
8081
8082 fn update_visible_edit_prediction(
8083 &mut self,
8084 _window: &mut Window,
8085 cx: &mut Context<Self>,
8086 ) -> Option<()> {
8087 if DisableAiSettings::get_global(cx).disable_ai {
8088 return None;
8089 }
8090
8091 if self.ime_transaction.is_some() {
8092 self.discard_edit_prediction(false, cx);
8093 return None;
8094 }
8095
8096 let selection = self.selections.newest_anchor();
8097 let cursor = selection.head();
8098 let multibuffer = self.buffer.read(cx).snapshot(cx);
8099 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8100 let excerpt_id = cursor.excerpt_id;
8101
8102 let show_in_menu = self.show_edit_predictions_in_menu();
8103 let completions_menu_has_precedence = !show_in_menu
8104 && (self.context_menu.borrow().is_some()
8105 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8106
8107 if completions_menu_has_precedence
8108 || !offset_selection.is_empty()
8109 || self
8110 .active_edit_prediction
8111 .as_ref()
8112 .is_some_and(|completion| {
8113 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8114 return false;
8115 };
8116 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8117 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8118 !invalidation_range.contains(&offset_selection.head())
8119 })
8120 {
8121 self.discard_edit_prediction(false, cx);
8122 return None;
8123 }
8124
8125 self.take_active_edit_prediction(cx);
8126 let Some(provider) = self.edit_prediction_provider() else {
8127 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8128 return None;
8129 };
8130
8131 let (buffer, cursor_buffer_position) =
8132 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8133
8134 self.edit_prediction_settings =
8135 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8136
8137 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8138
8139 if self.edit_prediction_indent_conflict {
8140 let cursor_point = cursor.to_point(&multibuffer);
8141 let mut suggested_indent = None;
8142 multibuffer.suggested_indents_callback(
8143 cursor_point.row..cursor_point.row + 1,
8144 |_, indent| {
8145 suggested_indent = Some(indent);
8146 ControlFlow::Break(())
8147 },
8148 cx,
8149 );
8150
8151 if let Some(indent) = suggested_indent
8152 && indent.len == cursor_point.column
8153 {
8154 self.edit_prediction_indent_conflict = false;
8155 }
8156 }
8157
8158 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8159
8160 let (completion_id, edits, edit_preview) = match edit_prediction {
8161 edit_prediction_types::EditPrediction::Local {
8162 id,
8163 edits,
8164 edit_preview,
8165 } => (id, edits, edit_preview),
8166 edit_prediction_types::EditPrediction::Jump {
8167 id,
8168 snapshot,
8169 target,
8170 } => {
8171 self.stale_edit_prediction_in_menu = None;
8172 self.active_edit_prediction = Some(EditPredictionState {
8173 inlay_ids: vec![],
8174 completion: EditPrediction::MoveOutside { snapshot, target },
8175 completion_id: id,
8176 invalidation_range: None,
8177 });
8178 cx.notify();
8179 return Some(());
8180 }
8181 };
8182
8183 let edits = edits
8184 .into_iter()
8185 .flat_map(|(range, new_text)| {
8186 Some((
8187 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8188 new_text,
8189 ))
8190 })
8191 .collect::<Vec<_>>();
8192 if edits.is_empty() {
8193 return None;
8194 }
8195
8196 let first_edit_start = edits.first().unwrap().0.start;
8197 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8198 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8199
8200 let last_edit_end = edits.last().unwrap().0.end;
8201 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8202 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8203
8204 let cursor_row = cursor.to_point(&multibuffer).row;
8205
8206 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8207
8208 let mut inlay_ids = Vec::new();
8209 let invalidation_row_range;
8210 let move_invalidation_row_range = if cursor_row < edit_start_row {
8211 Some(cursor_row..edit_end_row)
8212 } else if cursor_row > edit_end_row {
8213 Some(edit_start_row..cursor_row)
8214 } else {
8215 None
8216 };
8217 let supports_jump = self
8218 .edit_prediction_provider
8219 .as_ref()
8220 .map(|provider| provider.provider.supports_jump_to_edit())
8221 .unwrap_or(true);
8222
8223 let is_move = supports_jump
8224 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8225 let completion = if is_move {
8226 invalidation_row_range =
8227 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8228 let target = first_edit_start;
8229 EditPrediction::MoveWithin { target, snapshot }
8230 } else {
8231 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8232 && !self.edit_predictions_hidden_for_vim_mode;
8233
8234 if show_completions_in_buffer {
8235 if let Some(provider) = &self.edit_prediction_provider {
8236 provider.provider.did_show(cx);
8237 }
8238 if edits
8239 .iter()
8240 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8241 {
8242 let mut inlays = Vec::new();
8243 for (range, new_text) in &edits {
8244 let inlay = Inlay::edit_prediction(
8245 post_inc(&mut self.next_inlay_id),
8246 range.start,
8247 new_text.as_ref(),
8248 );
8249 inlay_ids.push(inlay.id);
8250 inlays.push(inlay);
8251 }
8252
8253 self.splice_inlays(&[], inlays, cx);
8254 } else {
8255 let background_color = cx.theme().status().deleted_background;
8256 self.highlight_text::<EditPredictionHighlight>(
8257 edits.iter().map(|(range, _)| range.clone()).collect(),
8258 HighlightStyle {
8259 background_color: Some(background_color),
8260 ..Default::default()
8261 },
8262 cx,
8263 );
8264 }
8265 }
8266
8267 invalidation_row_range = edit_start_row..edit_end_row;
8268
8269 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8270 if provider.show_tab_accept_marker() {
8271 EditDisplayMode::TabAccept
8272 } else {
8273 EditDisplayMode::Inline
8274 }
8275 } else {
8276 EditDisplayMode::DiffPopover
8277 };
8278
8279 EditPrediction::Edit {
8280 edits,
8281 edit_preview,
8282 display_mode,
8283 snapshot,
8284 }
8285 };
8286
8287 let invalidation_range = multibuffer
8288 .anchor_before(Point::new(invalidation_row_range.start, 0))
8289 ..multibuffer.anchor_after(Point::new(
8290 invalidation_row_range.end,
8291 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8292 ));
8293
8294 self.stale_edit_prediction_in_menu = None;
8295 self.active_edit_prediction = Some(EditPredictionState {
8296 inlay_ids,
8297 completion,
8298 completion_id,
8299 invalidation_range: Some(invalidation_range),
8300 });
8301
8302 cx.notify();
8303
8304 Some(())
8305 }
8306
8307 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8308 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8309 }
8310
8311 fn clear_tasks(&mut self) {
8312 self.tasks.clear()
8313 }
8314
8315 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8316 if self.tasks.insert(key, value).is_some() {
8317 // This case should hopefully be rare, but just in case...
8318 log::error!(
8319 "multiple different run targets found on a single line, only the last target will be rendered"
8320 )
8321 }
8322 }
8323
8324 /// Get all display points of breakpoints that will be rendered within editor
8325 ///
8326 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8327 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8328 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8329 fn active_breakpoints(
8330 &self,
8331 range: Range<DisplayRow>,
8332 window: &mut Window,
8333 cx: &mut Context<Self>,
8334 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8335 let mut breakpoint_display_points = HashMap::default();
8336
8337 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8338 return breakpoint_display_points;
8339 };
8340
8341 let snapshot = self.snapshot(window, cx);
8342
8343 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8344 let Some(project) = self.project() else {
8345 return breakpoint_display_points;
8346 };
8347
8348 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8349 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8350
8351 for (buffer_snapshot, range, excerpt_id) in
8352 multi_buffer_snapshot.range_to_buffer_ranges(range)
8353 {
8354 let Some(buffer) = project
8355 .read(cx)
8356 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8357 else {
8358 continue;
8359 };
8360 let breakpoints = breakpoint_store.read(cx).breakpoints(
8361 &buffer,
8362 Some(
8363 buffer_snapshot.anchor_before(range.start)
8364 ..buffer_snapshot.anchor_after(range.end),
8365 ),
8366 buffer_snapshot,
8367 cx,
8368 );
8369 for (breakpoint, state) in breakpoints {
8370 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8371 let position = multi_buffer_anchor
8372 .to_point(&multi_buffer_snapshot)
8373 .to_display_point(&snapshot);
8374
8375 breakpoint_display_points.insert(
8376 position.row(),
8377 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8378 );
8379 }
8380 }
8381
8382 breakpoint_display_points
8383 }
8384
8385 fn breakpoint_context_menu(
8386 &self,
8387 anchor: Anchor,
8388 window: &mut Window,
8389 cx: &mut Context<Self>,
8390 ) -> Entity<ui::ContextMenu> {
8391 let weak_editor = cx.weak_entity();
8392 let focus_handle = self.focus_handle(cx);
8393
8394 let row = self
8395 .buffer
8396 .read(cx)
8397 .snapshot(cx)
8398 .summary_for_anchor::<Point>(&anchor)
8399 .row;
8400
8401 let breakpoint = self
8402 .breakpoint_at_row(row, window, cx)
8403 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8404
8405 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8406 "Edit Log Breakpoint"
8407 } else {
8408 "Set Log Breakpoint"
8409 };
8410
8411 let condition_breakpoint_msg = if breakpoint
8412 .as_ref()
8413 .is_some_and(|bp| bp.1.condition.is_some())
8414 {
8415 "Edit Condition Breakpoint"
8416 } else {
8417 "Set Condition Breakpoint"
8418 };
8419
8420 let hit_condition_breakpoint_msg = if breakpoint
8421 .as_ref()
8422 .is_some_and(|bp| bp.1.hit_condition.is_some())
8423 {
8424 "Edit Hit Condition Breakpoint"
8425 } else {
8426 "Set Hit Condition Breakpoint"
8427 };
8428
8429 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8430 "Unset Breakpoint"
8431 } else {
8432 "Set Breakpoint"
8433 };
8434
8435 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8436
8437 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8438 BreakpointState::Enabled => Some("Disable"),
8439 BreakpointState::Disabled => Some("Enable"),
8440 });
8441
8442 let (anchor, breakpoint) =
8443 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8444
8445 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8446 menu.on_blur_subscription(Subscription::new(|| {}))
8447 .context(focus_handle)
8448 .when(run_to_cursor, |this| {
8449 let weak_editor = weak_editor.clone();
8450 this.entry("Run to cursor", None, move |window, cx| {
8451 weak_editor
8452 .update(cx, |editor, cx| {
8453 editor.change_selections(
8454 SelectionEffects::no_scroll(),
8455 window,
8456 cx,
8457 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8458 );
8459 })
8460 .ok();
8461
8462 window.dispatch_action(Box::new(RunToCursor), cx);
8463 })
8464 .separator()
8465 })
8466 .when_some(toggle_state_msg, |this, msg| {
8467 this.entry(msg, None, {
8468 let weak_editor = weak_editor.clone();
8469 let breakpoint = breakpoint.clone();
8470 move |_window, cx| {
8471 weak_editor
8472 .update(cx, |this, cx| {
8473 this.edit_breakpoint_at_anchor(
8474 anchor,
8475 breakpoint.as_ref().clone(),
8476 BreakpointEditAction::InvertState,
8477 cx,
8478 );
8479 })
8480 .log_err();
8481 }
8482 })
8483 })
8484 .entry(set_breakpoint_msg, None, {
8485 let weak_editor = weak_editor.clone();
8486 let breakpoint = breakpoint.clone();
8487 move |_window, cx| {
8488 weak_editor
8489 .update(cx, |this, cx| {
8490 this.edit_breakpoint_at_anchor(
8491 anchor,
8492 breakpoint.as_ref().clone(),
8493 BreakpointEditAction::Toggle,
8494 cx,
8495 );
8496 })
8497 .log_err();
8498 }
8499 })
8500 .entry(log_breakpoint_msg, None, {
8501 let breakpoint = breakpoint.clone();
8502 let weak_editor = weak_editor.clone();
8503 move |window, cx| {
8504 weak_editor
8505 .update(cx, |this, cx| {
8506 this.add_edit_breakpoint_block(
8507 anchor,
8508 breakpoint.as_ref(),
8509 BreakpointPromptEditAction::Log,
8510 window,
8511 cx,
8512 );
8513 })
8514 .log_err();
8515 }
8516 })
8517 .entry(condition_breakpoint_msg, None, {
8518 let breakpoint = breakpoint.clone();
8519 let weak_editor = weak_editor.clone();
8520 move |window, cx| {
8521 weak_editor
8522 .update(cx, |this, cx| {
8523 this.add_edit_breakpoint_block(
8524 anchor,
8525 breakpoint.as_ref(),
8526 BreakpointPromptEditAction::Condition,
8527 window,
8528 cx,
8529 );
8530 })
8531 .log_err();
8532 }
8533 })
8534 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8535 weak_editor
8536 .update(cx, |this, cx| {
8537 this.add_edit_breakpoint_block(
8538 anchor,
8539 breakpoint.as_ref(),
8540 BreakpointPromptEditAction::HitCondition,
8541 window,
8542 cx,
8543 );
8544 })
8545 .log_err();
8546 })
8547 })
8548 }
8549
8550 fn render_breakpoint(
8551 &self,
8552 position: Anchor,
8553 row: DisplayRow,
8554 breakpoint: &Breakpoint,
8555 state: Option<BreakpointSessionState>,
8556 cx: &mut Context<Self>,
8557 ) -> IconButton {
8558 let is_rejected = state.is_some_and(|s| !s.verified);
8559 // Is it a breakpoint that shows up when hovering over gutter?
8560 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8561 (false, false),
8562 |PhantomBreakpointIndicator {
8563 is_active,
8564 display_row,
8565 collides_with_existing_breakpoint,
8566 }| {
8567 (
8568 is_active && display_row == row,
8569 collides_with_existing_breakpoint,
8570 )
8571 },
8572 );
8573
8574 let (color, icon) = {
8575 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8576 (false, false) => ui::IconName::DebugBreakpoint,
8577 (true, false) => ui::IconName::DebugLogBreakpoint,
8578 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8579 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8580 };
8581
8582 let color = cx.theme().colors();
8583
8584 let color = if is_phantom {
8585 if collides_with_existing {
8586 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8587 } else {
8588 Color::Hint
8589 }
8590 } else if is_rejected {
8591 Color::Disabled
8592 } else {
8593 Color::Debugger
8594 };
8595
8596 (color, icon)
8597 };
8598
8599 let breakpoint = Arc::from(breakpoint.clone());
8600
8601 let alt_as_text = gpui::Keystroke {
8602 modifiers: Modifiers::secondary_key(),
8603 ..Default::default()
8604 };
8605 let primary_action_text = if breakpoint.is_disabled() {
8606 "Enable breakpoint"
8607 } else if is_phantom && !collides_with_existing {
8608 "Set breakpoint"
8609 } else {
8610 "Unset breakpoint"
8611 };
8612 let focus_handle = self.focus_handle.clone();
8613
8614 let meta = if is_rejected {
8615 SharedString::from("No executable code is associated with this line.")
8616 } else if collides_with_existing && !breakpoint.is_disabled() {
8617 SharedString::from(format!(
8618 "{alt_as_text}-click to disable,\nright-click for more options."
8619 ))
8620 } else {
8621 SharedString::from("Right-click for more options.")
8622 };
8623 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8624 .icon_size(IconSize::XSmall)
8625 .size(ui::ButtonSize::None)
8626 .when(is_rejected, |this| {
8627 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8628 })
8629 .icon_color(color)
8630 .style(ButtonStyle::Transparent)
8631 .on_click(cx.listener({
8632 move |editor, event: &ClickEvent, window, cx| {
8633 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8634 BreakpointEditAction::InvertState
8635 } else {
8636 BreakpointEditAction::Toggle
8637 };
8638
8639 window.focus(&editor.focus_handle(cx));
8640 editor.edit_breakpoint_at_anchor(
8641 position,
8642 breakpoint.as_ref().clone(),
8643 edit_action,
8644 cx,
8645 );
8646 }
8647 }))
8648 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8649 editor.set_breakpoint_context_menu(
8650 row,
8651 Some(position),
8652 event.position(),
8653 window,
8654 cx,
8655 );
8656 }))
8657 .tooltip(move |_window, cx| {
8658 Tooltip::with_meta_in(
8659 primary_action_text,
8660 Some(&ToggleBreakpoint),
8661 meta.clone(),
8662 &focus_handle,
8663 cx,
8664 )
8665 })
8666 }
8667
8668 fn build_tasks_context(
8669 project: &Entity<Project>,
8670 buffer: &Entity<Buffer>,
8671 buffer_row: u32,
8672 tasks: &Arc<RunnableTasks>,
8673 cx: &mut Context<Self>,
8674 ) -> Task<Option<task::TaskContext>> {
8675 let position = Point::new(buffer_row, tasks.column);
8676 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8677 let location = Location {
8678 buffer: buffer.clone(),
8679 range: range_start..range_start,
8680 };
8681 // Fill in the environmental variables from the tree-sitter captures
8682 let mut captured_task_variables = TaskVariables::default();
8683 for (capture_name, value) in tasks.extra_variables.clone() {
8684 captured_task_variables.insert(
8685 task::VariableName::Custom(capture_name.into()),
8686 value.clone(),
8687 );
8688 }
8689 project.update(cx, |project, cx| {
8690 project.task_store().update(cx, |task_store, cx| {
8691 task_store.task_context_for_location(captured_task_variables, location, cx)
8692 })
8693 })
8694 }
8695
8696 pub fn spawn_nearest_task(
8697 &mut self,
8698 action: &SpawnNearestTask,
8699 window: &mut Window,
8700 cx: &mut Context<Self>,
8701 ) {
8702 let Some((workspace, _)) = self.workspace.clone() else {
8703 return;
8704 };
8705 let Some(project) = self.project.clone() else {
8706 return;
8707 };
8708
8709 // Try to find a closest, enclosing node using tree-sitter that has a task
8710 let Some((buffer, buffer_row, tasks)) = self
8711 .find_enclosing_node_task(cx)
8712 // Or find the task that's closest in row-distance.
8713 .or_else(|| self.find_closest_task(cx))
8714 else {
8715 return;
8716 };
8717
8718 let reveal_strategy = action.reveal;
8719 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8720 cx.spawn_in(window, async move |_, cx| {
8721 let context = task_context.await?;
8722 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8723
8724 let resolved = &mut resolved_task.resolved;
8725 resolved.reveal = reveal_strategy;
8726
8727 workspace
8728 .update_in(cx, |workspace, window, cx| {
8729 workspace.schedule_resolved_task(
8730 task_source_kind,
8731 resolved_task,
8732 false,
8733 window,
8734 cx,
8735 );
8736 })
8737 .ok()
8738 })
8739 .detach();
8740 }
8741
8742 fn find_closest_task(
8743 &mut self,
8744 cx: &mut Context<Self>,
8745 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8746 let cursor_row = self
8747 .selections
8748 .newest_adjusted(&self.display_snapshot(cx))
8749 .head()
8750 .row;
8751
8752 let ((buffer_id, row), tasks) = self
8753 .tasks
8754 .iter()
8755 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8756
8757 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8758 let tasks = Arc::new(tasks.to_owned());
8759 Some((buffer, *row, tasks))
8760 }
8761
8762 fn find_enclosing_node_task(
8763 &mut self,
8764 cx: &mut Context<Self>,
8765 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8766 let snapshot = self.buffer.read(cx).snapshot(cx);
8767 let offset = self
8768 .selections
8769 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8770 .head();
8771 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8772 let offset = excerpt.map_offset_to_buffer(offset);
8773 let buffer_id = excerpt.buffer().remote_id();
8774
8775 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8776 let mut cursor = layer.node().walk();
8777
8778 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8779 if cursor.node().end_byte() == offset.0 {
8780 cursor.goto_next_sibling();
8781 }
8782 }
8783
8784 // Ascend to the smallest ancestor that contains the range and has a task.
8785 loop {
8786 let node = cursor.node();
8787 let node_range = node.byte_range();
8788 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8789
8790 // Check if this node contains our offset
8791 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8792 // If it contains offset, check for task
8793 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8794 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8795 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8796 }
8797 }
8798
8799 if !cursor.goto_parent() {
8800 break;
8801 }
8802 }
8803 None
8804 }
8805
8806 fn render_run_indicator(
8807 &self,
8808 _style: &EditorStyle,
8809 is_active: bool,
8810 row: DisplayRow,
8811 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8812 cx: &mut Context<Self>,
8813 ) -> IconButton {
8814 let color = Color::Muted;
8815 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8816
8817 IconButton::new(
8818 ("run_indicator", row.0 as usize),
8819 ui::IconName::PlayOutlined,
8820 )
8821 .shape(ui::IconButtonShape::Square)
8822 .icon_size(IconSize::XSmall)
8823 .icon_color(color)
8824 .toggle_state(is_active)
8825 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8826 let quick_launch = match e {
8827 ClickEvent::Keyboard(_) => true,
8828 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8829 };
8830
8831 window.focus(&editor.focus_handle(cx));
8832 editor.toggle_code_actions(
8833 &ToggleCodeActions {
8834 deployed_from: Some(CodeActionSource::RunMenu(row)),
8835 quick_launch,
8836 },
8837 window,
8838 cx,
8839 );
8840 }))
8841 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8842 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8843 }))
8844 }
8845
8846 pub fn context_menu_visible(&self) -> bool {
8847 !self.edit_prediction_preview_is_active()
8848 && self
8849 .context_menu
8850 .borrow()
8851 .as_ref()
8852 .is_some_and(|menu| menu.visible())
8853 }
8854
8855 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8856 self.context_menu
8857 .borrow()
8858 .as_ref()
8859 .map(|menu| menu.origin())
8860 }
8861
8862 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8863 self.context_menu_options = Some(options);
8864 }
8865
8866 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8867 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8868
8869 fn render_edit_prediction_popover(
8870 &mut self,
8871 text_bounds: &Bounds<Pixels>,
8872 content_origin: gpui::Point<Pixels>,
8873 right_margin: Pixels,
8874 editor_snapshot: &EditorSnapshot,
8875 visible_row_range: Range<DisplayRow>,
8876 scroll_top: ScrollOffset,
8877 scroll_bottom: ScrollOffset,
8878 line_layouts: &[LineWithInvisibles],
8879 line_height: Pixels,
8880 scroll_position: gpui::Point<ScrollOffset>,
8881 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8882 newest_selection_head: Option<DisplayPoint>,
8883 editor_width: Pixels,
8884 style: &EditorStyle,
8885 window: &mut Window,
8886 cx: &mut App,
8887 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8888 if self.mode().is_minimap() {
8889 return None;
8890 }
8891 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8892
8893 if self.edit_prediction_visible_in_cursor_popover(true) {
8894 return None;
8895 }
8896
8897 match &active_edit_prediction.completion {
8898 EditPrediction::MoveWithin { target, .. } => {
8899 let target_display_point = target.to_display_point(editor_snapshot);
8900
8901 if self.edit_prediction_requires_modifier() {
8902 if !self.edit_prediction_preview_is_active() {
8903 return None;
8904 }
8905
8906 self.render_edit_prediction_modifier_jump_popover(
8907 text_bounds,
8908 content_origin,
8909 visible_row_range,
8910 line_layouts,
8911 line_height,
8912 scroll_pixel_position,
8913 newest_selection_head,
8914 target_display_point,
8915 window,
8916 cx,
8917 )
8918 } else {
8919 self.render_edit_prediction_eager_jump_popover(
8920 text_bounds,
8921 content_origin,
8922 editor_snapshot,
8923 visible_row_range,
8924 scroll_top,
8925 scroll_bottom,
8926 line_height,
8927 scroll_pixel_position,
8928 target_display_point,
8929 editor_width,
8930 window,
8931 cx,
8932 )
8933 }
8934 }
8935 EditPrediction::Edit {
8936 display_mode: EditDisplayMode::Inline,
8937 ..
8938 } => None,
8939 EditPrediction::Edit {
8940 display_mode: EditDisplayMode::TabAccept,
8941 edits,
8942 ..
8943 } => {
8944 let range = &edits.first()?.0;
8945 let target_display_point = range.end.to_display_point(editor_snapshot);
8946
8947 self.render_edit_prediction_end_of_line_popover(
8948 "Accept",
8949 editor_snapshot,
8950 visible_row_range,
8951 target_display_point,
8952 line_height,
8953 scroll_pixel_position,
8954 content_origin,
8955 editor_width,
8956 window,
8957 cx,
8958 )
8959 }
8960 EditPrediction::Edit {
8961 edits,
8962 edit_preview,
8963 display_mode: EditDisplayMode::DiffPopover,
8964 snapshot,
8965 } => self.render_edit_prediction_diff_popover(
8966 text_bounds,
8967 content_origin,
8968 right_margin,
8969 editor_snapshot,
8970 visible_row_range,
8971 line_layouts,
8972 line_height,
8973 scroll_position,
8974 scroll_pixel_position,
8975 newest_selection_head,
8976 editor_width,
8977 style,
8978 edits,
8979 edit_preview,
8980 snapshot,
8981 window,
8982 cx,
8983 ),
8984 EditPrediction::MoveOutside { snapshot, .. } => {
8985 let mut element = self
8986 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8987 .into_any();
8988
8989 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8990 let origin_x = text_bounds.size.width - size.width - px(30.);
8991 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8992 element.prepaint_at(origin, window, cx);
8993
8994 Some((element, origin))
8995 }
8996 }
8997 }
8998
8999 fn render_edit_prediction_modifier_jump_popover(
9000 &mut self,
9001 text_bounds: &Bounds<Pixels>,
9002 content_origin: gpui::Point<Pixels>,
9003 visible_row_range: Range<DisplayRow>,
9004 line_layouts: &[LineWithInvisibles],
9005 line_height: Pixels,
9006 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9007 newest_selection_head: Option<DisplayPoint>,
9008 target_display_point: DisplayPoint,
9009 window: &mut Window,
9010 cx: &mut App,
9011 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9012 let scrolled_content_origin =
9013 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9014
9015 const SCROLL_PADDING_Y: Pixels = px(12.);
9016
9017 if target_display_point.row() < visible_row_range.start {
9018 return self.render_edit_prediction_scroll_popover(
9019 |_| SCROLL_PADDING_Y,
9020 IconName::ArrowUp,
9021 visible_row_range,
9022 line_layouts,
9023 newest_selection_head,
9024 scrolled_content_origin,
9025 window,
9026 cx,
9027 );
9028 } else if target_display_point.row() >= visible_row_range.end {
9029 return self.render_edit_prediction_scroll_popover(
9030 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9031 IconName::ArrowDown,
9032 visible_row_range,
9033 line_layouts,
9034 newest_selection_head,
9035 scrolled_content_origin,
9036 window,
9037 cx,
9038 );
9039 }
9040
9041 const POLE_WIDTH: Pixels = px(2.);
9042
9043 let line_layout =
9044 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9045 let target_column = target_display_point.column() as usize;
9046
9047 let target_x = line_layout.x_for_index(target_column);
9048 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9049 - scroll_pixel_position.y;
9050
9051 let flag_on_right = target_x < text_bounds.size.width / 2.;
9052
9053 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9054 border_color.l += 0.001;
9055
9056 let mut element = v_flex()
9057 .items_end()
9058 .when(flag_on_right, |el| el.items_start())
9059 .child(if flag_on_right {
9060 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9061 .rounded_bl(px(0.))
9062 .rounded_tl(px(0.))
9063 .border_l_2()
9064 .border_color(border_color)
9065 } else {
9066 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9067 .rounded_br(px(0.))
9068 .rounded_tr(px(0.))
9069 .border_r_2()
9070 .border_color(border_color)
9071 })
9072 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9073 .into_any();
9074
9075 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9076
9077 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9078 - point(
9079 if flag_on_right {
9080 POLE_WIDTH
9081 } else {
9082 size.width - POLE_WIDTH
9083 },
9084 size.height - line_height,
9085 );
9086
9087 origin.x = origin.x.max(content_origin.x);
9088
9089 element.prepaint_at(origin, window, cx);
9090
9091 Some((element, origin))
9092 }
9093
9094 fn render_edit_prediction_scroll_popover(
9095 &mut self,
9096 to_y: impl Fn(Size<Pixels>) -> Pixels,
9097 scroll_icon: IconName,
9098 visible_row_range: Range<DisplayRow>,
9099 line_layouts: &[LineWithInvisibles],
9100 newest_selection_head: Option<DisplayPoint>,
9101 scrolled_content_origin: gpui::Point<Pixels>,
9102 window: &mut Window,
9103 cx: &mut App,
9104 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9105 let mut element = self
9106 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9107 .into_any();
9108
9109 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9110
9111 let cursor = newest_selection_head?;
9112 let cursor_row_layout =
9113 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9114 let cursor_column = cursor.column() as usize;
9115
9116 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9117
9118 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9119
9120 element.prepaint_at(origin, window, cx);
9121 Some((element, origin))
9122 }
9123
9124 fn render_edit_prediction_eager_jump_popover(
9125 &mut self,
9126 text_bounds: &Bounds<Pixels>,
9127 content_origin: gpui::Point<Pixels>,
9128 editor_snapshot: &EditorSnapshot,
9129 visible_row_range: Range<DisplayRow>,
9130 scroll_top: ScrollOffset,
9131 scroll_bottom: ScrollOffset,
9132 line_height: Pixels,
9133 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9134 target_display_point: DisplayPoint,
9135 editor_width: Pixels,
9136 window: &mut Window,
9137 cx: &mut App,
9138 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9139 if target_display_point.row().as_f64() < scroll_top {
9140 let mut element = self
9141 .render_edit_prediction_line_popover(
9142 "Jump to Edit",
9143 Some(IconName::ArrowUp),
9144 window,
9145 cx,
9146 )
9147 .into_any();
9148
9149 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9150 let offset = point(
9151 (text_bounds.size.width - size.width) / 2.,
9152 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9153 );
9154
9155 let origin = text_bounds.origin + offset;
9156 element.prepaint_at(origin, window, cx);
9157 Some((element, origin))
9158 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9159 let mut element = self
9160 .render_edit_prediction_line_popover(
9161 "Jump to Edit",
9162 Some(IconName::ArrowDown),
9163 window,
9164 cx,
9165 )
9166 .into_any();
9167
9168 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9169 let offset = point(
9170 (text_bounds.size.width - size.width) / 2.,
9171 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9172 );
9173
9174 let origin = text_bounds.origin + offset;
9175 element.prepaint_at(origin, window, cx);
9176 Some((element, origin))
9177 } else {
9178 self.render_edit_prediction_end_of_line_popover(
9179 "Jump to Edit",
9180 editor_snapshot,
9181 visible_row_range,
9182 target_display_point,
9183 line_height,
9184 scroll_pixel_position,
9185 content_origin,
9186 editor_width,
9187 window,
9188 cx,
9189 )
9190 }
9191 }
9192
9193 fn render_edit_prediction_end_of_line_popover(
9194 self: &mut Editor,
9195 label: &'static str,
9196 editor_snapshot: &EditorSnapshot,
9197 visible_row_range: Range<DisplayRow>,
9198 target_display_point: DisplayPoint,
9199 line_height: Pixels,
9200 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9201 content_origin: gpui::Point<Pixels>,
9202 editor_width: Pixels,
9203 window: &mut Window,
9204 cx: &mut App,
9205 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9206 let target_line_end = DisplayPoint::new(
9207 target_display_point.row(),
9208 editor_snapshot.line_len(target_display_point.row()),
9209 );
9210
9211 let mut element = self
9212 .render_edit_prediction_line_popover(label, None, window, cx)
9213 .into_any();
9214
9215 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9216
9217 let line_origin =
9218 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9219
9220 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9221 let mut origin = start_point
9222 + line_origin
9223 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9224 origin.x = origin.x.max(content_origin.x);
9225
9226 let max_x = content_origin.x + editor_width - size.width;
9227
9228 if origin.x > max_x {
9229 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9230
9231 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9232 origin.y += offset;
9233 IconName::ArrowUp
9234 } else {
9235 origin.y -= offset;
9236 IconName::ArrowDown
9237 };
9238
9239 element = self
9240 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9241 .into_any();
9242
9243 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9244
9245 origin.x = content_origin.x + editor_width - size.width - px(2.);
9246 }
9247
9248 element.prepaint_at(origin, window, cx);
9249 Some((element, origin))
9250 }
9251
9252 fn render_edit_prediction_diff_popover(
9253 self: &Editor,
9254 text_bounds: &Bounds<Pixels>,
9255 content_origin: gpui::Point<Pixels>,
9256 right_margin: Pixels,
9257 editor_snapshot: &EditorSnapshot,
9258 visible_row_range: Range<DisplayRow>,
9259 line_layouts: &[LineWithInvisibles],
9260 line_height: Pixels,
9261 scroll_position: gpui::Point<ScrollOffset>,
9262 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9263 newest_selection_head: Option<DisplayPoint>,
9264 editor_width: Pixels,
9265 style: &EditorStyle,
9266 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9267 edit_preview: &Option<language::EditPreview>,
9268 snapshot: &language::BufferSnapshot,
9269 window: &mut Window,
9270 cx: &mut App,
9271 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9272 let edit_start = edits
9273 .first()
9274 .unwrap()
9275 .0
9276 .start
9277 .to_display_point(editor_snapshot);
9278 let edit_end = edits
9279 .last()
9280 .unwrap()
9281 .0
9282 .end
9283 .to_display_point(editor_snapshot);
9284
9285 let is_visible = visible_row_range.contains(&edit_start.row())
9286 || visible_row_range.contains(&edit_end.row());
9287 if !is_visible {
9288 return None;
9289 }
9290
9291 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9292 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9293 } else {
9294 // Fallback for providers without edit_preview
9295 crate::edit_prediction_fallback_text(edits, cx)
9296 };
9297
9298 let styled_text = highlighted_edits.to_styled_text(&style.text);
9299 let line_count = highlighted_edits.text.lines().count();
9300
9301 const BORDER_WIDTH: Pixels = px(1.);
9302
9303 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9304 let has_keybind = keybind.is_some();
9305
9306 let mut element = h_flex()
9307 .items_start()
9308 .child(
9309 h_flex()
9310 .bg(cx.theme().colors().editor_background)
9311 .border(BORDER_WIDTH)
9312 .shadow_xs()
9313 .border_color(cx.theme().colors().border)
9314 .rounded_l_lg()
9315 .when(line_count > 1, |el| el.rounded_br_lg())
9316 .pr_1()
9317 .child(styled_text),
9318 )
9319 .child(
9320 h_flex()
9321 .h(line_height + BORDER_WIDTH * 2.)
9322 .px_1p5()
9323 .gap_1()
9324 // Workaround: For some reason, there's a gap if we don't do this
9325 .ml(-BORDER_WIDTH)
9326 .shadow(vec![gpui::BoxShadow {
9327 color: gpui::black().opacity(0.05),
9328 offset: point(px(1.), px(1.)),
9329 blur_radius: px(2.),
9330 spread_radius: px(0.),
9331 }])
9332 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9333 .border(BORDER_WIDTH)
9334 .border_color(cx.theme().colors().border)
9335 .rounded_r_lg()
9336 .id("edit_prediction_diff_popover_keybind")
9337 .when(!has_keybind, |el| {
9338 let status_colors = cx.theme().status();
9339
9340 el.bg(status_colors.error_background)
9341 .border_color(status_colors.error.opacity(0.6))
9342 .child(Icon::new(IconName::Info).color(Color::Error))
9343 .cursor_default()
9344 .hoverable_tooltip(move |_window, cx| {
9345 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9346 })
9347 })
9348 .children(keybind),
9349 )
9350 .into_any();
9351
9352 let longest_row =
9353 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9354 let longest_line_width = if visible_row_range.contains(&longest_row) {
9355 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9356 } else {
9357 layout_line(
9358 longest_row,
9359 editor_snapshot,
9360 style,
9361 editor_width,
9362 |_| false,
9363 window,
9364 cx,
9365 )
9366 .width
9367 };
9368
9369 let viewport_bounds =
9370 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9371 right: -right_margin,
9372 ..Default::default()
9373 });
9374
9375 let x_after_longest = Pixels::from(
9376 ScrollPixelOffset::from(
9377 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9378 ) - scroll_pixel_position.x,
9379 );
9380
9381 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9382
9383 // Fully visible if it can be displayed within the window (allow overlapping other
9384 // panes). However, this is only allowed if the popover starts within text_bounds.
9385 let can_position_to_the_right = x_after_longest < text_bounds.right()
9386 && x_after_longest + element_bounds.width < viewport_bounds.right();
9387
9388 let mut origin = if can_position_to_the_right {
9389 point(
9390 x_after_longest,
9391 text_bounds.origin.y
9392 + Pixels::from(
9393 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9394 - scroll_pixel_position.y,
9395 ),
9396 )
9397 } else {
9398 let cursor_row = newest_selection_head.map(|head| head.row());
9399 let above_edit = edit_start
9400 .row()
9401 .0
9402 .checked_sub(line_count as u32)
9403 .map(DisplayRow);
9404 let below_edit = Some(edit_end.row() + 1);
9405 let above_cursor =
9406 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9407 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9408
9409 // Place the edit popover adjacent to the edit if there is a location
9410 // available that is onscreen and does not obscure the cursor. Otherwise,
9411 // place it adjacent to the cursor.
9412 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9413 .into_iter()
9414 .flatten()
9415 .find(|&start_row| {
9416 let end_row = start_row + line_count as u32;
9417 visible_row_range.contains(&start_row)
9418 && visible_row_range.contains(&end_row)
9419 && cursor_row
9420 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9421 })?;
9422
9423 content_origin
9424 + point(
9425 Pixels::from(-scroll_pixel_position.x),
9426 Pixels::from(
9427 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9428 ),
9429 )
9430 };
9431
9432 origin.x -= BORDER_WIDTH;
9433
9434 window.defer_draw(element, origin, 1);
9435
9436 // Do not return an element, since it will already be drawn due to defer_draw.
9437 None
9438 }
9439
9440 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9441 px(30.)
9442 }
9443
9444 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9445 if self.read_only(cx) {
9446 cx.theme().players().read_only()
9447 } else {
9448 self.style.as_ref().unwrap().local_player
9449 }
9450 }
9451
9452 fn render_edit_prediction_accept_keybind(
9453 &self,
9454 window: &mut Window,
9455 cx: &mut App,
9456 ) -> Option<AnyElement> {
9457 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9458 let accept_keystroke = accept_binding.keystroke()?;
9459
9460 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9461
9462 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9463 Color::Accent
9464 } else {
9465 Color::Muted
9466 };
9467
9468 h_flex()
9469 .px_0p5()
9470 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9471 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9472 .text_size(TextSize::XSmall.rems(cx))
9473 .child(h_flex().children(ui::render_modifiers(
9474 accept_keystroke.modifiers(),
9475 PlatformStyle::platform(),
9476 Some(modifiers_color),
9477 Some(IconSize::XSmall.rems().into()),
9478 true,
9479 )))
9480 .when(is_platform_style_mac, |parent| {
9481 parent.child(accept_keystroke.key().to_string())
9482 })
9483 .when(!is_platform_style_mac, |parent| {
9484 parent.child(
9485 Key::new(
9486 util::capitalize(accept_keystroke.key()),
9487 Some(Color::Default),
9488 )
9489 .size(Some(IconSize::XSmall.rems().into())),
9490 )
9491 })
9492 .into_any()
9493 .into()
9494 }
9495
9496 fn render_edit_prediction_line_popover(
9497 &self,
9498 label: impl Into<SharedString>,
9499 icon: Option<IconName>,
9500 window: &mut Window,
9501 cx: &mut App,
9502 ) -> Stateful<Div> {
9503 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9504
9505 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9506 let has_keybind = keybind.is_some();
9507
9508 h_flex()
9509 .id("ep-line-popover")
9510 .py_0p5()
9511 .pl_1()
9512 .pr(padding_right)
9513 .gap_1()
9514 .rounded_md()
9515 .border_1()
9516 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9517 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9518 .shadow_xs()
9519 .when(!has_keybind, |el| {
9520 let status_colors = cx.theme().status();
9521
9522 el.bg(status_colors.error_background)
9523 .border_color(status_colors.error.opacity(0.6))
9524 .pl_2()
9525 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9526 .cursor_default()
9527 .hoverable_tooltip(move |_window, cx| {
9528 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9529 })
9530 })
9531 .children(keybind)
9532 .child(
9533 Label::new(label)
9534 .size(LabelSize::Small)
9535 .when(!has_keybind, |el| {
9536 el.color(cx.theme().status().error.into()).strikethrough()
9537 }),
9538 )
9539 .when(!has_keybind, |el| {
9540 el.child(
9541 h_flex().ml_1().child(
9542 Icon::new(IconName::Info)
9543 .size(IconSize::Small)
9544 .color(cx.theme().status().error.into()),
9545 ),
9546 )
9547 })
9548 .when_some(icon, |element, icon| {
9549 element.child(
9550 div()
9551 .mt(px(1.5))
9552 .child(Icon::new(icon).size(IconSize::Small)),
9553 )
9554 })
9555 }
9556
9557 fn render_edit_prediction_jump_outside_popover(
9558 &self,
9559 snapshot: &BufferSnapshot,
9560 window: &mut Window,
9561 cx: &mut App,
9562 ) -> Stateful<Div> {
9563 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9564 let has_keybind = keybind.is_some();
9565
9566 let file_name = snapshot
9567 .file()
9568 .map(|file| SharedString::new(file.file_name(cx)))
9569 .unwrap_or(SharedString::new_static("untitled"));
9570
9571 h_flex()
9572 .id("ep-jump-outside-popover")
9573 .py_1()
9574 .px_2()
9575 .gap_1()
9576 .rounded_md()
9577 .border_1()
9578 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9579 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9580 .shadow_xs()
9581 .when(!has_keybind, |el| {
9582 let status_colors = cx.theme().status();
9583
9584 el.bg(status_colors.error_background)
9585 .border_color(status_colors.error.opacity(0.6))
9586 .pl_2()
9587 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9588 .cursor_default()
9589 .hoverable_tooltip(move |_window, cx| {
9590 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9591 })
9592 })
9593 .children(keybind)
9594 .child(
9595 Label::new(file_name)
9596 .size(LabelSize::Small)
9597 .buffer_font(cx)
9598 .when(!has_keybind, |el| {
9599 el.color(cx.theme().status().error.into()).strikethrough()
9600 }),
9601 )
9602 .when(!has_keybind, |el| {
9603 el.child(
9604 h_flex().ml_1().child(
9605 Icon::new(IconName::Info)
9606 .size(IconSize::Small)
9607 .color(cx.theme().status().error.into()),
9608 ),
9609 )
9610 })
9611 .child(
9612 div()
9613 .mt(px(1.5))
9614 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9615 )
9616 }
9617
9618 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9619 let accent_color = cx.theme().colors().text_accent;
9620 let editor_bg_color = cx.theme().colors().editor_background;
9621 editor_bg_color.blend(accent_color.opacity(0.1))
9622 }
9623
9624 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9625 let accent_color = cx.theme().colors().text_accent;
9626 let editor_bg_color = cx.theme().colors().editor_background;
9627 editor_bg_color.blend(accent_color.opacity(0.6))
9628 }
9629 fn get_prediction_provider_icon_name(
9630 provider: &Option<RegisteredEditPredictionDelegate>,
9631 ) -> IconName {
9632 match provider {
9633 Some(provider) => match provider.provider.name() {
9634 "copilot" => IconName::Copilot,
9635 "supermaven" => IconName::Supermaven,
9636 _ => IconName::ZedPredict,
9637 },
9638 None => IconName::ZedPredict,
9639 }
9640 }
9641
9642 fn render_edit_prediction_cursor_popover(
9643 &self,
9644 min_width: Pixels,
9645 max_width: Pixels,
9646 cursor_point: Point,
9647 style: &EditorStyle,
9648 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9649 _window: &Window,
9650 cx: &mut Context<Editor>,
9651 ) -> Option<AnyElement> {
9652 let provider = self.edit_prediction_provider.as_ref()?;
9653 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9654
9655 let is_refreshing = provider.provider.is_refreshing(cx);
9656
9657 fn pending_completion_container(icon: IconName) -> Div {
9658 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9659 }
9660
9661 let completion = match &self.active_edit_prediction {
9662 Some(prediction) => {
9663 if !self.has_visible_completions_menu() {
9664 const RADIUS: Pixels = px(6.);
9665 const BORDER_WIDTH: Pixels = px(1.);
9666
9667 return Some(
9668 h_flex()
9669 .elevation_2(cx)
9670 .border(BORDER_WIDTH)
9671 .border_color(cx.theme().colors().border)
9672 .when(accept_keystroke.is_none(), |el| {
9673 el.border_color(cx.theme().status().error)
9674 })
9675 .rounded(RADIUS)
9676 .rounded_tl(px(0.))
9677 .overflow_hidden()
9678 .child(div().px_1p5().child(match &prediction.completion {
9679 EditPrediction::MoveWithin { target, snapshot } => {
9680 use text::ToPoint as _;
9681 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9682 {
9683 Icon::new(IconName::ZedPredictDown)
9684 } else {
9685 Icon::new(IconName::ZedPredictUp)
9686 }
9687 }
9688 EditPrediction::MoveOutside { .. } => {
9689 // TODO [zeta2] custom icon for external jump?
9690 Icon::new(provider_icon)
9691 }
9692 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9693 }))
9694 .child(
9695 h_flex()
9696 .gap_1()
9697 .py_1()
9698 .px_2()
9699 .rounded_r(RADIUS - BORDER_WIDTH)
9700 .border_l_1()
9701 .border_color(cx.theme().colors().border)
9702 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9703 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9704 el.child(
9705 Label::new("Hold")
9706 .size(LabelSize::Small)
9707 .when(accept_keystroke.is_none(), |el| {
9708 el.strikethrough()
9709 })
9710 .line_height_style(LineHeightStyle::UiLabel),
9711 )
9712 })
9713 .id("edit_prediction_cursor_popover_keybind")
9714 .when(accept_keystroke.is_none(), |el| {
9715 let status_colors = cx.theme().status();
9716
9717 el.bg(status_colors.error_background)
9718 .border_color(status_colors.error.opacity(0.6))
9719 .child(Icon::new(IconName::Info).color(Color::Error))
9720 .cursor_default()
9721 .hoverable_tooltip(move |_window, cx| {
9722 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9723 .into()
9724 })
9725 })
9726 .when_some(
9727 accept_keystroke.as_ref(),
9728 |el, accept_keystroke| {
9729 el.child(h_flex().children(ui::render_modifiers(
9730 accept_keystroke.modifiers(),
9731 PlatformStyle::platform(),
9732 Some(Color::Default),
9733 Some(IconSize::XSmall.rems().into()),
9734 false,
9735 )))
9736 },
9737 ),
9738 )
9739 .into_any(),
9740 );
9741 }
9742
9743 self.render_edit_prediction_cursor_popover_preview(
9744 prediction,
9745 cursor_point,
9746 style,
9747 cx,
9748 )?
9749 }
9750
9751 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9752 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9753 stale_completion,
9754 cursor_point,
9755 style,
9756 cx,
9757 )?,
9758
9759 None => pending_completion_container(provider_icon)
9760 .child(Label::new("...").size(LabelSize::Small)),
9761 },
9762
9763 None => pending_completion_container(provider_icon)
9764 .child(Label::new("...").size(LabelSize::Small)),
9765 };
9766
9767 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9768 completion
9769 .with_animation(
9770 "loading-completion",
9771 Animation::new(Duration::from_secs(2))
9772 .repeat()
9773 .with_easing(pulsating_between(0.4, 0.8)),
9774 |label, delta| label.opacity(delta),
9775 )
9776 .into_any_element()
9777 } else {
9778 completion.into_any_element()
9779 };
9780
9781 let has_completion = self.active_edit_prediction.is_some();
9782
9783 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9784 Some(
9785 h_flex()
9786 .min_w(min_width)
9787 .max_w(max_width)
9788 .flex_1()
9789 .elevation_2(cx)
9790 .border_color(cx.theme().colors().border)
9791 .child(
9792 div()
9793 .flex_1()
9794 .py_1()
9795 .px_2()
9796 .overflow_hidden()
9797 .child(completion),
9798 )
9799 .when_some(accept_keystroke, |el, accept_keystroke| {
9800 if !accept_keystroke.modifiers().modified() {
9801 return el;
9802 }
9803
9804 el.child(
9805 h_flex()
9806 .h_full()
9807 .border_l_1()
9808 .rounded_r_lg()
9809 .border_color(cx.theme().colors().border)
9810 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9811 .gap_1()
9812 .py_1()
9813 .px_2()
9814 .child(
9815 h_flex()
9816 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9817 .when(is_platform_style_mac, |parent| parent.gap_1())
9818 .child(h_flex().children(ui::render_modifiers(
9819 accept_keystroke.modifiers(),
9820 PlatformStyle::platform(),
9821 Some(if !has_completion {
9822 Color::Muted
9823 } else {
9824 Color::Default
9825 }),
9826 None,
9827 false,
9828 ))),
9829 )
9830 .child(Label::new("Preview").into_any_element())
9831 .opacity(if has_completion { 1.0 } else { 0.4 }),
9832 )
9833 })
9834 .into_any(),
9835 )
9836 }
9837
9838 fn render_edit_prediction_cursor_popover_preview(
9839 &self,
9840 completion: &EditPredictionState,
9841 cursor_point: Point,
9842 style: &EditorStyle,
9843 cx: &mut Context<Editor>,
9844 ) -> Option<Div> {
9845 use text::ToPoint as _;
9846
9847 fn render_relative_row_jump(
9848 prefix: impl Into<String>,
9849 current_row: u32,
9850 target_row: u32,
9851 ) -> Div {
9852 let (row_diff, arrow) = if target_row < current_row {
9853 (current_row - target_row, IconName::ArrowUp)
9854 } else {
9855 (target_row - current_row, IconName::ArrowDown)
9856 };
9857
9858 h_flex()
9859 .child(
9860 Label::new(format!("{}{}", prefix.into(), row_diff))
9861 .color(Color::Muted)
9862 .size(LabelSize::Small),
9863 )
9864 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9865 }
9866
9867 let supports_jump = self
9868 .edit_prediction_provider
9869 .as_ref()
9870 .map(|provider| provider.provider.supports_jump_to_edit())
9871 .unwrap_or(true);
9872
9873 match &completion.completion {
9874 EditPrediction::MoveWithin {
9875 target, snapshot, ..
9876 } => {
9877 if !supports_jump {
9878 return None;
9879 }
9880
9881 Some(
9882 h_flex()
9883 .px_2()
9884 .gap_2()
9885 .flex_1()
9886 .child(
9887 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9888 Icon::new(IconName::ZedPredictDown)
9889 } else {
9890 Icon::new(IconName::ZedPredictUp)
9891 },
9892 )
9893 .child(Label::new("Jump to Edit")),
9894 )
9895 }
9896 EditPrediction::MoveOutside { snapshot, .. } => {
9897 let file_name = snapshot
9898 .file()
9899 .map(|file| file.file_name(cx))
9900 .unwrap_or("untitled");
9901 Some(
9902 h_flex()
9903 .px_2()
9904 .gap_2()
9905 .flex_1()
9906 .child(Icon::new(IconName::ZedPredict))
9907 .child(Label::new(format!("Jump to {file_name}"))),
9908 )
9909 }
9910 EditPrediction::Edit {
9911 edits,
9912 edit_preview,
9913 snapshot,
9914 display_mode: _,
9915 } => {
9916 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9917
9918 let (highlighted_edits, has_more_lines) =
9919 if let Some(edit_preview) = edit_preview.as_ref() {
9920 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9921 .first_line_preview()
9922 } else {
9923 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9924 };
9925
9926 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9927 .with_default_highlights(&style.text, highlighted_edits.highlights);
9928
9929 let preview = h_flex()
9930 .gap_1()
9931 .min_w_16()
9932 .child(styled_text)
9933 .when(has_more_lines, |parent| parent.child("…"));
9934
9935 let left = if supports_jump && first_edit_row != cursor_point.row {
9936 render_relative_row_jump("", cursor_point.row, first_edit_row)
9937 .into_any_element()
9938 } else {
9939 let icon_name =
9940 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9941 Icon::new(icon_name).into_any_element()
9942 };
9943
9944 Some(
9945 h_flex()
9946 .h_full()
9947 .flex_1()
9948 .gap_2()
9949 .pr_1()
9950 .overflow_x_hidden()
9951 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9952 .child(left)
9953 .child(preview),
9954 )
9955 }
9956 }
9957 }
9958
9959 pub fn render_context_menu(
9960 &mut self,
9961 max_height_in_lines: u32,
9962 window: &mut Window,
9963 cx: &mut Context<Editor>,
9964 ) -> Option<AnyElement> {
9965 let menu = self.context_menu.borrow();
9966 let menu = menu.as_ref()?;
9967 if !menu.visible() {
9968 return None;
9969 };
9970 self.style
9971 .as_ref()
9972 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9973 }
9974
9975 fn render_context_menu_aside(
9976 &mut self,
9977 max_size: Size<Pixels>,
9978 window: &mut Window,
9979 cx: &mut Context<Editor>,
9980 ) -> Option<AnyElement> {
9981 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9982 if menu.visible() {
9983 menu.render_aside(max_size, window, cx)
9984 } else {
9985 None
9986 }
9987 })
9988 }
9989
9990 fn hide_context_menu(
9991 &mut self,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) -> Option<CodeContextMenu> {
9995 cx.notify();
9996 self.completion_tasks.clear();
9997 let context_menu = self.context_menu.borrow_mut().take();
9998 self.stale_edit_prediction_in_menu.take();
9999 self.update_visible_edit_prediction(window, cx);
10000 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10001 && let Some(completion_provider) = &self.completion_provider
10002 {
10003 completion_provider.selection_changed(None, window, cx);
10004 }
10005 context_menu
10006 }
10007
10008 fn show_snippet_choices(
10009 &mut self,
10010 choices: &Vec<String>,
10011 selection: Range<Anchor>,
10012 cx: &mut Context<Self>,
10013 ) {
10014 let Some((_, buffer, _)) = self
10015 .buffer()
10016 .read(cx)
10017 .excerpt_containing(selection.start, cx)
10018 else {
10019 return;
10020 };
10021 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10022 else {
10023 return;
10024 };
10025 if buffer != end_buffer {
10026 log::error!("expected anchor range to have matching buffer IDs");
10027 return;
10028 }
10029
10030 let id = post_inc(&mut self.next_completion_id);
10031 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10032 let mut context_menu = self.context_menu.borrow_mut();
10033 let old_menu = context_menu.take();
10034 *context_menu = Some(CodeContextMenu::Completions(
10035 CompletionsMenu::new_snippet_choices(
10036 id,
10037 true,
10038 choices,
10039 selection,
10040 buffer,
10041 old_menu.map(|menu| menu.primary_scroll_handle()),
10042 snippet_sort_order,
10043 ),
10044 ));
10045 }
10046
10047 pub fn insert_snippet(
10048 &mut self,
10049 insertion_ranges: &[Range<MultiBufferOffset>],
10050 snippet: Snippet,
10051 window: &mut Window,
10052 cx: &mut Context<Self>,
10053 ) -> Result<()> {
10054 struct Tabstop<T> {
10055 is_end_tabstop: bool,
10056 ranges: Vec<Range<T>>,
10057 choices: Option<Vec<String>>,
10058 }
10059
10060 let tabstops = self.buffer.update(cx, |buffer, cx| {
10061 let snippet_text: Arc<str> = snippet.text.clone().into();
10062 let edits = insertion_ranges
10063 .iter()
10064 .cloned()
10065 .map(|range| (range, snippet_text.clone()));
10066 let autoindent_mode = AutoindentMode::Block {
10067 original_indent_columns: Vec::new(),
10068 };
10069 buffer.edit(edits, Some(autoindent_mode), cx);
10070
10071 let snapshot = &*buffer.read(cx);
10072 let snippet = &snippet;
10073 snippet
10074 .tabstops
10075 .iter()
10076 .map(|tabstop| {
10077 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10078 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10079 });
10080 let mut tabstop_ranges = tabstop
10081 .ranges
10082 .iter()
10083 .flat_map(|tabstop_range| {
10084 let mut delta = 0_isize;
10085 insertion_ranges.iter().map(move |insertion_range| {
10086 let insertion_start = insertion_range.start + delta;
10087 delta += snippet.text.len() as isize
10088 - (insertion_range.end - insertion_range.start) as isize;
10089
10090 let start =
10091 (insertion_start + tabstop_range.start).min(snapshot.len());
10092 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10093 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10094 })
10095 })
10096 .collect::<Vec<_>>();
10097 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10098
10099 Tabstop {
10100 is_end_tabstop,
10101 ranges: tabstop_ranges,
10102 choices: tabstop.choices.clone(),
10103 }
10104 })
10105 .collect::<Vec<_>>()
10106 });
10107 if let Some(tabstop) = tabstops.first() {
10108 self.change_selections(Default::default(), window, cx, |s| {
10109 // Reverse order so that the first range is the newest created selection.
10110 // Completions will use it and autoscroll will prioritize it.
10111 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10112 });
10113
10114 if let Some(choices) = &tabstop.choices
10115 && let Some(selection) = tabstop.ranges.first()
10116 {
10117 self.show_snippet_choices(choices, selection.clone(), cx)
10118 }
10119
10120 // If we're already at the last tabstop and it's at the end of the snippet,
10121 // we're done, we don't need to keep the state around.
10122 if !tabstop.is_end_tabstop {
10123 let choices = tabstops
10124 .iter()
10125 .map(|tabstop| tabstop.choices.clone())
10126 .collect();
10127
10128 let ranges = tabstops
10129 .into_iter()
10130 .map(|tabstop| tabstop.ranges)
10131 .collect::<Vec<_>>();
10132
10133 self.snippet_stack.push(SnippetState {
10134 active_index: 0,
10135 ranges,
10136 choices,
10137 });
10138 }
10139
10140 // Check whether the just-entered snippet ends with an auto-closable bracket.
10141 if self.autoclose_regions.is_empty() {
10142 let snapshot = self.buffer.read(cx).snapshot(cx);
10143 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10144 let selection_head = selection.head();
10145 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10146 continue;
10147 };
10148
10149 let mut bracket_pair = None;
10150 let max_lookup_length = scope
10151 .brackets()
10152 .map(|(pair, _)| {
10153 pair.start
10154 .as_str()
10155 .chars()
10156 .count()
10157 .max(pair.end.as_str().chars().count())
10158 })
10159 .max();
10160 if let Some(max_lookup_length) = max_lookup_length {
10161 let next_text = snapshot
10162 .chars_at(selection_head)
10163 .take(max_lookup_length)
10164 .collect::<String>();
10165 let prev_text = snapshot
10166 .reversed_chars_at(selection_head)
10167 .take(max_lookup_length)
10168 .collect::<String>();
10169
10170 for (pair, enabled) in scope.brackets() {
10171 if enabled
10172 && pair.close
10173 && prev_text.starts_with(pair.start.as_str())
10174 && next_text.starts_with(pair.end.as_str())
10175 {
10176 bracket_pair = Some(pair.clone());
10177 break;
10178 }
10179 }
10180 }
10181
10182 if let Some(pair) = bracket_pair {
10183 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10184 let autoclose_enabled =
10185 self.use_autoclose && snapshot_settings.use_autoclose;
10186 if autoclose_enabled {
10187 let start = snapshot.anchor_after(selection_head);
10188 let end = snapshot.anchor_after(selection_head);
10189 self.autoclose_regions.push(AutocloseRegion {
10190 selection_id: selection.id,
10191 range: start..end,
10192 pair,
10193 });
10194 }
10195 }
10196 }
10197 }
10198 }
10199 Ok(())
10200 }
10201
10202 pub fn move_to_next_snippet_tabstop(
10203 &mut self,
10204 window: &mut Window,
10205 cx: &mut Context<Self>,
10206 ) -> bool {
10207 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10208 }
10209
10210 pub fn move_to_prev_snippet_tabstop(
10211 &mut self,
10212 window: &mut Window,
10213 cx: &mut Context<Self>,
10214 ) -> bool {
10215 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10216 }
10217
10218 pub fn move_to_snippet_tabstop(
10219 &mut self,
10220 bias: Bias,
10221 window: &mut Window,
10222 cx: &mut Context<Self>,
10223 ) -> bool {
10224 if let Some(mut snippet) = self.snippet_stack.pop() {
10225 match bias {
10226 Bias::Left => {
10227 if snippet.active_index > 0 {
10228 snippet.active_index -= 1;
10229 } else {
10230 self.snippet_stack.push(snippet);
10231 return false;
10232 }
10233 }
10234 Bias::Right => {
10235 if snippet.active_index + 1 < snippet.ranges.len() {
10236 snippet.active_index += 1;
10237 } else {
10238 self.snippet_stack.push(snippet);
10239 return false;
10240 }
10241 }
10242 }
10243 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10244 self.change_selections(Default::default(), window, cx, |s| {
10245 // Reverse order so that the first range is the newest created selection.
10246 // Completions will use it and autoscroll will prioritize it.
10247 s.select_ranges(current_ranges.iter().rev().cloned())
10248 });
10249
10250 if let Some(choices) = &snippet.choices[snippet.active_index]
10251 && let Some(selection) = current_ranges.first()
10252 {
10253 self.show_snippet_choices(choices, selection.clone(), cx);
10254 }
10255
10256 // If snippet state is not at the last tabstop, push it back on the stack
10257 if snippet.active_index + 1 < snippet.ranges.len() {
10258 self.snippet_stack.push(snippet);
10259 }
10260 return true;
10261 }
10262 }
10263
10264 false
10265 }
10266
10267 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10268 self.transact(window, cx, |this, window, cx| {
10269 this.select_all(&SelectAll, window, cx);
10270 this.insert("", window, cx);
10271 });
10272 }
10273
10274 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10275 if self.read_only(cx) {
10276 return;
10277 }
10278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10279 self.transact(window, cx, |this, window, cx| {
10280 this.select_autoclose_pair(window, cx);
10281
10282 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10283
10284 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10285 if !this.linked_edit_ranges.is_empty() {
10286 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10287 let snapshot = this.buffer.read(cx).snapshot(cx);
10288
10289 for selection in selections.iter() {
10290 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10291 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10292 if selection_start.buffer_id != selection_end.buffer_id {
10293 continue;
10294 }
10295 if let Some(ranges) =
10296 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10297 {
10298 for (buffer, entries) in ranges {
10299 linked_ranges.entry(buffer).or_default().extend(entries);
10300 }
10301 }
10302 }
10303 }
10304
10305 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10306 for selection in &mut selections {
10307 if selection.is_empty() {
10308 let old_head = selection.head();
10309 let mut new_head =
10310 movement::left(&display_map, old_head.to_display_point(&display_map))
10311 .to_point(&display_map);
10312 if let Some((buffer, line_buffer_range)) = display_map
10313 .buffer_snapshot()
10314 .buffer_line_for_row(MultiBufferRow(old_head.row))
10315 {
10316 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10317 let indent_len = match indent_size.kind {
10318 IndentKind::Space => {
10319 buffer.settings_at(line_buffer_range.start, cx).tab_size
10320 }
10321 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10322 };
10323 if old_head.column <= indent_size.len && old_head.column > 0 {
10324 let indent_len = indent_len.get();
10325 new_head = cmp::min(
10326 new_head,
10327 MultiBufferPoint::new(
10328 old_head.row,
10329 ((old_head.column - 1) / indent_len) * indent_len,
10330 ),
10331 );
10332 }
10333 }
10334
10335 selection.set_head(new_head, SelectionGoal::None);
10336 }
10337 }
10338
10339 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10340 this.insert("", window, cx);
10341 let empty_str: Arc<str> = Arc::from("");
10342 for (buffer, edits) in linked_ranges {
10343 let snapshot = buffer.read(cx).snapshot();
10344 use text::ToPoint as TP;
10345
10346 let edits = edits
10347 .into_iter()
10348 .map(|range| {
10349 let end_point = TP::to_point(&range.end, &snapshot);
10350 let mut start_point = TP::to_point(&range.start, &snapshot);
10351
10352 if end_point == start_point {
10353 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10354 .saturating_sub(1);
10355 start_point =
10356 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10357 };
10358
10359 (start_point..end_point, empty_str.clone())
10360 })
10361 .sorted_by_key(|(range, _)| range.start)
10362 .collect::<Vec<_>>();
10363 buffer.update(cx, |this, cx| {
10364 this.edit(edits, None, cx);
10365 })
10366 }
10367 this.refresh_edit_prediction(true, false, window, cx);
10368 refresh_linked_ranges(this, window, cx);
10369 });
10370 }
10371
10372 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10373 if self.read_only(cx) {
10374 return;
10375 }
10376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10377 self.transact(window, cx, |this, window, cx| {
10378 this.change_selections(Default::default(), window, cx, |s| {
10379 s.move_with(|map, selection| {
10380 if selection.is_empty() {
10381 let cursor = movement::right(map, selection.head());
10382 selection.end = cursor;
10383 selection.reversed = true;
10384 selection.goal = SelectionGoal::None;
10385 }
10386 })
10387 });
10388 this.insert("", window, cx);
10389 this.refresh_edit_prediction(true, false, window, cx);
10390 });
10391 }
10392
10393 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10394 if self.mode.is_single_line() {
10395 cx.propagate();
10396 return;
10397 }
10398
10399 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10400 if self.move_to_prev_snippet_tabstop(window, cx) {
10401 return;
10402 }
10403 self.outdent(&Outdent, window, cx);
10404 }
10405
10406 pub fn next_snippet_tabstop(
10407 &mut self,
10408 _: &NextSnippetTabstop,
10409 window: &mut Window,
10410 cx: &mut Context<Self>,
10411 ) {
10412 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10413 cx.propagate();
10414 return;
10415 }
10416
10417 if self.move_to_next_snippet_tabstop(window, cx) {
10418 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10419 return;
10420 }
10421 cx.propagate();
10422 }
10423
10424 pub fn previous_snippet_tabstop(
10425 &mut self,
10426 _: &PreviousSnippetTabstop,
10427 window: &mut Window,
10428 cx: &mut Context<Self>,
10429 ) {
10430 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10431 cx.propagate();
10432 return;
10433 }
10434
10435 if self.move_to_prev_snippet_tabstop(window, cx) {
10436 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10437 return;
10438 }
10439 cx.propagate();
10440 }
10441
10442 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10443 if self.mode.is_single_line() {
10444 cx.propagate();
10445 return;
10446 }
10447
10448 if self.move_to_next_snippet_tabstop(window, cx) {
10449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10450 return;
10451 }
10452 if self.read_only(cx) {
10453 return;
10454 }
10455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10456 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10457 let buffer = self.buffer.read(cx);
10458 let snapshot = buffer.snapshot(cx);
10459 let rows_iter = selections.iter().map(|s| s.head().row);
10460 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10461
10462 let has_some_cursor_in_whitespace = selections
10463 .iter()
10464 .filter(|selection| selection.is_empty())
10465 .any(|selection| {
10466 let cursor = selection.head();
10467 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10468 cursor.column < current_indent.len
10469 });
10470
10471 let mut edits = Vec::new();
10472 let mut prev_edited_row = 0;
10473 let mut row_delta = 0;
10474 for selection in &mut selections {
10475 if selection.start.row != prev_edited_row {
10476 row_delta = 0;
10477 }
10478 prev_edited_row = selection.end.row;
10479
10480 // If the selection is non-empty, then increase the indentation of the selected lines.
10481 if !selection.is_empty() {
10482 row_delta =
10483 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10484 continue;
10485 }
10486
10487 let cursor = selection.head();
10488 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10489 if let Some(suggested_indent) =
10490 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10491 {
10492 // Don't do anything if already at suggested indent
10493 // and there is any other cursor which is not
10494 if has_some_cursor_in_whitespace
10495 && cursor.column == current_indent.len
10496 && current_indent.len == suggested_indent.len
10497 {
10498 continue;
10499 }
10500
10501 // Adjust line and move cursor to suggested indent
10502 // if cursor is not at suggested indent
10503 if cursor.column < suggested_indent.len
10504 && cursor.column <= current_indent.len
10505 && current_indent.len <= suggested_indent.len
10506 {
10507 selection.start = Point::new(cursor.row, suggested_indent.len);
10508 selection.end = selection.start;
10509 if row_delta == 0 {
10510 edits.extend(Buffer::edit_for_indent_size_adjustment(
10511 cursor.row,
10512 current_indent,
10513 suggested_indent,
10514 ));
10515 row_delta = suggested_indent.len - current_indent.len;
10516 }
10517 continue;
10518 }
10519
10520 // If current indent is more than suggested indent
10521 // only move cursor to current indent and skip indent
10522 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10523 selection.start = Point::new(cursor.row, current_indent.len);
10524 selection.end = selection.start;
10525 continue;
10526 }
10527 }
10528
10529 // Otherwise, insert a hard or soft tab.
10530 let settings = buffer.language_settings_at(cursor, cx);
10531 let tab_size = if settings.hard_tabs {
10532 IndentSize::tab()
10533 } else {
10534 let tab_size = settings.tab_size.get();
10535 let indent_remainder = snapshot
10536 .text_for_range(Point::new(cursor.row, 0)..cursor)
10537 .flat_map(str::chars)
10538 .fold(row_delta % tab_size, |counter: u32, c| {
10539 if c == '\t' {
10540 0
10541 } else {
10542 (counter + 1) % tab_size
10543 }
10544 });
10545
10546 let chars_to_next_tab_stop = tab_size - indent_remainder;
10547 IndentSize::spaces(chars_to_next_tab_stop)
10548 };
10549 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10550 selection.end = selection.start;
10551 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10552 row_delta += tab_size.len;
10553 }
10554
10555 self.transact(window, cx, |this, window, cx| {
10556 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10557 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10558 this.refresh_edit_prediction(true, false, window, cx);
10559 });
10560 }
10561
10562 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10563 if self.read_only(cx) {
10564 return;
10565 }
10566 if self.mode.is_single_line() {
10567 cx.propagate();
10568 return;
10569 }
10570
10571 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10572 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10573 let mut prev_edited_row = 0;
10574 let mut row_delta = 0;
10575 let mut edits = Vec::new();
10576 let buffer = self.buffer.read(cx);
10577 let snapshot = buffer.snapshot(cx);
10578 for selection in &mut selections {
10579 if selection.start.row != prev_edited_row {
10580 row_delta = 0;
10581 }
10582 prev_edited_row = selection.end.row;
10583
10584 row_delta =
10585 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10586 }
10587
10588 self.transact(window, cx, |this, window, cx| {
10589 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10590 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10591 });
10592 }
10593
10594 fn indent_selection(
10595 buffer: &MultiBuffer,
10596 snapshot: &MultiBufferSnapshot,
10597 selection: &mut Selection<Point>,
10598 edits: &mut Vec<(Range<Point>, String)>,
10599 delta_for_start_row: u32,
10600 cx: &App,
10601 ) -> u32 {
10602 let settings = buffer.language_settings_at(selection.start, cx);
10603 let tab_size = settings.tab_size.get();
10604 let indent_kind = if settings.hard_tabs {
10605 IndentKind::Tab
10606 } else {
10607 IndentKind::Space
10608 };
10609 let mut start_row = selection.start.row;
10610 let mut end_row = selection.end.row + 1;
10611
10612 // If a selection ends at the beginning of a line, don't indent
10613 // that last line.
10614 if selection.end.column == 0 && selection.end.row > selection.start.row {
10615 end_row -= 1;
10616 }
10617
10618 // Avoid re-indenting a row that has already been indented by a
10619 // previous selection, but still update this selection's column
10620 // to reflect that indentation.
10621 if delta_for_start_row > 0 {
10622 start_row += 1;
10623 selection.start.column += delta_for_start_row;
10624 if selection.end.row == selection.start.row {
10625 selection.end.column += delta_for_start_row;
10626 }
10627 }
10628
10629 let mut delta_for_end_row = 0;
10630 let has_multiple_rows = start_row + 1 != end_row;
10631 for row in start_row..end_row {
10632 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10633 let indent_delta = match (current_indent.kind, indent_kind) {
10634 (IndentKind::Space, IndentKind::Space) => {
10635 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10636 IndentSize::spaces(columns_to_next_tab_stop)
10637 }
10638 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10639 (_, IndentKind::Tab) => IndentSize::tab(),
10640 };
10641
10642 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10643 0
10644 } else {
10645 selection.start.column
10646 };
10647 let row_start = Point::new(row, start);
10648 edits.push((
10649 row_start..row_start,
10650 indent_delta.chars().collect::<String>(),
10651 ));
10652
10653 // Update this selection's endpoints to reflect the indentation.
10654 if row == selection.start.row {
10655 selection.start.column += indent_delta.len;
10656 }
10657 if row == selection.end.row {
10658 selection.end.column += indent_delta.len;
10659 delta_for_end_row = indent_delta.len;
10660 }
10661 }
10662
10663 if selection.start.row == selection.end.row {
10664 delta_for_start_row + delta_for_end_row
10665 } else {
10666 delta_for_end_row
10667 }
10668 }
10669
10670 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10671 if self.read_only(cx) {
10672 return;
10673 }
10674 if self.mode.is_single_line() {
10675 cx.propagate();
10676 return;
10677 }
10678
10679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10681 let selections = self.selections.all::<Point>(&display_map);
10682 let mut deletion_ranges = Vec::new();
10683 let mut last_outdent = None;
10684 {
10685 let buffer = self.buffer.read(cx);
10686 let snapshot = buffer.snapshot(cx);
10687 for selection in &selections {
10688 let settings = buffer.language_settings_at(selection.start, cx);
10689 let tab_size = settings.tab_size.get();
10690 let mut rows = selection.spanned_rows(false, &display_map);
10691
10692 // Avoid re-outdenting a row that has already been outdented by a
10693 // previous selection.
10694 if let Some(last_row) = last_outdent
10695 && last_row == rows.start
10696 {
10697 rows.start = rows.start.next_row();
10698 }
10699 let has_multiple_rows = rows.len() > 1;
10700 for row in rows.iter_rows() {
10701 let indent_size = snapshot.indent_size_for_line(row);
10702 if indent_size.len > 0 {
10703 let deletion_len = match indent_size.kind {
10704 IndentKind::Space => {
10705 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10706 if columns_to_prev_tab_stop == 0 {
10707 tab_size
10708 } else {
10709 columns_to_prev_tab_stop
10710 }
10711 }
10712 IndentKind::Tab => 1,
10713 };
10714 let start = if has_multiple_rows
10715 || deletion_len > selection.start.column
10716 || indent_size.len < selection.start.column
10717 {
10718 0
10719 } else {
10720 selection.start.column - deletion_len
10721 };
10722 deletion_ranges.push(
10723 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10724 );
10725 last_outdent = Some(row);
10726 }
10727 }
10728 }
10729 }
10730
10731 self.transact(window, cx, |this, window, cx| {
10732 this.buffer.update(cx, |buffer, cx| {
10733 let empty_str: Arc<str> = Arc::default();
10734 buffer.edit(
10735 deletion_ranges
10736 .into_iter()
10737 .map(|range| (range, empty_str.clone())),
10738 None,
10739 cx,
10740 );
10741 });
10742 let selections = this
10743 .selections
10744 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10745 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10746 });
10747 }
10748
10749 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10750 if self.read_only(cx) {
10751 return;
10752 }
10753 if self.mode.is_single_line() {
10754 cx.propagate();
10755 return;
10756 }
10757
10758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10759 let selections = self
10760 .selections
10761 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10762 .into_iter()
10763 .map(|s| s.range());
10764
10765 self.transact(window, cx, |this, window, cx| {
10766 this.buffer.update(cx, |buffer, cx| {
10767 buffer.autoindent_ranges(selections, cx);
10768 });
10769 let selections = this
10770 .selections
10771 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10772 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10773 });
10774 }
10775
10776 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10777 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10779 let selections = self.selections.all::<Point>(&display_map);
10780
10781 let mut new_cursors = Vec::new();
10782 let mut edit_ranges = Vec::new();
10783 let mut selections = selections.iter().peekable();
10784 while let Some(selection) = selections.next() {
10785 let mut rows = selection.spanned_rows(false, &display_map);
10786
10787 // Accumulate contiguous regions of rows that we want to delete.
10788 while let Some(next_selection) = selections.peek() {
10789 let next_rows = next_selection.spanned_rows(false, &display_map);
10790 if next_rows.start <= rows.end {
10791 rows.end = next_rows.end;
10792 selections.next().unwrap();
10793 } else {
10794 break;
10795 }
10796 }
10797
10798 let buffer = display_map.buffer_snapshot();
10799 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10800 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10801 // If there's a line after the range, delete the \n from the end of the row range
10802 (
10803 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10804 rows.end,
10805 )
10806 } else {
10807 // If there isn't a line after the range, delete the \n from the line before the
10808 // start of the row range
10809 edit_start = edit_start.saturating_sub_usize(1);
10810 (buffer.len(), rows.start.previous_row())
10811 };
10812
10813 let text_layout_details = self.text_layout_details(window);
10814 let x = display_map.x_for_display_point(
10815 selection.head().to_display_point(&display_map),
10816 &text_layout_details,
10817 );
10818 let row = Point::new(target_row.0, 0)
10819 .to_display_point(&display_map)
10820 .row();
10821 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10822
10823 new_cursors.push((
10824 selection.id,
10825 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10826 SelectionGoal::None,
10827 ));
10828 edit_ranges.push(edit_start..edit_end);
10829 }
10830
10831 self.transact(window, cx, |this, window, cx| {
10832 let buffer = this.buffer.update(cx, |buffer, cx| {
10833 let empty_str: Arc<str> = Arc::default();
10834 buffer.edit(
10835 edit_ranges
10836 .into_iter()
10837 .map(|range| (range, empty_str.clone())),
10838 None,
10839 cx,
10840 );
10841 buffer.snapshot(cx)
10842 });
10843 let new_selections = new_cursors
10844 .into_iter()
10845 .map(|(id, cursor, goal)| {
10846 let cursor = cursor.to_point(&buffer);
10847 Selection {
10848 id,
10849 start: cursor,
10850 end: cursor,
10851 reversed: false,
10852 goal,
10853 }
10854 })
10855 .collect();
10856
10857 this.change_selections(Default::default(), window, cx, |s| {
10858 s.select(new_selections);
10859 });
10860 });
10861 }
10862
10863 pub fn join_lines_impl(
10864 &mut self,
10865 insert_whitespace: bool,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 if self.read_only(cx) {
10870 return;
10871 }
10872 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10873 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10874 let start = MultiBufferRow(selection.start.row);
10875 // Treat single line selections as if they include the next line. Otherwise this action
10876 // would do nothing for single line selections individual cursors.
10877 let end = if selection.start.row == selection.end.row {
10878 MultiBufferRow(selection.start.row + 1)
10879 } else {
10880 MultiBufferRow(selection.end.row)
10881 };
10882
10883 if let Some(last_row_range) = row_ranges.last_mut()
10884 && start <= last_row_range.end
10885 {
10886 last_row_range.end = end;
10887 continue;
10888 }
10889 row_ranges.push(start..end);
10890 }
10891
10892 let snapshot = self.buffer.read(cx).snapshot(cx);
10893 let mut cursor_positions = Vec::new();
10894 for row_range in &row_ranges {
10895 let anchor = snapshot.anchor_before(Point::new(
10896 row_range.end.previous_row().0,
10897 snapshot.line_len(row_range.end.previous_row()),
10898 ));
10899 cursor_positions.push(anchor..anchor);
10900 }
10901
10902 self.transact(window, cx, |this, window, cx| {
10903 for row_range in row_ranges.into_iter().rev() {
10904 for row in row_range.iter_rows().rev() {
10905 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10906 let next_line_row = row.next_row();
10907 let indent = snapshot.indent_size_for_line(next_line_row);
10908 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10909
10910 let replace =
10911 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10912 " "
10913 } else {
10914 ""
10915 };
10916
10917 this.buffer.update(cx, |buffer, cx| {
10918 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10919 });
10920 }
10921 }
10922
10923 this.change_selections(Default::default(), window, cx, |s| {
10924 s.select_anchor_ranges(cursor_positions)
10925 });
10926 });
10927 }
10928
10929 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10931 self.join_lines_impl(true, window, cx);
10932 }
10933
10934 pub fn sort_lines_case_sensitive(
10935 &mut self,
10936 _: &SortLinesCaseSensitive,
10937 window: &mut Window,
10938 cx: &mut Context<Self>,
10939 ) {
10940 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10941 }
10942
10943 pub fn sort_lines_by_length(
10944 &mut self,
10945 _: &SortLinesByLength,
10946 window: &mut Window,
10947 cx: &mut Context<Self>,
10948 ) {
10949 self.manipulate_immutable_lines(window, cx, |lines| {
10950 lines.sort_by_key(|&line| line.chars().count())
10951 })
10952 }
10953
10954 pub fn sort_lines_case_insensitive(
10955 &mut self,
10956 _: &SortLinesCaseInsensitive,
10957 window: &mut Window,
10958 cx: &mut Context<Self>,
10959 ) {
10960 self.manipulate_immutable_lines(window, cx, |lines| {
10961 lines.sort_by_key(|line| line.to_lowercase())
10962 })
10963 }
10964
10965 pub fn unique_lines_case_insensitive(
10966 &mut self,
10967 _: &UniqueLinesCaseInsensitive,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 ) {
10971 self.manipulate_immutable_lines(window, cx, |lines| {
10972 let mut seen = HashSet::default();
10973 lines.retain(|line| seen.insert(line.to_lowercase()));
10974 })
10975 }
10976
10977 pub fn unique_lines_case_sensitive(
10978 &mut self,
10979 _: &UniqueLinesCaseSensitive,
10980 window: &mut Window,
10981 cx: &mut Context<Self>,
10982 ) {
10983 self.manipulate_immutable_lines(window, cx, |lines| {
10984 let mut seen = HashSet::default();
10985 lines.retain(|line| seen.insert(*line));
10986 })
10987 }
10988
10989 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10990 let snapshot = self.buffer.read(cx).snapshot(cx);
10991 for selection in self.selections.disjoint_anchors_arc().iter() {
10992 if snapshot
10993 .language_at(selection.start)
10994 .and_then(|lang| lang.config().wrap_characters.as_ref())
10995 .is_some()
10996 {
10997 return true;
10998 }
10999 }
11000 false
11001 }
11002
11003 fn wrap_selections_in_tag(
11004 &mut self,
11005 _: &WrapSelectionsInTag,
11006 window: &mut Window,
11007 cx: &mut Context<Self>,
11008 ) {
11009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11010
11011 let snapshot = self.buffer.read(cx).snapshot(cx);
11012
11013 let mut edits = Vec::new();
11014 let mut boundaries = Vec::new();
11015
11016 for selection in self
11017 .selections
11018 .all_adjusted(&self.display_snapshot(cx))
11019 .iter()
11020 {
11021 let Some(wrap_config) = snapshot
11022 .language_at(selection.start)
11023 .and_then(|lang| lang.config().wrap_characters.clone())
11024 else {
11025 continue;
11026 };
11027
11028 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11029 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11030
11031 let start_before = snapshot.anchor_before(selection.start);
11032 let end_after = snapshot.anchor_after(selection.end);
11033
11034 edits.push((start_before..start_before, open_tag));
11035 edits.push((end_after..end_after, close_tag));
11036
11037 boundaries.push((
11038 start_before,
11039 end_after,
11040 wrap_config.start_prefix.len(),
11041 wrap_config.end_suffix.len(),
11042 ));
11043 }
11044
11045 if edits.is_empty() {
11046 return;
11047 }
11048
11049 self.transact(window, cx, |this, window, cx| {
11050 let buffer = this.buffer.update(cx, |buffer, cx| {
11051 buffer.edit(edits, None, cx);
11052 buffer.snapshot(cx)
11053 });
11054
11055 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11056 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11057 boundaries.into_iter()
11058 {
11059 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11060 let close_offset = end_after
11061 .to_offset(&buffer)
11062 .saturating_sub_usize(end_suffix_len);
11063 new_selections.push(open_offset..open_offset);
11064 new_selections.push(close_offset..close_offset);
11065 }
11066
11067 this.change_selections(Default::default(), window, cx, |s| {
11068 s.select_ranges(new_selections);
11069 });
11070
11071 this.request_autoscroll(Autoscroll::fit(), cx);
11072 });
11073 }
11074
11075 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11076 let Some(project) = self.project.clone() else {
11077 return;
11078 };
11079 self.reload(project, window, cx)
11080 .detach_and_notify_err(window, cx);
11081 }
11082
11083 pub fn restore_file(
11084 &mut self,
11085 _: &::git::RestoreFile,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) {
11089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11090 let mut buffer_ids = HashSet::default();
11091 let snapshot = self.buffer().read(cx).snapshot(cx);
11092 for selection in self
11093 .selections
11094 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11095 {
11096 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11097 }
11098
11099 let buffer = self.buffer().read(cx);
11100 let ranges = buffer_ids
11101 .into_iter()
11102 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11103 .collect::<Vec<_>>();
11104
11105 self.restore_hunks_in_ranges(ranges, window, cx);
11106 }
11107
11108 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11109 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11110 let selections = self
11111 .selections
11112 .all(&self.display_snapshot(cx))
11113 .into_iter()
11114 .map(|s| s.range())
11115 .collect();
11116 self.restore_hunks_in_ranges(selections, window, cx);
11117 }
11118
11119 pub fn restore_hunks_in_ranges(
11120 &mut self,
11121 ranges: Vec<Range<Point>>,
11122 window: &mut Window,
11123 cx: &mut Context<Editor>,
11124 ) {
11125 let mut revert_changes = HashMap::default();
11126 let chunk_by = self
11127 .snapshot(window, cx)
11128 .hunks_for_ranges(ranges)
11129 .into_iter()
11130 .chunk_by(|hunk| hunk.buffer_id);
11131 for (buffer_id, hunks) in &chunk_by {
11132 let hunks = hunks.collect::<Vec<_>>();
11133 for hunk in &hunks {
11134 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11135 }
11136 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11137 }
11138 drop(chunk_by);
11139 if !revert_changes.is_empty() {
11140 self.transact(window, cx, |editor, window, cx| {
11141 editor.restore(revert_changes, window, cx);
11142 });
11143 }
11144 }
11145
11146 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11147 if let Some(status) = self
11148 .addons
11149 .iter()
11150 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11151 {
11152 return Some(status);
11153 }
11154 self.project
11155 .as_ref()?
11156 .read(cx)
11157 .status_for_buffer_id(buffer_id, cx)
11158 }
11159
11160 pub fn open_active_item_in_terminal(
11161 &mut self,
11162 _: &OpenInTerminal,
11163 window: &mut Window,
11164 cx: &mut Context<Self>,
11165 ) {
11166 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11167 let project_path = buffer.read(cx).project_path(cx)?;
11168 let project = self.project()?.read(cx);
11169 let entry = project.entry_for_path(&project_path, cx)?;
11170 let parent = match &entry.canonical_path {
11171 Some(canonical_path) => canonical_path.to_path_buf(),
11172 None => project.absolute_path(&project_path, cx)?,
11173 }
11174 .parent()?
11175 .to_path_buf();
11176 Some(parent)
11177 }) {
11178 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11179 }
11180 }
11181
11182 fn set_breakpoint_context_menu(
11183 &mut self,
11184 display_row: DisplayRow,
11185 position: Option<Anchor>,
11186 clicked_point: gpui::Point<Pixels>,
11187 window: &mut Window,
11188 cx: &mut Context<Self>,
11189 ) {
11190 let source = self
11191 .buffer
11192 .read(cx)
11193 .snapshot(cx)
11194 .anchor_before(Point::new(display_row.0, 0u32));
11195
11196 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11197
11198 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11199 self,
11200 source,
11201 clicked_point,
11202 context_menu,
11203 window,
11204 cx,
11205 );
11206 }
11207
11208 fn add_edit_breakpoint_block(
11209 &mut self,
11210 anchor: Anchor,
11211 breakpoint: &Breakpoint,
11212 edit_action: BreakpointPromptEditAction,
11213 window: &mut Window,
11214 cx: &mut Context<Self>,
11215 ) {
11216 let weak_editor = cx.weak_entity();
11217 let bp_prompt = cx.new(|cx| {
11218 BreakpointPromptEditor::new(
11219 weak_editor,
11220 anchor,
11221 breakpoint.clone(),
11222 edit_action,
11223 window,
11224 cx,
11225 )
11226 });
11227
11228 let height = bp_prompt.update(cx, |this, cx| {
11229 this.prompt
11230 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11231 });
11232 let cloned_prompt = bp_prompt.clone();
11233 let blocks = vec![BlockProperties {
11234 style: BlockStyle::Sticky,
11235 placement: BlockPlacement::Above(anchor),
11236 height: Some(height),
11237 render: Arc::new(move |cx| {
11238 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11239 cloned_prompt.clone().into_any_element()
11240 }),
11241 priority: 0,
11242 }];
11243
11244 let focus_handle = bp_prompt.focus_handle(cx);
11245 window.focus(&focus_handle);
11246
11247 let block_ids = self.insert_blocks(blocks, None, cx);
11248 bp_prompt.update(cx, |prompt, _| {
11249 prompt.add_block_ids(block_ids);
11250 });
11251 }
11252
11253 pub(crate) fn breakpoint_at_row(
11254 &self,
11255 row: u32,
11256 window: &mut Window,
11257 cx: &mut Context<Self>,
11258 ) -> Option<(Anchor, Breakpoint)> {
11259 let snapshot = self.snapshot(window, cx);
11260 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11261
11262 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11263 }
11264
11265 pub(crate) fn breakpoint_at_anchor(
11266 &self,
11267 breakpoint_position: Anchor,
11268 snapshot: &EditorSnapshot,
11269 cx: &mut Context<Self>,
11270 ) -> Option<(Anchor, Breakpoint)> {
11271 let buffer = self
11272 .buffer
11273 .read(cx)
11274 .buffer_for_anchor(breakpoint_position, cx)?;
11275
11276 let enclosing_excerpt = breakpoint_position.excerpt_id;
11277 let buffer_snapshot = buffer.read(cx).snapshot();
11278
11279 let row = buffer_snapshot
11280 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11281 .row;
11282
11283 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11284 let anchor_end = snapshot
11285 .buffer_snapshot()
11286 .anchor_after(Point::new(row, line_len));
11287
11288 self.breakpoint_store
11289 .as_ref()?
11290 .read_with(cx, |breakpoint_store, cx| {
11291 breakpoint_store
11292 .breakpoints(
11293 &buffer,
11294 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11295 &buffer_snapshot,
11296 cx,
11297 )
11298 .next()
11299 .and_then(|(bp, _)| {
11300 let breakpoint_row = buffer_snapshot
11301 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11302 .row;
11303
11304 if breakpoint_row == row {
11305 snapshot
11306 .buffer_snapshot()
11307 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11308 .map(|position| (position, bp.bp.clone()))
11309 } else {
11310 None
11311 }
11312 })
11313 })
11314 }
11315
11316 pub fn edit_log_breakpoint(
11317 &mut self,
11318 _: &EditLogBreakpoint,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 if self.breakpoint_store.is_none() {
11323 return;
11324 }
11325
11326 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11327 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11328 message: None,
11329 state: BreakpointState::Enabled,
11330 condition: None,
11331 hit_condition: None,
11332 });
11333
11334 self.add_edit_breakpoint_block(
11335 anchor,
11336 &breakpoint,
11337 BreakpointPromptEditAction::Log,
11338 window,
11339 cx,
11340 );
11341 }
11342 }
11343
11344 fn breakpoints_at_cursors(
11345 &self,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11349 let snapshot = self.snapshot(window, cx);
11350 let cursors = self
11351 .selections
11352 .disjoint_anchors_arc()
11353 .iter()
11354 .map(|selection| {
11355 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11356
11357 let breakpoint_position = self
11358 .breakpoint_at_row(cursor_position.row, window, cx)
11359 .map(|bp| bp.0)
11360 .unwrap_or_else(|| {
11361 snapshot
11362 .display_snapshot
11363 .buffer_snapshot()
11364 .anchor_after(Point::new(cursor_position.row, 0))
11365 });
11366
11367 let breakpoint = self
11368 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11369 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11370
11371 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11372 })
11373 // 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.
11374 .collect::<HashMap<Anchor, _>>();
11375
11376 cursors.into_iter().collect()
11377 }
11378
11379 pub fn enable_breakpoint(
11380 &mut self,
11381 _: &crate::actions::EnableBreakpoint,
11382 window: &mut Window,
11383 cx: &mut Context<Self>,
11384 ) {
11385 if self.breakpoint_store.is_none() {
11386 return;
11387 }
11388
11389 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11390 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11391 continue;
11392 };
11393 self.edit_breakpoint_at_anchor(
11394 anchor,
11395 breakpoint,
11396 BreakpointEditAction::InvertState,
11397 cx,
11398 );
11399 }
11400 }
11401
11402 pub fn disable_breakpoint(
11403 &mut self,
11404 _: &crate::actions::DisableBreakpoint,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 if self.breakpoint_store.is_none() {
11409 return;
11410 }
11411
11412 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11413 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11414 continue;
11415 };
11416 self.edit_breakpoint_at_anchor(
11417 anchor,
11418 breakpoint,
11419 BreakpointEditAction::InvertState,
11420 cx,
11421 );
11422 }
11423 }
11424
11425 pub fn toggle_breakpoint(
11426 &mut self,
11427 _: &crate::actions::ToggleBreakpoint,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 if self.breakpoint_store.is_none() {
11432 return;
11433 }
11434
11435 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11436 if let Some(breakpoint) = breakpoint {
11437 self.edit_breakpoint_at_anchor(
11438 anchor,
11439 breakpoint,
11440 BreakpointEditAction::Toggle,
11441 cx,
11442 );
11443 } else {
11444 self.edit_breakpoint_at_anchor(
11445 anchor,
11446 Breakpoint::new_standard(),
11447 BreakpointEditAction::Toggle,
11448 cx,
11449 );
11450 }
11451 }
11452 }
11453
11454 pub fn edit_breakpoint_at_anchor(
11455 &mut self,
11456 breakpoint_position: Anchor,
11457 breakpoint: Breakpoint,
11458 edit_action: BreakpointEditAction,
11459 cx: &mut Context<Self>,
11460 ) {
11461 let Some(breakpoint_store) = &self.breakpoint_store else {
11462 return;
11463 };
11464
11465 let Some(buffer) = self
11466 .buffer
11467 .read(cx)
11468 .buffer_for_anchor(breakpoint_position, cx)
11469 else {
11470 return;
11471 };
11472
11473 breakpoint_store.update(cx, |breakpoint_store, cx| {
11474 breakpoint_store.toggle_breakpoint(
11475 buffer,
11476 BreakpointWithPosition {
11477 position: breakpoint_position.text_anchor,
11478 bp: breakpoint,
11479 },
11480 edit_action,
11481 cx,
11482 );
11483 });
11484
11485 cx.notify();
11486 }
11487
11488 #[cfg(any(test, feature = "test-support"))]
11489 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11490 self.breakpoint_store.clone()
11491 }
11492
11493 pub fn prepare_restore_change(
11494 &self,
11495 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11496 hunk: &MultiBufferDiffHunk,
11497 cx: &mut App,
11498 ) -> Option<()> {
11499 if hunk.is_created_file() {
11500 return None;
11501 }
11502 let buffer = self.buffer.read(cx);
11503 let diff = buffer.diff_for(hunk.buffer_id)?;
11504 let buffer = buffer.buffer(hunk.buffer_id)?;
11505 let buffer = buffer.read(cx);
11506 let original_text = diff
11507 .read(cx)
11508 .base_text()
11509 .as_rope()
11510 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11511 let buffer_snapshot = buffer.snapshot();
11512 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11513 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11514 probe
11515 .0
11516 .start
11517 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11518 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11519 }) {
11520 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11521 Some(())
11522 } else {
11523 None
11524 }
11525 }
11526
11527 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11528 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11529 }
11530
11531 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11532 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11533 }
11534
11535 pub fn rotate_selections_forward(
11536 &mut self,
11537 _: &RotateSelectionsForward,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 self.rotate_selections(window, cx, false)
11542 }
11543
11544 pub fn rotate_selections_backward(
11545 &mut self,
11546 _: &RotateSelectionsBackward,
11547 window: &mut Window,
11548 cx: &mut Context<Self>,
11549 ) {
11550 self.rotate_selections(window, cx, true)
11551 }
11552
11553 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11554 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11555 let display_snapshot = self.display_snapshot(cx);
11556 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11557
11558 if selections.len() < 2 {
11559 return;
11560 }
11561
11562 let (edits, new_selections) = {
11563 let buffer = self.buffer.read(cx).read(cx);
11564 let has_selections = selections.iter().any(|s| !s.is_empty());
11565 if has_selections {
11566 let mut selected_texts: Vec<String> = selections
11567 .iter()
11568 .map(|selection| {
11569 buffer
11570 .text_for_range(selection.start..selection.end)
11571 .collect()
11572 })
11573 .collect();
11574
11575 if reverse {
11576 selected_texts.rotate_left(1);
11577 } else {
11578 selected_texts.rotate_right(1);
11579 }
11580
11581 let mut offset_delta: i64 = 0;
11582 let mut new_selections = Vec::new();
11583 let edits: Vec<_> = selections
11584 .iter()
11585 .zip(selected_texts.iter())
11586 .map(|(selection, new_text)| {
11587 let old_len = (selection.end.0 - selection.start.0) as i64;
11588 let new_len = new_text.len() as i64;
11589 let adjusted_start =
11590 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11591 let adjusted_end =
11592 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11593
11594 new_selections.push(Selection {
11595 id: selection.id,
11596 start: adjusted_start,
11597 end: adjusted_end,
11598 reversed: selection.reversed,
11599 goal: selection.goal,
11600 });
11601
11602 offset_delta += new_len - old_len;
11603 (selection.start..selection.end, new_text.clone())
11604 })
11605 .collect();
11606 (edits, new_selections)
11607 } else {
11608 let mut all_rows: Vec<u32> = selections
11609 .iter()
11610 .map(|selection| buffer.offset_to_point(selection.start).row)
11611 .collect();
11612 all_rows.sort_unstable();
11613 all_rows.dedup();
11614
11615 if all_rows.len() < 2 {
11616 return;
11617 }
11618
11619 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11620 .iter()
11621 .map(|&row| {
11622 let start = Point::new(row, 0);
11623 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11624 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11625 })
11626 .collect();
11627
11628 let mut line_texts: Vec<String> = line_ranges
11629 .iter()
11630 .map(|range| buffer.text_for_range(range.clone()).collect())
11631 .collect();
11632
11633 if reverse {
11634 line_texts.rotate_left(1);
11635 } else {
11636 line_texts.rotate_right(1);
11637 }
11638
11639 let edits = line_ranges
11640 .iter()
11641 .zip(line_texts.iter())
11642 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11643 .collect();
11644
11645 let num_rows = all_rows.len();
11646 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11647 .iter()
11648 .enumerate()
11649 .map(|(i, &row)| (row, i))
11650 .collect();
11651
11652 // Compute new line start offsets after rotation (handles CRLF)
11653 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11654 let first_line_start = line_ranges[0].start.0;
11655 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11656 for text in line_texts.iter().take(num_rows - 1) {
11657 let prev_start = *new_line_starts.last().unwrap();
11658 new_line_starts.push(prev_start + text.len() + newline_len);
11659 }
11660
11661 let new_selections = selections
11662 .iter()
11663 .map(|selection| {
11664 let point = buffer.offset_to_point(selection.start);
11665 let old_index = row_to_index[&point.row];
11666 let new_index = if reverse {
11667 (old_index + num_rows - 1) % num_rows
11668 } else {
11669 (old_index + 1) % num_rows
11670 };
11671 let new_offset =
11672 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11673 Selection {
11674 id: selection.id,
11675 start: new_offset,
11676 end: new_offset,
11677 reversed: selection.reversed,
11678 goal: selection.goal,
11679 }
11680 })
11681 .collect();
11682
11683 (edits, new_selections)
11684 }
11685 };
11686
11687 self.transact(window, cx, |this, window, cx| {
11688 this.buffer.update(cx, |buffer, cx| {
11689 buffer.edit(edits, None, cx);
11690 });
11691 this.change_selections(Default::default(), window, cx, |s| {
11692 s.select(new_selections);
11693 });
11694 });
11695 }
11696
11697 fn manipulate_lines<M>(
11698 &mut self,
11699 window: &mut Window,
11700 cx: &mut Context<Self>,
11701 mut manipulate: M,
11702 ) where
11703 M: FnMut(&str) -> LineManipulationResult,
11704 {
11705 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11706
11707 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11708 let buffer = self.buffer.read(cx).snapshot(cx);
11709
11710 let mut edits = Vec::new();
11711
11712 let selections = self.selections.all::<Point>(&display_map);
11713 let mut selections = selections.iter().peekable();
11714 let mut contiguous_row_selections = Vec::new();
11715 let mut new_selections = Vec::new();
11716 let mut added_lines = 0;
11717 let mut removed_lines = 0;
11718
11719 while let Some(selection) = selections.next() {
11720 let (start_row, end_row) = consume_contiguous_rows(
11721 &mut contiguous_row_selections,
11722 selection,
11723 &display_map,
11724 &mut selections,
11725 );
11726
11727 let start_point = Point::new(start_row.0, 0);
11728 let end_point = Point::new(
11729 end_row.previous_row().0,
11730 buffer.line_len(end_row.previous_row()),
11731 );
11732 let text = buffer
11733 .text_for_range(start_point..end_point)
11734 .collect::<String>();
11735
11736 let LineManipulationResult {
11737 new_text,
11738 line_count_before,
11739 line_count_after,
11740 } = manipulate(&text);
11741
11742 edits.push((start_point..end_point, new_text));
11743
11744 // Selections must change based on added and removed line count
11745 let start_row =
11746 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11747 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11748 new_selections.push(Selection {
11749 id: selection.id,
11750 start: start_row,
11751 end: end_row,
11752 goal: SelectionGoal::None,
11753 reversed: selection.reversed,
11754 });
11755
11756 if line_count_after > line_count_before {
11757 added_lines += line_count_after - line_count_before;
11758 } else if line_count_before > line_count_after {
11759 removed_lines += line_count_before - line_count_after;
11760 }
11761 }
11762
11763 self.transact(window, cx, |this, window, cx| {
11764 let buffer = this.buffer.update(cx, |buffer, cx| {
11765 buffer.edit(edits, None, cx);
11766 buffer.snapshot(cx)
11767 });
11768
11769 // Recalculate offsets on newly edited buffer
11770 let new_selections = new_selections
11771 .iter()
11772 .map(|s| {
11773 let start_point = Point::new(s.start.0, 0);
11774 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11775 Selection {
11776 id: s.id,
11777 start: buffer.point_to_offset(start_point),
11778 end: buffer.point_to_offset(end_point),
11779 goal: s.goal,
11780 reversed: s.reversed,
11781 }
11782 })
11783 .collect();
11784
11785 this.change_selections(Default::default(), window, cx, |s| {
11786 s.select(new_selections);
11787 });
11788
11789 this.request_autoscroll(Autoscroll::fit(), cx);
11790 });
11791 }
11792
11793 fn manipulate_immutable_lines<Fn>(
11794 &mut self,
11795 window: &mut Window,
11796 cx: &mut Context<Self>,
11797 mut callback: Fn,
11798 ) where
11799 Fn: FnMut(&mut Vec<&str>),
11800 {
11801 self.manipulate_lines(window, cx, |text| {
11802 let mut lines: Vec<&str> = text.split('\n').collect();
11803 let line_count_before = lines.len();
11804
11805 callback(&mut lines);
11806
11807 LineManipulationResult {
11808 new_text: lines.join("\n"),
11809 line_count_before,
11810 line_count_after: lines.len(),
11811 }
11812 });
11813 }
11814
11815 fn manipulate_mutable_lines<Fn>(
11816 &mut self,
11817 window: &mut Window,
11818 cx: &mut Context<Self>,
11819 mut callback: Fn,
11820 ) where
11821 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11822 {
11823 self.manipulate_lines(window, cx, |text| {
11824 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11825 let line_count_before = lines.len();
11826
11827 callback(&mut lines);
11828
11829 LineManipulationResult {
11830 new_text: lines.join("\n"),
11831 line_count_before,
11832 line_count_after: lines.len(),
11833 }
11834 });
11835 }
11836
11837 pub fn convert_indentation_to_spaces(
11838 &mut self,
11839 _: &ConvertIndentationToSpaces,
11840 window: &mut Window,
11841 cx: &mut Context<Self>,
11842 ) {
11843 let settings = self.buffer.read(cx).language_settings(cx);
11844 let tab_size = settings.tab_size.get() as usize;
11845
11846 self.manipulate_mutable_lines(window, cx, |lines| {
11847 // Allocates a reasonably sized scratch buffer once for the whole loop
11848 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11849 // Avoids recomputing spaces that could be inserted many times
11850 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11851 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11852 .collect();
11853
11854 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11855 let mut chars = line.as_ref().chars();
11856 let mut col = 0;
11857 let mut changed = false;
11858
11859 for ch in chars.by_ref() {
11860 match ch {
11861 ' ' => {
11862 reindented_line.push(' ');
11863 col += 1;
11864 }
11865 '\t' => {
11866 // \t are converted to spaces depending on the current column
11867 let spaces_len = tab_size - (col % tab_size);
11868 reindented_line.extend(&space_cache[spaces_len - 1]);
11869 col += spaces_len;
11870 changed = true;
11871 }
11872 _ => {
11873 // If we dont append before break, the character is consumed
11874 reindented_line.push(ch);
11875 break;
11876 }
11877 }
11878 }
11879
11880 if !changed {
11881 reindented_line.clear();
11882 continue;
11883 }
11884 // Append the rest of the line and replace old reference with new one
11885 reindented_line.extend(chars);
11886 *line = Cow::Owned(reindented_line.clone());
11887 reindented_line.clear();
11888 }
11889 });
11890 }
11891
11892 pub fn convert_indentation_to_tabs(
11893 &mut self,
11894 _: &ConvertIndentationToTabs,
11895 window: &mut Window,
11896 cx: &mut Context<Self>,
11897 ) {
11898 let settings = self.buffer.read(cx).language_settings(cx);
11899 let tab_size = settings.tab_size.get() as usize;
11900
11901 self.manipulate_mutable_lines(window, cx, |lines| {
11902 // Allocates a reasonably sized buffer once for the whole loop
11903 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11904 // Avoids recomputing spaces that could be inserted many times
11905 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11906 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11907 .collect();
11908
11909 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11910 let mut chars = line.chars();
11911 let mut spaces_count = 0;
11912 let mut first_non_indent_char = None;
11913 let mut changed = false;
11914
11915 for ch in chars.by_ref() {
11916 match ch {
11917 ' ' => {
11918 // Keep track of spaces. Append \t when we reach tab_size
11919 spaces_count += 1;
11920 changed = true;
11921 if spaces_count == tab_size {
11922 reindented_line.push('\t');
11923 spaces_count = 0;
11924 }
11925 }
11926 '\t' => {
11927 reindented_line.push('\t');
11928 spaces_count = 0;
11929 }
11930 _ => {
11931 // Dont append it yet, we might have remaining spaces
11932 first_non_indent_char = Some(ch);
11933 break;
11934 }
11935 }
11936 }
11937
11938 if !changed {
11939 reindented_line.clear();
11940 continue;
11941 }
11942 // Remaining spaces that didn't make a full tab stop
11943 if spaces_count > 0 {
11944 reindented_line.extend(&space_cache[spaces_count - 1]);
11945 }
11946 // If we consume an extra character that was not indentation, add it back
11947 if let Some(extra_char) = first_non_indent_char {
11948 reindented_line.push(extra_char);
11949 }
11950 // Append the rest of the line and replace old reference with new one
11951 reindented_line.extend(chars);
11952 *line = Cow::Owned(reindented_line.clone());
11953 reindented_line.clear();
11954 }
11955 });
11956 }
11957
11958 pub fn convert_to_upper_case(
11959 &mut self,
11960 _: &ConvertToUpperCase,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.manipulate_text(window, cx, |text| text.to_uppercase())
11965 }
11966
11967 pub fn convert_to_lower_case(
11968 &mut self,
11969 _: &ConvertToLowerCase,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 self.manipulate_text(window, cx, |text| text.to_lowercase())
11974 }
11975
11976 pub fn convert_to_title_case(
11977 &mut self,
11978 _: &ConvertToTitleCase,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) {
11982 self.manipulate_text(window, cx, |text| {
11983 text.split('\n')
11984 .map(|line| line.to_case(Case::Title))
11985 .join("\n")
11986 })
11987 }
11988
11989 pub fn convert_to_snake_case(
11990 &mut self,
11991 _: &ConvertToSnakeCase,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11996 }
11997
11998 pub fn convert_to_kebab_case(
11999 &mut self,
12000 _: &ConvertToKebabCase,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12005 }
12006
12007 pub fn convert_to_upper_camel_case(
12008 &mut self,
12009 _: &ConvertToUpperCamelCase,
12010 window: &mut Window,
12011 cx: &mut Context<Self>,
12012 ) {
12013 self.manipulate_text(window, cx, |text| {
12014 text.split('\n')
12015 .map(|line| line.to_case(Case::UpperCamel))
12016 .join("\n")
12017 })
12018 }
12019
12020 pub fn convert_to_lower_camel_case(
12021 &mut self,
12022 _: &ConvertToLowerCamelCase,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12027 }
12028
12029 pub fn convert_to_opposite_case(
12030 &mut self,
12031 _: &ConvertToOppositeCase,
12032 window: &mut Window,
12033 cx: &mut Context<Self>,
12034 ) {
12035 self.manipulate_text(window, cx, |text| {
12036 text.chars()
12037 .fold(String::with_capacity(text.len()), |mut t, c| {
12038 if c.is_uppercase() {
12039 t.extend(c.to_lowercase());
12040 } else {
12041 t.extend(c.to_uppercase());
12042 }
12043 t
12044 })
12045 })
12046 }
12047
12048 pub fn convert_to_sentence_case(
12049 &mut self,
12050 _: &ConvertToSentenceCase,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12055 }
12056
12057 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12058 self.manipulate_text(window, cx, |text| {
12059 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12060 if has_upper_case_characters {
12061 text.to_lowercase()
12062 } else {
12063 text.to_uppercase()
12064 }
12065 })
12066 }
12067
12068 pub fn convert_to_rot13(
12069 &mut self,
12070 _: &ConvertToRot13,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) {
12074 self.manipulate_text(window, cx, |text| {
12075 text.chars()
12076 .map(|c| match c {
12077 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12078 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12079 _ => c,
12080 })
12081 .collect()
12082 })
12083 }
12084
12085 pub fn convert_to_rot47(
12086 &mut self,
12087 _: &ConvertToRot47,
12088 window: &mut Window,
12089 cx: &mut Context<Self>,
12090 ) {
12091 self.manipulate_text(window, cx, |text| {
12092 text.chars()
12093 .map(|c| {
12094 let code_point = c as u32;
12095 if code_point >= 33 && code_point <= 126 {
12096 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12097 }
12098 c
12099 })
12100 .collect()
12101 })
12102 }
12103
12104 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12105 where
12106 Fn: FnMut(&str) -> String,
12107 {
12108 let buffer = self.buffer.read(cx).snapshot(cx);
12109
12110 let mut new_selections = Vec::new();
12111 let mut edits = Vec::new();
12112 let mut selection_adjustment = 0isize;
12113
12114 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12115 let selection_is_empty = selection.is_empty();
12116
12117 let (start, end) = if selection_is_empty {
12118 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12119 (word_range.start, word_range.end)
12120 } else {
12121 (
12122 buffer.point_to_offset(selection.start),
12123 buffer.point_to_offset(selection.end),
12124 )
12125 };
12126
12127 let text = buffer.text_for_range(start..end).collect::<String>();
12128 let old_length = text.len() as isize;
12129 let text = callback(&text);
12130
12131 new_selections.push(Selection {
12132 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12133 end: MultiBufferOffset(
12134 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12135 ),
12136 goal: SelectionGoal::None,
12137 id: selection.id,
12138 reversed: selection.reversed,
12139 });
12140
12141 selection_adjustment += old_length - text.len() as isize;
12142
12143 edits.push((start..end, text));
12144 }
12145
12146 self.transact(window, cx, |this, window, cx| {
12147 this.buffer.update(cx, |buffer, cx| {
12148 buffer.edit(edits, None, cx);
12149 });
12150
12151 this.change_selections(Default::default(), window, cx, |s| {
12152 s.select(new_selections);
12153 });
12154
12155 this.request_autoscroll(Autoscroll::fit(), cx);
12156 });
12157 }
12158
12159 pub fn move_selection_on_drop(
12160 &mut self,
12161 selection: &Selection<Anchor>,
12162 target: DisplayPoint,
12163 is_cut: bool,
12164 window: &mut Window,
12165 cx: &mut Context<Self>,
12166 ) {
12167 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12168 let buffer = display_map.buffer_snapshot();
12169 let mut edits = Vec::new();
12170 let insert_point = display_map
12171 .clip_point(target, Bias::Left)
12172 .to_point(&display_map);
12173 let text = buffer
12174 .text_for_range(selection.start..selection.end)
12175 .collect::<String>();
12176 if is_cut {
12177 edits.push(((selection.start..selection.end), String::new()));
12178 }
12179 let insert_anchor = buffer.anchor_before(insert_point);
12180 edits.push(((insert_anchor..insert_anchor), text));
12181 let last_edit_start = insert_anchor.bias_left(buffer);
12182 let last_edit_end = insert_anchor.bias_right(buffer);
12183 self.transact(window, cx, |this, window, cx| {
12184 this.buffer.update(cx, |buffer, cx| {
12185 buffer.edit(edits, None, cx);
12186 });
12187 this.change_selections(Default::default(), window, cx, |s| {
12188 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12189 });
12190 });
12191 }
12192
12193 pub fn clear_selection_drag_state(&mut self) {
12194 self.selection_drag_state = SelectionDragState::None;
12195 }
12196
12197 pub fn duplicate(
12198 &mut self,
12199 upwards: bool,
12200 whole_lines: bool,
12201 window: &mut Window,
12202 cx: &mut Context<Self>,
12203 ) {
12204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12205
12206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12207 let buffer = display_map.buffer_snapshot();
12208 let selections = self.selections.all::<Point>(&display_map);
12209
12210 let mut edits = Vec::new();
12211 let mut selections_iter = selections.iter().peekable();
12212 while let Some(selection) = selections_iter.next() {
12213 let mut rows = selection.spanned_rows(false, &display_map);
12214 // duplicate line-wise
12215 if whole_lines || selection.start == selection.end {
12216 // Avoid duplicating the same lines twice.
12217 while let Some(next_selection) = selections_iter.peek() {
12218 let next_rows = next_selection.spanned_rows(false, &display_map);
12219 if next_rows.start < rows.end {
12220 rows.end = next_rows.end;
12221 selections_iter.next().unwrap();
12222 } else {
12223 break;
12224 }
12225 }
12226
12227 // Copy the text from the selected row region and splice it either at the start
12228 // or end of the region.
12229 let start = Point::new(rows.start.0, 0);
12230 let end = Point::new(
12231 rows.end.previous_row().0,
12232 buffer.line_len(rows.end.previous_row()),
12233 );
12234
12235 let mut text = buffer.text_for_range(start..end).collect::<String>();
12236
12237 let insert_location = if upwards {
12238 // When duplicating upward, we need to insert before the current line.
12239 // If we're on the last line and it doesn't end with a newline,
12240 // we need to add a newline before the duplicated content.
12241 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12242 && buffer.max_point().column > 0
12243 && !text.ends_with('\n');
12244
12245 if needs_leading_newline {
12246 text.insert(0, '\n');
12247 end
12248 } else {
12249 text.push('\n');
12250 Point::new(rows.start.0, 0)
12251 }
12252 } else {
12253 text.push('\n');
12254 start
12255 };
12256 edits.push((insert_location..insert_location, text));
12257 } else {
12258 // duplicate character-wise
12259 let start = selection.start;
12260 let end = selection.end;
12261 let text = buffer.text_for_range(start..end).collect::<String>();
12262 edits.push((selection.end..selection.end, text));
12263 }
12264 }
12265
12266 self.transact(window, cx, |this, window, cx| {
12267 this.buffer.update(cx, |buffer, cx| {
12268 buffer.edit(edits, None, cx);
12269 });
12270
12271 // When duplicating upward with whole lines, move the cursor to the duplicated line
12272 if upwards && whole_lines {
12273 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12274
12275 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12276 let mut new_ranges = Vec::new();
12277 let selections = s.all::<Point>(&display_map);
12278 let mut selections_iter = selections.iter().peekable();
12279
12280 while let Some(first_selection) = selections_iter.next() {
12281 // Group contiguous selections together to find the total row span
12282 let mut group_selections = vec![first_selection];
12283 let mut rows = first_selection.spanned_rows(false, &display_map);
12284
12285 while let Some(next_selection) = selections_iter.peek() {
12286 let next_rows = next_selection.spanned_rows(false, &display_map);
12287 if next_rows.start < rows.end {
12288 rows.end = next_rows.end;
12289 group_selections.push(selections_iter.next().unwrap());
12290 } else {
12291 break;
12292 }
12293 }
12294
12295 let row_count = rows.end.0 - rows.start.0;
12296
12297 // Move all selections in this group up by the total number of duplicated rows
12298 for selection in group_selections {
12299 let new_start = Point::new(
12300 selection.start.row.saturating_sub(row_count),
12301 selection.start.column,
12302 );
12303
12304 let new_end = Point::new(
12305 selection.end.row.saturating_sub(row_count),
12306 selection.end.column,
12307 );
12308
12309 new_ranges.push(new_start..new_end);
12310 }
12311 }
12312
12313 s.select_ranges(new_ranges);
12314 });
12315 }
12316
12317 this.request_autoscroll(Autoscroll::fit(), cx);
12318 });
12319 }
12320
12321 pub fn duplicate_line_up(
12322 &mut self,
12323 _: &DuplicateLineUp,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.duplicate(true, true, window, cx);
12328 }
12329
12330 pub fn duplicate_line_down(
12331 &mut self,
12332 _: &DuplicateLineDown,
12333 window: &mut Window,
12334 cx: &mut Context<Self>,
12335 ) {
12336 self.duplicate(false, true, window, cx);
12337 }
12338
12339 pub fn duplicate_selection(
12340 &mut self,
12341 _: &DuplicateSelection,
12342 window: &mut Window,
12343 cx: &mut Context<Self>,
12344 ) {
12345 self.duplicate(false, false, window, cx);
12346 }
12347
12348 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12350 if self.mode.is_single_line() {
12351 cx.propagate();
12352 return;
12353 }
12354
12355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12356 let buffer = self.buffer.read(cx).snapshot(cx);
12357
12358 let mut edits = Vec::new();
12359 let mut unfold_ranges = Vec::new();
12360 let mut refold_creases = Vec::new();
12361
12362 let selections = self.selections.all::<Point>(&display_map);
12363 let mut selections = selections.iter().peekable();
12364 let mut contiguous_row_selections = Vec::new();
12365 let mut new_selections = Vec::new();
12366
12367 while let Some(selection) = selections.next() {
12368 // Find all the selections that span a contiguous row range
12369 let (start_row, end_row) = consume_contiguous_rows(
12370 &mut contiguous_row_selections,
12371 selection,
12372 &display_map,
12373 &mut selections,
12374 );
12375
12376 // Move the text spanned by the row range to be before the line preceding the row range
12377 if start_row.0 > 0 {
12378 let range_to_move = Point::new(
12379 start_row.previous_row().0,
12380 buffer.line_len(start_row.previous_row()),
12381 )
12382 ..Point::new(
12383 end_row.previous_row().0,
12384 buffer.line_len(end_row.previous_row()),
12385 );
12386 let insertion_point = display_map
12387 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12388 .0;
12389
12390 // Don't move lines across excerpts
12391 if buffer
12392 .excerpt_containing(insertion_point..range_to_move.end)
12393 .is_some()
12394 {
12395 let text = buffer
12396 .text_for_range(range_to_move.clone())
12397 .flat_map(|s| s.chars())
12398 .skip(1)
12399 .chain(['\n'])
12400 .collect::<String>();
12401
12402 edits.push((
12403 buffer.anchor_after(range_to_move.start)
12404 ..buffer.anchor_before(range_to_move.end),
12405 String::new(),
12406 ));
12407 let insertion_anchor = buffer.anchor_after(insertion_point);
12408 edits.push((insertion_anchor..insertion_anchor, text));
12409
12410 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12411
12412 // Move selections up
12413 new_selections.extend(contiguous_row_selections.drain(..).map(
12414 |mut selection| {
12415 selection.start.row -= row_delta;
12416 selection.end.row -= row_delta;
12417 selection
12418 },
12419 ));
12420
12421 // Move folds up
12422 unfold_ranges.push(range_to_move.clone());
12423 for fold in display_map.folds_in_range(
12424 buffer.anchor_before(range_to_move.start)
12425 ..buffer.anchor_after(range_to_move.end),
12426 ) {
12427 let mut start = fold.range.start.to_point(&buffer);
12428 let mut end = fold.range.end.to_point(&buffer);
12429 start.row -= row_delta;
12430 end.row -= row_delta;
12431 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12432 }
12433 }
12434 }
12435
12436 // If we didn't move line(s), preserve the existing selections
12437 new_selections.append(&mut contiguous_row_selections);
12438 }
12439
12440 self.transact(window, cx, |this, window, cx| {
12441 this.unfold_ranges(&unfold_ranges, true, true, cx);
12442 this.buffer.update(cx, |buffer, cx| {
12443 for (range, text) in edits {
12444 buffer.edit([(range, text)], None, cx);
12445 }
12446 });
12447 this.fold_creases(refold_creases, true, window, cx);
12448 this.change_selections(Default::default(), window, cx, |s| {
12449 s.select(new_selections);
12450 })
12451 });
12452 }
12453
12454 pub fn move_line_down(
12455 &mut self,
12456 _: &MoveLineDown,
12457 window: &mut Window,
12458 cx: &mut Context<Self>,
12459 ) {
12460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12461 if self.mode.is_single_line() {
12462 cx.propagate();
12463 return;
12464 }
12465
12466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12467 let buffer = self.buffer.read(cx).snapshot(cx);
12468
12469 let mut edits = Vec::new();
12470 let mut unfold_ranges = Vec::new();
12471 let mut refold_creases = Vec::new();
12472
12473 let selections = self.selections.all::<Point>(&display_map);
12474 let mut selections = selections.iter().peekable();
12475 let mut contiguous_row_selections = Vec::new();
12476 let mut new_selections = Vec::new();
12477
12478 while let Some(selection) = selections.next() {
12479 // Find all the selections that span a contiguous row range
12480 let (start_row, end_row) = consume_contiguous_rows(
12481 &mut contiguous_row_selections,
12482 selection,
12483 &display_map,
12484 &mut selections,
12485 );
12486
12487 // Move the text spanned by the row range to be after the last line of the row range
12488 if end_row.0 <= buffer.max_point().row {
12489 let range_to_move =
12490 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12491 let insertion_point = display_map
12492 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12493 .0;
12494
12495 // Don't move lines across excerpt boundaries
12496 if buffer
12497 .excerpt_containing(range_to_move.start..insertion_point)
12498 .is_some()
12499 {
12500 let mut text = String::from("\n");
12501 text.extend(buffer.text_for_range(range_to_move.clone()));
12502 text.pop(); // Drop trailing newline
12503 edits.push((
12504 buffer.anchor_after(range_to_move.start)
12505 ..buffer.anchor_before(range_to_move.end),
12506 String::new(),
12507 ));
12508 let insertion_anchor = buffer.anchor_after(insertion_point);
12509 edits.push((insertion_anchor..insertion_anchor, text));
12510
12511 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12512
12513 // Move selections down
12514 new_selections.extend(contiguous_row_selections.drain(..).map(
12515 |mut selection| {
12516 selection.start.row += row_delta;
12517 selection.end.row += row_delta;
12518 selection
12519 },
12520 ));
12521
12522 // Move folds down
12523 unfold_ranges.push(range_to_move.clone());
12524 for fold in display_map.folds_in_range(
12525 buffer.anchor_before(range_to_move.start)
12526 ..buffer.anchor_after(range_to_move.end),
12527 ) {
12528 let mut start = fold.range.start.to_point(&buffer);
12529 let mut end = fold.range.end.to_point(&buffer);
12530 start.row += row_delta;
12531 end.row += row_delta;
12532 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12533 }
12534 }
12535 }
12536
12537 // If we didn't move line(s), preserve the existing selections
12538 new_selections.append(&mut contiguous_row_selections);
12539 }
12540
12541 self.transact(window, cx, |this, window, cx| {
12542 this.unfold_ranges(&unfold_ranges, true, true, cx);
12543 this.buffer.update(cx, |buffer, cx| {
12544 for (range, text) in edits {
12545 buffer.edit([(range, text)], None, cx);
12546 }
12547 });
12548 this.fold_creases(refold_creases, true, window, cx);
12549 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12550 });
12551 }
12552
12553 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12554 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12555 let text_layout_details = &self.text_layout_details(window);
12556 self.transact(window, cx, |this, window, cx| {
12557 let edits = this.change_selections(Default::default(), window, cx, |s| {
12558 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12559 s.move_with(|display_map, selection| {
12560 if !selection.is_empty() {
12561 return;
12562 }
12563
12564 let mut head = selection.head();
12565 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12566 if head.column() == display_map.line_len(head.row()) {
12567 transpose_offset = display_map
12568 .buffer_snapshot()
12569 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12570 }
12571
12572 if transpose_offset == MultiBufferOffset(0) {
12573 return;
12574 }
12575
12576 *head.column_mut() += 1;
12577 head = display_map.clip_point(head, Bias::Right);
12578 let goal = SelectionGoal::HorizontalPosition(
12579 display_map
12580 .x_for_display_point(head, text_layout_details)
12581 .into(),
12582 );
12583 selection.collapse_to(head, goal);
12584
12585 let transpose_start = display_map
12586 .buffer_snapshot()
12587 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12588 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12589 let transpose_end = display_map
12590 .buffer_snapshot()
12591 .clip_offset(transpose_offset + 1usize, Bias::Right);
12592 if let Some(ch) = display_map
12593 .buffer_snapshot()
12594 .chars_at(transpose_start)
12595 .next()
12596 {
12597 edits.push((transpose_start..transpose_offset, String::new()));
12598 edits.push((transpose_end..transpose_end, ch.to_string()));
12599 }
12600 }
12601 });
12602 edits
12603 });
12604 this.buffer
12605 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12606 let selections = this
12607 .selections
12608 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12609 this.change_selections(Default::default(), window, cx, |s| {
12610 s.select(selections);
12611 });
12612 });
12613 }
12614
12615 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12617 if self.mode.is_single_line() {
12618 cx.propagate();
12619 return;
12620 }
12621
12622 self.rewrap_impl(RewrapOptions::default(), cx)
12623 }
12624
12625 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12626 let buffer = self.buffer.read(cx).snapshot(cx);
12627 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12628
12629 #[derive(Clone, Debug, PartialEq)]
12630 enum CommentFormat {
12631 /// single line comment, with prefix for line
12632 Line(String),
12633 /// single line within a block comment, with prefix for line
12634 BlockLine(String),
12635 /// a single line of a block comment that includes the initial delimiter
12636 BlockCommentWithStart(BlockCommentConfig),
12637 /// a single line of a block comment that includes the ending delimiter
12638 BlockCommentWithEnd(BlockCommentConfig),
12639 }
12640
12641 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12642 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12643 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12644 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12645 .peekable();
12646
12647 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12648 row
12649 } else {
12650 return Vec::new();
12651 };
12652
12653 let language_settings = buffer.language_settings_at(selection.head(), cx);
12654 let language_scope = buffer.language_scope_at(selection.head());
12655
12656 let indent_and_prefix_for_row =
12657 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12658 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12659 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12660 &language_scope
12661 {
12662 let indent_end = Point::new(row, indent.len);
12663 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12664 let line_text_after_indent = buffer
12665 .text_for_range(indent_end..line_end)
12666 .collect::<String>();
12667
12668 let is_within_comment_override = buffer
12669 .language_scope_at(indent_end)
12670 .is_some_and(|scope| scope.override_name() == Some("comment"));
12671 let comment_delimiters = if is_within_comment_override {
12672 // we are within a comment syntax node, but we don't
12673 // yet know what kind of comment: block, doc or line
12674 match (
12675 language_scope.documentation_comment(),
12676 language_scope.block_comment(),
12677 ) {
12678 (Some(config), _) | (_, Some(config))
12679 if buffer.contains_str_at(indent_end, &config.start) =>
12680 {
12681 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12682 }
12683 (Some(config), _) | (_, Some(config))
12684 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12685 {
12686 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12687 }
12688 (Some(config), _) | (_, Some(config))
12689 if buffer.contains_str_at(indent_end, &config.prefix) =>
12690 {
12691 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12692 }
12693 (_, _) => language_scope
12694 .line_comment_prefixes()
12695 .iter()
12696 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12697 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12698 }
12699 } else {
12700 // we not in an overridden comment node, but we may
12701 // be within a non-overridden line comment node
12702 language_scope
12703 .line_comment_prefixes()
12704 .iter()
12705 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12706 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12707 };
12708
12709 let rewrap_prefix = language_scope
12710 .rewrap_prefixes()
12711 .iter()
12712 .find_map(|prefix_regex| {
12713 prefix_regex.find(&line_text_after_indent).map(|mat| {
12714 if mat.start() == 0 {
12715 Some(mat.as_str().to_string())
12716 } else {
12717 None
12718 }
12719 })
12720 })
12721 .flatten();
12722 (comment_delimiters, rewrap_prefix)
12723 } else {
12724 (None, None)
12725 };
12726 (indent, comment_prefix, rewrap_prefix)
12727 };
12728
12729 let mut ranges = Vec::new();
12730 let from_empty_selection = selection.is_empty();
12731
12732 let mut current_range_start = first_row;
12733 let mut prev_row = first_row;
12734 let (
12735 mut current_range_indent,
12736 mut current_range_comment_delimiters,
12737 mut current_range_rewrap_prefix,
12738 ) = indent_and_prefix_for_row(first_row);
12739
12740 for row in non_blank_rows_iter.skip(1) {
12741 let has_paragraph_break = row > prev_row + 1;
12742
12743 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12744 indent_and_prefix_for_row(row);
12745
12746 let has_indent_change = row_indent != current_range_indent;
12747 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12748
12749 let has_boundary_change = has_comment_change
12750 || row_rewrap_prefix.is_some()
12751 || (has_indent_change && current_range_comment_delimiters.is_some());
12752
12753 if has_paragraph_break || has_boundary_change {
12754 ranges.push((
12755 language_settings.clone(),
12756 Point::new(current_range_start, 0)
12757 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12758 current_range_indent,
12759 current_range_comment_delimiters.clone(),
12760 current_range_rewrap_prefix.clone(),
12761 from_empty_selection,
12762 ));
12763 current_range_start = row;
12764 current_range_indent = row_indent;
12765 current_range_comment_delimiters = row_comment_delimiters;
12766 current_range_rewrap_prefix = row_rewrap_prefix;
12767 }
12768 prev_row = row;
12769 }
12770
12771 ranges.push((
12772 language_settings.clone(),
12773 Point::new(current_range_start, 0)
12774 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12775 current_range_indent,
12776 current_range_comment_delimiters,
12777 current_range_rewrap_prefix,
12778 from_empty_selection,
12779 ));
12780
12781 ranges
12782 });
12783
12784 let mut edits = Vec::new();
12785 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12786
12787 for (
12788 language_settings,
12789 wrap_range,
12790 mut indent_size,
12791 comment_prefix,
12792 rewrap_prefix,
12793 from_empty_selection,
12794 ) in wrap_ranges
12795 {
12796 let mut start_row = wrap_range.start.row;
12797 let mut end_row = wrap_range.end.row;
12798
12799 // Skip selections that overlap with a range that has already been rewrapped.
12800 let selection_range = start_row..end_row;
12801 if rewrapped_row_ranges
12802 .iter()
12803 .any(|range| range.overlaps(&selection_range))
12804 {
12805 continue;
12806 }
12807
12808 let tab_size = language_settings.tab_size;
12809
12810 let (line_prefix, inside_comment) = match &comment_prefix {
12811 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12812 (Some(prefix.as_str()), true)
12813 }
12814 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12815 (Some(prefix.as_ref()), true)
12816 }
12817 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12818 start: _,
12819 end: _,
12820 prefix,
12821 tab_size,
12822 })) => {
12823 indent_size.len += tab_size;
12824 (Some(prefix.as_ref()), true)
12825 }
12826 None => (None, false),
12827 };
12828 let indent_prefix = indent_size.chars().collect::<String>();
12829 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12830
12831 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12832 RewrapBehavior::InComments => inside_comment,
12833 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12834 RewrapBehavior::Anywhere => true,
12835 };
12836
12837 let should_rewrap = options.override_language_settings
12838 || allow_rewrap_based_on_language
12839 || self.hard_wrap.is_some();
12840 if !should_rewrap {
12841 continue;
12842 }
12843
12844 if from_empty_selection {
12845 'expand_upwards: while start_row > 0 {
12846 let prev_row = start_row - 1;
12847 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12848 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12849 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12850 {
12851 start_row = prev_row;
12852 } else {
12853 break 'expand_upwards;
12854 }
12855 }
12856
12857 'expand_downwards: while end_row < buffer.max_point().row {
12858 let next_row = end_row + 1;
12859 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12860 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12861 && !buffer.is_line_blank(MultiBufferRow(next_row))
12862 {
12863 end_row = next_row;
12864 } else {
12865 break 'expand_downwards;
12866 }
12867 }
12868 }
12869
12870 let start = Point::new(start_row, 0);
12871 let start_offset = ToOffset::to_offset(&start, &buffer);
12872 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12873 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12874 let mut first_line_delimiter = None;
12875 let mut last_line_delimiter = None;
12876 let Some(lines_without_prefixes) = selection_text
12877 .lines()
12878 .enumerate()
12879 .map(|(ix, line)| {
12880 let line_trimmed = line.trim_start();
12881 if rewrap_prefix.is_some() && ix > 0 {
12882 Ok(line_trimmed)
12883 } else if let Some(
12884 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12885 start,
12886 prefix,
12887 end,
12888 tab_size,
12889 })
12890 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12891 start,
12892 prefix,
12893 end,
12894 tab_size,
12895 }),
12896 ) = &comment_prefix
12897 {
12898 let line_trimmed = line_trimmed
12899 .strip_prefix(start.as_ref())
12900 .map(|s| {
12901 let mut indent_size = indent_size;
12902 indent_size.len -= tab_size;
12903 let indent_prefix: String = indent_size.chars().collect();
12904 first_line_delimiter = Some((indent_prefix, start));
12905 s.trim_start()
12906 })
12907 .unwrap_or(line_trimmed);
12908 let line_trimmed = line_trimmed
12909 .strip_suffix(end.as_ref())
12910 .map(|s| {
12911 last_line_delimiter = Some(end);
12912 s.trim_end()
12913 })
12914 .unwrap_or(line_trimmed);
12915 let line_trimmed = line_trimmed
12916 .strip_prefix(prefix.as_ref())
12917 .unwrap_or(line_trimmed);
12918 Ok(line_trimmed)
12919 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12920 line_trimmed.strip_prefix(prefix).with_context(|| {
12921 format!("line did not start with prefix {prefix:?}: {line:?}")
12922 })
12923 } else {
12924 line_trimmed
12925 .strip_prefix(&line_prefix.trim_start())
12926 .with_context(|| {
12927 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12928 })
12929 }
12930 })
12931 .collect::<Result<Vec<_>, _>>()
12932 .log_err()
12933 else {
12934 continue;
12935 };
12936
12937 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12938 buffer
12939 .language_settings_at(Point::new(start_row, 0), cx)
12940 .preferred_line_length as usize
12941 });
12942
12943 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12944 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12945 } else {
12946 line_prefix.clone()
12947 };
12948
12949 let wrapped_text = {
12950 let mut wrapped_text = wrap_with_prefix(
12951 line_prefix,
12952 subsequent_lines_prefix,
12953 lines_without_prefixes.join("\n"),
12954 wrap_column,
12955 tab_size,
12956 options.preserve_existing_whitespace,
12957 );
12958
12959 if let Some((indent, delimiter)) = first_line_delimiter {
12960 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12961 }
12962 if let Some(last_line) = last_line_delimiter {
12963 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12964 }
12965
12966 wrapped_text
12967 };
12968
12969 // TODO: should always use char-based diff while still supporting cursor behavior that
12970 // matches vim.
12971 let mut diff_options = DiffOptions::default();
12972 if options.override_language_settings {
12973 diff_options.max_word_diff_len = 0;
12974 diff_options.max_word_diff_line_count = 0;
12975 } else {
12976 diff_options.max_word_diff_len = usize::MAX;
12977 diff_options.max_word_diff_line_count = usize::MAX;
12978 }
12979
12980 for (old_range, new_text) in
12981 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12982 {
12983 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12984 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12985 edits.push((edit_start..edit_end, new_text));
12986 }
12987
12988 rewrapped_row_ranges.push(start_row..=end_row);
12989 }
12990
12991 self.buffer
12992 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12993 }
12994
12995 pub fn cut_common(
12996 &mut self,
12997 cut_no_selection_line: bool,
12998 window: &mut Window,
12999 cx: &mut Context<Self>,
13000 ) -> ClipboardItem {
13001 let mut text = String::new();
13002 let buffer = self.buffer.read(cx).snapshot(cx);
13003 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13004 let mut clipboard_selections = Vec::with_capacity(selections.len());
13005 {
13006 let max_point = buffer.max_point();
13007 let mut is_first = true;
13008 let mut prev_selection_was_entire_line = false;
13009 for selection in &mut selections {
13010 let is_entire_line =
13011 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13012 if is_entire_line {
13013 selection.start = Point::new(selection.start.row, 0);
13014 if !selection.is_empty() && selection.end.column == 0 {
13015 selection.end = cmp::min(max_point, selection.end);
13016 } else {
13017 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13018 }
13019 selection.goal = SelectionGoal::None;
13020 }
13021 if is_first {
13022 is_first = false;
13023 } else if !prev_selection_was_entire_line {
13024 text += "\n";
13025 }
13026 prev_selection_was_entire_line = is_entire_line;
13027 let mut len = 0;
13028 for chunk in buffer.text_for_range(selection.start..selection.end) {
13029 text.push_str(chunk);
13030 len += chunk.len();
13031 }
13032
13033 clipboard_selections.push(ClipboardSelection::for_buffer(
13034 len,
13035 is_entire_line,
13036 selection.range(),
13037 &buffer,
13038 self.project.as_ref(),
13039 cx,
13040 ));
13041 }
13042 }
13043
13044 self.transact(window, cx, |this, window, cx| {
13045 this.change_selections(Default::default(), window, cx, |s| {
13046 s.select(selections);
13047 });
13048 this.insert("", window, cx);
13049 });
13050 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13051 }
13052
13053 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13055 let item = self.cut_common(true, window, cx);
13056 cx.write_to_clipboard(item);
13057 }
13058
13059 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13062 s.move_with(|snapshot, sel| {
13063 if sel.is_empty() {
13064 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13065 }
13066 if sel.is_empty() {
13067 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13068 }
13069 });
13070 });
13071 let item = self.cut_common(false, window, cx);
13072 cx.set_global(KillRing(item))
13073 }
13074
13075 pub fn kill_ring_yank(
13076 &mut self,
13077 _: &KillRingYank,
13078 window: &mut Window,
13079 cx: &mut Context<Self>,
13080 ) {
13081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13082 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13083 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13084 (kill_ring.text().to_string(), kill_ring.metadata_json())
13085 } else {
13086 return;
13087 }
13088 } else {
13089 return;
13090 };
13091 self.do_paste(&text, metadata, false, window, cx);
13092 }
13093
13094 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13095 self.do_copy(true, cx);
13096 }
13097
13098 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13099 self.do_copy(false, cx);
13100 }
13101
13102 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13103 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13104 let buffer = self.buffer.read(cx).read(cx);
13105 let mut text = String::new();
13106
13107 let mut clipboard_selections = Vec::with_capacity(selections.len());
13108 {
13109 let max_point = buffer.max_point();
13110 let mut is_first = true;
13111 let mut prev_selection_was_entire_line = false;
13112 for selection in &selections {
13113 let mut start = selection.start;
13114 let mut end = selection.end;
13115 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13116 let mut add_trailing_newline = false;
13117 if is_entire_line {
13118 start = Point::new(start.row, 0);
13119 let next_line_start = Point::new(end.row + 1, 0);
13120 if next_line_start <= max_point {
13121 end = next_line_start;
13122 } else {
13123 // We're on the last line without a trailing newline.
13124 // Copy to the end of the line and add a newline afterwards.
13125 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13126 add_trailing_newline = true;
13127 }
13128 }
13129
13130 let mut trimmed_selections = Vec::new();
13131 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13132 let row = MultiBufferRow(start.row);
13133 let first_indent = buffer.indent_size_for_line(row);
13134 if first_indent.len == 0 || start.column > first_indent.len {
13135 trimmed_selections.push(start..end);
13136 } else {
13137 trimmed_selections.push(
13138 Point::new(row.0, first_indent.len)
13139 ..Point::new(row.0, buffer.line_len(row)),
13140 );
13141 for row in start.row + 1..=end.row {
13142 let mut line_len = buffer.line_len(MultiBufferRow(row));
13143 if row == end.row {
13144 line_len = end.column;
13145 }
13146 if line_len == 0 {
13147 trimmed_selections
13148 .push(Point::new(row, 0)..Point::new(row, line_len));
13149 continue;
13150 }
13151 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13152 if row_indent_size.len >= first_indent.len {
13153 trimmed_selections.push(
13154 Point::new(row, first_indent.len)..Point::new(row, line_len),
13155 );
13156 } else {
13157 trimmed_selections.clear();
13158 trimmed_selections.push(start..end);
13159 break;
13160 }
13161 }
13162 }
13163 } else {
13164 trimmed_selections.push(start..end);
13165 }
13166
13167 for trimmed_range in trimmed_selections {
13168 if is_first {
13169 is_first = false;
13170 } else if !prev_selection_was_entire_line {
13171 text += "\n";
13172 }
13173 prev_selection_was_entire_line = is_entire_line;
13174 let mut len = 0;
13175 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13176 text.push_str(chunk);
13177 len += chunk.len();
13178 }
13179 if add_trailing_newline {
13180 text.push('\n');
13181 len += 1;
13182 }
13183 clipboard_selections.push(ClipboardSelection::for_buffer(
13184 len,
13185 is_entire_line,
13186 trimmed_range,
13187 &buffer,
13188 self.project.as_ref(),
13189 cx,
13190 ));
13191 }
13192 }
13193 }
13194
13195 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13196 text,
13197 clipboard_selections,
13198 ));
13199 }
13200
13201 pub fn do_paste(
13202 &mut self,
13203 text: &String,
13204 clipboard_selections: Option<Vec<ClipboardSelection>>,
13205 handle_entire_lines: bool,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) {
13209 if self.read_only(cx) {
13210 return;
13211 }
13212
13213 let clipboard_text = Cow::Borrowed(text.as_str());
13214
13215 self.transact(window, cx, |this, window, cx| {
13216 let had_active_edit_prediction = this.has_active_edit_prediction();
13217 let display_map = this.display_snapshot(cx);
13218 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13219 let cursor_offset = this
13220 .selections
13221 .last::<MultiBufferOffset>(&display_map)
13222 .head();
13223
13224 if let Some(mut clipboard_selections) = clipboard_selections {
13225 let all_selections_were_entire_line =
13226 clipboard_selections.iter().all(|s| s.is_entire_line);
13227 let first_selection_indent_column =
13228 clipboard_selections.first().map(|s| s.first_line_indent);
13229 if clipboard_selections.len() != old_selections.len() {
13230 clipboard_selections.drain(..);
13231 }
13232 let mut auto_indent_on_paste = true;
13233
13234 this.buffer.update(cx, |buffer, cx| {
13235 let snapshot = buffer.read(cx);
13236 auto_indent_on_paste = snapshot
13237 .language_settings_at(cursor_offset, cx)
13238 .auto_indent_on_paste;
13239
13240 let mut start_offset = 0;
13241 let mut edits = Vec::new();
13242 let mut original_indent_columns = Vec::new();
13243 for (ix, selection) in old_selections.iter().enumerate() {
13244 let to_insert;
13245 let entire_line;
13246 let original_indent_column;
13247 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13248 let end_offset = start_offset + clipboard_selection.len;
13249 to_insert = &clipboard_text[start_offset..end_offset];
13250 entire_line = clipboard_selection.is_entire_line;
13251 start_offset = if entire_line {
13252 end_offset
13253 } else {
13254 end_offset + 1
13255 };
13256 original_indent_column = Some(clipboard_selection.first_line_indent);
13257 } else {
13258 to_insert = &*clipboard_text;
13259 entire_line = all_selections_were_entire_line;
13260 original_indent_column = first_selection_indent_column
13261 }
13262
13263 let (range, to_insert) =
13264 if selection.is_empty() && handle_entire_lines && entire_line {
13265 // If the corresponding selection was empty when this slice of the
13266 // clipboard text was written, then the entire line containing the
13267 // selection was copied. If this selection is also currently empty,
13268 // then paste the line before the current line of the buffer.
13269 let column = selection.start.to_point(&snapshot).column as usize;
13270 let line_start = selection.start - column;
13271 (line_start..line_start, Cow::Borrowed(to_insert))
13272 } else {
13273 let language = snapshot.language_at(selection.head());
13274 let range = selection.range();
13275 if let Some(language) = language
13276 && language.name() == "Markdown".into()
13277 {
13278 edit_for_markdown_paste(
13279 &snapshot,
13280 range,
13281 to_insert,
13282 url::Url::parse(to_insert).ok(),
13283 )
13284 } else {
13285 (range, Cow::Borrowed(to_insert))
13286 }
13287 };
13288
13289 edits.push((range, to_insert));
13290 original_indent_columns.push(original_indent_column);
13291 }
13292 drop(snapshot);
13293
13294 buffer.edit(
13295 edits,
13296 if auto_indent_on_paste {
13297 Some(AutoindentMode::Block {
13298 original_indent_columns,
13299 })
13300 } else {
13301 None
13302 },
13303 cx,
13304 );
13305 });
13306
13307 let selections = this
13308 .selections
13309 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13310 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13311 } else {
13312 let url = url::Url::parse(&clipboard_text).ok();
13313
13314 let auto_indent_mode = if !clipboard_text.is_empty() {
13315 Some(AutoindentMode::Block {
13316 original_indent_columns: Vec::new(),
13317 })
13318 } else {
13319 None
13320 };
13321
13322 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13323 let snapshot = buffer.snapshot(cx);
13324
13325 let anchors = old_selections
13326 .iter()
13327 .map(|s| {
13328 let anchor = snapshot.anchor_after(s.head());
13329 s.map(|_| anchor)
13330 })
13331 .collect::<Vec<_>>();
13332
13333 let mut edits = Vec::new();
13334
13335 for selection in old_selections.iter() {
13336 let language = snapshot.language_at(selection.head());
13337 let range = selection.range();
13338
13339 let (edit_range, edit_text) = if let Some(language) = language
13340 && language.name() == "Markdown".into()
13341 {
13342 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13343 } else {
13344 (range, clipboard_text.clone())
13345 };
13346
13347 edits.push((edit_range, edit_text));
13348 }
13349
13350 drop(snapshot);
13351 buffer.edit(edits, auto_indent_mode, cx);
13352
13353 anchors
13354 });
13355
13356 this.change_selections(Default::default(), window, cx, |s| {
13357 s.select_anchors(selection_anchors);
13358 });
13359 }
13360
13361 // 🤔 | .. | show_in_menu |
13362 // | .. | true true
13363 // | had_edit_prediction | false true
13364
13365 let trigger_in_words =
13366 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13367
13368 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13369 });
13370 }
13371
13372 pub fn diff_clipboard_with_selection(
13373 &mut self,
13374 _: &DiffClipboardWithSelection,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 let selections = self
13379 .selections
13380 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13381
13382 if selections.is_empty() {
13383 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13384 return;
13385 };
13386
13387 let clipboard_text = match cx.read_from_clipboard() {
13388 Some(item) => match item.entries().first() {
13389 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13390 _ => None,
13391 },
13392 None => None,
13393 };
13394
13395 let Some(clipboard_text) = clipboard_text else {
13396 log::warn!("Clipboard doesn't contain text.");
13397 return;
13398 };
13399
13400 window.dispatch_action(
13401 Box::new(DiffClipboardWithSelectionData {
13402 clipboard_text,
13403 editor: cx.entity(),
13404 }),
13405 cx,
13406 );
13407 }
13408
13409 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13411 if let Some(item) = cx.read_from_clipboard() {
13412 let entries = item.entries();
13413
13414 match entries.first() {
13415 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13416 // of all the pasted entries.
13417 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13418 .do_paste(
13419 clipboard_string.text(),
13420 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13421 true,
13422 window,
13423 cx,
13424 ),
13425 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13426 }
13427 }
13428 }
13429
13430 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13431 if self.read_only(cx) {
13432 return;
13433 }
13434
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13436
13437 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13438 if let Some((selections, _)) =
13439 self.selection_history.transaction(transaction_id).cloned()
13440 {
13441 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13442 s.select_anchors(selections.to_vec());
13443 });
13444 } else {
13445 log::error!(
13446 "No entry in selection_history found for undo. \
13447 This may correspond to a bug where undo does not update the selection. \
13448 If this is occurring, please add details to \
13449 https://github.com/zed-industries/zed/issues/22692"
13450 );
13451 }
13452 self.request_autoscroll(Autoscroll::fit(), cx);
13453 self.unmark_text(window, cx);
13454 self.refresh_edit_prediction(true, false, window, cx);
13455 cx.emit(EditorEvent::Edited { transaction_id });
13456 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13457 }
13458 }
13459
13460 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13461 if self.read_only(cx) {
13462 return;
13463 }
13464
13465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13466
13467 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13468 if let Some((_, Some(selections))) =
13469 self.selection_history.transaction(transaction_id).cloned()
13470 {
13471 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13472 s.select_anchors(selections.to_vec());
13473 });
13474 } else {
13475 log::error!(
13476 "No entry in selection_history found for redo. \
13477 This may correspond to a bug where undo does not update the selection. \
13478 If this is occurring, please add details to \
13479 https://github.com/zed-industries/zed/issues/22692"
13480 );
13481 }
13482 self.request_autoscroll(Autoscroll::fit(), cx);
13483 self.unmark_text(window, cx);
13484 self.refresh_edit_prediction(true, false, window, cx);
13485 cx.emit(EditorEvent::Edited { transaction_id });
13486 }
13487 }
13488
13489 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13490 self.buffer
13491 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13492 }
13493
13494 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13495 self.buffer
13496 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13497 }
13498
13499 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13501 self.change_selections(Default::default(), window, cx, |s| {
13502 s.move_with(|map, selection| {
13503 let cursor = if selection.is_empty() {
13504 movement::left(map, selection.start)
13505 } else {
13506 selection.start
13507 };
13508 selection.collapse_to(cursor, SelectionGoal::None);
13509 });
13510 })
13511 }
13512
13513 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13515 self.change_selections(Default::default(), window, cx, |s| {
13516 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13517 })
13518 }
13519
13520 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.move_with(|map, selection| {
13524 let cursor = if selection.is_empty() {
13525 movement::right(map, selection.end)
13526 } else {
13527 selection.end
13528 };
13529 selection.collapse_to(cursor, SelectionGoal::None)
13530 });
13531 })
13532 }
13533
13534 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13536 self.change_selections(Default::default(), window, cx, |s| {
13537 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13538 });
13539 }
13540
13541 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13542 if self.take_rename(true, window, cx).is_some() {
13543 return;
13544 }
13545
13546 if self.mode.is_single_line() {
13547 cx.propagate();
13548 return;
13549 }
13550
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552
13553 let text_layout_details = &self.text_layout_details(window);
13554 let selection_count = self.selections.count();
13555 let first_selection = self.selections.first_anchor();
13556
13557 self.change_selections(Default::default(), window, cx, |s| {
13558 s.move_with(|map, selection| {
13559 if !selection.is_empty() {
13560 selection.goal = SelectionGoal::None;
13561 }
13562 let (cursor, goal) = movement::up(
13563 map,
13564 selection.start,
13565 selection.goal,
13566 false,
13567 text_layout_details,
13568 );
13569 selection.collapse_to(cursor, goal);
13570 });
13571 });
13572
13573 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13574 {
13575 cx.propagate();
13576 }
13577 }
13578
13579 pub fn move_up_by_lines(
13580 &mut self,
13581 action: &MoveUpByLines,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 if self.take_rename(true, window, cx).is_some() {
13586 return;
13587 }
13588
13589 if self.mode.is_single_line() {
13590 cx.propagate();
13591 return;
13592 }
13593
13594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13595
13596 let text_layout_details = &self.text_layout_details(window);
13597
13598 self.change_selections(Default::default(), window, cx, |s| {
13599 s.move_with(|map, selection| {
13600 if !selection.is_empty() {
13601 selection.goal = SelectionGoal::None;
13602 }
13603 let (cursor, goal) = movement::up_by_rows(
13604 map,
13605 selection.start,
13606 action.lines,
13607 selection.goal,
13608 false,
13609 text_layout_details,
13610 );
13611 selection.collapse_to(cursor, goal);
13612 });
13613 })
13614 }
13615
13616 pub fn move_down_by_lines(
13617 &mut self,
13618 action: &MoveDownByLines,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 if self.take_rename(true, window, cx).is_some() {
13623 return;
13624 }
13625
13626 if self.mode.is_single_line() {
13627 cx.propagate();
13628 return;
13629 }
13630
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632
13633 let text_layout_details = &self.text_layout_details(window);
13634
13635 self.change_selections(Default::default(), window, cx, |s| {
13636 s.move_with(|map, selection| {
13637 if !selection.is_empty() {
13638 selection.goal = SelectionGoal::None;
13639 }
13640 let (cursor, goal) = movement::down_by_rows(
13641 map,
13642 selection.start,
13643 action.lines,
13644 selection.goal,
13645 false,
13646 text_layout_details,
13647 );
13648 selection.collapse_to(cursor, goal);
13649 });
13650 })
13651 }
13652
13653 pub fn select_down_by_lines(
13654 &mut self,
13655 action: &SelectDownByLines,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13660 let text_layout_details = &self.text_layout_details(window);
13661 self.change_selections(Default::default(), window, cx, |s| {
13662 s.move_heads_with(|map, head, goal| {
13663 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13664 })
13665 })
13666 }
13667
13668 pub fn select_up_by_lines(
13669 &mut self,
13670 action: &SelectUpByLines,
13671 window: &mut Window,
13672 cx: &mut Context<Self>,
13673 ) {
13674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13675 let text_layout_details = &self.text_layout_details(window);
13676 self.change_selections(Default::default(), window, cx, |s| {
13677 s.move_heads_with(|map, head, goal| {
13678 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13679 })
13680 })
13681 }
13682
13683 pub fn select_page_up(
13684 &mut self,
13685 _: &SelectPageUp,
13686 window: &mut Window,
13687 cx: &mut Context<Self>,
13688 ) {
13689 let Some(row_count) = self.visible_row_count() else {
13690 return;
13691 };
13692
13693 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13694
13695 let text_layout_details = &self.text_layout_details(window);
13696
13697 self.change_selections(Default::default(), window, cx, |s| {
13698 s.move_heads_with(|map, head, goal| {
13699 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13700 })
13701 })
13702 }
13703
13704 pub fn move_page_up(
13705 &mut self,
13706 action: &MovePageUp,
13707 window: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 if self.take_rename(true, window, cx).is_some() {
13711 return;
13712 }
13713
13714 if self
13715 .context_menu
13716 .borrow_mut()
13717 .as_mut()
13718 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13719 .unwrap_or(false)
13720 {
13721 return;
13722 }
13723
13724 if matches!(self.mode, EditorMode::SingleLine) {
13725 cx.propagate();
13726 return;
13727 }
13728
13729 let Some(row_count) = self.visible_row_count() else {
13730 return;
13731 };
13732
13733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13734
13735 let effects = if action.center_cursor {
13736 SelectionEffects::scroll(Autoscroll::center())
13737 } else {
13738 SelectionEffects::default()
13739 };
13740
13741 let text_layout_details = &self.text_layout_details(window);
13742
13743 self.change_selections(effects, window, cx, |s| {
13744 s.move_with(|map, selection| {
13745 if !selection.is_empty() {
13746 selection.goal = SelectionGoal::None;
13747 }
13748 let (cursor, goal) = movement::up_by_rows(
13749 map,
13750 selection.end,
13751 row_count,
13752 selection.goal,
13753 false,
13754 text_layout_details,
13755 );
13756 selection.collapse_to(cursor, goal);
13757 });
13758 });
13759 }
13760
13761 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13763 let text_layout_details = &self.text_layout_details(window);
13764 self.change_selections(Default::default(), window, cx, |s| {
13765 s.move_heads_with(|map, head, goal| {
13766 movement::up(map, head, goal, false, text_layout_details)
13767 })
13768 })
13769 }
13770
13771 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13772 self.take_rename(true, window, cx);
13773
13774 if self.mode.is_single_line() {
13775 cx.propagate();
13776 return;
13777 }
13778
13779 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13780
13781 let text_layout_details = &self.text_layout_details(window);
13782 let selection_count = self.selections.count();
13783 let first_selection = self.selections.first_anchor();
13784
13785 self.change_selections(Default::default(), window, cx, |s| {
13786 s.move_with(|map, selection| {
13787 if !selection.is_empty() {
13788 selection.goal = SelectionGoal::None;
13789 }
13790 let (cursor, goal) = movement::down(
13791 map,
13792 selection.end,
13793 selection.goal,
13794 false,
13795 text_layout_details,
13796 );
13797 selection.collapse_to(cursor, goal);
13798 });
13799 });
13800
13801 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13802 {
13803 cx.propagate();
13804 }
13805 }
13806
13807 pub fn select_page_down(
13808 &mut self,
13809 _: &SelectPageDown,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) {
13813 let Some(row_count) = self.visible_row_count() else {
13814 return;
13815 };
13816
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818
13819 let text_layout_details = &self.text_layout_details(window);
13820
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.move_heads_with(|map, head, goal| {
13823 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13824 })
13825 })
13826 }
13827
13828 pub fn move_page_down(
13829 &mut self,
13830 action: &MovePageDown,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) {
13834 if self.take_rename(true, window, cx).is_some() {
13835 return;
13836 }
13837
13838 if self
13839 .context_menu
13840 .borrow_mut()
13841 .as_mut()
13842 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13843 .unwrap_or(false)
13844 {
13845 return;
13846 }
13847
13848 if matches!(self.mode, EditorMode::SingleLine) {
13849 cx.propagate();
13850 return;
13851 }
13852
13853 let Some(row_count) = self.visible_row_count() else {
13854 return;
13855 };
13856
13857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13858
13859 let effects = if action.center_cursor {
13860 SelectionEffects::scroll(Autoscroll::center())
13861 } else {
13862 SelectionEffects::default()
13863 };
13864
13865 let text_layout_details = &self.text_layout_details(window);
13866 self.change_selections(effects, window, cx, |s| {
13867 s.move_with(|map, selection| {
13868 if !selection.is_empty() {
13869 selection.goal = SelectionGoal::None;
13870 }
13871 let (cursor, goal) = movement::down_by_rows(
13872 map,
13873 selection.end,
13874 row_count,
13875 selection.goal,
13876 false,
13877 text_layout_details,
13878 );
13879 selection.collapse_to(cursor, goal);
13880 });
13881 });
13882 }
13883
13884 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13886 let text_layout_details = &self.text_layout_details(window);
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.move_heads_with(|map, head, goal| {
13889 movement::down(map, head, goal, false, text_layout_details)
13890 })
13891 });
13892 }
13893
13894 pub fn context_menu_first(
13895 &mut self,
13896 _: &ContextMenuFirst,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13901 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13902 }
13903 }
13904
13905 pub fn context_menu_prev(
13906 &mut self,
13907 _: &ContextMenuPrevious,
13908 window: &mut Window,
13909 cx: &mut Context<Self>,
13910 ) {
13911 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13912 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13913 }
13914 }
13915
13916 pub fn context_menu_next(
13917 &mut self,
13918 _: &ContextMenuNext,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13923 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13924 }
13925 }
13926
13927 pub fn context_menu_last(
13928 &mut self,
13929 _: &ContextMenuLast,
13930 window: &mut Window,
13931 cx: &mut Context<Self>,
13932 ) {
13933 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13934 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13935 }
13936 }
13937
13938 pub fn signature_help_prev(
13939 &mut self,
13940 _: &SignatureHelpPrevious,
13941 _: &mut Window,
13942 cx: &mut Context<Self>,
13943 ) {
13944 if let Some(popover) = self.signature_help_state.popover_mut() {
13945 if popover.current_signature == 0 {
13946 popover.current_signature = popover.signatures.len() - 1;
13947 } else {
13948 popover.current_signature -= 1;
13949 }
13950 cx.notify();
13951 }
13952 }
13953
13954 pub fn signature_help_next(
13955 &mut self,
13956 _: &SignatureHelpNext,
13957 _: &mut Window,
13958 cx: &mut Context<Self>,
13959 ) {
13960 if let Some(popover) = self.signature_help_state.popover_mut() {
13961 if popover.current_signature + 1 == popover.signatures.len() {
13962 popover.current_signature = 0;
13963 } else {
13964 popover.current_signature += 1;
13965 }
13966 cx.notify();
13967 }
13968 }
13969
13970 pub fn move_to_previous_word_start(
13971 &mut self,
13972 _: &MoveToPreviousWordStart,
13973 window: &mut Window,
13974 cx: &mut Context<Self>,
13975 ) {
13976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13977 self.change_selections(Default::default(), window, cx, |s| {
13978 s.move_cursors_with(|map, head, _| {
13979 (
13980 movement::previous_word_start(map, head),
13981 SelectionGoal::None,
13982 )
13983 });
13984 })
13985 }
13986
13987 pub fn move_to_previous_subword_start(
13988 &mut self,
13989 _: &MoveToPreviousSubwordStart,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13994 self.change_selections(Default::default(), window, cx, |s| {
13995 s.move_cursors_with(|map, head, _| {
13996 (
13997 movement::previous_subword_start(map, head),
13998 SelectionGoal::None,
13999 )
14000 });
14001 })
14002 }
14003
14004 pub fn select_to_previous_word_start(
14005 &mut self,
14006 _: &SelectToPreviousWordStart,
14007 window: &mut Window,
14008 cx: &mut Context<Self>,
14009 ) {
14010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14011 self.change_selections(Default::default(), window, cx, |s| {
14012 s.move_heads_with(|map, head, _| {
14013 (
14014 movement::previous_word_start(map, head),
14015 SelectionGoal::None,
14016 )
14017 });
14018 })
14019 }
14020
14021 pub fn select_to_previous_subword_start(
14022 &mut self,
14023 _: &SelectToPreviousSubwordStart,
14024 window: &mut Window,
14025 cx: &mut Context<Self>,
14026 ) {
14027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14028 self.change_selections(Default::default(), window, cx, |s| {
14029 s.move_heads_with(|map, head, _| {
14030 (
14031 movement::previous_subword_start(map, head),
14032 SelectionGoal::None,
14033 )
14034 });
14035 })
14036 }
14037
14038 pub fn delete_to_previous_word_start(
14039 &mut self,
14040 action: &DeleteToPreviousWordStart,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14045 self.transact(window, cx, |this, window, cx| {
14046 this.select_autoclose_pair(window, cx);
14047 this.change_selections(Default::default(), window, cx, |s| {
14048 s.move_with(|map, selection| {
14049 if selection.is_empty() {
14050 let mut cursor = if action.ignore_newlines {
14051 movement::previous_word_start(map, selection.head())
14052 } else {
14053 movement::previous_word_start_or_newline(map, selection.head())
14054 };
14055 cursor = movement::adjust_greedy_deletion(
14056 map,
14057 selection.head(),
14058 cursor,
14059 action.ignore_brackets,
14060 );
14061 selection.set_head(cursor, SelectionGoal::None);
14062 }
14063 });
14064 });
14065 this.insert("", window, cx);
14066 });
14067 }
14068
14069 pub fn delete_to_previous_subword_start(
14070 &mut self,
14071 _: &DeleteToPreviousSubwordStart,
14072 window: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14076 self.transact(window, cx, |this, window, cx| {
14077 this.select_autoclose_pair(window, cx);
14078 this.change_selections(Default::default(), window, cx, |s| {
14079 s.move_with(|map, selection| {
14080 if selection.is_empty() {
14081 let mut cursor = movement::previous_subword_start(map, selection.head());
14082 cursor =
14083 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14084 selection.set_head(cursor, SelectionGoal::None);
14085 }
14086 });
14087 });
14088 this.insert("", window, cx);
14089 });
14090 }
14091
14092 pub fn move_to_next_word_end(
14093 &mut self,
14094 _: &MoveToNextWordEnd,
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 (movement::next_word_end(map, head), SelectionGoal::None)
14102 });
14103 })
14104 }
14105
14106 pub fn move_to_next_subword_end(
14107 &mut self,
14108 _: &MoveToNextSubwordEnd,
14109 window: &mut Window,
14110 cx: &mut Context<Self>,
14111 ) {
14112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14113 self.change_selections(Default::default(), window, cx, |s| {
14114 s.move_cursors_with(|map, head, _| {
14115 (movement::next_subword_end(map, head), SelectionGoal::None)
14116 });
14117 })
14118 }
14119
14120 pub fn select_to_next_word_end(
14121 &mut self,
14122 _: &SelectToNextWordEnd,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) {
14126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14127 self.change_selections(Default::default(), window, cx, |s| {
14128 s.move_heads_with(|map, head, _| {
14129 (movement::next_word_end(map, head), SelectionGoal::None)
14130 });
14131 })
14132 }
14133
14134 pub fn select_to_next_subword_end(
14135 &mut self,
14136 _: &SelectToNextSubwordEnd,
14137 window: &mut Window,
14138 cx: &mut Context<Self>,
14139 ) {
14140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14141 self.change_selections(Default::default(), window, cx, |s| {
14142 s.move_heads_with(|map, head, _| {
14143 (movement::next_subword_end(map, head), SelectionGoal::None)
14144 });
14145 })
14146 }
14147
14148 pub fn delete_to_next_word_end(
14149 &mut self,
14150 action: &DeleteToNextWordEnd,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) {
14154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14155 self.transact(window, cx, |this, window, cx| {
14156 this.change_selections(Default::default(), window, cx, |s| {
14157 s.move_with(|map, selection| {
14158 if selection.is_empty() {
14159 let mut cursor = if action.ignore_newlines {
14160 movement::next_word_end(map, selection.head())
14161 } else {
14162 movement::next_word_end_or_newline(map, selection.head())
14163 };
14164 cursor = movement::adjust_greedy_deletion(
14165 map,
14166 selection.head(),
14167 cursor,
14168 action.ignore_brackets,
14169 );
14170 selection.set_head(cursor, SelectionGoal::None);
14171 }
14172 });
14173 });
14174 this.insert("", window, cx);
14175 });
14176 }
14177
14178 pub fn delete_to_next_subword_end(
14179 &mut self,
14180 _: &DeleteToNextSubwordEnd,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14185 self.transact(window, cx, |this, window, cx| {
14186 this.change_selections(Default::default(), window, cx, |s| {
14187 s.move_with(|map, selection| {
14188 if selection.is_empty() {
14189 let mut cursor = movement::next_subword_end(map, selection.head());
14190 cursor =
14191 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14192 selection.set_head(cursor, SelectionGoal::None);
14193 }
14194 });
14195 });
14196 this.insert("", window, cx);
14197 });
14198 }
14199
14200 pub fn move_to_beginning_of_line(
14201 &mut self,
14202 action: &MoveToBeginningOfLine,
14203 window: &mut Window,
14204 cx: &mut Context<Self>,
14205 ) {
14206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14207 self.change_selections(Default::default(), window, cx, |s| {
14208 s.move_cursors_with(|map, head, _| {
14209 (
14210 movement::indented_line_beginning(
14211 map,
14212 head,
14213 action.stop_at_soft_wraps,
14214 action.stop_at_indent,
14215 ),
14216 SelectionGoal::None,
14217 )
14218 });
14219 })
14220 }
14221
14222 pub fn select_to_beginning_of_line(
14223 &mut self,
14224 action: &SelectToBeginningOfLine,
14225 window: &mut Window,
14226 cx: &mut Context<Self>,
14227 ) {
14228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14229 self.change_selections(Default::default(), window, cx, |s| {
14230 s.move_heads_with(|map, head, _| {
14231 (
14232 movement::indented_line_beginning(
14233 map,
14234 head,
14235 action.stop_at_soft_wraps,
14236 action.stop_at_indent,
14237 ),
14238 SelectionGoal::None,
14239 )
14240 });
14241 });
14242 }
14243
14244 pub fn delete_to_beginning_of_line(
14245 &mut self,
14246 action: &DeleteToBeginningOfLine,
14247 window: &mut Window,
14248 cx: &mut Context<Self>,
14249 ) {
14250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14251 self.transact(window, cx, |this, window, cx| {
14252 this.change_selections(Default::default(), window, cx, |s| {
14253 s.move_with(|_, selection| {
14254 selection.reversed = true;
14255 });
14256 });
14257
14258 this.select_to_beginning_of_line(
14259 &SelectToBeginningOfLine {
14260 stop_at_soft_wraps: false,
14261 stop_at_indent: action.stop_at_indent,
14262 },
14263 window,
14264 cx,
14265 );
14266 this.backspace(&Backspace, window, cx);
14267 });
14268 }
14269
14270 pub fn move_to_end_of_line(
14271 &mut self,
14272 action: &MoveToEndOfLine,
14273 window: &mut Window,
14274 cx: &mut Context<Self>,
14275 ) {
14276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14277 self.change_selections(Default::default(), window, cx, |s| {
14278 s.move_cursors_with(|map, head, _| {
14279 (
14280 movement::line_end(map, head, action.stop_at_soft_wraps),
14281 SelectionGoal::None,
14282 )
14283 });
14284 })
14285 }
14286
14287 pub fn select_to_end_of_line(
14288 &mut self,
14289 action: &SelectToEndOfLine,
14290 window: &mut Window,
14291 cx: &mut Context<Self>,
14292 ) {
14293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14294 self.change_selections(Default::default(), window, cx, |s| {
14295 s.move_heads_with(|map, head, _| {
14296 (
14297 movement::line_end(map, head, action.stop_at_soft_wraps),
14298 SelectionGoal::None,
14299 )
14300 });
14301 })
14302 }
14303
14304 pub fn delete_to_end_of_line(
14305 &mut self,
14306 _: &DeleteToEndOfLine,
14307 window: &mut Window,
14308 cx: &mut Context<Self>,
14309 ) {
14310 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14311 self.transact(window, cx, |this, window, cx| {
14312 this.select_to_end_of_line(
14313 &SelectToEndOfLine {
14314 stop_at_soft_wraps: false,
14315 },
14316 window,
14317 cx,
14318 );
14319 this.delete(&Delete, window, cx);
14320 });
14321 }
14322
14323 pub fn cut_to_end_of_line(
14324 &mut self,
14325 action: &CutToEndOfLine,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) {
14329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14330 self.transact(window, cx, |this, window, cx| {
14331 this.select_to_end_of_line(
14332 &SelectToEndOfLine {
14333 stop_at_soft_wraps: false,
14334 },
14335 window,
14336 cx,
14337 );
14338 if !action.stop_at_newlines {
14339 this.change_selections(Default::default(), window, cx, |s| {
14340 s.move_with(|_, sel| {
14341 if sel.is_empty() {
14342 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14343 }
14344 });
14345 });
14346 }
14347 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14348 let item = this.cut_common(false, window, cx);
14349 cx.write_to_clipboard(item);
14350 });
14351 }
14352
14353 pub fn move_to_start_of_paragraph(
14354 &mut self,
14355 _: &MoveToStartOfParagraph,
14356 window: &mut Window,
14357 cx: &mut Context<Self>,
14358 ) {
14359 if matches!(self.mode, EditorMode::SingleLine) {
14360 cx.propagate();
14361 return;
14362 }
14363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14364 self.change_selections(Default::default(), window, cx, |s| {
14365 s.move_with(|map, selection| {
14366 selection.collapse_to(
14367 movement::start_of_paragraph(map, selection.head(), 1),
14368 SelectionGoal::None,
14369 )
14370 });
14371 })
14372 }
14373
14374 pub fn move_to_end_of_paragraph(
14375 &mut self,
14376 _: &MoveToEndOfParagraph,
14377 window: &mut Window,
14378 cx: &mut Context<Self>,
14379 ) {
14380 if matches!(self.mode, EditorMode::SingleLine) {
14381 cx.propagate();
14382 return;
14383 }
14384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14385 self.change_selections(Default::default(), window, cx, |s| {
14386 s.move_with(|map, selection| {
14387 selection.collapse_to(
14388 movement::end_of_paragraph(map, selection.head(), 1),
14389 SelectionGoal::None,
14390 )
14391 });
14392 })
14393 }
14394
14395 pub fn select_to_start_of_paragraph(
14396 &mut self,
14397 _: &SelectToStartOfParagraph,
14398 window: &mut Window,
14399 cx: &mut Context<Self>,
14400 ) {
14401 if matches!(self.mode, EditorMode::SingleLine) {
14402 cx.propagate();
14403 return;
14404 }
14405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14406 self.change_selections(Default::default(), window, cx, |s| {
14407 s.move_heads_with(|map, head, _| {
14408 (
14409 movement::start_of_paragraph(map, head, 1),
14410 SelectionGoal::None,
14411 )
14412 });
14413 })
14414 }
14415
14416 pub fn select_to_end_of_paragraph(
14417 &mut self,
14418 _: &SelectToEndOfParagraph,
14419 window: &mut Window,
14420 cx: &mut Context<Self>,
14421 ) {
14422 if matches!(self.mode, EditorMode::SingleLine) {
14423 cx.propagate();
14424 return;
14425 }
14426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14427 self.change_selections(Default::default(), window, cx, |s| {
14428 s.move_heads_with(|map, head, _| {
14429 (
14430 movement::end_of_paragraph(map, head, 1),
14431 SelectionGoal::None,
14432 )
14433 });
14434 })
14435 }
14436
14437 pub fn move_to_start_of_excerpt(
14438 &mut self,
14439 _: &MoveToStartOfExcerpt,
14440 window: &mut Window,
14441 cx: &mut Context<Self>,
14442 ) {
14443 if matches!(self.mode, EditorMode::SingleLine) {
14444 cx.propagate();
14445 return;
14446 }
14447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14448 self.change_selections(Default::default(), window, cx, |s| {
14449 s.move_with(|map, selection| {
14450 selection.collapse_to(
14451 movement::start_of_excerpt(
14452 map,
14453 selection.head(),
14454 workspace::searchable::Direction::Prev,
14455 ),
14456 SelectionGoal::None,
14457 )
14458 });
14459 })
14460 }
14461
14462 pub fn move_to_start_of_next_excerpt(
14463 &mut self,
14464 _: &MoveToStartOfNextExcerpt,
14465 window: &mut Window,
14466 cx: &mut Context<Self>,
14467 ) {
14468 if matches!(self.mode, EditorMode::SingleLine) {
14469 cx.propagate();
14470 return;
14471 }
14472
14473 self.change_selections(Default::default(), window, cx, |s| {
14474 s.move_with(|map, selection| {
14475 selection.collapse_to(
14476 movement::start_of_excerpt(
14477 map,
14478 selection.head(),
14479 workspace::searchable::Direction::Next,
14480 ),
14481 SelectionGoal::None,
14482 )
14483 });
14484 })
14485 }
14486
14487 pub fn move_to_end_of_excerpt(
14488 &mut self,
14489 _: &MoveToEndOfExcerpt,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 if matches!(self.mode, EditorMode::SingleLine) {
14494 cx.propagate();
14495 return;
14496 }
14497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14498 self.change_selections(Default::default(), window, cx, |s| {
14499 s.move_with(|map, selection| {
14500 selection.collapse_to(
14501 movement::end_of_excerpt(
14502 map,
14503 selection.head(),
14504 workspace::searchable::Direction::Next,
14505 ),
14506 SelectionGoal::None,
14507 )
14508 });
14509 })
14510 }
14511
14512 pub fn move_to_end_of_previous_excerpt(
14513 &mut self,
14514 _: &MoveToEndOfPreviousExcerpt,
14515 window: &mut Window,
14516 cx: &mut Context<Self>,
14517 ) {
14518 if matches!(self.mode, EditorMode::SingleLine) {
14519 cx.propagate();
14520 return;
14521 }
14522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14523 self.change_selections(Default::default(), window, cx, |s| {
14524 s.move_with(|map, selection| {
14525 selection.collapse_to(
14526 movement::end_of_excerpt(
14527 map,
14528 selection.head(),
14529 workspace::searchable::Direction::Prev,
14530 ),
14531 SelectionGoal::None,
14532 )
14533 });
14534 })
14535 }
14536
14537 pub fn select_to_start_of_excerpt(
14538 &mut self,
14539 _: &SelectToStartOfExcerpt,
14540 window: &mut Window,
14541 cx: &mut Context<Self>,
14542 ) {
14543 if matches!(self.mode, EditorMode::SingleLine) {
14544 cx.propagate();
14545 return;
14546 }
14547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14548 self.change_selections(Default::default(), window, cx, |s| {
14549 s.move_heads_with(|map, head, _| {
14550 (
14551 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14552 SelectionGoal::None,
14553 )
14554 });
14555 })
14556 }
14557
14558 pub fn select_to_start_of_next_excerpt(
14559 &mut self,
14560 _: &SelectToStartOfNextExcerpt,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 if matches!(self.mode, EditorMode::SingleLine) {
14565 cx.propagate();
14566 return;
14567 }
14568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14569 self.change_selections(Default::default(), window, cx, |s| {
14570 s.move_heads_with(|map, head, _| {
14571 (
14572 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14573 SelectionGoal::None,
14574 )
14575 });
14576 })
14577 }
14578
14579 pub fn select_to_end_of_excerpt(
14580 &mut self,
14581 _: &SelectToEndOfExcerpt,
14582 window: &mut Window,
14583 cx: &mut Context<Self>,
14584 ) {
14585 if matches!(self.mode, EditorMode::SingleLine) {
14586 cx.propagate();
14587 return;
14588 }
14589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14590 self.change_selections(Default::default(), window, cx, |s| {
14591 s.move_heads_with(|map, head, _| {
14592 (
14593 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14594 SelectionGoal::None,
14595 )
14596 });
14597 })
14598 }
14599
14600 pub fn select_to_end_of_previous_excerpt(
14601 &mut self,
14602 _: &SelectToEndOfPreviousExcerpt,
14603 window: &mut Window,
14604 cx: &mut Context<Self>,
14605 ) {
14606 if matches!(self.mode, EditorMode::SingleLine) {
14607 cx.propagate();
14608 return;
14609 }
14610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14611 self.change_selections(Default::default(), window, cx, |s| {
14612 s.move_heads_with(|map, head, _| {
14613 (
14614 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14615 SelectionGoal::None,
14616 )
14617 });
14618 })
14619 }
14620
14621 pub fn move_to_beginning(
14622 &mut self,
14623 _: &MoveToBeginning,
14624 window: &mut Window,
14625 cx: &mut Context<Self>,
14626 ) {
14627 if matches!(self.mode, EditorMode::SingleLine) {
14628 cx.propagate();
14629 return;
14630 }
14631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14632 self.change_selections(Default::default(), window, cx, |s| {
14633 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14634 });
14635 }
14636
14637 pub fn select_to_beginning(
14638 &mut self,
14639 _: &SelectToBeginning,
14640 window: &mut Window,
14641 cx: &mut Context<Self>,
14642 ) {
14643 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14644 selection.set_head(Point::zero(), SelectionGoal::None);
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14646 self.change_selections(Default::default(), window, cx, |s| {
14647 s.select(vec![selection]);
14648 });
14649 }
14650
14651 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14652 if matches!(self.mode, EditorMode::SingleLine) {
14653 cx.propagate();
14654 return;
14655 }
14656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14657 let cursor = self.buffer.read(cx).read(cx).len();
14658 self.change_selections(Default::default(), window, cx, |s| {
14659 s.select_ranges(vec![cursor..cursor])
14660 });
14661 }
14662
14663 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14664 self.nav_history = nav_history;
14665 }
14666
14667 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14668 self.nav_history.as_ref()
14669 }
14670
14671 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14672 self.push_to_nav_history(
14673 self.selections.newest_anchor().head(),
14674 None,
14675 false,
14676 true,
14677 cx,
14678 );
14679 }
14680
14681 fn push_to_nav_history(
14682 &mut self,
14683 cursor_anchor: Anchor,
14684 new_position: Option<Point>,
14685 is_deactivate: bool,
14686 always: bool,
14687 cx: &mut Context<Self>,
14688 ) {
14689 if let Some(nav_history) = self.nav_history.as_mut() {
14690 let buffer = self.buffer.read(cx).read(cx);
14691 let cursor_position = cursor_anchor.to_point(&buffer);
14692 let scroll_state = self.scroll_manager.anchor();
14693 let scroll_top_row = scroll_state.top_row(&buffer);
14694 drop(buffer);
14695
14696 if let Some(new_position) = new_position {
14697 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14698 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14699 return;
14700 }
14701 }
14702
14703 nav_history.push(
14704 Some(NavigationData {
14705 cursor_anchor,
14706 cursor_position,
14707 scroll_anchor: scroll_state,
14708 scroll_top_row,
14709 }),
14710 cx,
14711 );
14712 cx.emit(EditorEvent::PushedToNavHistory {
14713 anchor: cursor_anchor,
14714 is_deactivate,
14715 })
14716 }
14717 }
14718
14719 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14721 let buffer = self.buffer.read(cx).snapshot(cx);
14722 let mut selection = self
14723 .selections
14724 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14725 selection.set_head(buffer.len(), SelectionGoal::None);
14726 self.change_selections(Default::default(), window, cx, |s| {
14727 s.select(vec![selection]);
14728 });
14729 }
14730
14731 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14733 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14734 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14735 });
14736 }
14737
14738 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14741 let mut selections = self.selections.all::<Point>(&display_map);
14742 let max_point = display_map.buffer_snapshot().max_point();
14743 for selection in &mut selections {
14744 let rows = selection.spanned_rows(true, &display_map);
14745 selection.start = Point::new(rows.start.0, 0);
14746 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14747 selection.reversed = false;
14748 }
14749 self.change_selections(Default::default(), window, cx, |s| {
14750 s.select(selections);
14751 });
14752 }
14753
14754 pub fn split_selection_into_lines(
14755 &mut self,
14756 action: &SplitSelectionIntoLines,
14757 window: &mut Window,
14758 cx: &mut Context<Self>,
14759 ) {
14760 let selections = self
14761 .selections
14762 .all::<Point>(&self.display_snapshot(cx))
14763 .into_iter()
14764 .map(|selection| selection.start..selection.end)
14765 .collect::<Vec<_>>();
14766 self.unfold_ranges(&selections, true, true, cx);
14767
14768 let mut new_selection_ranges = Vec::new();
14769 {
14770 let buffer = self.buffer.read(cx).read(cx);
14771 for selection in selections {
14772 for row in selection.start.row..selection.end.row {
14773 let line_start = Point::new(row, 0);
14774 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14775
14776 if action.keep_selections {
14777 // Keep the selection range for each line
14778 let selection_start = if row == selection.start.row {
14779 selection.start
14780 } else {
14781 line_start
14782 };
14783 new_selection_ranges.push(selection_start..line_end);
14784 } else {
14785 // Collapse to cursor at end of line
14786 new_selection_ranges.push(line_end..line_end);
14787 }
14788 }
14789
14790 let is_multiline_selection = selection.start.row != selection.end.row;
14791 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14792 // so this action feels more ergonomic when paired with other selection operations
14793 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14794 if !should_skip_last {
14795 if action.keep_selections {
14796 if is_multiline_selection {
14797 let line_start = Point::new(selection.end.row, 0);
14798 new_selection_ranges.push(line_start..selection.end);
14799 } else {
14800 new_selection_ranges.push(selection.start..selection.end);
14801 }
14802 } else {
14803 new_selection_ranges.push(selection.end..selection.end);
14804 }
14805 }
14806 }
14807 }
14808 self.change_selections(Default::default(), window, cx, |s| {
14809 s.select_ranges(new_selection_ranges);
14810 });
14811 }
14812
14813 pub fn add_selection_above(
14814 &mut self,
14815 action: &AddSelectionAbove,
14816 window: &mut Window,
14817 cx: &mut Context<Self>,
14818 ) {
14819 self.add_selection(true, action.skip_soft_wrap, window, cx);
14820 }
14821
14822 pub fn add_selection_below(
14823 &mut self,
14824 action: &AddSelectionBelow,
14825 window: &mut Window,
14826 cx: &mut Context<Self>,
14827 ) {
14828 self.add_selection(false, action.skip_soft_wrap, window, cx);
14829 }
14830
14831 fn add_selection(
14832 &mut self,
14833 above: bool,
14834 skip_soft_wrap: bool,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) {
14838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14839
14840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14841 let all_selections = self.selections.all::<Point>(&display_map);
14842 let text_layout_details = self.text_layout_details(window);
14843
14844 let (mut columnar_selections, new_selections_to_columnarize) = {
14845 if let Some(state) = self.add_selections_state.as_ref() {
14846 let columnar_selection_ids: HashSet<_> = state
14847 .groups
14848 .iter()
14849 .flat_map(|group| group.stack.iter())
14850 .copied()
14851 .collect();
14852
14853 all_selections
14854 .into_iter()
14855 .partition(|s| columnar_selection_ids.contains(&s.id))
14856 } else {
14857 (Vec::new(), all_selections)
14858 }
14859 };
14860
14861 let mut state = self
14862 .add_selections_state
14863 .take()
14864 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14865
14866 for selection in new_selections_to_columnarize {
14867 let range = selection.display_range(&display_map).sorted();
14868 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14869 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14870 let positions = start_x.min(end_x)..start_x.max(end_x);
14871 let mut stack = Vec::new();
14872 for row in range.start.row().0..=range.end.row().0 {
14873 if let Some(selection) = self.selections.build_columnar_selection(
14874 &display_map,
14875 DisplayRow(row),
14876 &positions,
14877 selection.reversed,
14878 &text_layout_details,
14879 ) {
14880 stack.push(selection.id);
14881 columnar_selections.push(selection);
14882 }
14883 }
14884 if !stack.is_empty() {
14885 if above {
14886 stack.reverse();
14887 }
14888 state.groups.push(AddSelectionsGroup { above, stack });
14889 }
14890 }
14891
14892 let mut final_selections = Vec::new();
14893 let end_row = if above {
14894 DisplayRow(0)
14895 } else {
14896 display_map.max_point().row()
14897 };
14898
14899 let mut last_added_item_per_group = HashMap::default();
14900 for group in state.groups.iter_mut() {
14901 if let Some(last_id) = group.stack.last() {
14902 last_added_item_per_group.insert(*last_id, group);
14903 }
14904 }
14905
14906 for selection in columnar_selections {
14907 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14908 if above == group.above {
14909 let range = selection.display_range(&display_map).sorted();
14910 debug_assert_eq!(range.start.row(), range.end.row());
14911 let mut row = range.start.row();
14912 let positions =
14913 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14914 Pixels::from(start)..Pixels::from(end)
14915 } else {
14916 let start_x =
14917 display_map.x_for_display_point(range.start, &text_layout_details);
14918 let end_x =
14919 display_map.x_for_display_point(range.end, &text_layout_details);
14920 start_x.min(end_x)..start_x.max(end_x)
14921 };
14922
14923 let mut maybe_new_selection = None;
14924 let direction = if above { -1 } else { 1 };
14925
14926 while row != end_row {
14927 if skip_soft_wrap {
14928 row = display_map
14929 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14930 .row();
14931 } else if above {
14932 row.0 -= 1;
14933 } else {
14934 row.0 += 1;
14935 }
14936
14937 if let Some(new_selection) = self.selections.build_columnar_selection(
14938 &display_map,
14939 row,
14940 &positions,
14941 selection.reversed,
14942 &text_layout_details,
14943 ) {
14944 maybe_new_selection = Some(new_selection);
14945 break;
14946 }
14947 }
14948
14949 if let Some(new_selection) = maybe_new_selection {
14950 group.stack.push(new_selection.id);
14951 if above {
14952 final_selections.push(new_selection);
14953 final_selections.push(selection);
14954 } else {
14955 final_selections.push(selection);
14956 final_selections.push(new_selection);
14957 }
14958 } else {
14959 final_selections.push(selection);
14960 }
14961 } else {
14962 group.stack.pop();
14963 }
14964 } else {
14965 final_selections.push(selection);
14966 }
14967 }
14968
14969 self.change_selections(Default::default(), window, cx, |s| {
14970 s.select(final_selections);
14971 });
14972
14973 let final_selection_ids: HashSet<_> = self
14974 .selections
14975 .all::<Point>(&display_map)
14976 .iter()
14977 .map(|s| s.id)
14978 .collect();
14979 state.groups.retain_mut(|group| {
14980 // selections might get merged above so we remove invalid items from stacks
14981 group.stack.retain(|id| final_selection_ids.contains(id));
14982
14983 // single selection in stack can be treated as initial state
14984 group.stack.len() > 1
14985 });
14986
14987 if !state.groups.is_empty() {
14988 self.add_selections_state = Some(state);
14989 }
14990 }
14991
14992 pub fn insert_snippet_at_selections(
14993 &mut self,
14994 action: &InsertSnippet,
14995 window: &mut Window,
14996 cx: &mut Context<Self>,
14997 ) {
14998 self.try_insert_snippet_at_selections(action, window, cx)
14999 .log_err();
15000 }
15001
15002 fn try_insert_snippet_at_selections(
15003 &mut self,
15004 action: &InsertSnippet,
15005 window: &mut Window,
15006 cx: &mut Context<Self>,
15007 ) -> Result<()> {
15008 let insertion_ranges = self
15009 .selections
15010 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15011 .into_iter()
15012 .map(|selection| selection.range())
15013 .collect_vec();
15014
15015 let snippet = if let Some(snippet_body) = &action.snippet {
15016 if action.language.is_none() && action.name.is_none() {
15017 Snippet::parse(snippet_body)?
15018 } else {
15019 bail!("`snippet` is mutually exclusive with `language` and `name`")
15020 }
15021 } else if let Some(name) = &action.name {
15022 let project = self.project().context("no project")?;
15023 let snippet_store = project.read(cx).snippets().read(cx);
15024 let snippet = snippet_store
15025 .snippets_for(action.language.clone(), cx)
15026 .into_iter()
15027 .find(|snippet| snippet.name == *name)
15028 .context("snippet not found")?;
15029 Snippet::parse(&snippet.body)?
15030 } else {
15031 // todo(andrew): open modal to select snippet
15032 bail!("`name` or `snippet` is required")
15033 };
15034
15035 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15036 }
15037
15038 fn select_match_ranges(
15039 &mut self,
15040 range: Range<MultiBufferOffset>,
15041 reversed: bool,
15042 replace_newest: bool,
15043 auto_scroll: Option<Autoscroll>,
15044 window: &mut Window,
15045 cx: &mut Context<Editor>,
15046 ) {
15047 self.unfold_ranges(
15048 std::slice::from_ref(&range),
15049 false,
15050 auto_scroll.is_some(),
15051 cx,
15052 );
15053 let effects = if let Some(scroll) = auto_scroll {
15054 SelectionEffects::scroll(scroll)
15055 } else {
15056 SelectionEffects::no_scroll()
15057 };
15058 self.change_selections(effects, window, cx, |s| {
15059 if replace_newest {
15060 s.delete(s.newest_anchor().id);
15061 }
15062 if reversed {
15063 s.insert_range(range.end..range.start);
15064 } else {
15065 s.insert_range(range);
15066 }
15067 });
15068 }
15069
15070 pub fn select_next_match_internal(
15071 &mut self,
15072 display_map: &DisplaySnapshot,
15073 replace_newest: bool,
15074 autoscroll: Option<Autoscroll>,
15075 window: &mut Window,
15076 cx: &mut Context<Self>,
15077 ) -> Result<()> {
15078 let buffer = display_map.buffer_snapshot();
15079 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15080 if let Some(mut select_next_state) = self.select_next_state.take() {
15081 let query = &select_next_state.query;
15082 if !select_next_state.done {
15083 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15084 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15085 let mut next_selected_range = None;
15086
15087 let bytes_after_last_selection =
15088 buffer.bytes_in_range(last_selection.end..buffer.len());
15089 let bytes_before_first_selection =
15090 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15091 let query_matches = query
15092 .stream_find_iter(bytes_after_last_selection)
15093 .map(|result| (last_selection.end, result))
15094 .chain(
15095 query
15096 .stream_find_iter(bytes_before_first_selection)
15097 .map(|result| (MultiBufferOffset(0), result)),
15098 );
15099
15100 for (start_offset, query_match) in query_matches {
15101 let query_match = query_match.unwrap(); // can only fail due to I/O
15102 let offset_range =
15103 start_offset + query_match.start()..start_offset + query_match.end();
15104
15105 if !select_next_state.wordwise
15106 || (!buffer.is_inside_word(offset_range.start, None)
15107 && !buffer.is_inside_word(offset_range.end, None))
15108 {
15109 let idx = selections
15110 .partition_point(|selection| selection.end <= offset_range.start);
15111 let overlaps = selections
15112 .get(idx)
15113 .map_or(false, |selection| selection.start < offset_range.end);
15114
15115 if !overlaps {
15116 next_selected_range = Some(offset_range);
15117 break;
15118 }
15119 }
15120 }
15121
15122 if let Some(next_selected_range) = next_selected_range {
15123 self.select_match_ranges(
15124 next_selected_range,
15125 last_selection.reversed,
15126 replace_newest,
15127 autoscroll,
15128 window,
15129 cx,
15130 );
15131 } else {
15132 select_next_state.done = true;
15133 }
15134 }
15135
15136 self.select_next_state = Some(select_next_state);
15137 } else {
15138 let mut only_carets = true;
15139 let mut same_text_selected = true;
15140 let mut selected_text = None;
15141
15142 let mut selections_iter = selections.iter().peekable();
15143 while let Some(selection) = selections_iter.next() {
15144 if selection.start != selection.end {
15145 only_carets = false;
15146 }
15147
15148 if same_text_selected {
15149 if selected_text.is_none() {
15150 selected_text =
15151 Some(buffer.text_for_range(selection.range()).collect::<String>());
15152 }
15153
15154 if let Some(next_selection) = selections_iter.peek() {
15155 if next_selection.len() == selection.len() {
15156 let next_selected_text = buffer
15157 .text_for_range(next_selection.range())
15158 .collect::<String>();
15159 if Some(next_selected_text) != selected_text {
15160 same_text_selected = false;
15161 selected_text = None;
15162 }
15163 } else {
15164 same_text_selected = false;
15165 selected_text = None;
15166 }
15167 }
15168 }
15169 }
15170
15171 if only_carets {
15172 for selection in &mut selections {
15173 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15174 selection.start = word_range.start;
15175 selection.end = word_range.end;
15176 selection.goal = SelectionGoal::None;
15177 selection.reversed = false;
15178 self.select_match_ranges(
15179 selection.start..selection.end,
15180 selection.reversed,
15181 replace_newest,
15182 autoscroll,
15183 window,
15184 cx,
15185 );
15186 }
15187
15188 if selections.len() == 1 {
15189 let selection = selections
15190 .last()
15191 .expect("ensured that there's only one selection");
15192 let query = buffer
15193 .text_for_range(selection.start..selection.end)
15194 .collect::<String>();
15195 let is_empty = query.is_empty();
15196 let select_state = SelectNextState {
15197 query: self.build_query(&[query], cx)?,
15198 wordwise: true,
15199 done: is_empty,
15200 };
15201 self.select_next_state = Some(select_state);
15202 } else {
15203 self.select_next_state = None;
15204 }
15205 } else if let Some(selected_text) = selected_text {
15206 self.select_next_state = Some(SelectNextState {
15207 query: self.build_query(&[selected_text], cx)?,
15208 wordwise: false,
15209 done: false,
15210 });
15211 self.select_next_match_internal(
15212 display_map,
15213 replace_newest,
15214 autoscroll,
15215 window,
15216 cx,
15217 )?;
15218 }
15219 }
15220 Ok(())
15221 }
15222
15223 pub fn select_all_matches(
15224 &mut self,
15225 _action: &SelectAllMatches,
15226 window: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) -> Result<()> {
15229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15230
15231 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15232
15233 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15234 let Some(select_next_state) = self.select_next_state.as_mut() else {
15235 return Ok(());
15236 };
15237 if select_next_state.done {
15238 return Ok(());
15239 }
15240
15241 let mut new_selections = Vec::new();
15242
15243 let reversed = self
15244 .selections
15245 .oldest::<MultiBufferOffset>(&display_map)
15246 .reversed;
15247 let buffer = display_map.buffer_snapshot();
15248 let query_matches = select_next_state
15249 .query
15250 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15251
15252 for query_match in query_matches.into_iter() {
15253 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15254 let offset_range = if reversed {
15255 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15256 } else {
15257 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15258 };
15259
15260 if !select_next_state.wordwise
15261 || (!buffer.is_inside_word(offset_range.start, None)
15262 && !buffer.is_inside_word(offset_range.end, None))
15263 {
15264 new_selections.push(offset_range.start..offset_range.end);
15265 }
15266 }
15267
15268 select_next_state.done = true;
15269
15270 if new_selections.is_empty() {
15271 log::error!("bug: new_selections is empty in select_all_matches");
15272 return Ok(());
15273 }
15274
15275 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15276 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15277 selections.select_ranges(new_selections)
15278 });
15279
15280 Ok(())
15281 }
15282
15283 pub fn select_next(
15284 &mut self,
15285 action: &SelectNext,
15286 window: &mut Window,
15287 cx: &mut Context<Self>,
15288 ) -> Result<()> {
15289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15290 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15291 self.select_next_match_internal(
15292 &display_map,
15293 action.replace_newest,
15294 Some(Autoscroll::newest()),
15295 window,
15296 cx,
15297 )?;
15298 Ok(())
15299 }
15300
15301 pub fn select_previous(
15302 &mut self,
15303 action: &SelectPrevious,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) -> Result<()> {
15307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15308 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15309 let buffer = display_map.buffer_snapshot();
15310 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15311 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15312 let query = &select_prev_state.query;
15313 if !select_prev_state.done {
15314 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15315 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15316 let mut next_selected_range = None;
15317 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15318 let bytes_before_last_selection =
15319 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15320 let bytes_after_first_selection =
15321 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15322 let query_matches = query
15323 .stream_find_iter(bytes_before_last_selection)
15324 .map(|result| (last_selection.start, result))
15325 .chain(
15326 query
15327 .stream_find_iter(bytes_after_first_selection)
15328 .map(|result| (buffer.len(), result)),
15329 );
15330 for (end_offset, query_match) in query_matches {
15331 let query_match = query_match.unwrap(); // can only fail due to I/O
15332 let offset_range =
15333 end_offset - query_match.end()..end_offset - query_match.start();
15334
15335 if !select_prev_state.wordwise
15336 || (!buffer.is_inside_word(offset_range.start, None)
15337 && !buffer.is_inside_word(offset_range.end, None))
15338 {
15339 next_selected_range = Some(offset_range);
15340 break;
15341 }
15342 }
15343
15344 if let Some(next_selected_range) = next_selected_range {
15345 self.select_match_ranges(
15346 next_selected_range,
15347 last_selection.reversed,
15348 action.replace_newest,
15349 Some(Autoscroll::newest()),
15350 window,
15351 cx,
15352 );
15353 } else {
15354 select_prev_state.done = true;
15355 }
15356 }
15357
15358 self.select_prev_state = Some(select_prev_state);
15359 } else {
15360 let mut only_carets = true;
15361 let mut same_text_selected = true;
15362 let mut selected_text = None;
15363
15364 let mut selections_iter = selections.iter().peekable();
15365 while let Some(selection) = selections_iter.next() {
15366 if selection.start != selection.end {
15367 only_carets = false;
15368 }
15369
15370 if same_text_selected {
15371 if selected_text.is_none() {
15372 selected_text =
15373 Some(buffer.text_for_range(selection.range()).collect::<String>());
15374 }
15375
15376 if let Some(next_selection) = selections_iter.peek() {
15377 if next_selection.len() == selection.len() {
15378 let next_selected_text = buffer
15379 .text_for_range(next_selection.range())
15380 .collect::<String>();
15381 if Some(next_selected_text) != selected_text {
15382 same_text_selected = false;
15383 selected_text = None;
15384 }
15385 } else {
15386 same_text_selected = false;
15387 selected_text = None;
15388 }
15389 }
15390 }
15391 }
15392
15393 if only_carets {
15394 for selection in &mut selections {
15395 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15396 selection.start = word_range.start;
15397 selection.end = word_range.end;
15398 selection.goal = SelectionGoal::None;
15399 selection.reversed = false;
15400 self.select_match_ranges(
15401 selection.start..selection.end,
15402 selection.reversed,
15403 action.replace_newest,
15404 Some(Autoscroll::newest()),
15405 window,
15406 cx,
15407 );
15408 }
15409 if selections.len() == 1 {
15410 let selection = selections
15411 .last()
15412 .expect("ensured that there's only one selection");
15413 let query = buffer
15414 .text_for_range(selection.start..selection.end)
15415 .collect::<String>();
15416 let is_empty = query.is_empty();
15417 let select_state = SelectNextState {
15418 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15419 wordwise: true,
15420 done: is_empty,
15421 };
15422 self.select_prev_state = Some(select_state);
15423 } else {
15424 self.select_prev_state = None;
15425 }
15426 } else if let Some(selected_text) = selected_text {
15427 self.select_prev_state = Some(SelectNextState {
15428 query: self
15429 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15430 wordwise: false,
15431 done: false,
15432 });
15433 self.select_previous(action, window, cx)?;
15434 }
15435 }
15436 Ok(())
15437 }
15438
15439 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15440 /// setting the case sensitivity based on the global
15441 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15442 /// editor's settings.
15443 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15444 where
15445 I: IntoIterator<Item = P>,
15446 P: AsRef<[u8]>,
15447 {
15448 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15449 || EditorSettings::get_global(cx).search.case_sensitive,
15450 |value| value,
15451 );
15452
15453 let mut builder = AhoCorasickBuilder::new();
15454 builder.ascii_case_insensitive(!case_sensitive);
15455 builder.build(patterns)
15456 }
15457
15458 pub fn find_next_match(
15459 &mut self,
15460 _: &FindNextMatch,
15461 window: &mut Window,
15462 cx: &mut Context<Self>,
15463 ) -> Result<()> {
15464 let selections = self.selections.disjoint_anchors_arc();
15465 match selections.first() {
15466 Some(first) if selections.len() >= 2 => {
15467 self.change_selections(Default::default(), window, cx, |s| {
15468 s.select_ranges([first.range()]);
15469 });
15470 }
15471 _ => self.select_next(
15472 &SelectNext {
15473 replace_newest: true,
15474 },
15475 window,
15476 cx,
15477 )?,
15478 }
15479 Ok(())
15480 }
15481
15482 pub fn find_previous_match(
15483 &mut self,
15484 _: &FindPreviousMatch,
15485 window: &mut Window,
15486 cx: &mut Context<Self>,
15487 ) -> Result<()> {
15488 let selections = self.selections.disjoint_anchors_arc();
15489 match selections.last() {
15490 Some(last) if selections.len() >= 2 => {
15491 self.change_selections(Default::default(), window, cx, |s| {
15492 s.select_ranges([last.range()]);
15493 });
15494 }
15495 _ => self.select_previous(
15496 &SelectPrevious {
15497 replace_newest: true,
15498 },
15499 window,
15500 cx,
15501 )?,
15502 }
15503 Ok(())
15504 }
15505
15506 pub fn toggle_comments(
15507 &mut self,
15508 action: &ToggleComments,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 ) {
15512 if self.read_only(cx) {
15513 return;
15514 }
15515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15516 let text_layout_details = &self.text_layout_details(window);
15517 self.transact(window, cx, |this, window, cx| {
15518 let mut selections = this
15519 .selections
15520 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15521 let mut edits = Vec::new();
15522 let mut selection_edit_ranges = Vec::new();
15523 let mut last_toggled_row = None;
15524 let snapshot = this.buffer.read(cx).read(cx);
15525 let empty_str: Arc<str> = Arc::default();
15526 let mut suffixes_inserted = Vec::new();
15527 let ignore_indent = action.ignore_indent;
15528
15529 fn comment_prefix_range(
15530 snapshot: &MultiBufferSnapshot,
15531 row: MultiBufferRow,
15532 comment_prefix: &str,
15533 comment_prefix_whitespace: &str,
15534 ignore_indent: bool,
15535 ) -> Range<Point> {
15536 let indent_size = if ignore_indent {
15537 0
15538 } else {
15539 snapshot.indent_size_for_line(row).len
15540 };
15541
15542 let start = Point::new(row.0, indent_size);
15543
15544 let mut line_bytes = snapshot
15545 .bytes_in_range(start..snapshot.max_point())
15546 .flatten()
15547 .copied();
15548
15549 // If this line currently begins with the line comment prefix, then record
15550 // the range containing the prefix.
15551 if line_bytes
15552 .by_ref()
15553 .take(comment_prefix.len())
15554 .eq(comment_prefix.bytes())
15555 {
15556 // Include any whitespace that matches the comment prefix.
15557 let matching_whitespace_len = line_bytes
15558 .zip(comment_prefix_whitespace.bytes())
15559 .take_while(|(a, b)| a == b)
15560 .count() as u32;
15561 let end = Point::new(
15562 start.row,
15563 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15564 );
15565 start..end
15566 } else {
15567 start..start
15568 }
15569 }
15570
15571 fn comment_suffix_range(
15572 snapshot: &MultiBufferSnapshot,
15573 row: MultiBufferRow,
15574 comment_suffix: &str,
15575 comment_suffix_has_leading_space: bool,
15576 ) -> Range<Point> {
15577 let end = Point::new(row.0, snapshot.line_len(row));
15578 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15579
15580 let mut line_end_bytes = snapshot
15581 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15582 .flatten()
15583 .copied();
15584
15585 let leading_space_len = if suffix_start_column > 0
15586 && line_end_bytes.next() == Some(b' ')
15587 && comment_suffix_has_leading_space
15588 {
15589 1
15590 } else {
15591 0
15592 };
15593
15594 // If this line currently begins with the line comment prefix, then record
15595 // the range containing the prefix.
15596 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15597 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15598 start..end
15599 } else {
15600 end..end
15601 }
15602 }
15603
15604 // TODO: Handle selections that cross excerpts
15605 for selection in &mut selections {
15606 let start_column = snapshot
15607 .indent_size_for_line(MultiBufferRow(selection.start.row))
15608 .len;
15609 let language = if let Some(language) =
15610 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15611 {
15612 language
15613 } else {
15614 continue;
15615 };
15616
15617 selection_edit_ranges.clear();
15618
15619 // If multiple selections contain a given row, avoid processing that
15620 // row more than once.
15621 let mut start_row = MultiBufferRow(selection.start.row);
15622 if last_toggled_row == Some(start_row) {
15623 start_row = start_row.next_row();
15624 }
15625 let end_row =
15626 if selection.end.row > selection.start.row && selection.end.column == 0 {
15627 MultiBufferRow(selection.end.row - 1)
15628 } else {
15629 MultiBufferRow(selection.end.row)
15630 };
15631 last_toggled_row = Some(end_row);
15632
15633 if start_row > end_row {
15634 continue;
15635 }
15636
15637 // If the language has line comments, toggle those.
15638 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15639
15640 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15641 if ignore_indent {
15642 full_comment_prefixes = full_comment_prefixes
15643 .into_iter()
15644 .map(|s| Arc::from(s.trim_end()))
15645 .collect();
15646 }
15647
15648 if !full_comment_prefixes.is_empty() {
15649 let first_prefix = full_comment_prefixes
15650 .first()
15651 .expect("prefixes is non-empty");
15652 let prefix_trimmed_lengths = full_comment_prefixes
15653 .iter()
15654 .map(|p| p.trim_end_matches(' ').len())
15655 .collect::<SmallVec<[usize; 4]>>();
15656
15657 let mut all_selection_lines_are_comments = true;
15658
15659 for row in start_row.0..=end_row.0 {
15660 let row = MultiBufferRow(row);
15661 if start_row < end_row && snapshot.is_line_blank(row) {
15662 continue;
15663 }
15664
15665 let prefix_range = full_comment_prefixes
15666 .iter()
15667 .zip(prefix_trimmed_lengths.iter().copied())
15668 .map(|(prefix, trimmed_prefix_len)| {
15669 comment_prefix_range(
15670 snapshot.deref(),
15671 row,
15672 &prefix[..trimmed_prefix_len],
15673 &prefix[trimmed_prefix_len..],
15674 ignore_indent,
15675 )
15676 })
15677 .max_by_key(|range| range.end.column - range.start.column)
15678 .expect("prefixes is non-empty");
15679
15680 if prefix_range.is_empty() {
15681 all_selection_lines_are_comments = false;
15682 }
15683
15684 selection_edit_ranges.push(prefix_range);
15685 }
15686
15687 if all_selection_lines_are_comments {
15688 edits.extend(
15689 selection_edit_ranges
15690 .iter()
15691 .cloned()
15692 .map(|range| (range, empty_str.clone())),
15693 );
15694 } else {
15695 let min_column = selection_edit_ranges
15696 .iter()
15697 .map(|range| range.start.column)
15698 .min()
15699 .unwrap_or(0);
15700 edits.extend(selection_edit_ranges.iter().map(|range| {
15701 let position = Point::new(range.start.row, min_column);
15702 (position..position, first_prefix.clone())
15703 }));
15704 }
15705 } else if let Some(BlockCommentConfig {
15706 start: full_comment_prefix,
15707 end: comment_suffix,
15708 ..
15709 }) = language.block_comment()
15710 {
15711 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15712 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15713 let prefix_range = comment_prefix_range(
15714 snapshot.deref(),
15715 start_row,
15716 comment_prefix,
15717 comment_prefix_whitespace,
15718 ignore_indent,
15719 );
15720 let suffix_range = comment_suffix_range(
15721 snapshot.deref(),
15722 end_row,
15723 comment_suffix.trim_start_matches(' '),
15724 comment_suffix.starts_with(' '),
15725 );
15726
15727 if prefix_range.is_empty() || suffix_range.is_empty() {
15728 edits.push((
15729 prefix_range.start..prefix_range.start,
15730 full_comment_prefix.clone(),
15731 ));
15732 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15733 suffixes_inserted.push((end_row, comment_suffix.len()));
15734 } else {
15735 edits.push((prefix_range, empty_str.clone()));
15736 edits.push((suffix_range, empty_str.clone()));
15737 }
15738 } else {
15739 continue;
15740 }
15741 }
15742
15743 drop(snapshot);
15744 this.buffer.update(cx, |buffer, cx| {
15745 buffer.edit(edits, None, cx);
15746 });
15747
15748 // Adjust selections so that they end before any comment suffixes that
15749 // were inserted.
15750 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15751 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15752 let snapshot = this.buffer.read(cx).read(cx);
15753 for selection in &mut selections {
15754 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15755 match row.cmp(&MultiBufferRow(selection.end.row)) {
15756 Ordering::Less => {
15757 suffixes_inserted.next();
15758 continue;
15759 }
15760 Ordering::Greater => break,
15761 Ordering::Equal => {
15762 if selection.end.column == snapshot.line_len(row) {
15763 if selection.is_empty() {
15764 selection.start.column -= suffix_len as u32;
15765 }
15766 selection.end.column -= suffix_len as u32;
15767 }
15768 break;
15769 }
15770 }
15771 }
15772 }
15773
15774 drop(snapshot);
15775 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15776
15777 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15778 let selections_on_single_row = selections.windows(2).all(|selections| {
15779 selections[0].start.row == selections[1].start.row
15780 && selections[0].end.row == selections[1].end.row
15781 && selections[0].start.row == selections[0].end.row
15782 });
15783 let selections_selecting = selections
15784 .iter()
15785 .any(|selection| selection.start != selection.end);
15786 let advance_downwards = action.advance_downwards
15787 && selections_on_single_row
15788 && !selections_selecting
15789 && !matches!(this.mode, EditorMode::SingleLine);
15790
15791 if advance_downwards {
15792 let snapshot = this.buffer.read(cx).snapshot(cx);
15793
15794 this.change_selections(Default::default(), window, cx, |s| {
15795 s.move_cursors_with(|display_snapshot, display_point, _| {
15796 let mut point = display_point.to_point(display_snapshot);
15797 point.row += 1;
15798 point = snapshot.clip_point(point, Bias::Left);
15799 let display_point = point.to_display_point(display_snapshot);
15800 let goal = SelectionGoal::HorizontalPosition(
15801 display_snapshot
15802 .x_for_display_point(display_point, text_layout_details)
15803 .into(),
15804 );
15805 (display_point, goal)
15806 })
15807 });
15808 }
15809 });
15810 }
15811
15812 pub fn select_enclosing_symbol(
15813 &mut self,
15814 _: &SelectEnclosingSymbol,
15815 window: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15819
15820 let buffer = self.buffer.read(cx).snapshot(cx);
15821 let old_selections = self
15822 .selections
15823 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15824 .into_boxed_slice();
15825
15826 fn update_selection(
15827 selection: &Selection<MultiBufferOffset>,
15828 buffer_snap: &MultiBufferSnapshot,
15829 ) -> Option<Selection<MultiBufferOffset>> {
15830 let cursor = selection.head();
15831 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15832 for symbol in symbols.iter().rev() {
15833 let start = symbol.range.start.to_offset(buffer_snap);
15834 let end = symbol.range.end.to_offset(buffer_snap);
15835 let new_range = start..end;
15836 if start < selection.start || end > selection.end {
15837 return Some(Selection {
15838 id: selection.id,
15839 start: new_range.start,
15840 end: new_range.end,
15841 goal: SelectionGoal::None,
15842 reversed: selection.reversed,
15843 });
15844 }
15845 }
15846 None
15847 }
15848
15849 let mut selected_larger_symbol = false;
15850 let new_selections = old_selections
15851 .iter()
15852 .map(|selection| match update_selection(selection, &buffer) {
15853 Some(new_selection) => {
15854 if new_selection.range() != selection.range() {
15855 selected_larger_symbol = true;
15856 }
15857 new_selection
15858 }
15859 None => selection.clone(),
15860 })
15861 .collect::<Vec<_>>();
15862
15863 if selected_larger_symbol {
15864 self.change_selections(Default::default(), window, cx, |s| {
15865 s.select(new_selections);
15866 });
15867 }
15868 }
15869
15870 pub fn select_larger_syntax_node(
15871 &mut self,
15872 _: &SelectLargerSyntaxNode,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) {
15876 let Some(visible_row_count) = self.visible_row_count() else {
15877 return;
15878 };
15879 let old_selections: Box<[_]> = self
15880 .selections
15881 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15882 .into();
15883 if old_selections.is_empty() {
15884 return;
15885 }
15886
15887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15888
15889 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15890 let buffer = self.buffer.read(cx).snapshot(cx);
15891
15892 let mut selected_larger_node = false;
15893 let mut new_selections = old_selections
15894 .iter()
15895 .map(|selection| {
15896 let old_range = selection.start..selection.end;
15897
15898 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15899 // manually select word at selection
15900 if ["string_content", "inline"].contains(&node.kind()) {
15901 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15902 // ignore if word is already selected
15903 if !word_range.is_empty() && old_range != word_range {
15904 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15905 // only select word if start and end point belongs to same word
15906 if word_range == last_word_range {
15907 selected_larger_node = true;
15908 return Selection {
15909 id: selection.id,
15910 start: word_range.start,
15911 end: word_range.end,
15912 goal: SelectionGoal::None,
15913 reversed: selection.reversed,
15914 };
15915 }
15916 }
15917 }
15918 }
15919
15920 let mut new_range = old_range.clone();
15921 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15922 new_range = range;
15923 if !node.is_named() {
15924 continue;
15925 }
15926 if !display_map.intersects_fold(new_range.start)
15927 && !display_map.intersects_fold(new_range.end)
15928 {
15929 break;
15930 }
15931 }
15932
15933 selected_larger_node |= new_range != old_range;
15934 Selection {
15935 id: selection.id,
15936 start: new_range.start,
15937 end: new_range.end,
15938 goal: SelectionGoal::None,
15939 reversed: selection.reversed,
15940 }
15941 })
15942 .collect::<Vec<_>>();
15943
15944 if !selected_larger_node {
15945 return; // don't put this call in the history
15946 }
15947
15948 // scroll based on transformation done to the last selection created by the user
15949 let (last_old, last_new) = old_selections
15950 .last()
15951 .zip(new_selections.last().cloned())
15952 .expect("old_selections isn't empty");
15953
15954 // revert selection
15955 let is_selection_reversed = {
15956 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15957 new_selections.last_mut().expect("checked above").reversed =
15958 should_newest_selection_be_reversed;
15959 should_newest_selection_be_reversed
15960 };
15961
15962 if selected_larger_node {
15963 self.select_syntax_node_history.disable_clearing = true;
15964 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15965 s.select(new_selections.clone());
15966 });
15967 self.select_syntax_node_history.disable_clearing = false;
15968 }
15969
15970 let start_row = last_new.start.to_display_point(&display_map).row().0;
15971 let end_row = last_new.end.to_display_point(&display_map).row().0;
15972 let selection_height = end_row - start_row + 1;
15973 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15974
15975 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15976 let scroll_behavior = if fits_on_the_screen {
15977 self.request_autoscroll(Autoscroll::fit(), cx);
15978 SelectSyntaxNodeScrollBehavior::FitSelection
15979 } else if is_selection_reversed {
15980 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15981 SelectSyntaxNodeScrollBehavior::CursorTop
15982 } else {
15983 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15984 SelectSyntaxNodeScrollBehavior::CursorBottom
15985 };
15986
15987 self.select_syntax_node_history.push((
15988 old_selections,
15989 scroll_behavior,
15990 is_selection_reversed,
15991 ));
15992 }
15993
15994 pub fn select_smaller_syntax_node(
15995 &mut self,
15996 _: &SelectSmallerSyntaxNode,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16001
16002 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16003 self.select_syntax_node_history.pop()
16004 {
16005 if let Some(selection) = selections.last_mut() {
16006 selection.reversed = is_selection_reversed;
16007 }
16008
16009 self.select_syntax_node_history.disable_clearing = true;
16010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16011 s.select(selections.to_vec());
16012 });
16013 self.select_syntax_node_history.disable_clearing = false;
16014
16015 match scroll_behavior {
16016 SelectSyntaxNodeScrollBehavior::CursorTop => {
16017 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16018 }
16019 SelectSyntaxNodeScrollBehavior::FitSelection => {
16020 self.request_autoscroll(Autoscroll::fit(), cx);
16021 }
16022 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16023 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16024 }
16025 }
16026 }
16027 }
16028
16029 pub fn unwrap_syntax_node(
16030 &mut self,
16031 _: &UnwrapSyntaxNode,
16032 window: &mut Window,
16033 cx: &mut Context<Self>,
16034 ) {
16035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16036
16037 let buffer = self.buffer.read(cx).snapshot(cx);
16038 let selections = self
16039 .selections
16040 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16041 .into_iter()
16042 // subtracting the offset requires sorting
16043 .sorted_by_key(|i| i.start);
16044
16045 let full_edits = selections
16046 .into_iter()
16047 .filter_map(|selection| {
16048 let child = if selection.is_empty()
16049 && let Some((_, ancestor_range)) =
16050 buffer.syntax_ancestor(selection.start..selection.end)
16051 {
16052 ancestor_range
16053 } else {
16054 selection.range()
16055 };
16056
16057 let mut parent = child.clone();
16058 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16059 parent = ancestor_range;
16060 if parent.start < child.start || parent.end > child.end {
16061 break;
16062 }
16063 }
16064
16065 if parent == child {
16066 return None;
16067 }
16068 let text = buffer.text_for_range(child).collect::<String>();
16069 Some((selection.id, parent, text))
16070 })
16071 .collect::<Vec<_>>();
16072 if full_edits.is_empty() {
16073 return;
16074 }
16075
16076 self.transact(window, cx, |this, window, cx| {
16077 this.buffer.update(cx, |buffer, cx| {
16078 buffer.edit(
16079 full_edits
16080 .iter()
16081 .map(|(_, p, t)| (p.clone(), t.clone()))
16082 .collect::<Vec<_>>(),
16083 None,
16084 cx,
16085 );
16086 });
16087 this.change_selections(Default::default(), window, cx, |s| {
16088 let mut offset = 0;
16089 let mut selections = vec![];
16090 for (id, parent, text) in full_edits {
16091 let start = parent.start - offset;
16092 offset += (parent.end - parent.start) - text.len();
16093 selections.push(Selection {
16094 id,
16095 start,
16096 end: start + text.len(),
16097 reversed: false,
16098 goal: Default::default(),
16099 });
16100 }
16101 s.select(selections);
16102 });
16103 });
16104 }
16105
16106 pub fn select_next_syntax_node(
16107 &mut self,
16108 _: &SelectNextSyntaxNode,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) {
16112 let old_selections: Box<[_]> = self
16113 .selections
16114 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16115 .into();
16116 if old_selections.is_empty() {
16117 return;
16118 }
16119
16120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16121
16122 let buffer = self.buffer.read(cx).snapshot(cx);
16123 let mut selected_sibling = false;
16124
16125 let new_selections = old_selections
16126 .iter()
16127 .map(|selection| {
16128 let old_range = selection.start..selection.end;
16129
16130 let old_range =
16131 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16132 let excerpt = buffer.excerpt_containing(old_range.clone());
16133
16134 if let Some(mut excerpt) = excerpt
16135 && let Some(node) = excerpt
16136 .buffer()
16137 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16138 {
16139 let new_range = excerpt.map_range_from_buffer(
16140 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16141 );
16142 selected_sibling = true;
16143 Selection {
16144 id: selection.id,
16145 start: new_range.start,
16146 end: new_range.end,
16147 goal: SelectionGoal::None,
16148 reversed: selection.reversed,
16149 }
16150 } else {
16151 selection.clone()
16152 }
16153 })
16154 .collect::<Vec<_>>();
16155
16156 if selected_sibling {
16157 self.change_selections(
16158 SelectionEffects::scroll(Autoscroll::fit()),
16159 window,
16160 cx,
16161 |s| {
16162 s.select(new_selections);
16163 },
16164 );
16165 }
16166 }
16167
16168 pub fn select_prev_syntax_node(
16169 &mut self,
16170 _: &SelectPreviousSyntaxNode,
16171 window: &mut Window,
16172 cx: &mut Context<Self>,
16173 ) {
16174 let old_selections: Box<[_]> = self
16175 .selections
16176 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16177 .into();
16178 if old_selections.is_empty() {
16179 return;
16180 }
16181
16182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16183
16184 let buffer = self.buffer.read(cx).snapshot(cx);
16185 let mut selected_sibling = false;
16186
16187 let new_selections = old_selections
16188 .iter()
16189 .map(|selection| {
16190 let old_range = selection.start..selection.end;
16191 let old_range =
16192 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16193 let excerpt = buffer.excerpt_containing(old_range.clone());
16194
16195 if let Some(mut excerpt) = excerpt
16196 && let Some(node) = excerpt
16197 .buffer()
16198 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16199 {
16200 let new_range = excerpt.map_range_from_buffer(
16201 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16202 );
16203 selected_sibling = true;
16204 Selection {
16205 id: selection.id,
16206 start: new_range.start,
16207 end: new_range.end,
16208 goal: SelectionGoal::None,
16209 reversed: selection.reversed,
16210 }
16211 } else {
16212 selection.clone()
16213 }
16214 })
16215 .collect::<Vec<_>>();
16216
16217 if selected_sibling {
16218 self.change_selections(
16219 SelectionEffects::scroll(Autoscroll::fit()),
16220 window,
16221 cx,
16222 |s| {
16223 s.select(new_selections);
16224 },
16225 );
16226 }
16227 }
16228
16229 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16230 if !EditorSettings::get_global(cx).gutter.runnables {
16231 self.clear_tasks();
16232 return Task::ready(());
16233 }
16234 let project = self.project().map(Entity::downgrade);
16235 let task_sources = self.lsp_task_sources(cx);
16236 let multi_buffer = self.buffer.downgrade();
16237 cx.spawn_in(window, async move |editor, cx| {
16238 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16239 let Some(project) = project.and_then(|p| p.upgrade()) else {
16240 return;
16241 };
16242 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16243 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16244 }) else {
16245 return;
16246 };
16247
16248 let hide_runnables = project
16249 .update(cx, |project, _| project.is_via_collab())
16250 .unwrap_or(true);
16251 if hide_runnables {
16252 return;
16253 }
16254 let new_rows =
16255 cx.background_spawn({
16256 let snapshot = display_snapshot.clone();
16257 async move {
16258 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16259 }
16260 })
16261 .await;
16262 let Ok(lsp_tasks) =
16263 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16264 else {
16265 return;
16266 };
16267 let lsp_tasks = lsp_tasks.await;
16268
16269 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16270 lsp_tasks
16271 .into_iter()
16272 .flat_map(|(kind, tasks)| {
16273 tasks.into_iter().filter_map(move |(location, task)| {
16274 Some((kind.clone(), location?, task))
16275 })
16276 })
16277 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16278 let buffer = location.target.buffer;
16279 let buffer_snapshot = buffer.read(cx).snapshot();
16280 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16281 |(excerpt_id, snapshot, _)| {
16282 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16283 display_snapshot
16284 .buffer_snapshot()
16285 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16286 } else {
16287 None
16288 }
16289 },
16290 );
16291 if let Some(offset) = offset {
16292 let task_buffer_range =
16293 location.target.range.to_point(&buffer_snapshot);
16294 let context_buffer_range =
16295 task_buffer_range.to_offset(&buffer_snapshot);
16296 let context_range = BufferOffset(context_buffer_range.start)
16297 ..BufferOffset(context_buffer_range.end);
16298
16299 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16300 .or_insert_with(|| RunnableTasks {
16301 templates: Vec::new(),
16302 offset,
16303 column: task_buffer_range.start.column,
16304 extra_variables: HashMap::default(),
16305 context_range,
16306 })
16307 .templates
16308 .push((kind, task.original_task().clone()));
16309 }
16310
16311 acc
16312 })
16313 }) else {
16314 return;
16315 };
16316
16317 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16318 buffer.language_settings(cx).tasks.prefer_lsp
16319 }) else {
16320 return;
16321 };
16322
16323 let rows = Self::runnable_rows(
16324 project,
16325 display_snapshot,
16326 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16327 new_rows,
16328 cx.clone(),
16329 )
16330 .await;
16331 editor
16332 .update(cx, |editor, _| {
16333 editor.clear_tasks();
16334 for (key, mut value) in rows {
16335 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16336 value.templates.extend(lsp_tasks.templates);
16337 }
16338
16339 editor.insert_tasks(key, value);
16340 }
16341 for (key, value) in lsp_tasks_by_rows {
16342 editor.insert_tasks(key, value);
16343 }
16344 })
16345 .ok();
16346 })
16347 }
16348 fn fetch_runnable_ranges(
16349 snapshot: &DisplaySnapshot,
16350 range: Range<Anchor>,
16351 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16352 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16353 }
16354
16355 fn runnable_rows(
16356 project: Entity<Project>,
16357 snapshot: DisplaySnapshot,
16358 prefer_lsp: bool,
16359 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16360 cx: AsyncWindowContext,
16361 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16362 cx.spawn(async move |cx| {
16363 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16364 for (run_range, mut runnable) in runnable_ranges {
16365 let Some(tasks) = cx
16366 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16367 .ok()
16368 else {
16369 continue;
16370 };
16371 let mut tasks = tasks.await;
16372
16373 if prefer_lsp {
16374 tasks.retain(|(task_kind, _)| {
16375 !matches!(task_kind, TaskSourceKind::Language { .. })
16376 });
16377 }
16378 if tasks.is_empty() {
16379 continue;
16380 }
16381
16382 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16383 let Some(row) = snapshot
16384 .buffer_snapshot()
16385 .buffer_line_for_row(MultiBufferRow(point.row))
16386 .map(|(_, range)| range.start.row)
16387 else {
16388 continue;
16389 };
16390
16391 let context_range =
16392 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16393 runnable_rows.push((
16394 (runnable.buffer_id, row),
16395 RunnableTasks {
16396 templates: tasks,
16397 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16398 context_range,
16399 column: point.column,
16400 extra_variables: runnable.extra_captures,
16401 },
16402 ));
16403 }
16404 runnable_rows
16405 })
16406 }
16407
16408 fn templates_with_tags(
16409 project: &Entity<Project>,
16410 runnable: &mut Runnable,
16411 cx: &mut App,
16412 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16413 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16414 let (worktree_id, file) = project
16415 .buffer_for_id(runnable.buffer, cx)
16416 .and_then(|buffer| buffer.read(cx).file())
16417 .map(|file| (file.worktree_id(cx), file.clone()))
16418 .unzip();
16419
16420 (
16421 project.task_store().read(cx).task_inventory().cloned(),
16422 worktree_id,
16423 file,
16424 )
16425 });
16426
16427 let tags = mem::take(&mut runnable.tags);
16428 let language = runnable.language.clone();
16429 cx.spawn(async move |cx| {
16430 let mut templates_with_tags = Vec::new();
16431 if let Some(inventory) = inventory {
16432 for RunnableTag(tag) in tags {
16433 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16434 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16435 }) else {
16436 return templates_with_tags;
16437 };
16438 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16439 move |(_, template)| {
16440 template.tags.iter().any(|source_tag| source_tag == &tag)
16441 },
16442 ));
16443 }
16444 }
16445 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16446
16447 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16448 // Strongest source wins; if we have worktree tag binding, prefer that to
16449 // global and language bindings;
16450 // if we have a global binding, prefer that to language binding.
16451 let first_mismatch = templates_with_tags
16452 .iter()
16453 .position(|(tag_source, _)| tag_source != leading_tag_source);
16454 if let Some(index) = first_mismatch {
16455 templates_with_tags.truncate(index);
16456 }
16457 }
16458
16459 templates_with_tags
16460 })
16461 }
16462
16463 pub fn move_to_enclosing_bracket(
16464 &mut self,
16465 _: &MoveToEnclosingBracket,
16466 window: &mut Window,
16467 cx: &mut Context<Self>,
16468 ) {
16469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16470 self.change_selections(Default::default(), window, cx, |s| {
16471 s.move_offsets_with(|snapshot, selection| {
16472 let Some(enclosing_bracket_ranges) =
16473 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16474 else {
16475 return;
16476 };
16477
16478 let mut best_length = usize::MAX;
16479 let mut best_inside = false;
16480 let mut best_in_bracket_range = false;
16481 let mut best_destination = None;
16482 for (open, close) in enclosing_bracket_ranges {
16483 let close = close.to_inclusive();
16484 let length = *close.end() - open.start;
16485 let inside = selection.start >= open.end && selection.end <= *close.start();
16486 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16487 || close.contains(&selection.head());
16488
16489 // If best is next to a bracket and current isn't, skip
16490 if !in_bracket_range && best_in_bracket_range {
16491 continue;
16492 }
16493
16494 // Prefer smaller lengths unless best is inside and current isn't
16495 if length > best_length && (best_inside || !inside) {
16496 continue;
16497 }
16498
16499 best_length = length;
16500 best_inside = inside;
16501 best_in_bracket_range = in_bracket_range;
16502 best_destination = Some(
16503 if close.contains(&selection.start) && close.contains(&selection.end) {
16504 if inside { open.end } else { open.start }
16505 } else if inside {
16506 *close.start()
16507 } else {
16508 *close.end()
16509 },
16510 );
16511 }
16512
16513 if let Some(destination) = best_destination {
16514 selection.collapse_to(destination, SelectionGoal::None);
16515 }
16516 })
16517 });
16518 }
16519
16520 pub fn undo_selection(
16521 &mut self,
16522 _: &UndoSelection,
16523 window: &mut Window,
16524 cx: &mut Context<Self>,
16525 ) {
16526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16527 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16528 self.selection_history.mode = SelectionHistoryMode::Undoing;
16529 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16530 this.end_selection(window, cx);
16531 this.change_selections(
16532 SelectionEffects::scroll(Autoscroll::newest()),
16533 window,
16534 cx,
16535 |s| s.select_anchors(entry.selections.to_vec()),
16536 );
16537 });
16538 self.selection_history.mode = SelectionHistoryMode::Normal;
16539
16540 self.select_next_state = entry.select_next_state;
16541 self.select_prev_state = entry.select_prev_state;
16542 self.add_selections_state = entry.add_selections_state;
16543 }
16544 }
16545
16546 pub fn redo_selection(
16547 &mut self,
16548 _: &RedoSelection,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) {
16552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16553 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16554 self.selection_history.mode = SelectionHistoryMode::Redoing;
16555 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16556 this.end_selection(window, cx);
16557 this.change_selections(
16558 SelectionEffects::scroll(Autoscroll::newest()),
16559 window,
16560 cx,
16561 |s| s.select_anchors(entry.selections.to_vec()),
16562 );
16563 });
16564 self.selection_history.mode = SelectionHistoryMode::Normal;
16565
16566 self.select_next_state = entry.select_next_state;
16567 self.select_prev_state = entry.select_prev_state;
16568 self.add_selections_state = entry.add_selections_state;
16569 }
16570 }
16571
16572 pub fn expand_excerpts(
16573 &mut self,
16574 action: &ExpandExcerpts,
16575 _: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) {
16578 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16579 }
16580
16581 pub fn expand_excerpts_down(
16582 &mut self,
16583 action: &ExpandExcerptsDown,
16584 _: &mut Window,
16585 cx: &mut Context<Self>,
16586 ) {
16587 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16588 }
16589
16590 pub fn expand_excerpts_up(
16591 &mut self,
16592 action: &ExpandExcerptsUp,
16593 _: &mut Window,
16594 cx: &mut Context<Self>,
16595 ) {
16596 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16597 }
16598
16599 pub fn expand_excerpts_for_direction(
16600 &mut self,
16601 lines: u32,
16602 direction: ExpandExcerptDirection,
16603
16604 cx: &mut Context<Self>,
16605 ) {
16606 let selections = self.selections.disjoint_anchors_arc();
16607
16608 let lines = if lines == 0 {
16609 EditorSettings::get_global(cx).expand_excerpt_lines
16610 } else {
16611 lines
16612 };
16613
16614 self.buffer.update(cx, |buffer, cx| {
16615 let snapshot = buffer.snapshot(cx);
16616 let mut excerpt_ids = selections
16617 .iter()
16618 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16619 .collect::<Vec<_>>();
16620 excerpt_ids.sort();
16621 excerpt_ids.dedup();
16622 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16623 })
16624 }
16625
16626 pub fn expand_excerpt(
16627 &mut self,
16628 excerpt: ExcerptId,
16629 direction: ExpandExcerptDirection,
16630 window: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) {
16633 let current_scroll_position = self.scroll_position(cx);
16634 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16635 let mut scroll = None;
16636
16637 if direction == ExpandExcerptDirection::Down {
16638 let multi_buffer = self.buffer.read(cx);
16639 let snapshot = multi_buffer.snapshot(cx);
16640 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16641 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16642 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16643 {
16644 let buffer_snapshot = buffer.read(cx).snapshot();
16645 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16646 let last_row = buffer_snapshot.max_point().row;
16647 let lines_below = last_row.saturating_sub(excerpt_end_row);
16648 if lines_below >= lines_to_expand {
16649 scroll = Some(
16650 current_scroll_position
16651 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16652 );
16653 }
16654 }
16655 }
16656 if direction == ExpandExcerptDirection::Up
16657 && self
16658 .buffer
16659 .read(cx)
16660 .snapshot(cx)
16661 .excerpt_before(excerpt)
16662 .is_none()
16663 {
16664 scroll = Some(current_scroll_position);
16665 }
16666
16667 self.buffer.update(cx, |buffer, cx| {
16668 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16669 });
16670
16671 if let Some(new_scroll_position) = scroll {
16672 self.set_scroll_position(new_scroll_position, window, cx);
16673 }
16674 }
16675
16676 pub fn go_to_singleton_buffer_point(
16677 &mut self,
16678 point: Point,
16679 window: &mut Window,
16680 cx: &mut Context<Self>,
16681 ) {
16682 self.go_to_singleton_buffer_range(point..point, window, cx);
16683 }
16684
16685 pub fn go_to_singleton_buffer_range(
16686 &mut self,
16687 range: Range<Point>,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) {
16691 let multibuffer = self.buffer().read(cx);
16692 let Some(buffer) = multibuffer.as_singleton() else {
16693 return;
16694 };
16695 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16696 return;
16697 };
16698 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16699 return;
16700 };
16701 self.change_selections(
16702 SelectionEffects::default().nav_history(true),
16703 window,
16704 cx,
16705 |s| s.select_anchor_ranges([start..end]),
16706 );
16707 }
16708
16709 pub fn go_to_diagnostic(
16710 &mut self,
16711 action: &GoToDiagnostic,
16712 window: &mut Window,
16713 cx: &mut Context<Self>,
16714 ) {
16715 if !self.diagnostics_enabled() {
16716 return;
16717 }
16718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16719 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16720 }
16721
16722 pub fn go_to_prev_diagnostic(
16723 &mut self,
16724 action: &GoToPreviousDiagnostic,
16725 window: &mut Window,
16726 cx: &mut Context<Self>,
16727 ) {
16728 if !self.diagnostics_enabled() {
16729 return;
16730 }
16731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16732 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16733 }
16734
16735 pub fn go_to_diagnostic_impl(
16736 &mut self,
16737 direction: Direction,
16738 severity: GoToDiagnosticSeverityFilter,
16739 window: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) {
16742 let buffer = self.buffer.read(cx).snapshot(cx);
16743 let selection = self
16744 .selections
16745 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16746
16747 let mut active_group_id = None;
16748 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16749 && active_group.active_range.start.to_offset(&buffer) == selection.start
16750 {
16751 active_group_id = Some(active_group.group_id);
16752 }
16753
16754 fn filtered<'a>(
16755 severity: GoToDiagnosticSeverityFilter,
16756 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16757 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16758 diagnostics
16759 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16760 .filter(|entry| entry.range.start != entry.range.end)
16761 .filter(|entry| !entry.diagnostic.is_unnecessary)
16762 }
16763
16764 let before = filtered(
16765 severity,
16766 buffer
16767 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16768 .filter(|entry| entry.range.start <= selection.start),
16769 );
16770 let after = filtered(
16771 severity,
16772 buffer
16773 .diagnostics_in_range(selection.start..buffer.len())
16774 .filter(|entry| entry.range.start >= selection.start),
16775 );
16776
16777 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16778 if direction == Direction::Prev {
16779 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16780 {
16781 for diagnostic in prev_diagnostics.into_iter().rev() {
16782 if diagnostic.range.start != selection.start
16783 || active_group_id
16784 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16785 {
16786 found = Some(diagnostic);
16787 break 'outer;
16788 }
16789 }
16790 }
16791 } else {
16792 for diagnostic in after.chain(before) {
16793 if diagnostic.range.start != selection.start
16794 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16795 {
16796 found = Some(diagnostic);
16797 break;
16798 }
16799 }
16800 }
16801 let Some(next_diagnostic) = found else {
16802 return;
16803 };
16804
16805 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16806 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16807 return;
16808 };
16809 let snapshot = self.snapshot(window, cx);
16810 if snapshot.intersects_fold(next_diagnostic.range.start) {
16811 self.unfold_ranges(
16812 std::slice::from_ref(&next_diagnostic.range),
16813 true,
16814 false,
16815 cx,
16816 );
16817 }
16818 self.change_selections(Default::default(), window, cx, |s| {
16819 s.select_ranges(vec![
16820 next_diagnostic.range.start..next_diagnostic.range.start,
16821 ])
16822 });
16823 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16824 self.refresh_edit_prediction(false, true, window, cx);
16825 }
16826
16827 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16829 let snapshot = self.snapshot(window, cx);
16830 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16831 self.go_to_hunk_before_or_after_position(
16832 &snapshot,
16833 selection.head(),
16834 Direction::Next,
16835 window,
16836 cx,
16837 );
16838 }
16839
16840 pub fn go_to_hunk_before_or_after_position(
16841 &mut self,
16842 snapshot: &EditorSnapshot,
16843 position: Point,
16844 direction: Direction,
16845 window: &mut Window,
16846 cx: &mut Context<Editor>,
16847 ) {
16848 let row = if direction == Direction::Next {
16849 self.hunk_after_position(snapshot, position)
16850 .map(|hunk| hunk.row_range.start)
16851 } else {
16852 self.hunk_before_position(snapshot, position)
16853 };
16854
16855 if let Some(row) = row {
16856 let destination = Point::new(row.0, 0);
16857 let autoscroll = Autoscroll::center();
16858
16859 self.unfold_ranges(&[destination..destination], false, false, cx);
16860 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16861 s.select_ranges([destination..destination]);
16862 });
16863 }
16864 }
16865
16866 fn hunk_after_position(
16867 &mut self,
16868 snapshot: &EditorSnapshot,
16869 position: Point,
16870 ) -> Option<MultiBufferDiffHunk> {
16871 snapshot
16872 .buffer_snapshot()
16873 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16874 .find(|hunk| hunk.row_range.start.0 > position.row)
16875 .or_else(|| {
16876 snapshot
16877 .buffer_snapshot()
16878 .diff_hunks_in_range(Point::zero()..position)
16879 .find(|hunk| hunk.row_range.end.0 < position.row)
16880 })
16881 }
16882
16883 fn go_to_prev_hunk(
16884 &mut self,
16885 _: &GoToPreviousHunk,
16886 window: &mut Window,
16887 cx: &mut Context<Self>,
16888 ) {
16889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16890 let snapshot = self.snapshot(window, cx);
16891 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16892 self.go_to_hunk_before_or_after_position(
16893 &snapshot,
16894 selection.head(),
16895 Direction::Prev,
16896 window,
16897 cx,
16898 );
16899 }
16900
16901 fn hunk_before_position(
16902 &mut self,
16903 snapshot: &EditorSnapshot,
16904 position: Point,
16905 ) -> Option<MultiBufferRow> {
16906 snapshot
16907 .buffer_snapshot()
16908 .diff_hunk_before(position)
16909 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16910 }
16911
16912 fn go_to_next_change(
16913 &mut self,
16914 _: &GoToNextChange,
16915 window: &mut Window,
16916 cx: &mut Context<Self>,
16917 ) {
16918 if let Some(selections) = self
16919 .change_list
16920 .next_change(1, Direction::Next)
16921 .map(|s| s.to_vec())
16922 {
16923 self.change_selections(Default::default(), window, cx, |s| {
16924 let map = s.display_snapshot();
16925 s.select_display_ranges(selections.iter().map(|a| {
16926 let point = a.to_display_point(&map);
16927 point..point
16928 }))
16929 })
16930 }
16931 }
16932
16933 fn go_to_previous_change(
16934 &mut self,
16935 _: &GoToPreviousChange,
16936 window: &mut Window,
16937 cx: &mut Context<Self>,
16938 ) {
16939 if let Some(selections) = self
16940 .change_list
16941 .next_change(1, Direction::Prev)
16942 .map(|s| s.to_vec())
16943 {
16944 self.change_selections(Default::default(), window, cx, |s| {
16945 let map = s.display_snapshot();
16946 s.select_display_ranges(selections.iter().map(|a| {
16947 let point = a.to_display_point(&map);
16948 point..point
16949 }))
16950 })
16951 }
16952 }
16953
16954 pub fn go_to_next_document_highlight(
16955 &mut self,
16956 _: &GoToNextDocumentHighlight,
16957 window: &mut Window,
16958 cx: &mut Context<Self>,
16959 ) {
16960 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16961 }
16962
16963 pub fn go_to_prev_document_highlight(
16964 &mut self,
16965 _: &GoToPreviousDocumentHighlight,
16966 window: &mut Window,
16967 cx: &mut Context<Self>,
16968 ) {
16969 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16970 }
16971
16972 pub fn go_to_document_highlight_before_or_after_position(
16973 &mut self,
16974 direction: Direction,
16975 window: &mut Window,
16976 cx: &mut Context<Editor>,
16977 ) {
16978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16979 let snapshot = self.snapshot(window, cx);
16980 let buffer = &snapshot.buffer_snapshot();
16981 let position = self
16982 .selections
16983 .newest::<Point>(&snapshot.display_snapshot)
16984 .head();
16985 let anchor_position = buffer.anchor_after(position);
16986
16987 // Get all document highlights (both read and write)
16988 let mut all_highlights = Vec::new();
16989
16990 if let Some((_, read_highlights)) = self
16991 .background_highlights
16992 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16993 {
16994 all_highlights.extend(read_highlights.iter());
16995 }
16996
16997 if let Some((_, write_highlights)) = self
16998 .background_highlights
16999 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17000 {
17001 all_highlights.extend(write_highlights.iter());
17002 }
17003
17004 if all_highlights.is_empty() {
17005 return;
17006 }
17007
17008 // Sort highlights by position
17009 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17010
17011 let target_highlight = match direction {
17012 Direction::Next => {
17013 // Find the first highlight after the current position
17014 all_highlights
17015 .iter()
17016 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17017 }
17018 Direction::Prev => {
17019 // Find the last highlight before the current position
17020 all_highlights
17021 .iter()
17022 .rev()
17023 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17024 }
17025 };
17026
17027 if let Some(highlight) = target_highlight {
17028 let destination = highlight.start.to_point(buffer);
17029 let autoscroll = Autoscroll::center();
17030
17031 self.unfold_ranges(&[destination..destination], false, false, cx);
17032 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17033 s.select_ranges([destination..destination]);
17034 });
17035 }
17036 }
17037
17038 fn go_to_line<T: 'static>(
17039 &mut self,
17040 position: Anchor,
17041 highlight_color: Option<Hsla>,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) {
17045 let snapshot = self.snapshot(window, cx).display_snapshot;
17046 let position = position.to_point(&snapshot.buffer_snapshot());
17047 let start = snapshot
17048 .buffer_snapshot()
17049 .clip_point(Point::new(position.row, 0), Bias::Left);
17050 let end = start + Point::new(1, 0);
17051 let start = snapshot.buffer_snapshot().anchor_before(start);
17052 let end = snapshot.buffer_snapshot().anchor_before(end);
17053
17054 self.highlight_rows::<T>(
17055 start..end,
17056 highlight_color
17057 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17058 Default::default(),
17059 cx,
17060 );
17061
17062 if self.buffer.read(cx).is_singleton() {
17063 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17064 }
17065 }
17066
17067 pub fn go_to_definition(
17068 &mut self,
17069 _: &GoToDefinition,
17070 window: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) -> Task<Result<Navigated>> {
17073 let definition =
17074 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17075 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17076 cx.spawn_in(window, async move |editor, cx| {
17077 if definition.await? == Navigated::Yes {
17078 return Ok(Navigated::Yes);
17079 }
17080 match fallback_strategy {
17081 GoToDefinitionFallback::None => Ok(Navigated::No),
17082 GoToDefinitionFallback::FindAllReferences => {
17083 match editor.update_in(cx, |editor, window, cx| {
17084 editor.find_all_references(&FindAllReferences::default(), window, cx)
17085 })? {
17086 Some(references) => references.await,
17087 None => Ok(Navigated::No),
17088 }
17089 }
17090 }
17091 })
17092 }
17093
17094 pub fn go_to_declaration(
17095 &mut self,
17096 _: &GoToDeclaration,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) -> Task<Result<Navigated>> {
17100 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17101 }
17102
17103 pub fn go_to_declaration_split(
17104 &mut self,
17105 _: &GoToDeclaration,
17106 window: &mut Window,
17107 cx: &mut Context<Self>,
17108 ) -> Task<Result<Navigated>> {
17109 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17110 }
17111
17112 pub fn go_to_implementation(
17113 &mut self,
17114 _: &GoToImplementation,
17115 window: &mut Window,
17116 cx: &mut Context<Self>,
17117 ) -> Task<Result<Navigated>> {
17118 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17119 }
17120
17121 pub fn go_to_implementation_split(
17122 &mut self,
17123 _: &GoToImplementationSplit,
17124 window: &mut Window,
17125 cx: &mut Context<Self>,
17126 ) -> Task<Result<Navigated>> {
17127 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17128 }
17129
17130 pub fn go_to_type_definition(
17131 &mut self,
17132 _: &GoToTypeDefinition,
17133 window: &mut Window,
17134 cx: &mut Context<Self>,
17135 ) -> Task<Result<Navigated>> {
17136 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17137 }
17138
17139 pub fn go_to_definition_split(
17140 &mut self,
17141 _: &GoToDefinitionSplit,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) -> Task<Result<Navigated>> {
17145 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17146 }
17147
17148 pub fn go_to_type_definition_split(
17149 &mut self,
17150 _: &GoToTypeDefinitionSplit,
17151 window: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) -> Task<Result<Navigated>> {
17154 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17155 }
17156
17157 fn go_to_definition_of_kind(
17158 &mut self,
17159 kind: GotoDefinitionKind,
17160 split: bool,
17161 window: &mut Window,
17162 cx: &mut Context<Self>,
17163 ) -> Task<Result<Navigated>> {
17164 let Some(provider) = self.semantics_provider.clone() else {
17165 return Task::ready(Ok(Navigated::No));
17166 };
17167 let head = self
17168 .selections
17169 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17170 .head();
17171 let buffer = self.buffer.read(cx);
17172 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17173 return Task::ready(Ok(Navigated::No));
17174 };
17175 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17176 return Task::ready(Ok(Navigated::No));
17177 };
17178
17179 cx.spawn_in(window, async move |editor, cx| {
17180 let Some(definitions) = definitions.await? else {
17181 return Ok(Navigated::No);
17182 };
17183 let navigated = editor
17184 .update_in(cx, |editor, window, cx| {
17185 editor.navigate_to_hover_links(
17186 Some(kind),
17187 definitions
17188 .into_iter()
17189 .filter(|location| {
17190 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17191 })
17192 .map(HoverLink::Text)
17193 .collect::<Vec<_>>(),
17194 split,
17195 window,
17196 cx,
17197 )
17198 })?
17199 .await?;
17200 anyhow::Ok(navigated)
17201 })
17202 }
17203
17204 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17205 let selection = self.selections.newest_anchor();
17206 let head = selection.head();
17207 let tail = selection.tail();
17208
17209 let Some((buffer, start_position)) =
17210 self.buffer.read(cx).text_anchor_for_position(head, cx)
17211 else {
17212 return;
17213 };
17214
17215 let end_position = if head != tail {
17216 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17217 return;
17218 };
17219 Some(pos)
17220 } else {
17221 None
17222 };
17223
17224 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17225 let url = if let Some(end_pos) = end_position {
17226 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17227 } else {
17228 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17229 };
17230
17231 if let Some(url) = url {
17232 cx.update(|window, cx| {
17233 if parse_zed_link(&url, cx).is_some() {
17234 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17235 } else {
17236 cx.open_url(&url);
17237 }
17238 })?;
17239 }
17240
17241 anyhow::Ok(())
17242 });
17243
17244 url_finder.detach();
17245 }
17246
17247 pub fn open_selected_filename(
17248 &mut self,
17249 _: &OpenSelectedFilename,
17250 window: &mut Window,
17251 cx: &mut Context<Self>,
17252 ) {
17253 let Some(workspace) = self.workspace() else {
17254 return;
17255 };
17256
17257 let position = self.selections.newest_anchor().head();
17258
17259 let Some((buffer, buffer_position)) =
17260 self.buffer.read(cx).text_anchor_for_position(position, cx)
17261 else {
17262 return;
17263 };
17264
17265 let project = self.project.clone();
17266
17267 cx.spawn_in(window, async move |_, cx| {
17268 let result = find_file(&buffer, project, buffer_position, cx).await;
17269
17270 if let Some((_, path)) = result {
17271 workspace
17272 .update_in(cx, |workspace, window, cx| {
17273 workspace.open_resolved_path(path, window, cx)
17274 })?
17275 .await?;
17276 }
17277 anyhow::Ok(())
17278 })
17279 .detach();
17280 }
17281
17282 pub(crate) fn navigate_to_hover_links(
17283 &mut self,
17284 kind: Option<GotoDefinitionKind>,
17285 definitions: Vec<HoverLink>,
17286 split: bool,
17287 window: &mut Window,
17288 cx: &mut Context<Editor>,
17289 ) -> Task<Result<Navigated>> {
17290 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17291 let mut first_url_or_file = None;
17292 let definitions: Vec<_> = definitions
17293 .into_iter()
17294 .filter_map(|def| match def {
17295 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17296 HoverLink::InlayHint(lsp_location, server_id) => {
17297 let computation =
17298 self.compute_target_location(lsp_location, server_id, window, cx);
17299 Some(cx.background_spawn(computation))
17300 }
17301 HoverLink::Url(url) => {
17302 first_url_or_file = Some(Either::Left(url));
17303 None
17304 }
17305 HoverLink::File(path) => {
17306 first_url_or_file = Some(Either::Right(path));
17307 None
17308 }
17309 })
17310 .collect();
17311
17312 let workspace = self.workspace();
17313
17314 cx.spawn_in(window, async move |editor, cx| {
17315 let locations: Vec<Location> = future::join_all(definitions)
17316 .await
17317 .into_iter()
17318 .filter_map(|location| location.transpose())
17319 .collect::<Result<_>>()
17320 .context("location tasks")?;
17321 let mut locations = cx.update(|_, cx| {
17322 locations
17323 .into_iter()
17324 .map(|location| {
17325 let buffer = location.buffer.read(cx);
17326 (location.buffer, location.range.to_point(buffer))
17327 })
17328 .into_group_map()
17329 })?;
17330 let mut num_locations = 0;
17331 for ranges in locations.values_mut() {
17332 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17333 ranges.dedup();
17334 num_locations += ranges.len();
17335 }
17336
17337 if num_locations > 1 {
17338 let tab_kind = match kind {
17339 Some(GotoDefinitionKind::Implementation) => "Implementations",
17340 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17341 Some(GotoDefinitionKind::Declaration) => "Declarations",
17342 Some(GotoDefinitionKind::Type) => "Types",
17343 };
17344 let title = editor
17345 .update_in(cx, |_, _, cx| {
17346 let target = locations
17347 .iter()
17348 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17349 .map(|(buffer, location)| {
17350 buffer
17351 .read(cx)
17352 .text_for_range(location.clone())
17353 .collect::<String>()
17354 })
17355 .filter(|text| !text.contains('\n'))
17356 .unique()
17357 .take(3)
17358 .join(", ");
17359 if target.is_empty() {
17360 tab_kind.to_owned()
17361 } else {
17362 format!("{tab_kind} for {target}")
17363 }
17364 })
17365 .context("buffer title")?;
17366
17367 let Some(workspace) = workspace else {
17368 return Ok(Navigated::No);
17369 };
17370
17371 let opened = workspace
17372 .update_in(cx, |workspace, window, cx| {
17373 let allow_preview = PreviewTabsSettings::get_global(cx)
17374 .enable_preview_multibuffer_from_code_navigation;
17375 Self::open_locations_in_multibuffer(
17376 workspace,
17377 locations,
17378 title,
17379 split,
17380 allow_preview,
17381 MultibufferSelectionMode::First,
17382 window,
17383 cx,
17384 )
17385 })
17386 .is_ok();
17387
17388 anyhow::Ok(Navigated::from_bool(opened))
17389 } else if num_locations == 0 {
17390 // If there is one url or file, open it directly
17391 match first_url_or_file {
17392 Some(Either::Left(url)) => {
17393 cx.update(|_, cx| cx.open_url(&url))?;
17394 Ok(Navigated::Yes)
17395 }
17396 Some(Either::Right(path)) => {
17397 // TODO(andrew): respect preview tab settings
17398 // `enable_keep_preview_on_code_navigation` and
17399 // `enable_preview_file_from_code_navigation`
17400 let Some(workspace) = workspace else {
17401 return Ok(Navigated::No);
17402 };
17403 workspace
17404 .update_in(cx, |workspace, window, cx| {
17405 workspace.open_resolved_path(path, window, cx)
17406 })?
17407 .await?;
17408 Ok(Navigated::Yes)
17409 }
17410 None => Ok(Navigated::No),
17411 }
17412 } else {
17413 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17414 let target_range = target_ranges.first().unwrap().clone();
17415
17416 editor.update_in(cx, |editor, window, cx| {
17417 let range = target_range.to_point(target_buffer.read(cx));
17418 let range = editor.range_for_match(&range);
17419 let range = collapse_multiline_range(range);
17420
17421 if !split
17422 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17423 {
17424 editor.go_to_singleton_buffer_range(range, window, cx);
17425 } else {
17426 let Some(workspace) = workspace else {
17427 return Navigated::No;
17428 };
17429 let pane = workspace.read(cx).active_pane().clone();
17430 window.defer(cx, move |window, cx| {
17431 let target_editor: Entity<Self> =
17432 workspace.update(cx, |workspace, cx| {
17433 let pane = if split {
17434 workspace.adjacent_pane(window, cx)
17435 } else {
17436 workspace.active_pane().clone()
17437 };
17438
17439 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17440 let keep_old_preview = preview_tabs_settings
17441 .enable_keep_preview_on_code_navigation;
17442 let allow_new_preview = preview_tabs_settings
17443 .enable_preview_file_from_code_navigation;
17444
17445 workspace.open_project_item(
17446 pane,
17447 target_buffer.clone(),
17448 true,
17449 true,
17450 keep_old_preview,
17451 allow_new_preview,
17452 window,
17453 cx,
17454 )
17455 });
17456 target_editor.update(cx, |target_editor, cx| {
17457 // When selecting a definition in a different buffer, disable the nav history
17458 // to avoid creating a history entry at the previous cursor location.
17459 pane.update(cx, |pane, _| pane.disable_history());
17460 target_editor.go_to_singleton_buffer_range(range, window, cx);
17461 pane.update(cx, |pane, _| pane.enable_history());
17462 });
17463 });
17464 }
17465 Navigated::Yes
17466 })
17467 }
17468 })
17469 }
17470
17471 fn compute_target_location(
17472 &self,
17473 lsp_location: lsp::Location,
17474 server_id: LanguageServerId,
17475 window: &mut Window,
17476 cx: &mut Context<Self>,
17477 ) -> Task<anyhow::Result<Option<Location>>> {
17478 let Some(project) = self.project.clone() else {
17479 return Task::ready(Ok(None));
17480 };
17481
17482 cx.spawn_in(window, async move |editor, cx| {
17483 let location_task = editor.update(cx, |_, cx| {
17484 project.update(cx, |project, cx| {
17485 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17486 })
17487 })?;
17488 let location = Some({
17489 let target_buffer_handle = location_task.await.context("open local buffer")?;
17490 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17491 let target_start = target_buffer
17492 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17493 let target_end = target_buffer
17494 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17495 target_buffer.anchor_after(target_start)
17496 ..target_buffer.anchor_before(target_end)
17497 })?;
17498 Location {
17499 buffer: target_buffer_handle,
17500 range,
17501 }
17502 });
17503 Ok(location)
17504 })
17505 }
17506
17507 fn go_to_next_reference(
17508 &mut self,
17509 _: &GoToNextReference,
17510 window: &mut Window,
17511 cx: &mut Context<Self>,
17512 ) {
17513 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17514 if let Some(task) = task {
17515 task.detach();
17516 };
17517 }
17518
17519 fn go_to_prev_reference(
17520 &mut self,
17521 _: &GoToPreviousReference,
17522 window: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) {
17525 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17526 if let Some(task) = task {
17527 task.detach();
17528 };
17529 }
17530
17531 pub fn go_to_reference_before_or_after_position(
17532 &mut self,
17533 direction: Direction,
17534 count: usize,
17535 window: &mut Window,
17536 cx: &mut Context<Self>,
17537 ) -> Option<Task<Result<()>>> {
17538 let selection = self.selections.newest_anchor();
17539 let head = selection.head();
17540
17541 let multi_buffer = self.buffer.read(cx);
17542
17543 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17544 let workspace = self.workspace()?;
17545 let project = workspace.read(cx).project().clone();
17546 let references =
17547 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17548 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17549 let Some(locations) = references.await? else {
17550 return Ok(());
17551 };
17552
17553 if locations.is_empty() {
17554 // totally normal - the cursor may be on something which is not
17555 // a symbol (e.g. a keyword)
17556 log::info!("no references found under cursor");
17557 return Ok(());
17558 }
17559
17560 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17561
17562 let (locations, current_location_index) =
17563 multi_buffer.update(cx, |multi_buffer, cx| {
17564 let mut locations = locations
17565 .into_iter()
17566 .filter_map(|loc| {
17567 let start = multi_buffer.buffer_anchor_to_anchor(
17568 &loc.buffer,
17569 loc.range.start,
17570 cx,
17571 )?;
17572 let end = multi_buffer.buffer_anchor_to_anchor(
17573 &loc.buffer,
17574 loc.range.end,
17575 cx,
17576 )?;
17577 Some(start..end)
17578 })
17579 .collect::<Vec<_>>();
17580
17581 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17582 // There is an O(n) implementation, but given this list will be
17583 // small (usually <100 items), the extra O(log(n)) factor isn't
17584 // worth the (surprisingly large amount of) extra complexity.
17585 locations
17586 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17587
17588 let head_offset = head.to_offset(&multi_buffer_snapshot);
17589
17590 let current_location_index = locations.iter().position(|loc| {
17591 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17592 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17593 });
17594
17595 (locations, current_location_index)
17596 })?;
17597
17598 let Some(current_location_index) = current_location_index else {
17599 // This indicates something has gone wrong, because we already
17600 // handle the "no references" case above
17601 log::error!(
17602 "failed to find current reference under cursor. Total references: {}",
17603 locations.len()
17604 );
17605 return Ok(());
17606 };
17607
17608 let destination_location_index = match direction {
17609 Direction::Next => (current_location_index + count) % locations.len(),
17610 Direction::Prev => {
17611 (current_location_index + locations.len() - count % locations.len())
17612 % locations.len()
17613 }
17614 };
17615
17616 // TODO(cameron): is this needed?
17617 // the thinking is to avoid "jumping to the current location" (avoid
17618 // polluting "jumplist" in vim terms)
17619 if current_location_index == destination_location_index {
17620 return Ok(());
17621 }
17622
17623 let Range { start, end } = locations[destination_location_index];
17624
17625 editor.update_in(cx, |editor, window, cx| {
17626 let effects = SelectionEffects::default();
17627
17628 editor.unfold_ranges(&[start..end], false, false, cx);
17629 editor.change_selections(effects, window, cx, |s| {
17630 s.select_ranges([start..start]);
17631 });
17632 })?;
17633
17634 Ok(())
17635 }))
17636 }
17637
17638 pub fn find_all_references(
17639 &mut self,
17640 action: &FindAllReferences,
17641 window: &mut Window,
17642 cx: &mut Context<Self>,
17643 ) -> Option<Task<Result<Navigated>>> {
17644 let always_open_multibuffer = action.always_open_multibuffer;
17645 let selection = self.selections.newest_anchor();
17646 let multi_buffer = self.buffer.read(cx);
17647 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17648 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17649 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17650 let head = selection_offset.head();
17651
17652 let head_anchor = multi_buffer_snapshot.anchor_at(
17653 head,
17654 if head < selection_offset.tail() {
17655 Bias::Right
17656 } else {
17657 Bias::Left
17658 },
17659 );
17660
17661 match self
17662 .find_all_references_task_sources
17663 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17664 {
17665 Ok(_) => {
17666 log::info!(
17667 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17668 );
17669 return None;
17670 }
17671 Err(i) => {
17672 self.find_all_references_task_sources.insert(i, head_anchor);
17673 }
17674 }
17675
17676 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17677 let workspace = self.workspace()?;
17678 let project = workspace.read(cx).project().clone();
17679 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17680 Some(cx.spawn_in(window, async move |editor, cx| {
17681 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17682 if let Ok(i) = editor
17683 .find_all_references_task_sources
17684 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17685 {
17686 editor.find_all_references_task_sources.remove(i);
17687 }
17688 });
17689
17690 let Some(locations) = references.await? else {
17691 return anyhow::Ok(Navigated::No);
17692 };
17693 let mut locations = cx.update(|_, cx| {
17694 locations
17695 .into_iter()
17696 .map(|location| {
17697 let buffer = location.buffer.read(cx);
17698 (location.buffer, location.range.to_point(buffer))
17699 })
17700 // if special-casing the single-match case, remove ranges
17701 // that intersect current selection
17702 .filter(|(location_buffer, location)| {
17703 if always_open_multibuffer || &buffer != location_buffer {
17704 return true;
17705 }
17706
17707 !location.contains_inclusive(&selection_point.range())
17708 })
17709 .into_group_map()
17710 })?;
17711 if locations.is_empty() {
17712 return anyhow::Ok(Navigated::No);
17713 }
17714 for ranges in locations.values_mut() {
17715 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17716 ranges.dedup();
17717 }
17718 let mut num_locations = 0;
17719 for ranges in locations.values_mut() {
17720 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17721 ranges.dedup();
17722 num_locations += ranges.len();
17723 }
17724
17725 if num_locations == 1 && !always_open_multibuffer {
17726 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17727 let target_range = target_ranges.first().unwrap().clone();
17728
17729 return editor.update_in(cx, |editor, window, cx| {
17730 let range = target_range.to_point(target_buffer.read(cx));
17731 let range = editor.range_for_match(&range);
17732 let range = range.start..range.start;
17733
17734 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17735 editor.go_to_singleton_buffer_range(range, window, cx);
17736 } else {
17737 let pane = workspace.read(cx).active_pane().clone();
17738 window.defer(cx, move |window, cx| {
17739 let target_editor: Entity<Self> =
17740 workspace.update(cx, |workspace, cx| {
17741 let pane = workspace.active_pane().clone();
17742
17743 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17744 let keep_old_preview = preview_tabs_settings
17745 .enable_keep_preview_on_code_navigation;
17746 let allow_new_preview = preview_tabs_settings
17747 .enable_preview_file_from_code_navigation;
17748
17749 workspace.open_project_item(
17750 pane,
17751 target_buffer.clone(),
17752 true,
17753 true,
17754 keep_old_preview,
17755 allow_new_preview,
17756 window,
17757 cx,
17758 )
17759 });
17760 target_editor.update(cx, |target_editor, cx| {
17761 // When selecting a definition in a different buffer, disable the nav history
17762 // to avoid creating a history entry at the previous cursor location.
17763 pane.update(cx, |pane, _| pane.disable_history());
17764 target_editor.go_to_singleton_buffer_range(range, window, cx);
17765 pane.update(cx, |pane, _| pane.enable_history());
17766 });
17767 });
17768 }
17769 Navigated::No
17770 });
17771 }
17772
17773 workspace.update_in(cx, |workspace, window, cx| {
17774 let target = locations
17775 .iter()
17776 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17777 .map(|(buffer, location)| {
17778 buffer
17779 .read(cx)
17780 .text_for_range(location.clone())
17781 .collect::<String>()
17782 })
17783 .filter(|text| !text.contains('\n'))
17784 .unique()
17785 .take(3)
17786 .join(", ");
17787 let title = if target.is_empty() {
17788 "References".to_owned()
17789 } else {
17790 format!("References to {target}")
17791 };
17792 let allow_preview = PreviewTabsSettings::get_global(cx)
17793 .enable_preview_multibuffer_from_code_navigation;
17794 Self::open_locations_in_multibuffer(
17795 workspace,
17796 locations,
17797 title,
17798 false,
17799 allow_preview,
17800 MultibufferSelectionMode::First,
17801 window,
17802 cx,
17803 );
17804 Navigated::Yes
17805 })
17806 }))
17807 }
17808
17809 /// Opens a multibuffer with the given project locations in it.
17810 pub fn open_locations_in_multibuffer(
17811 workspace: &mut Workspace,
17812 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17813 title: String,
17814 split: bool,
17815 allow_preview: bool,
17816 multibuffer_selection_mode: MultibufferSelectionMode,
17817 window: &mut Window,
17818 cx: &mut Context<Workspace>,
17819 ) {
17820 if locations.is_empty() {
17821 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17822 return;
17823 }
17824
17825 let capability = workspace.project().read(cx).capability();
17826 let mut ranges = <Vec<Range<Anchor>>>::new();
17827
17828 // a key to find existing multibuffer editors with the same set of locations
17829 // to prevent us from opening more and more multibuffer tabs for searches and the like
17830 let mut key = (title.clone(), vec![]);
17831 let excerpt_buffer = cx.new(|cx| {
17832 let key = &mut key.1;
17833 let mut multibuffer = MultiBuffer::new(capability);
17834 for (buffer, mut ranges_for_buffer) in locations {
17835 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17836 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17837 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17838 PathKey::for_buffer(&buffer, cx),
17839 buffer.clone(),
17840 ranges_for_buffer,
17841 multibuffer_context_lines(cx),
17842 cx,
17843 );
17844 ranges.extend(new_ranges)
17845 }
17846
17847 multibuffer.with_title(title)
17848 });
17849 let existing = workspace.active_pane().update(cx, |pane, cx| {
17850 pane.items()
17851 .filter_map(|item| item.downcast::<Editor>())
17852 .find(|editor| {
17853 editor
17854 .read(cx)
17855 .lookup_key
17856 .as_ref()
17857 .and_then(|it| {
17858 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17859 })
17860 .is_some_and(|it| *it == key)
17861 })
17862 });
17863 let was_existing = existing.is_some();
17864 let editor = existing.unwrap_or_else(|| {
17865 cx.new(|cx| {
17866 let mut editor = Editor::for_multibuffer(
17867 excerpt_buffer,
17868 Some(workspace.project().clone()),
17869 window,
17870 cx,
17871 );
17872 editor.lookup_key = Some(Box::new(key));
17873 editor
17874 })
17875 });
17876 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17877 MultibufferSelectionMode::First => {
17878 if let Some(first_range) = ranges.first() {
17879 editor.change_selections(
17880 SelectionEffects::no_scroll(),
17881 window,
17882 cx,
17883 |selections| {
17884 selections.clear_disjoint();
17885 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17886 },
17887 );
17888 }
17889 editor.highlight_background::<Self>(
17890 &ranges,
17891 |_, theme| theme.colors().editor_highlighted_line_background,
17892 cx,
17893 );
17894 }
17895 MultibufferSelectionMode::All => {
17896 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17897 selections.clear_disjoint();
17898 selections.select_anchor_ranges(ranges);
17899 });
17900 }
17901 });
17902
17903 let item = Box::new(editor);
17904
17905 let pane = if split {
17906 workspace.adjacent_pane(window, cx)
17907 } else {
17908 workspace.active_pane().clone()
17909 };
17910 let activate_pane = split;
17911
17912 let mut destination_index = None;
17913 pane.update(cx, |pane, cx| {
17914 if allow_preview && !was_existing {
17915 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17916 }
17917 if was_existing && !allow_preview {
17918 pane.unpreview_item_if_preview(item.item_id());
17919 }
17920 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17921 });
17922 }
17923
17924 pub fn rename(
17925 &mut self,
17926 _: &Rename,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) -> Option<Task<Result<()>>> {
17930 use language::ToOffset as _;
17931
17932 let provider = self.semantics_provider.clone()?;
17933 let selection = self.selections.newest_anchor().clone();
17934 let (cursor_buffer, cursor_buffer_position) = self
17935 .buffer
17936 .read(cx)
17937 .text_anchor_for_position(selection.head(), cx)?;
17938 let (tail_buffer, cursor_buffer_position_end) = self
17939 .buffer
17940 .read(cx)
17941 .text_anchor_for_position(selection.tail(), cx)?;
17942 if tail_buffer != cursor_buffer {
17943 return None;
17944 }
17945
17946 let snapshot = cursor_buffer.read(cx).snapshot();
17947 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17948 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17949 let prepare_rename = provider
17950 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17951 .unwrap_or_else(|| Task::ready(Ok(None)));
17952 drop(snapshot);
17953
17954 Some(cx.spawn_in(window, async move |this, cx| {
17955 let rename_range = if let Some(range) = prepare_rename.await? {
17956 Some(range)
17957 } else {
17958 this.update(cx, |this, cx| {
17959 let buffer = this.buffer.read(cx).snapshot(cx);
17960 let mut buffer_highlights = this
17961 .document_highlights_for_position(selection.head(), &buffer)
17962 .filter(|highlight| {
17963 highlight.start.excerpt_id == selection.head().excerpt_id
17964 && highlight.end.excerpt_id == selection.head().excerpt_id
17965 });
17966 buffer_highlights
17967 .next()
17968 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17969 })?
17970 };
17971 if let Some(rename_range) = rename_range {
17972 this.update_in(cx, |this, window, cx| {
17973 let snapshot = cursor_buffer.read(cx).snapshot();
17974 let rename_buffer_range = rename_range.to_offset(&snapshot);
17975 let cursor_offset_in_rename_range =
17976 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17977 let cursor_offset_in_rename_range_end =
17978 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17979
17980 this.take_rename(false, window, cx);
17981 let buffer = this.buffer.read(cx).read(cx);
17982 let cursor_offset = selection.head().to_offset(&buffer);
17983 let rename_start =
17984 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17985 let rename_end = rename_start + rename_buffer_range.len();
17986 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17987 let mut old_highlight_id = None;
17988 let old_name: Arc<str> = buffer
17989 .chunks(rename_start..rename_end, true)
17990 .map(|chunk| {
17991 if old_highlight_id.is_none() {
17992 old_highlight_id = chunk.syntax_highlight_id;
17993 }
17994 chunk.text
17995 })
17996 .collect::<String>()
17997 .into();
17998
17999 drop(buffer);
18000
18001 // Position the selection in the rename editor so that it matches the current selection.
18002 this.show_local_selections = false;
18003 let rename_editor = cx.new(|cx| {
18004 let mut editor = Editor::single_line(window, cx);
18005 editor.buffer.update(cx, |buffer, cx| {
18006 buffer.edit(
18007 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18008 None,
18009 cx,
18010 )
18011 });
18012 let cursor_offset_in_rename_range =
18013 MultiBufferOffset(cursor_offset_in_rename_range);
18014 let cursor_offset_in_rename_range_end =
18015 MultiBufferOffset(cursor_offset_in_rename_range_end);
18016 let rename_selection_range = match cursor_offset_in_rename_range
18017 .cmp(&cursor_offset_in_rename_range_end)
18018 {
18019 Ordering::Equal => {
18020 editor.select_all(&SelectAll, window, cx);
18021 return editor;
18022 }
18023 Ordering::Less => {
18024 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18025 }
18026 Ordering::Greater => {
18027 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18028 }
18029 };
18030 if rename_selection_range.end.0 > old_name.len() {
18031 editor.select_all(&SelectAll, window, cx);
18032 } else {
18033 editor.change_selections(Default::default(), window, cx, |s| {
18034 s.select_ranges([rename_selection_range]);
18035 });
18036 }
18037 editor
18038 });
18039 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18040 if e == &EditorEvent::Focused {
18041 cx.emit(EditorEvent::FocusedIn)
18042 }
18043 })
18044 .detach();
18045
18046 let write_highlights =
18047 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18048 let read_highlights =
18049 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18050 let ranges = write_highlights
18051 .iter()
18052 .flat_map(|(_, ranges)| ranges.iter())
18053 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18054 .cloned()
18055 .collect();
18056
18057 this.highlight_text::<Rename>(
18058 ranges,
18059 HighlightStyle {
18060 fade_out: Some(0.6),
18061 ..Default::default()
18062 },
18063 cx,
18064 );
18065 let rename_focus_handle = rename_editor.focus_handle(cx);
18066 window.focus(&rename_focus_handle);
18067 let block_id = this.insert_blocks(
18068 [BlockProperties {
18069 style: BlockStyle::Flex,
18070 placement: BlockPlacement::Below(range.start),
18071 height: Some(1),
18072 render: Arc::new({
18073 let rename_editor = rename_editor.clone();
18074 move |cx: &mut BlockContext| {
18075 let mut text_style = cx.editor_style.text.clone();
18076 if let Some(highlight_style) = old_highlight_id
18077 .and_then(|h| h.style(&cx.editor_style.syntax))
18078 {
18079 text_style = text_style.highlight(highlight_style);
18080 }
18081 div()
18082 .block_mouse_except_scroll()
18083 .pl(cx.anchor_x)
18084 .child(EditorElement::new(
18085 &rename_editor,
18086 EditorStyle {
18087 background: cx.theme().system().transparent,
18088 local_player: cx.editor_style.local_player,
18089 text: text_style,
18090 scrollbar_width: cx.editor_style.scrollbar_width,
18091 syntax: cx.editor_style.syntax.clone(),
18092 status: cx.editor_style.status.clone(),
18093 inlay_hints_style: HighlightStyle {
18094 font_weight: Some(FontWeight::BOLD),
18095 ..make_inlay_hints_style(cx.app)
18096 },
18097 edit_prediction_styles: make_suggestion_styles(
18098 cx.app,
18099 ),
18100 ..EditorStyle::default()
18101 },
18102 ))
18103 .into_any_element()
18104 }
18105 }),
18106 priority: 0,
18107 }],
18108 Some(Autoscroll::fit()),
18109 cx,
18110 )[0];
18111 this.pending_rename = Some(RenameState {
18112 range,
18113 old_name,
18114 editor: rename_editor,
18115 block_id,
18116 });
18117 })?;
18118 }
18119
18120 Ok(())
18121 }))
18122 }
18123
18124 pub fn confirm_rename(
18125 &mut self,
18126 _: &ConfirmRename,
18127 window: &mut Window,
18128 cx: &mut Context<Self>,
18129 ) -> Option<Task<Result<()>>> {
18130 let rename = self.take_rename(false, window, cx)?;
18131 let workspace = self.workspace()?.downgrade();
18132 let (buffer, start) = self
18133 .buffer
18134 .read(cx)
18135 .text_anchor_for_position(rename.range.start, cx)?;
18136 let (end_buffer, _) = self
18137 .buffer
18138 .read(cx)
18139 .text_anchor_for_position(rename.range.end, cx)?;
18140 if buffer != end_buffer {
18141 return None;
18142 }
18143
18144 let old_name = rename.old_name;
18145 let new_name = rename.editor.read(cx).text(cx);
18146
18147 let rename = self.semantics_provider.as_ref()?.perform_rename(
18148 &buffer,
18149 start,
18150 new_name.clone(),
18151 cx,
18152 )?;
18153
18154 Some(cx.spawn_in(window, async move |editor, cx| {
18155 let project_transaction = rename.await?;
18156 Self::open_project_transaction(
18157 &editor,
18158 workspace,
18159 project_transaction,
18160 format!("Rename: {} → {}", old_name, new_name),
18161 cx,
18162 )
18163 .await?;
18164
18165 editor.update(cx, |editor, cx| {
18166 editor.refresh_document_highlights(cx);
18167 })?;
18168 Ok(())
18169 }))
18170 }
18171
18172 fn take_rename(
18173 &mut self,
18174 moving_cursor: bool,
18175 window: &mut Window,
18176 cx: &mut Context<Self>,
18177 ) -> Option<RenameState> {
18178 let rename = self.pending_rename.take()?;
18179 if rename.editor.focus_handle(cx).is_focused(window) {
18180 window.focus(&self.focus_handle);
18181 }
18182
18183 self.remove_blocks(
18184 [rename.block_id].into_iter().collect(),
18185 Some(Autoscroll::fit()),
18186 cx,
18187 );
18188 self.clear_highlights::<Rename>(cx);
18189 self.show_local_selections = true;
18190
18191 if moving_cursor {
18192 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18193 editor
18194 .selections
18195 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18196 .head()
18197 });
18198
18199 // Update the selection to match the position of the selection inside
18200 // the rename editor.
18201 let snapshot = self.buffer.read(cx).read(cx);
18202 let rename_range = rename.range.to_offset(&snapshot);
18203 let cursor_in_editor = snapshot
18204 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18205 .min(rename_range.end);
18206 drop(snapshot);
18207
18208 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18209 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18210 });
18211 } else {
18212 self.refresh_document_highlights(cx);
18213 }
18214
18215 Some(rename)
18216 }
18217
18218 pub fn pending_rename(&self) -> Option<&RenameState> {
18219 self.pending_rename.as_ref()
18220 }
18221
18222 fn format(
18223 &mut self,
18224 _: &Format,
18225 window: &mut Window,
18226 cx: &mut Context<Self>,
18227 ) -> Option<Task<Result<()>>> {
18228 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18229
18230 let project = match &self.project {
18231 Some(project) => project.clone(),
18232 None => return None,
18233 };
18234
18235 Some(self.perform_format(
18236 project,
18237 FormatTrigger::Manual,
18238 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18239 window,
18240 cx,
18241 ))
18242 }
18243
18244 fn format_selections(
18245 &mut self,
18246 _: &FormatSelections,
18247 window: &mut Window,
18248 cx: &mut Context<Self>,
18249 ) -> Option<Task<Result<()>>> {
18250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18251
18252 let project = match &self.project {
18253 Some(project) => project.clone(),
18254 None => return None,
18255 };
18256
18257 let ranges = self
18258 .selections
18259 .all_adjusted(&self.display_snapshot(cx))
18260 .into_iter()
18261 .map(|selection| selection.range())
18262 .collect_vec();
18263
18264 Some(self.perform_format(
18265 project,
18266 FormatTrigger::Manual,
18267 FormatTarget::Ranges(ranges),
18268 window,
18269 cx,
18270 ))
18271 }
18272
18273 fn perform_format(
18274 &mut self,
18275 project: Entity<Project>,
18276 trigger: FormatTrigger,
18277 target: FormatTarget,
18278 window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) -> Task<Result<()>> {
18281 let buffer = self.buffer.clone();
18282 let (buffers, target) = match target {
18283 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18284 FormatTarget::Ranges(selection_ranges) => {
18285 let multi_buffer = buffer.read(cx);
18286 let snapshot = multi_buffer.read(cx);
18287 let mut buffers = HashSet::default();
18288 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18289 BTreeMap::new();
18290 for selection_range in selection_ranges {
18291 for (buffer, buffer_range, _) in
18292 snapshot.range_to_buffer_ranges(selection_range)
18293 {
18294 let buffer_id = buffer.remote_id();
18295 let start = buffer.anchor_before(buffer_range.start);
18296 let end = buffer.anchor_after(buffer_range.end);
18297 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18298 buffer_id_to_ranges
18299 .entry(buffer_id)
18300 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18301 .or_insert_with(|| vec![start..end]);
18302 }
18303 }
18304 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18305 }
18306 };
18307
18308 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18309 let selections_prev = transaction_id_prev
18310 .and_then(|transaction_id_prev| {
18311 // default to selections as they were after the last edit, if we have them,
18312 // instead of how they are now.
18313 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18314 // will take you back to where you made the last edit, instead of staying where you scrolled
18315 self.selection_history
18316 .transaction(transaction_id_prev)
18317 .map(|t| t.0.clone())
18318 })
18319 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18320
18321 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18322 let format = project.update(cx, |project, cx| {
18323 project.format(buffers, target, true, trigger, cx)
18324 });
18325
18326 cx.spawn_in(window, async move |editor, cx| {
18327 let transaction = futures::select_biased! {
18328 transaction = format.log_err().fuse() => transaction,
18329 () = timeout => {
18330 log::warn!("timed out waiting for formatting");
18331 None
18332 }
18333 };
18334
18335 buffer
18336 .update(cx, |buffer, cx| {
18337 if let Some(transaction) = transaction
18338 && !buffer.is_singleton()
18339 {
18340 buffer.push_transaction(&transaction.0, cx);
18341 }
18342 cx.notify();
18343 })
18344 .ok();
18345
18346 if let Some(transaction_id_now) =
18347 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18348 {
18349 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18350 if has_new_transaction {
18351 _ = editor.update(cx, |editor, _| {
18352 editor
18353 .selection_history
18354 .insert_transaction(transaction_id_now, selections_prev);
18355 });
18356 }
18357 }
18358
18359 Ok(())
18360 })
18361 }
18362
18363 fn organize_imports(
18364 &mut self,
18365 _: &OrganizeImports,
18366 window: &mut Window,
18367 cx: &mut Context<Self>,
18368 ) -> Option<Task<Result<()>>> {
18369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18370 let project = match &self.project {
18371 Some(project) => project.clone(),
18372 None => return None,
18373 };
18374 Some(self.perform_code_action_kind(
18375 project,
18376 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18377 window,
18378 cx,
18379 ))
18380 }
18381
18382 fn perform_code_action_kind(
18383 &mut self,
18384 project: Entity<Project>,
18385 kind: CodeActionKind,
18386 window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) -> Task<Result<()>> {
18389 let buffer = self.buffer.clone();
18390 let buffers = buffer.read(cx).all_buffers();
18391 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18392 let apply_action = project.update(cx, |project, cx| {
18393 project.apply_code_action_kind(buffers, kind, true, cx)
18394 });
18395 cx.spawn_in(window, async move |_, cx| {
18396 let transaction = futures::select_biased! {
18397 () = timeout => {
18398 log::warn!("timed out waiting for executing code action");
18399 None
18400 }
18401 transaction = apply_action.log_err().fuse() => transaction,
18402 };
18403 buffer
18404 .update(cx, |buffer, cx| {
18405 // check if we need this
18406 if let Some(transaction) = transaction
18407 && !buffer.is_singleton()
18408 {
18409 buffer.push_transaction(&transaction.0, cx);
18410 }
18411 cx.notify();
18412 })
18413 .ok();
18414 Ok(())
18415 })
18416 }
18417
18418 pub fn restart_language_server(
18419 &mut self,
18420 _: &RestartLanguageServer,
18421 _: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) {
18424 if let Some(project) = self.project.clone() {
18425 self.buffer.update(cx, |multi_buffer, cx| {
18426 project.update(cx, |project, cx| {
18427 project.restart_language_servers_for_buffers(
18428 multi_buffer.all_buffers().into_iter().collect(),
18429 HashSet::default(),
18430 cx,
18431 );
18432 });
18433 })
18434 }
18435 }
18436
18437 pub fn stop_language_server(
18438 &mut self,
18439 _: &StopLanguageServer,
18440 _: &mut Window,
18441 cx: &mut Context<Self>,
18442 ) {
18443 if let Some(project) = self.project.clone() {
18444 self.buffer.update(cx, |multi_buffer, cx| {
18445 project.update(cx, |project, cx| {
18446 project.stop_language_servers_for_buffers(
18447 multi_buffer.all_buffers().into_iter().collect(),
18448 HashSet::default(),
18449 cx,
18450 );
18451 });
18452 });
18453 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18454 }
18455 }
18456
18457 fn cancel_language_server_work(
18458 workspace: &mut Workspace,
18459 _: &actions::CancelLanguageServerWork,
18460 _: &mut Window,
18461 cx: &mut Context<Workspace>,
18462 ) {
18463 let project = workspace.project();
18464 let buffers = workspace
18465 .active_item(cx)
18466 .and_then(|item| item.act_as::<Editor>(cx))
18467 .map_or(HashSet::default(), |editor| {
18468 editor.read(cx).buffer.read(cx).all_buffers()
18469 });
18470 project.update(cx, |project, cx| {
18471 project.cancel_language_server_work_for_buffers(buffers, cx);
18472 });
18473 }
18474
18475 fn show_character_palette(
18476 &mut self,
18477 _: &ShowCharacterPalette,
18478 window: &mut Window,
18479 _: &mut Context<Self>,
18480 ) {
18481 window.show_character_palette();
18482 }
18483
18484 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18485 if !self.diagnostics_enabled() {
18486 return;
18487 }
18488
18489 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18490 let buffer = self.buffer.read(cx).snapshot(cx);
18491 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18492 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18493 let is_valid = buffer
18494 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18495 .any(|entry| {
18496 entry.diagnostic.is_primary
18497 && !entry.range.is_empty()
18498 && entry.range.start == primary_range_start
18499 && entry.diagnostic.message == active_diagnostics.active_message
18500 });
18501
18502 if !is_valid {
18503 self.dismiss_diagnostics(cx);
18504 }
18505 }
18506 }
18507
18508 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18509 match &self.active_diagnostics {
18510 ActiveDiagnostic::Group(group) => Some(group),
18511 _ => None,
18512 }
18513 }
18514
18515 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18516 if !self.diagnostics_enabled() {
18517 return;
18518 }
18519 self.dismiss_diagnostics(cx);
18520 self.active_diagnostics = ActiveDiagnostic::All;
18521 }
18522
18523 fn activate_diagnostics(
18524 &mut self,
18525 buffer_id: BufferId,
18526 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18527 window: &mut Window,
18528 cx: &mut Context<Self>,
18529 ) {
18530 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18531 return;
18532 }
18533 self.dismiss_diagnostics(cx);
18534 let snapshot = self.snapshot(window, cx);
18535 let buffer = self.buffer.read(cx).snapshot(cx);
18536 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18537 return;
18538 };
18539
18540 let diagnostic_group = buffer
18541 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18542 .collect::<Vec<_>>();
18543
18544 let language_registry = self
18545 .project()
18546 .map(|project| project.read(cx).languages().clone());
18547
18548 let blocks = renderer.render_group(
18549 diagnostic_group,
18550 buffer_id,
18551 snapshot,
18552 cx.weak_entity(),
18553 language_registry,
18554 cx,
18555 );
18556
18557 let blocks = self.display_map.update(cx, |display_map, cx| {
18558 display_map.insert_blocks(blocks, cx).into_iter().collect()
18559 });
18560 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18561 active_range: buffer.anchor_before(diagnostic.range.start)
18562 ..buffer.anchor_after(diagnostic.range.end),
18563 active_message: diagnostic.diagnostic.message.clone(),
18564 group_id: diagnostic.diagnostic.group_id,
18565 blocks,
18566 });
18567 cx.notify();
18568 }
18569
18570 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18571 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18572 return;
18573 };
18574
18575 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18576 if let ActiveDiagnostic::Group(group) = prev {
18577 self.display_map.update(cx, |display_map, cx| {
18578 display_map.remove_blocks(group.blocks, cx);
18579 });
18580 cx.notify();
18581 }
18582 }
18583
18584 /// Disable inline diagnostics rendering for this editor.
18585 pub fn disable_inline_diagnostics(&mut self) {
18586 self.inline_diagnostics_enabled = false;
18587 self.inline_diagnostics_update = Task::ready(());
18588 self.inline_diagnostics.clear();
18589 }
18590
18591 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18592 self.diagnostics_enabled = false;
18593 self.dismiss_diagnostics(cx);
18594 self.inline_diagnostics_update = Task::ready(());
18595 self.inline_diagnostics.clear();
18596 }
18597
18598 pub fn disable_word_completions(&mut self) {
18599 self.word_completions_enabled = false;
18600 }
18601
18602 pub fn diagnostics_enabled(&self) -> bool {
18603 self.diagnostics_enabled && self.mode.is_full()
18604 }
18605
18606 pub fn inline_diagnostics_enabled(&self) -> bool {
18607 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18608 }
18609
18610 pub fn show_inline_diagnostics(&self) -> bool {
18611 self.show_inline_diagnostics
18612 }
18613
18614 pub fn toggle_inline_diagnostics(
18615 &mut self,
18616 _: &ToggleInlineDiagnostics,
18617 window: &mut Window,
18618 cx: &mut Context<Editor>,
18619 ) {
18620 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18621 self.refresh_inline_diagnostics(false, window, cx);
18622 }
18623
18624 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18625 self.diagnostics_max_severity = severity;
18626 self.display_map.update(cx, |display_map, _| {
18627 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18628 });
18629 }
18630
18631 pub fn toggle_diagnostics(
18632 &mut self,
18633 _: &ToggleDiagnostics,
18634 window: &mut Window,
18635 cx: &mut Context<Editor>,
18636 ) {
18637 if !self.diagnostics_enabled() {
18638 return;
18639 }
18640
18641 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18642 EditorSettings::get_global(cx)
18643 .diagnostics_max_severity
18644 .filter(|severity| severity != &DiagnosticSeverity::Off)
18645 .unwrap_or(DiagnosticSeverity::Hint)
18646 } else {
18647 DiagnosticSeverity::Off
18648 };
18649 self.set_max_diagnostics_severity(new_severity, cx);
18650 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18651 self.active_diagnostics = ActiveDiagnostic::None;
18652 self.inline_diagnostics_update = Task::ready(());
18653 self.inline_diagnostics.clear();
18654 } else {
18655 self.refresh_inline_diagnostics(false, window, cx);
18656 }
18657
18658 cx.notify();
18659 }
18660
18661 pub fn toggle_minimap(
18662 &mut self,
18663 _: &ToggleMinimap,
18664 window: &mut Window,
18665 cx: &mut Context<Editor>,
18666 ) {
18667 if self.supports_minimap(cx) {
18668 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18669 }
18670 }
18671
18672 fn refresh_inline_diagnostics(
18673 &mut self,
18674 debounce: bool,
18675 window: &mut Window,
18676 cx: &mut Context<Self>,
18677 ) {
18678 let max_severity = ProjectSettings::get_global(cx)
18679 .diagnostics
18680 .inline
18681 .max_severity
18682 .unwrap_or(self.diagnostics_max_severity);
18683
18684 if !self.inline_diagnostics_enabled()
18685 || !self.diagnostics_enabled()
18686 || !self.show_inline_diagnostics
18687 || max_severity == DiagnosticSeverity::Off
18688 {
18689 self.inline_diagnostics_update = Task::ready(());
18690 self.inline_diagnostics.clear();
18691 return;
18692 }
18693
18694 let debounce_ms = ProjectSettings::get_global(cx)
18695 .diagnostics
18696 .inline
18697 .update_debounce_ms;
18698 let debounce = if debounce && debounce_ms > 0 {
18699 Some(Duration::from_millis(debounce_ms))
18700 } else {
18701 None
18702 };
18703 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18704 if let Some(debounce) = debounce {
18705 cx.background_executor().timer(debounce).await;
18706 }
18707 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18708 editor
18709 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18710 .ok()
18711 }) else {
18712 return;
18713 };
18714
18715 let new_inline_diagnostics = cx
18716 .background_spawn(async move {
18717 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18718 for diagnostic_entry in
18719 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18720 {
18721 let message = diagnostic_entry
18722 .diagnostic
18723 .message
18724 .split_once('\n')
18725 .map(|(line, _)| line)
18726 .map(SharedString::new)
18727 .unwrap_or_else(|| {
18728 SharedString::new(&*diagnostic_entry.diagnostic.message)
18729 });
18730 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18731 let (Ok(i) | Err(i)) = inline_diagnostics
18732 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18733 inline_diagnostics.insert(
18734 i,
18735 (
18736 start_anchor,
18737 InlineDiagnostic {
18738 message,
18739 group_id: diagnostic_entry.diagnostic.group_id,
18740 start: diagnostic_entry.range.start.to_point(&snapshot),
18741 is_primary: diagnostic_entry.diagnostic.is_primary,
18742 severity: diagnostic_entry.diagnostic.severity,
18743 },
18744 ),
18745 );
18746 }
18747 inline_diagnostics
18748 })
18749 .await;
18750
18751 editor
18752 .update(cx, |editor, cx| {
18753 editor.inline_diagnostics = new_inline_diagnostics;
18754 cx.notify();
18755 })
18756 .ok();
18757 });
18758 }
18759
18760 fn pull_diagnostics(
18761 &mut self,
18762 buffer_id: Option<BufferId>,
18763 window: &Window,
18764 cx: &mut Context<Self>,
18765 ) -> Option<()> {
18766 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18767 return None;
18768 }
18769 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18770 .diagnostics
18771 .lsp_pull_diagnostics;
18772 if !pull_diagnostics_settings.enabled {
18773 return None;
18774 }
18775 let project = self.project()?.downgrade();
18776
18777 let mut edited_buffer_ids = HashSet::default();
18778 let mut edited_worktree_ids = HashSet::default();
18779 let edited_buffers = match buffer_id {
18780 Some(buffer_id) => {
18781 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18782 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18783 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18784 edited_worktree_ids.insert(worktree_id);
18785 vec![buffer]
18786 }
18787 None => self
18788 .buffer()
18789 .read(cx)
18790 .all_buffers()
18791 .into_iter()
18792 .filter(|buffer| {
18793 let buffer = buffer.read(cx);
18794 match buffer.file().map(|f| f.worktree_id(cx)) {
18795 Some(worktree_id) => {
18796 edited_buffer_ids.insert(buffer.remote_id());
18797 edited_worktree_ids.insert(worktree_id);
18798 true
18799 }
18800 None => false,
18801 }
18802 })
18803 .collect::<Vec<_>>(),
18804 };
18805
18806 if edited_buffers.is_empty() {
18807 self.pull_diagnostics_task = Task::ready(());
18808 self.pull_diagnostics_background_task = Task::ready(());
18809 return None;
18810 }
18811
18812 let mut already_used_buffers = HashSet::default();
18813 let related_open_buffers = self
18814 .workspace
18815 .as_ref()
18816 .and_then(|(workspace, _)| workspace.upgrade())
18817 .into_iter()
18818 .flat_map(|workspace| workspace.read(cx).panes())
18819 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18820 .filter(|editor| editor != &cx.entity())
18821 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18822 .filter(|buffer| {
18823 let buffer = buffer.read(cx);
18824 let buffer_id = buffer.remote_id();
18825 if already_used_buffers.insert(buffer_id) {
18826 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18827 return !edited_buffer_ids.contains(&buffer_id)
18828 && !edited_worktree_ids.contains(&worktree_id);
18829 }
18830 }
18831 false
18832 })
18833 .collect::<Vec<_>>();
18834
18835 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18836 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18837 if buffers.is_empty() {
18838 return Task::ready(());
18839 }
18840 let project_weak = project.clone();
18841 cx.spawn_in(window, async move |_, cx| {
18842 cx.background_executor().timer(delay).await;
18843
18844 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18845 buffers
18846 .into_iter()
18847 .filter_map(|buffer| {
18848 project_weak
18849 .update(cx, |project, cx| {
18850 project.lsp_store().update(cx, |lsp_store, cx| {
18851 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18852 })
18853 })
18854 .ok()
18855 })
18856 .collect::<FuturesUnordered<_>>()
18857 }) else {
18858 return;
18859 };
18860
18861 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18862 if let Err(e) = pull_task {
18863 log::error!("Failed to update project diagnostics: {e:#}");
18864 }
18865 }
18866 })
18867 };
18868
18869 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18870 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18871
18872 Some(())
18873 }
18874
18875 pub fn set_selections_from_remote(
18876 &mut self,
18877 selections: Vec<Selection<Anchor>>,
18878 pending_selection: Option<Selection<Anchor>>,
18879 window: &mut Window,
18880 cx: &mut Context<Self>,
18881 ) {
18882 let old_cursor_position = self.selections.newest_anchor().head();
18883 self.selections
18884 .change_with(&self.display_snapshot(cx), |s| {
18885 s.select_anchors(selections);
18886 if let Some(pending_selection) = pending_selection {
18887 s.set_pending(pending_selection, SelectMode::Character);
18888 } else {
18889 s.clear_pending();
18890 }
18891 });
18892 self.selections_did_change(
18893 false,
18894 &old_cursor_position,
18895 SelectionEffects::default(),
18896 window,
18897 cx,
18898 );
18899 }
18900
18901 pub fn transact(
18902 &mut self,
18903 window: &mut Window,
18904 cx: &mut Context<Self>,
18905 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18906 ) -> Option<TransactionId> {
18907 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18908 this.start_transaction_at(Instant::now(), window, cx);
18909 update(this, window, cx);
18910 this.end_transaction_at(Instant::now(), cx)
18911 })
18912 }
18913
18914 pub fn start_transaction_at(
18915 &mut self,
18916 now: Instant,
18917 window: &mut Window,
18918 cx: &mut Context<Self>,
18919 ) -> Option<TransactionId> {
18920 self.end_selection(window, cx);
18921 if let Some(tx_id) = self
18922 .buffer
18923 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18924 {
18925 self.selection_history
18926 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18927 cx.emit(EditorEvent::TransactionBegun {
18928 transaction_id: tx_id,
18929 });
18930 Some(tx_id)
18931 } else {
18932 None
18933 }
18934 }
18935
18936 pub fn end_transaction_at(
18937 &mut self,
18938 now: Instant,
18939 cx: &mut Context<Self>,
18940 ) -> Option<TransactionId> {
18941 if let Some(transaction_id) = self
18942 .buffer
18943 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18944 {
18945 if let Some((_, end_selections)) =
18946 self.selection_history.transaction_mut(transaction_id)
18947 {
18948 *end_selections = Some(self.selections.disjoint_anchors_arc());
18949 } else {
18950 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18951 }
18952
18953 cx.emit(EditorEvent::Edited { transaction_id });
18954 Some(transaction_id)
18955 } else {
18956 None
18957 }
18958 }
18959
18960 pub fn modify_transaction_selection_history(
18961 &mut self,
18962 transaction_id: TransactionId,
18963 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18964 ) -> bool {
18965 self.selection_history
18966 .transaction_mut(transaction_id)
18967 .map(modify)
18968 .is_some()
18969 }
18970
18971 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18972 if self.selection_mark_mode {
18973 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18974 s.move_with(|_, sel| {
18975 sel.collapse_to(sel.head(), SelectionGoal::None);
18976 });
18977 })
18978 }
18979 self.selection_mark_mode = true;
18980 cx.notify();
18981 }
18982
18983 pub fn swap_selection_ends(
18984 &mut self,
18985 _: &actions::SwapSelectionEnds,
18986 window: &mut Window,
18987 cx: &mut Context<Self>,
18988 ) {
18989 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18990 s.move_with(|_, sel| {
18991 if sel.start != sel.end {
18992 sel.reversed = !sel.reversed
18993 }
18994 });
18995 });
18996 self.request_autoscroll(Autoscroll::newest(), cx);
18997 cx.notify();
18998 }
18999
19000 pub fn toggle_focus(
19001 workspace: &mut Workspace,
19002 _: &actions::ToggleFocus,
19003 window: &mut Window,
19004 cx: &mut Context<Workspace>,
19005 ) {
19006 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19007 return;
19008 };
19009 workspace.activate_item(&item, true, true, window, cx);
19010 }
19011
19012 pub fn toggle_fold(
19013 &mut self,
19014 _: &actions::ToggleFold,
19015 window: &mut Window,
19016 cx: &mut Context<Self>,
19017 ) {
19018 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19019 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19020 let selection = self.selections.newest::<Point>(&display_map);
19021
19022 let range = if selection.is_empty() {
19023 let point = selection.head().to_display_point(&display_map);
19024 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19025 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19026 .to_point(&display_map);
19027 start..end
19028 } else {
19029 selection.range()
19030 };
19031 if display_map.folds_in_range(range).next().is_some() {
19032 self.unfold_lines(&Default::default(), window, cx)
19033 } else {
19034 self.fold(&Default::default(), window, cx)
19035 }
19036 } else {
19037 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19038 let buffer_ids: HashSet<_> = self
19039 .selections
19040 .disjoint_anchor_ranges()
19041 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19042 .collect();
19043
19044 let should_unfold = buffer_ids
19045 .iter()
19046 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19047
19048 for buffer_id in buffer_ids {
19049 if should_unfold {
19050 self.unfold_buffer(buffer_id, cx);
19051 } else {
19052 self.fold_buffer(buffer_id, cx);
19053 }
19054 }
19055 }
19056 }
19057
19058 pub fn toggle_fold_recursive(
19059 &mut self,
19060 _: &actions::ToggleFoldRecursive,
19061 window: &mut Window,
19062 cx: &mut Context<Self>,
19063 ) {
19064 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19065
19066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19067 let range = if selection.is_empty() {
19068 let point = selection.head().to_display_point(&display_map);
19069 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19070 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19071 .to_point(&display_map);
19072 start..end
19073 } else {
19074 selection.range()
19075 };
19076 if display_map.folds_in_range(range).next().is_some() {
19077 self.unfold_recursive(&Default::default(), window, cx)
19078 } else {
19079 self.fold_recursive(&Default::default(), window, cx)
19080 }
19081 }
19082
19083 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19084 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19085 let mut to_fold = Vec::new();
19086 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19087 let selections = self.selections.all_adjusted(&display_map);
19088
19089 for selection in selections {
19090 let range = selection.range().sorted();
19091 let buffer_start_row = range.start.row;
19092
19093 if range.start.row != range.end.row {
19094 let mut found = false;
19095 let mut row = range.start.row;
19096 while row <= range.end.row {
19097 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19098 {
19099 found = true;
19100 row = crease.range().end.row + 1;
19101 to_fold.push(crease);
19102 } else {
19103 row += 1
19104 }
19105 }
19106 if found {
19107 continue;
19108 }
19109 }
19110
19111 for row in (0..=range.start.row).rev() {
19112 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19113 && crease.range().end.row >= buffer_start_row
19114 {
19115 to_fold.push(crease);
19116 if row <= range.start.row {
19117 break;
19118 }
19119 }
19120 }
19121 }
19122
19123 self.fold_creases(to_fold, true, window, cx);
19124 } else {
19125 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19126 let buffer_ids = self
19127 .selections
19128 .disjoint_anchor_ranges()
19129 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19130 .collect::<HashSet<_>>();
19131 for buffer_id in buffer_ids {
19132 self.fold_buffer(buffer_id, cx);
19133 }
19134 }
19135 }
19136
19137 pub fn toggle_fold_all(
19138 &mut self,
19139 _: &actions::ToggleFoldAll,
19140 window: &mut Window,
19141 cx: &mut Context<Self>,
19142 ) {
19143 if self.buffer.read(cx).is_singleton() {
19144 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19145 let has_folds = display_map
19146 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19147 .next()
19148 .is_some();
19149
19150 if has_folds {
19151 self.unfold_all(&actions::UnfoldAll, window, cx);
19152 } else {
19153 self.fold_all(&actions::FoldAll, window, cx);
19154 }
19155 } else {
19156 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19157 let should_unfold = buffer_ids
19158 .iter()
19159 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19160
19161 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19162 editor
19163 .update_in(cx, |editor, _, cx| {
19164 for buffer_id in buffer_ids {
19165 if should_unfold {
19166 editor.unfold_buffer(buffer_id, cx);
19167 } else {
19168 editor.fold_buffer(buffer_id, cx);
19169 }
19170 }
19171 })
19172 .ok();
19173 });
19174 }
19175 }
19176
19177 fn fold_at_level(
19178 &mut self,
19179 fold_at: &FoldAtLevel,
19180 window: &mut Window,
19181 cx: &mut Context<Self>,
19182 ) {
19183 if !self.buffer.read(cx).is_singleton() {
19184 return;
19185 }
19186
19187 let fold_at_level = fold_at.0;
19188 let snapshot = self.buffer.read(cx).snapshot(cx);
19189 let mut to_fold = Vec::new();
19190 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19191
19192 let row_ranges_to_keep: Vec<Range<u32>> = self
19193 .selections
19194 .all::<Point>(&self.display_snapshot(cx))
19195 .into_iter()
19196 .map(|sel| sel.start.row..sel.end.row)
19197 .collect();
19198
19199 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19200 while start_row < end_row {
19201 match self
19202 .snapshot(window, cx)
19203 .crease_for_buffer_row(MultiBufferRow(start_row))
19204 {
19205 Some(crease) => {
19206 let nested_start_row = crease.range().start.row + 1;
19207 let nested_end_row = crease.range().end.row;
19208
19209 if current_level < fold_at_level {
19210 stack.push((nested_start_row, nested_end_row, current_level + 1));
19211 } else if current_level == fold_at_level {
19212 // Fold iff there is no selection completely contained within the fold region
19213 if !row_ranges_to_keep.iter().any(|selection| {
19214 selection.end >= nested_start_row
19215 && selection.start <= nested_end_row
19216 }) {
19217 to_fold.push(crease);
19218 }
19219 }
19220
19221 start_row = nested_end_row + 1;
19222 }
19223 None => start_row += 1,
19224 }
19225 }
19226 }
19227
19228 self.fold_creases(to_fold, true, window, cx);
19229 }
19230
19231 pub fn fold_at_level_1(
19232 &mut self,
19233 _: &actions::FoldAtLevel1,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19238 }
19239
19240 pub fn fold_at_level_2(
19241 &mut self,
19242 _: &actions::FoldAtLevel2,
19243 window: &mut Window,
19244 cx: &mut Context<Self>,
19245 ) {
19246 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19247 }
19248
19249 pub fn fold_at_level_3(
19250 &mut self,
19251 _: &actions::FoldAtLevel3,
19252 window: &mut Window,
19253 cx: &mut Context<Self>,
19254 ) {
19255 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19256 }
19257
19258 pub fn fold_at_level_4(
19259 &mut self,
19260 _: &actions::FoldAtLevel4,
19261 window: &mut Window,
19262 cx: &mut Context<Self>,
19263 ) {
19264 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19265 }
19266
19267 pub fn fold_at_level_5(
19268 &mut self,
19269 _: &actions::FoldAtLevel5,
19270 window: &mut Window,
19271 cx: &mut Context<Self>,
19272 ) {
19273 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19274 }
19275
19276 pub fn fold_at_level_6(
19277 &mut self,
19278 _: &actions::FoldAtLevel6,
19279 window: &mut Window,
19280 cx: &mut Context<Self>,
19281 ) {
19282 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19283 }
19284
19285 pub fn fold_at_level_7(
19286 &mut self,
19287 _: &actions::FoldAtLevel7,
19288 window: &mut Window,
19289 cx: &mut Context<Self>,
19290 ) {
19291 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19292 }
19293
19294 pub fn fold_at_level_8(
19295 &mut self,
19296 _: &actions::FoldAtLevel8,
19297 window: &mut Window,
19298 cx: &mut Context<Self>,
19299 ) {
19300 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19301 }
19302
19303 pub fn fold_at_level_9(
19304 &mut self,
19305 _: &actions::FoldAtLevel9,
19306 window: &mut Window,
19307 cx: &mut Context<Self>,
19308 ) {
19309 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19310 }
19311
19312 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19313 if self.buffer.read(cx).is_singleton() {
19314 let mut fold_ranges = Vec::new();
19315 let snapshot = self.buffer.read(cx).snapshot(cx);
19316
19317 for row in 0..snapshot.max_row().0 {
19318 if let Some(foldable_range) = self
19319 .snapshot(window, cx)
19320 .crease_for_buffer_row(MultiBufferRow(row))
19321 {
19322 fold_ranges.push(foldable_range);
19323 }
19324 }
19325
19326 self.fold_creases(fold_ranges, true, window, cx);
19327 } else {
19328 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19329 editor
19330 .update_in(cx, |editor, _, cx| {
19331 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19332 editor.fold_buffer(buffer_id, cx);
19333 }
19334 })
19335 .ok();
19336 });
19337 }
19338 }
19339
19340 pub fn fold_function_bodies(
19341 &mut self,
19342 _: &actions::FoldFunctionBodies,
19343 window: &mut Window,
19344 cx: &mut Context<Self>,
19345 ) {
19346 let snapshot = self.buffer.read(cx).snapshot(cx);
19347
19348 let ranges = snapshot
19349 .text_object_ranges(
19350 MultiBufferOffset(0)..snapshot.len(),
19351 TreeSitterOptions::default(),
19352 )
19353 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19354 .collect::<Vec<_>>();
19355
19356 let creases = ranges
19357 .into_iter()
19358 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19359 .collect();
19360
19361 self.fold_creases(creases, true, window, cx);
19362 }
19363
19364 pub fn fold_recursive(
19365 &mut self,
19366 _: &actions::FoldRecursive,
19367 window: &mut Window,
19368 cx: &mut Context<Self>,
19369 ) {
19370 let mut to_fold = Vec::new();
19371 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19372 let selections = self.selections.all_adjusted(&display_map);
19373
19374 for selection in selections {
19375 let range = selection.range().sorted();
19376 let buffer_start_row = range.start.row;
19377
19378 if range.start.row != range.end.row {
19379 let mut found = false;
19380 for row in range.start.row..=range.end.row {
19381 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19382 found = true;
19383 to_fold.push(crease);
19384 }
19385 }
19386 if found {
19387 continue;
19388 }
19389 }
19390
19391 for row in (0..=range.start.row).rev() {
19392 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19393 if crease.range().end.row >= buffer_start_row {
19394 to_fold.push(crease);
19395 } else {
19396 break;
19397 }
19398 }
19399 }
19400 }
19401
19402 self.fold_creases(to_fold, true, window, cx);
19403 }
19404
19405 pub fn fold_at(
19406 &mut self,
19407 buffer_row: MultiBufferRow,
19408 window: &mut Window,
19409 cx: &mut Context<Self>,
19410 ) {
19411 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19412
19413 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19414 let autoscroll = self
19415 .selections
19416 .all::<Point>(&display_map)
19417 .iter()
19418 .any(|selection| crease.range().overlaps(&selection.range()));
19419
19420 self.fold_creases(vec![crease], autoscroll, window, cx);
19421 }
19422 }
19423
19424 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19425 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19427 let buffer = display_map.buffer_snapshot();
19428 let selections = self.selections.all::<Point>(&display_map);
19429 let ranges = selections
19430 .iter()
19431 .map(|s| {
19432 let range = s.display_range(&display_map).sorted();
19433 let mut start = range.start.to_point(&display_map);
19434 let mut end = range.end.to_point(&display_map);
19435 start.column = 0;
19436 end.column = buffer.line_len(MultiBufferRow(end.row));
19437 start..end
19438 })
19439 .collect::<Vec<_>>();
19440
19441 self.unfold_ranges(&ranges, true, true, cx);
19442 } else {
19443 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19444 let buffer_ids = self
19445 .selections
19446 .disjoint_anchor_ranges()
19447 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19448 .collect::<HashSet<_>>();
19449 for buffer_id in buffer_ids {
19450 self.unfold_buffer(buffer_id, cx);
19451 }
19452 }
19453 }
19454
19455 pub fn unfold_recursive(
19456 &mut self,
19457 _: &UnfoldRecursive,
19458 _window: &mut Window,
19459 cx: &mut Context<Self>,
19460 ) {
19461 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19462 let selections = self.selections.all::<Point>(&display_map);
19463 let ranges = selections
19464 .iter()
19465 .map(|s| {
19466 let mut range = s.display_range(&display_map).sorted();
19467 *range.start.column_mut() = 0;
19468 *range.end.column_mut() = display_map.line_len(range.end.row());
19469 let start = range.start.to_point(&display_map);
19470 let end = range.end.to_point(&display_map);
19471 start..end
19472 })
19473 .collect::<Vec<_>>();
19474
19475 self.unfold_ranges(&ranges, true, true, cx);
19476 }
19477
19478 pub fn unfold_at(
19479 &mut self,
19480 buffer_row: MultiBufferRow,
19481 _window: &mut Window,
19482 cx: &mut Context<Self>,
19483 ) {
19484 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19485
19486 let intersection_range = Point::new(buffer_row.0, 0)
19487 ..Point::new(
19488 buffer_row.0,
19489 display_map.buffer_snapshot().line_len(buffer_row),
19490 );
19491
19492 let autoscroll = self
19493 .selections
19494 .all::<Point>(&display_map)
19495 .iter()
19496 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19497
19498 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19499 }
19500
19501 pub fn unfold_all(
19502 &mut self,
19503 _: &actions::UnfoldAll,
19504 _window: &mut Window,
19505 cx: &mut Context<Self>,
19506 ) {
19507 if self.buffer.read(cx).is_singleton() {
19508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19509 self.unfold_ranges(
19510 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19511 true,
19512 true,
19513 cx,
19514 );
19515 } else {
19516 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19517 editor
19518 .update(cx, |editor, cx| {
19519 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19520 editor.unfold_buffer(buffer_id, cx);
19521 }
19522 })
19523 .ok();
19524 });
19525 }
19526 }
19527
19528 pub fn fold_selected_ranges(
19529 &mut self,
19530 _: &FoldSelectedRanges,
19531 window: &mut Window,
19532 cx: &mut Context<Self>,
19533 ) {
19534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19535 let selections = self.selections.all_adjusted(&display_map);
19536 let ranges = selections
19537 .into_iter()
19538 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19539 .collect::<Vec<_>>();
19540 self.fold_creases(ranges, true, window, cx);
19541 }
19542
19543 pub fn fold_ranges<T: ToOffset + Clone>(
19544 &mut self,
19545 ranges: Vec<Range<T>>,
19546 auto_scroll: bool,
19547 window: &mut Window,
19548 cx: &mut Context<Self>,
19549 ) {
19550 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19551 let ranges = ranges
19552 .into_iter()
19553 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19554 .collect::<Vec<_>>();
19555 self.fold_creases(ranges, auto_scroll, window, cx);
19556 }
19557
19558 pub fn fold_creases<T: ToOffset + Clone>(
19559 &mut self,
19560 creases: Vec<Crease<T>>,
19561 auto_scroll: bool,
19562 _window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) {
19565 if creases.is_empty() {
19566 return;
19567 }
19568
19569 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19570
19571 if auto_scroll {
19572 self.request_autoscroll(Autoscroll::fit(), cx);
19573 }
19574
19575 cx.notify();
19576
19577 self.scrollbar_marker_state.dirty = true;
19578 self.folds_did_change(cx);
19579 }
19580
19581 /// Removes any folds whose ranges intersect any of the given ranges.
19582 pub fn unfold_ranges<T: ToOffset + Clone>(
19583 &mut self,
19584 ranges: &[Range<T>],
19585 inclusive: bool,
19586 auto_scroll: bool,
19587 cx: &mut Context<Self>,
19588 ) {
19589 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19590 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19591 });
19592 self.folds_did_change(cx);
19593 }
19594
19595 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19596 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19597 return;
19598 }
19599
19600 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19601 self.display_map.update(cx, |display_map, cx| {
19602 display_map.fold_buffers([buffer_id], cx)
19603 });
19604
19605 let snapshot = self.display_snapshot(cx);
19606 self.selections.change_with(&snapshot, |selections| {
19607 selections.remove_selections_from_buffer(buffer_id);
19608 });
19609
19610 cx.emit(EditorEvent::BufferFoldToggled {
19611 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19612 folded: true,
19613 });
19614 cx.notify();
19615 }
19616
19617 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19618 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19619 return;
19620 }
19621 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19622 self.display_map.update(cx, |display_map, cx| {
19623 display_map.unfold_buffers([buffer_id], cx);
19624 });
19625 cx.emit(EditorEvent::BufferFoldToggled {
19626 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19627 folded: false,
19628 });
19629 cx.notify();
19630 }
19631
19632 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19633 self.display_map.read(cx).is_buffer_folded(buffer)
19634 }
19635
19636 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19637 self.display_map.read(cx).folded_buffers()
19638 }
19639
19640 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19641 self.display_map.update(cx, |display_map, cx| {
19642 display_map.disable_header_for_buffer(buffer_id, cx);
19643 });
19644 cx.notify();
19645 }
19646
19647 /// Removes any folds with the given ranges.
19648 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19649 &mut self,
19650 ranges: &[Range<T>],
19651 type_id: TypeId,
19652 auto_scroll: bool,
19653 cx: &mut Context<Self>,
19654 ) {
19655 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19656 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19657 });
19658 self.folds_did_change(cx);
19659 }
19660
19661 fn remove_folds_with<T: ToOffset + Clone>(
19662 &mut self,
19663 ranges: &[Range<T>],
19664 auto_scroll: bool,
19665 cx: &mut Context<Self>,
19666 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19667 ) {
19668 if ranges.is_empty() {
19669 return;
19670 }
19671
19672 let mut buffers_affected = HashSet::default();
19673 let multi_buffer = self.buffer().read(cx);
19674 for range in ranges {
19675 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19676 buffers_affected.insert(buffer.read(cx).remote_id());
19677 };
19678 }
19679
19680 self.display_map.update(cx, update);
19681
19682 if auto_scroll {
19683 self.request_autoscroll(Autoscroll::fit(), cx);
19684 }
19685
19686 cx.notify();
19687 self.scrollbar_marker_state.dirty = true;
19688 self.active_indent_guides_state.dirty = true;
19689 }
19690
19691 pub fn update_renderer_widths(
19692 &mut self,
19693 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19694 cx: &mut Context<Self>,
19695 ) -> bool {
19696 self.display_map
19697 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19698 }
19699
19700 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19701 self.display_map.read(cx).fold_placeholder.clone()
19702 }
19703
19704 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19705 self.use_base_text_line_numbers = show;
19706 }
19707
19708 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19709 self.buffer.update(cx, |buffer, cx| {
19710 buffer.set_all_diff_hunks_expanded(cx);
19711 });
19712 }
19713
19714 pub fn expand_all_diff_hunks(
19715 &mut self,
19716 _: &ExpandAllDiffHunks,
19717 _window: &mut Window,
19718 cx: &mut Context<Self>,
19719 ) {
19720 self.buffer.update(cx, |buffer, cx| {
19721 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19722 });
19723 }
19724
19725 pub fn collapse_all_diff_hunks(
19726 &mut self,
19727 _: &CollapseAllDiffHunks,
19728 _window: &mut Window,
19729 cx: &mut Context<Self>,
19730 ) {
19731 self.buffer.update(cx, |buffer, cx| {
19732 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19733 });
19734 }
19735
19736 pub fn toggle_selected_diff_hunks(
19737 &mut self,
19738 _: &ToggleSelectedDiffHunks,
19739 _window: &mut Window,
19740 cx: &mut Context<Self>,
19741 ) {
19742 let ranges: Vec<_> = self
19743 .selections
19744 .disjoint_anchors()
19745 .iter()
19746 .map(|s| s.range())
19747 .collect();
19748 self.toggle_diff_hunks_in_ranges(ranges, cx);
19749 }
19750
19751 pub fn diff_hunks_in_ranges<'a>(
19752 &'a self,
19753 ranges: &'a [Range<Anchor>],
19754 buffer: &'a MultiBufferSnapshot,
19755 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19756 ranges.iter().flat_map(move |range| {
19757 let end_excerpt_id = range.end.excerpt_id;
19758 let range = range.to_point(buffer);
19759 let mut peek_end = range.end;
19760 if range.end.row < buffer.max_row().0 {
19761 peek_end = Point::new(range.end.row + 1, 0);
19762 }
19763 buffer
19764 .diff_hunks_in_range(range.start..peek_end)
19765 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19766 })
19767 }
19768
19769 pub fn has_stageable_diff_hunks_in_ranges(
19770 &self,
19771 ranges: &[Range<Anchor>],
19772 snapshot: &MultiBufferSnapshot,
19773 ) -> bool {
19774 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19775 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19776 }
19777
19778 pub fn toggle_staged_selected_diff_hunks(
19779 &mut self,
19780 _: &::git::ToggleStaged,
19781 _: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 let snapshot = self.buffer.read(cx).snapshot(cx);
19785 let ranges: Vec<_> = self
19786 .selections
19787 .disjoint_anchors()
19788 .iter()
19789 .map(|s| s.range())
19790 .collect();
19791 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19792 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19793 }
19794
19795 pub fn set_render_diff_hunk_controls(
19796 &mut self,
19797 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19798 cx: &mut Context<Self>,
19799 ) {
19800 self.render_diff_hunk_controls = render_diff_hunk_controls;
19801 cx.notify();
19802 }
19803
19804 pub fn stage_and_next(
19805 &mut self,
19806 _: &::git::StageAndNext,
19807 window: &mut Window,
19808 cx: &mut Context<Self>,
19809 ) {
19810 self.do_stage_or_unstage_and_next(true, window, cx);
19811 }
19812
19813 pub fn unstage_and_next(
19814 &mut self,
19815 _: &::git::UnstageAndNext,
19816 window: &mut Window,
19817 cx: &mut Context<Self>,
19818 ) {
19819 self.do_stage_or_unstage_and_next(false, window, cx);
19820 }
19821
19822 pub fn stage_or_unstage_diff_hunks(
19823 &mut self,
19824 stage: bool,
19825 ranges: Vec<Range<Anchor>>,
19826 cx: &mut Context<Self>,
19827 ) {
19828 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19829 cx.spawn(async move |this, cx| {
19830 task.await?;
19831 this.update(cx, |this, cx| {
19832 let snapshot = this.buffer.read(cx).snapshot(cx);
19833 let chunk_by = this
19834 .diff_hunks_in_ranges(&ranges, &snapshot)
19835 .chunk_by(|hunk| hunk.buffer_id);
19836 for (buffer_id, hunks) in &chunk_by {
19837 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19838 }
19839 })
19840 })
19841 .detach_and_log_err(cx);
19842 }
19843
19844 fn save_buffers_for_ranges_if_needed(
19845 &mut self,
19846 ranges: &[Range<Anchor>],
19847 cx: &mut Context<Editor>,
19848 ) -> Task<Result<()>> {
19849 let multibuffer = self.buffer.read(cx);
19850 let snapshot = multibuffer.read(cx);
19851 let buffer_ids: HashSet<_> = ranges
19852 .iter()
19853 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19854 .collect();
19855 drop(snapshot);
19856
19857 let mut buffers = HashSet::default();
19858 for buffer_id in buffer_ids {
19859 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19860 let buffer = buffer_entity.read(cx);
19861 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19862 {
19863 buffers.insert(buffer_entity);
19864 }
19865 }
19866 }
19867
19868 if let Some(project) = &self.project {
19869 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19870 } else {
19871 Task::ready(Ok(()))
19872 }
19873 }
19874
19875 fn do_stage_or_unstage_and_next(
19876 &mut self,
19877 stage: bool,
19878 window: &mut Window,
19879 cx: &mut Context<Self>,
19880 ) {
19881 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19882
19883 if ranges.iter().any(|range| range.start != range.end) {
19884 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19885 return;
19886 }
19887
19888 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19889 let snapshot = self.snapshot(window, cx);
19890 let position = self
19891 .selections
19892 .newest::<Point>(&snapshot.display_snapshot)
19893 .head();
19894 let mut row = snapshot
19895 .buffer_snapshot()
19896 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19897 .find(|hunk| hunk.row_range.start.0 > position.row)
19898 .map(|hunk| hunk.row_range.start);
19899
19900 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19901 // Outside of the project diff editor, wrap around to the beginning.
19902 if !all_diff_hunks_expanded {
19903 row = row.or_else(|| {
19904 snapshot
19905 .buffer_snapshot()
19906 .diff_hunks_in_range(Point::zero()..position)
19907 .find(|hunk| hunk.row_range.end.0 < position.row)
19908 .map(|hunk| hunk.row_range.start)
19909 });
19910 }
19911
19912 if let Some(row) = row {
19913 let destination = Point::new(row.0, 0);
19914 let autoscroll = Autoscroll::center();
19915
19916 self.unfold_ranges(&[destination..destination], false, false, cx);
19917 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19918 s.select_ranges([destination..destination]);
19919 });
19920 }
19921 }
19922
19923 fn do_stage_or_unstage(
19924 &self,
19925 stage: bool,
19926 buffer_id: BufferId,
19927 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19928 cx: &mut App,
19929 ) -> Option<()> {
19930 let project = self.project()?;
19931 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19932 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19933 let buffer_snapshot = buffer.read(cx).snapshot();
19934 let file_exists = buffer_snapshot
19935 .file()
19936 .is_some_and(|file| file.disk_state().exists());
19937 diff.update(cx, |diff, cx| {
19938 diff.stage_or_unstage_hunks(
19939 stage,
19940 &hunks
19941 .map(|hunk| buffer_diff::DiffHunk {
19942 buffer_range: hunk.buffer_range,
19943 // We don't need to pass in word diffs here because they're only used for rendering and
19944 // this function changes internal state
19945 base_word_diffs: Vec::default(),
19946 buffer_word_diffs: Vec::default(),
19947 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19948 ..hunk.diff_base_byte_range.end.0,
19949 secondary_status: hunk.secondary_status,
19950 range: Point::zero()..Point::zero(), // unused
19951 })
19952 .collect::<Vec<_>>(),
19953 &buffer_snapshot,
19954 file_exists,
19955 cx,
19956 )
19957 });
19958 None
19959 }
19960
19961 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19962 let ranges: Vec<_> = self
19963 .selections
19964 .disjoint_anchors()
19965 .iter()
19966 .map(|s| s.range())
19967 .collect();
19968 self.buffer
19969 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19970 }
19971
19972 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19973 self.buffer.update(cx, |buffer, cx| {
19974 let ranges = vec![Anchor::min()..Anchor::max()];
19975 if !buffer.all_diff_hunks_expanded()
19976 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19977 {
19978 buffer.collapse_diff_hunks(ranges, cx);
19979 true
19980 } else {
19981 false
19982 }
19983 })
19984 }
19985
19986 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19987 if self.buffer.read(cx).all_diff_hunks_expanded() {
19988 return true;
19989 }
19990 let ranges = vec![Anchor::min()..Anchor::max()];
19991 self.buffer
19992 .read(cx)
19993 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19994 }
19995
19996 fn toggle_diff_hunks_in_ranges(
19997 &mut self,
19998 ranges: Vec<Range<Anchor>>,
19999 cx: &mut Context<Editor>,
20000 ) {
20001 self.buffer.update(cx, |buffer, cx| {
20002 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20003 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20004 })
20005 }
20006
20007 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20008 self.buffer.update(cx, |buffer, cx| {
20009 let snapshot = buffer.snapshot(cx);
20010 let excerpt_id = range.end.excerpt_id;
20011 let point_range = range.to_point(&snapshot);
20012 let expand = !buffer.single_hunk_is_expanded(range, cx);
20013 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20014 })
20015 }
20016
20017 pub(crate) fn apply_all_diff_hunks(
20018 &mut self,
20019 _: &ApplyAllDiffHunks,
20020 window: &mut Window,
20021 cx: &mut Context<Self>,
20022 ) {
20023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20024
20025 let buffers = self.buffer.read(cx).all_buffers();
20026 for branch_buffer in buffers {
20027 branch_buffer.update(cx, |branch_buffer, cx| {
20028 branch_buffer.merge_into_base(Vec::new(), cx);
20029 });
20030 }
20031
20032 if let Some(project) = self.project.clone() {
20033 self.save(
20034 SaveOptions {
20035 format: true,
20036 autosave: false,
20037 },
20038 project,
20039 window,
20040 cx,
20041 )
20042 .detach_and_log_err(cx);
20043 }
20044 }
20045
20046 pub(crate) fn apply_selected_diff_hunks(
20047 &mut self,
20048 _: &ApplyDiffHunk,
20049 window: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20053 let snapshot = self.snapshot(window, cx);
20054 let hunks = snapshot.hunks_for_ranges(
20055 self.selections
20056 .all(&snapshot.display_snapshot)
20057 .into_iter()
20058 .map(|selection| selection.range()),
20059 );
20060 let mut ranges_by_buffer = HashMap::default();
20061 self.transact(window, cx, |editor, _window, cx| {
20062 for hunk in hunks {
20063 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20064 ranges_by_buffer
20065 .entry(buffer.clone())
20066 .or_insert_with(Vec::new)
20067 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20068 }
20069 }
20070
20071 for (buffer, ranges) in ranges_by_buffer {
20072 buffer.update(cx, |buffer, cx| {
20073 buffer.merge_into_base(ranges, cx);
20074 });
20075 }
20076 });
20077
20078 if let Some(project) = self.project.clone() {
20079 self.save(
20080 SaveOptions {
20081 format: true,
20082 autosave: false,
20083 },
20084 project,
20085 window,
20086 cx,
20087 )
20088 .detach_and_log_err(cx);
20089 }
20090 }
20091
20092 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20093 if hovered != self.gutter_hovered {
20094 self.gutter_hovered = hovered;
20095 cx.notify();
20096 }
20097 }
20098
20099 pub fn insert_blocks(
20100 &mut self,
20101 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20102 autoscroll: Option<Autoscroll>,
20103 cx: &mut Context<Self>,
20104 ) -> Vec<CustomBlockId> {
20105 let blocks = self
20106 .display_map
20107 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20108 if let Some(autoscroll) = autoscroll {
20109 self.request_autoscroll(autoscroll, cx);
20110 }
20111 cx.notify();
20112 blocks
20113 }
20114
20115 pub fn resize_blocks(
20116 &mut self,
20117 heights: HashMap<CustomBlockId, u32>,
20118 autoscroll: Option<Autoscroll>,
20119 cx: &mut Context<Self>,
20120 ) {
20121 self.display_map
20122 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20123 if let Some(autoscroll) = autoscroll {
20124 self.request_autoscroll(autoscroll, cx);
20125 }
20126 cx.notify();
20127 }
20128
20129 pub fn replace_blocks(
20130 &mut self,
20131 renderers: HashMap<CustomBlockId, RenderBlock>,
20132 autoscroll: Option<Autoscroll>,
20133 cx: &mut Context<Self>,
20134 ) {
20135 self.display_map
20136 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20137 if let Some(autoscroll) = autoscroll {
20138 self.request_autoscroll(autoscroll, cx);
20139 }
20140 cx.notify();
20141 }
20142
20143 pub fn remove_blocks(
20144 &mut self,
20145 block_ids: HashSet<CustomBlockId>,
20146 autoscroll: Option<Autoscroll>,
20147 cx: &mut Context<Self>,
20148 ) {
20149 self.display_map.update(cx, |display_map, cx| {
20150 display_map.remove_blocks(block_ids, cx)
20151 });
20152 if let Some(autoscroll) = autoscroll {
20153 self.request_autoscroll(autoscroll, cx);
20154 }
20155 cx.notify();
20156 }
20157
20158 pub fn row_for_block(
20159 &self,
20160 block_id: CustomBlockId,
20161 cx: &mut Context<Self>,
20162 ) -> Option<DisplayRow> {
20163 self.display_map
20164 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20165 }
20166
20167 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20168 self.focused_block = Some(focused_block);
20169 }
20170
20171 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20172 self.focused_block.take()
20173 }
20174
20175 pub fn insert_creases(
20176 &mut self,
20177 creases: impl IntoIterator<Item = Crease<Anchor>>,
20178 cx: &mut Context<Self>,
20179 ) -> Vec<CreaseId> {
20180 self.display_map
20181 .update(cx, |map, cx| map.insert_creases(creases, cx))
20182 }
20183
20184 pub fn remove_creases(
20185 &mut self,
20186 ids: impl IntoIterator<Item = CreaseId>,
20187 cx: &mut Context<Self>,
20188 ) -> Vec<(CreaseId, Range<Anchor>)> {
20189 self.display_map
20190 .update(cx, |map, cx| map.remove_creases(ids, cx))
20191 }
20192
20193 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20194 self.display_map
20195 .update(cx, |map, cx| map.snapshot(cx))
20196 .longest_row()
20197 }
20198
20199 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20200 self.display_map
20201 .update(cx, |map, cx| map.snapshot(cx))
20202 .max_point()
20203 }
20204
20205 pub fn text(&self, cx: &App) -> String {
20206 self.buffer.read(cx).read(cx).text()
20207 }
20208
20209 pub fn is_empty(&self, cx: &App) -> bool {
20210 self.buffer.read(cx).read(cx).is_empty()
20211 }
20212
20213 pub fn text_option(&self, cx: &App) -> Option<String> {
20214 let text = self.text(cx);
20215 let text = text.trim();
20216
20217 if text.is_empty() {
20218 return None;
20219 }
20220
20221 Some(text.to_string())
20222 }
20223
20224 pub fn set_text(
20225 &mut self,
20226 text: impl Into<Arc<str>>,
20227 window: &mut Window,
20228 cx: &mut Context<Self>,
20229 ) {
20230 self.transact(window, cx, |this, _, cx| {
20231 this.buffer
20232 .read(cx)
20233 .as_singleton()
20234 .expect("you can only call set_text on editors for singleton buffers")
20235 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20236 });
20237 }
20238
20239 pub fn display_text(&self, cx: &mut App) -> String {
20240 self.display_map
20241 .update(cx, |map, cx| map.snapshot(cx))
20242 .text()
20243 }
20244
20245 fn create_minimap(
20246 &self,
20247 minimap_settings: MinimapSettings,
20248 window: &mut Window,
20249 cx: &mut Context<Self>,
20250 ) -> Option<Entity<Self>> {
20251 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20252 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20253 }
20254
20255 fn initialize_new_minimap(
20256 &self,
20257 minimap_settings: MinimapSettings,
20258 window: &mut Window,
20259 cx: &mut Context<Self>,
20260 ) -> Entity<Self> {
20261 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20262
20263 let mut minimap = Editor::new_internal(
20264 EditorMode::Minimap {
20265 parent: cx.weak_entity(),
20266 },
20267 self.buffer.clone(),
20268 None,
20269 Some(self.display_map.clone()),
20270 window,
20271 cx,
20272 );
20273 minimap.scroll_manager.clone_state(&self.scroll_manager);
20274 minimap.set_text_style_refinement(TextStyleRefinement {
20275 font_size: Some(MINIMAP_FONT_SIZE),
20276 font_weight: Some(MINIMAP_FONT_WEIGHT),
20277 ..Default::default()
20278 });
20279 minimap.update_minimap_configuration(minimap_settings, cx);
20280 cx.new(|_| minimap)
20281 }
20282
20283 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20284 let current_line_highlight = minimap_settings
20285 .current_line_highlight
20286 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20287 self.set_current_line_highlight(Some(current_line_highlight));
20288 }
20289
20290 pub fn minimap(&self) -> Option<&Entity<Self>> {
20291 self.minimap
20292 .as_ref()
20293 .filter(|_| self.minimap_visibility.visible())
20294 }
20295
20296 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20297 let mut wrap_guides = smallvec![];
20298
20299 if self.show_wrap_guides == Some(false) {
20300 return wrap_guides;
20301 }
20302
20303 let settings = self.buffer.read(cx).language_settings(cx);
20304 if settings.show_wrap_guides {
20305 match self.soft_wrap_mode(cx) {
20306 SoftWrap::Column(soft_wrap) => {
20307 wrap_guides.push((soft_wrap as usize, true));
20308 }
20309 SoftWrap::Bounded(soft_wrap) => {
20310 wrap_guides.push((soft_wrap as usize, true));
20311 }
20312 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20313 }
20314 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20315 }
20316
20317 wrap_guides
20318 }
20319
20320 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20321 let settings = self.buffer.read(cx).language_settings(cx);
20322 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20323 match mode {
20324 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20325 SoftWrap::None
20326 }
20327 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20328 language_settings::SoftWrap::PreferredLineLength => {
20329 SoftWrap::Column(settings.preferred_line_length)
20330 }
20331 language_settings::SoftWrap::Bounded => {
20332 SoftWrap::Bounded(settings.preferred_line_length)
20333 }
20334 }
20335 }
20336
20337 pub fn set_soft_wrap_mode(
20338 &mut self,
20339 mode: language_settings::SoftWrap,
20340
20341 cx: &mut Context<Self>,
20342 ) {
20343 self.soft_wrap_mode_override = Some(mode);
20344 cx.notify();
20345 }
20346
20347 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20348 self.hard_wrap = hard_wrap;
20349 cx.notify();
20350 }
20351
20352 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20353 self.text_style_refinement = Some(style);
20354 }
20355
20356 /// called by the Element so we know what style we were most recently rendered with.
20357 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20358 // We intentionally do not inform the display map about the minimap style
20359 // so that wrapping is not recalculated and stays consistent for the editor
20360 // and its linked minimap.
20361 if !self.mode.is_minimap() {
20362 let font = style.text.font();
20363 let font_size = style.text.font_size.to_pixels(window.rem_size());
20364 let display_map = self
20365 .placeholder_display_map
20366 .as_ref()
20367 .filter(|_| self.is_empty(cx))
20368 .unwrap_or(&self.display_map);
20369
20370 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20371 }
20372 self.style = Some(style);
20373 }
20374
20375 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20376 if self.style.is_none() {
20377 self.style = Some(self.create_style(cx));
20378 }
20379 self.style.as_ref().unwrap()
20380 }
20381
20382 // Called by the element. This method is not designed to be called outside of the editor
20383 // element's layout code because it does not notify when rewrapping is computed synchronously.
20384 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20385 if self.is_empty(cx) {
20386 self.placeholder_display_map
20387 .as_ref()
20388 .map_or(false, |display_map| {
20389 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20390 })
20391 } else {
20392 self.display_map
20393 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20394 }
20395 }
20396
20397 pub fn set_soft_wrap(&mut self) {
20398 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20399 }
20400
20401 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20402 if self.soft_wrap_mode_override.is_some() {
20403 self.soft_wrap_mode_override.take();
20404 } else {
20405 let soft_wrap = match self.soft_wrap_mode(cx) {
20406 SoftWrap::GitDiff => return,
20407 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20408 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20409 language_settings::SoftWrap::None
20410 }
20411 };
20412 self.soft_wrap_mode_override = Some(soft_wrap);
20413 }
20414 cx.notify();
20415 }
20416
20417 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20418 let Some(workspace) = self.workspace() else {
20419 return;
20420 };
20421 let fs = workspace.read(cx).app_state().fs.clone();
20422 let current_show = TabBarSettings::get_global(cx).show;
20423 update_settings_file(fs, cx, move |setting, _| {
20424 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20425 });
20426 }
20427
20428 pub fn toggle_indent_guides(
20429 &mut self,
20430 _: &ToggleIndentGuides,
20431 _: &mut Window,
20432 cx: &mut Context<Self>,
20433 ) {
20434 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20435 self.buffer
20436 .read(cx)
20437 .language_settings(cx)
20438 .indent_guides
20439 .enabled
20440 });
20441 self.show_indent_guides = Some(!currently_enabled);
20442 cx.notify();
20443 }
20444
20445 fn should_show_indent_guides(&self) -> Option<bool> {
20446 self.show_indent_guides
20447 }
20448
20449 pub fn disable_indent_guides_for_buffer(
20450 &mut self,
20451 buffer_id: BufferId,
20452 cx: &mut Context<Self>,
20453 ) {
20454 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20455 cx.notify();
20456 }
20457
20458 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20459 self.buffers_with_disabled_indent_guides
20460 .contains(&buffer_id)
20461 }
20462
20463 pub fn toggle_line_numbers(
20464 &mut self,
20465 _: &ToggleLineNumbers,
20466 _: &mut Window,
20467 cx: &mut Context<Self>,
20468 ) {
20469 let mut editor_settings = EditorSettings::get_global(cx).clone();
20470 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20471 EditorSettings::override_global(editor_settings, cx);
20472 }
20473
20474 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20475 if let Some(show_line_numbers) = self.show_line_numbers {
20476 return show_line_numbers;
20477 }
20478 EditorSettings::get_global(cx).gutter.line_numbers
20479 }
20480
20481 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20482 match (
20483 self.use_relative_line_numbers,
20484 EditorSettings::get_global(cx).relative_line_numbers,
20485 ) {
20486 (None, setting) => setting,
20487 (Some(false), _) => RelativeLineNumbers::Disabled,
20488 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20489 (Some(true), _) => RelativeLineNumbers::Enabled,
20490 }
20491 }
20492
20493 pub fn toggle_relative_line_numbers(
20494 &mut self,
20495 _: &ToggleRelativeLineNumbers,
20496 _: &mut Window,
20497 cx: &mut Context<Self>,
20498 ) {
20499 let is_relative = self.relative_line_numbers(cx);
20500 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20501 }
20502
20503 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20504 self.use_relative_line_numbers = is_relative;
20505 cx.notify();
20506 }
20507
20508 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20509 self.show_gutter = show_gutter;
20510 cx.notify();
20511 }
20512
20513 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20514 self.show_scrollbars = ScrollbarAxes {
20515 horizontal: show,
20516 vertical: show,
20517 };
20518 cx.notify();
20519 }
20520
20521 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20522 self.show_scrollbars.vertical = show;
20523 cx.notify();
20524 }
20525
20526 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20527 self.show_scrollbars.horizontal = show;
20528 cx.notify();
20529 }
20530
20531 pub fn set_minimap_visibility(
20532 &mut self,
20533 minimap_visibility: MinimapVisibility,
20534 window: &mut Window,
20535 cx: &mut Context<Self>,
20536 ) {
20537 if self.minimap_visibility != minimap_visibility {
20538 if minimap_visibility.visible() && self.minimap.is_none() {
20539 let minimap_settings = EditorSettings::get_global(cx).minimap;
20540 self.minimap =
20541 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20542 }
20543 self.minimap_visibility = minimap_visibility;
20544 cx.notify();
20545 }
20546 }
20547
20548 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20549 self.set_show_scrollbars(false, cx);
20550 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20551 }
20552
20553 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20554 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20555 }
20556
20557 /// Normally the text in full mode and auto height editors is padded on the
20558 /// left side by roughly half a character width for improved hit testing.
20559 ///
20560 /// Use this method to disable this for cases where this is not wanted (e.g.
20561 /// if you want to align the editor text with some other text above or below)
20562 /// or if you want to add this padding to single-line editors.
20563 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20564 self.offset_content = offset_content;
20565 cx.notify();
20566 }
20567
20568 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20569 self.show_line_numbers = Some(show_line_numbers);
20570 cx.notify();
20571 }
20572
20573 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20574 self.disable_expand_excerpt_buttons = true;
20575 cx.notify();
20576 }
20577
20578 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20579 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20580 cx.notify();
20581 }
20582
20583 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20584 self.show_code_actions = Some(show_code_actions);
20585 cx.notify();
20586 }
20587
20588 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20589 self.show_runnables = Some(show_runnables);
20590 cx.notify();
20591 }
20592
20593 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20594 self.show_breakpoints = Some(show_breakpoints);
20595 cx.notify();
20596 }
20597
20598 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20599 if self.display_map.read(cx).masked != masked {
20600 self.display_map.update(cx, |map, _| map.masked = masked);
20601 }
20602 cx.notify()
20603 }
20604
20605 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20606 self.show_wrap_guides = Some(show_wrap_guides);
20607 cx.notify();
20608 }
20609
20610 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20611 self.show_indent_guides = Some(show_indent_guides);
20612 cx.notify();
20613 }
20614
20615 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20616 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20617 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20618 && let Some(dir) = file.abs_path(cx).parent()
20619 {
20620 return Some(dir.to_owned());
20621 }
20622 }
20623
20624 None
20625 }
20626
20627 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20628 self.active_excerpt(cx)?
20629 .1
20630 .read(cx)
20631 .file()
20632 .and_then(|f| f.as_local())
20633 }
20634
20635 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20636 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20637 let buffer = buffer.read(cx);
20638 if let Some(project_path) = buffer.project_path(cx) {
20639 let project = self.project()?.read(cx);
20640 project.absolute_path(&project_path, cx)
20641 } else {
20642 buffer
20643 .file()
20644 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20645 }
20646 })
20647 }
20648
20649 pub fn reveal_in_finder(
20650 &mut self,
20651 _: &RevealInFileManager,
20652 _window: &mut Window,
20653 cx: &mut Context<Self>,
20654 ) {
20655 if let Some(target) = self.target_file(cx) {
20656 cx.reveal_path(&target.abs_path(cx));
20657 }
20658 }
20659
20660 pub fn copy_path(
20661 &mut self,
20662 _: &zed_actions::workspace::CopyPath,
20663 _window: &mut Window,
20664 cx: &mut Context<Self>,
20665 ) {
20666 if let Some(path) = self.target_file_abs_path(cx)
20667 && let Some(path) = path.to_str()
20668 {
20669 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20670 } else {
20671 cx.propagate();
20672 }
20673 }
20674
20675 pub fn copy_relative_path(
20676 &mut self,
20677 _: &zed_actions::workspace::CopyRelativePath,
20678 _window: &mut Window,
20679 cx: &mut Context<Self>,
20680 ) {
20681 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20682 let project = self.project()?.read(cx);
20683 let path = buffer.read(cx).file()?.path();
20684 let path = path.display(project.path_style(cx));
20685 Some(path)
20686 }) {
20687 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20688 } else {
20689 cx.propagate();
20690 }
20691 }
20692
20693 /// Returns the project path for the editor's buffer, if any buffer is
20694 /// opened in the editor.
20695 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20696 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20697 buffer.read(cx).project_path(cx)
20698 } else {
20699 None
20700 }
20701 }
20702
20703 // Returns true if the editor handled a go-to-line request
20704 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20705 maybe!({
20706 let breakpoint_store = self.breakpoint_store.as_ref()?;
20707
20708 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20709 else {
20710 self.clear_row_highlights::<ActiveDebugLine>();
20711 return None;
20712 };
20713
20714 let position = active_stack_frame.position;
20715 let buffer_id = position.buffer_id?;
20716 let snapshot = self
20717 .project
20718 .as_ref()?
20719 .read(cx)
20720 .buffer_for_id(buffer_id, cx)?
20721 .read(cx)
20722 .snapshot();
20723
20724 let mut handled = false;
20725 for (id, ExcerptRange { context, .. }) in
20726 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20727 {
20728 if context.start.cmp(&position, &snapshot).is_ge()
20729 || context.end.cmp(&position, &snapshot).is_lt()
20730 {
20731 continue;
20732 }
20733 let snapshot = self.buffer.read(cx).snapshot(cx);
20734 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20735
20736 handled = true;
20737 self.clear_row_highlights::<ActiveDebugLine>();
20738
20739 self.go_to_line::<ActiveDebugLine>(
20740 multibuffer_anchor,
20741 Some(cx.theme().colors().editor_debugger_active_line_background),
20742 window,
20743 cx,
20744 );
20745
20746 cx.notify();
20747 }
20748
20749 handled.then_some(())
20750 })
20751 .is_some()
20752 }
20753
20754 pub fn copy_file_name_without_extension(
20755 &mut self,
20756 _: &CopyFileNameWithoutExtension,
20757 _: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) {
20760 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20761 let file = buffer.read(cx).file()?;
20762 file.path().file_stem()
20763 }) {
20764 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20765 }
20766 }
20767
20768 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20769 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20770 let file = buffer.read(cx).file()?;
20771 Some(file.file_name(cx))
20772 }) {
20773 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20774 }
20775 }
20776
20777 pub fn toggle_git_blame(
20778 &mut self,
20779 _: &::git::Blame,
20780 window: &mut Window,
20781 cx: &mut Context<Self>,
20782 ) {
20783 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20784
20785 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20786 self.start_git_blame(true, window, cx);
20787 }
20788
20789 cx.notify();
20790 }
20791
20792 pub fn toggle_git_blame_inline(
20793 &mut self,
20794 _: &ToggleGitBlameInline,
20795 window: &mut Window,
20796 cx: &mut Context<Self>,
20797 ) {
20798 self.toggle_git_blame_inline_internal(true, window, cx);
20799 cx.notify();
20800 }
20801
20802 pub fn open_git_blame_commit(
20803 &mut self,
20804 _: &OpenGitBlameCommit,
20805 window: &mut Window,
20806 cx: &mut Context<Self>,
20807 ) {
20808 self.open_git_blame_commit_internal(window, cx);
20809 }
20810
20811 fn open_git_blame_commit_internal(
20812 &mut self,
20813 window: &mut Window,
20814 cx: &mut Context<Self>,
20815 ) -> Option<()> {
20816 let blame = self.blame.as_ref()?;
20817 let snapshot = self.snapshot(window, cx);
20818 let cursor = self
20819 .selections
20820 .newest::<Point>(&snapshot.display_snapshot)
20821 .head();
20822 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20823 let (_, blame_entry) = blame
20824 .update(cx, |blame, cx| {
20825 blame
20826 .blame_for_rows(
20827 &[RowInfo {
20828 buffer_id: Some(buffer.remote_id()),
20829 buffer_row: Some(point.row),
20830 ..Default::default()
20831 }],
20832 cx,
20833 )
20834 .next()
20835 })
20836 .flatten()?;
20837 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20838 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20839 let workspace = self.workspace()?.downgrade();
20840 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20841 None
20842 }
20843
20844 pub fn git_blame_inline_enabled(&self) -> bool {
20845 self.git_blame_inline_enabled
20846 }
20847
20848 pub fn toggle_selection_menu(
20849 &mut self,
20850 _: &ToggleSelectionMenu,
20851 _: &mut Window,
20852 cx: &mut Context<Self>,
20853 ) {
20854 self.show_selection_menu = self
20855 .show_selection_menu
20856 .map(|show_selections_menu| !show_selections_menu)
20857 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20858
20859 cx.notify();
20860 }
20861
20862 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20863 self.show_selection_menu
20864 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20865 }
20866
20867 fn start_git_blame(
20868 &mut self,
20869 user_triggered: bool,
20870 window: &mut Window,
20871 cx: &mut Context<Self>,
20872 ) {
20873 if let Some(project) = self.project() {
20874 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20875 && buffer.read(cx).file().is_none()
20876 {
20877 return;
20878 }
20879
20880 let focused = self.focus_handle(cx).contains_focused(window, cx);
20881
20882 let project = project.clone();
20883 let blame = cx
20884 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20885 self.blame_subscription =
20886 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20887 self.blame = Some(blame);
20888 }
20889 }
20890
20891 fn toggle_git_blame_inline_internal(
20892 &mut self,
20893 user_triggered: bool,
20894 window: &mut Window,
20895 cx: &mut Context<Self>,
20896 ) {
20897 if self.git_blame_inline_enabled {
20898 self.git_blame_inline_enabled = false;
20899 self.show_git_blame_inline = false;
20900 self.show_git_blame_inline_delay_task.take();
20901 } else {
20902 self.git_blame_inline_enabled = true;
20903 self.start_git_blame_inline(user_triggered, window, cx);
20904 }
20905
20906 cx.notify();
20907 }
20908
20909 fn start_git_blame_inline(
20910 &mut self,
20911 user_triggered: bool,
20912 window: &mut Window,
20913 cx: &mut Context<Self>,
20914 ) {
20915 self.start_git_blame(user_triggered, window, cx);
20916
20917 if ProjectSettings::get_global(cx)
20918 .git
20919 .inline_blame_delay()
20920 .is_some()
20921 {
20922 self.start_inline_blame_timer(window, cx);
20923 } else {
20924 self.show_git_blame_inline = true
20925 }
20926 }
20927
20928 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20929 self.blame.as_ref()
20930 }
20931
20932 pub fn show_git_blame_gutter(&self) -> bool {
20933 self.show_git_blame_gutter
20934 }
20935
20936 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20937 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20938 }
20939
20940 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20941 self.show_git_blame_inline
20942 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20943 && !self.newest_selection_head_on_empty_line(cx)
20944 && self.has_blame_entries(cx)
20945 }
20946
20947 fn has_blame_entries(&self, cx: &App) -> bool {
20948 self.blame()
20949 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20950 }
20951
20952 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20953 let cursor_anchor = self.selections.newest_anchor().head();
20954
20955 let snapshot = self.buffer.read(cx).snapshot(cx);
20956 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20957
20958 snapshot.line_len(buffer_row) == 0
20959 }
20960
20961 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20962 let buffer_and_selection = maybe!({
20963 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20964 let selection_range = selection.range();
20965
20966 let multi_buffer = self.buffer().read(cx);
20967 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20968 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20969
20970 let (buffer, range, _) = if selection.reversed {
20971 buffer_ranges.first()
20972 } else {
20973 buffer_ranges.last()
20974 }?;
20975
20976 let selection = text::ToPoint::to_point(&range.start, buffer).row
20977 ..text::ToPoint::to_point(&range.end, buffer).row;
20978 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20979 });
20980
20981 let Some((buffer, selection)) = buffer_and_selection else {
20982 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20983 };
20984
20985 let Some(project) = self.project() else {
20986 return Task::ready(Err(anyhow!("editor does not have project")));
20987 };
20988
20989 project.update(cx, |project, cx| {
20990 project.get_permalink_to_line(&buffer, selection, cx)
20991 })
20992 }
20993
20994 pub fn copy_permalink_to_line(
20995 &mut self,
20996 _: &CopyPermalinkToLine,
20997 window: &mut Window,
20998 cx: &mut Context<Self>,
20999 ) {
21000 let permalink_task = self.get_permalink_to_line(cx);
21001 let workspace = self.workspace();
21002
21003 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21004 Ok(permalink) => {
21005 cx.update(|_, cx| {
21006 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21007 })
21008 .ok();
21009 }
21010 Err(err) => {
21011 let message = format!("Failed to copy permalink: {err}");
21012
21013 anyhow::Result::<()>::Err(err).log_err();
21014
21015 if let Some(workspace) = workspace {
21016 workspace
21017 .update_in(cx, |workspace, _, cx| {
21018 struct CopyPermalinkToLine;
21019
21020 workspace.show_toast(
21021 Toast::new(
21022 NotificationId::unique::<CopyPermalinkToLine>(),
21023 message,
21024 ),
21025 cx,
21026 )
21027 })
21028 .ok();
21029 }
21030 }
21031 })
21032 .detach();
21033 }
21034
21035 pub fn copy_file_location(
21036 &mut self,
21037 _: &CopyFileLocation,
21038 _: &mut Window,
21039 cx: &mut Context<Self>,
21040 ) {
21041 let selection = self
21042 .selections
21043 .newest::<Point>(&self.display_snapshot(cx))
21044 .start
21045 .row
21046 + 1;
21047 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21048 let project = self.project()?.read(cx);
21049 let file = buffer.read(cx).file()?;
21050 let path = file.path().display(project.path_style(cx));
21051
21052 Some(format!("{path}:{selection}"))
21053 }) {
21054 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21055 }
21056 }
21057
21058 pub fn open_permalink_to_line(
21059 &mut self,
21060 _: &OpenPermalinkToLine,
21061 window: &mut Window,
21062 cx: &mut Context<Self>,
21063 ) {
21064 let permalink_task = self.get_permalink_to_line(cx);
21065 let workspace = self.workspace();
21066
21067 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21068 Ok(permalink) => {
21069 cx.update(|_, cx| {
21070 cx.open_url(permalink.as_ref());
21071 })
21072 .ok();
21073 }
21074 Err(err) => {
21075 let message = format!("Failed to open permalink: {err}");
21076
21077 anyhow::Result::<()>::Err(err).log_err();
21078
21079 if let Some(workspace) = workspace {
21080 workspace
21081 .update(cx, |workspace, cx| {
21082 struct OpenPermalinkToLine;
21083
21084 workspace.show_toast(
21085 Toast::new(
21086 NotificationId::unique::<OpenPermalinkToLine>(),
21087 message,
21088 ),
21089 cx,
21090 )
21091 })
21092 .ok();
21093 }
21094 }
21095 })
21096 .detach();
21097 }
21098
21099 pub fn insert_uuid_v4(
21100 &mut self,
21101 _: &InsertUuidV4,
21102 window: &mut Window,
21103 cx: &mut Context<Self>,
21104 ) {
21105 self.insert_uuid(UuidVersion::V4, window, cx);
21106 }
21107
21108 pub fn insert_uuid_v7(
21109 &mut self,
21110 _: &InsertUuidV7,
21111 window: &mut Window,
21112 cx: &mut Context<Self>,
21113 ) {
21114 self.insert_uuid(UuidVersion::V7, window, cx);
21115 }
21116
21117 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21119 self.transact(window, cx, |this, window, cx| {
21120 let edits = this
21121 .selections
21122 .all::<Point>(&this.display_snapshot(cx))
21123 .into_iter()
21124 .map(|selection| {
21125 let uuid = match version {
21126 UuidVersion::V4 => uuid::Uuid::new_v4(),
21127 UuidVersion::V7 => uuid::Uuid::now_v7(),
21128 };
21129
21130 (selection.range(), uuid.to_string())
21131 });
21132 this.edit(edits, cx);
21133 this.refresh_edit_prediction(true, false, window, cx);
21134 });
21135 }
21136
21137 pub fn open_selections_in_multibuffer(
21138 &mut self,
21139 _: &OpenSelectionsInMultibuffer,
21140 window: &mut Window,
21141 cx: &mut Context<Self>,
21142 ) {
21143 let multibuffer = self.buffer.read(cx);
21144
21145 let Some(buffer) = multibuffer.as_singleton() else {
21146 return;
21147 };
21148
21149 let Some(workspace) = self.workspace() else {
21150 return;
21151 };
21152
21153 let title = multibuffer.title(cx).to_string();
21154
21155 let locations = self
21156 .selections
21157 .all_anchors(&self.display_snapshot(cx))
21158 .iter()
21159 .map(|selection| {
21160 (
21161 buffer.clone(),
21162 (selection.start.text_anchor..selection.end.text_anchor)
21163 .to_point(buffer.read(cx)),
21164 )
21165 })
21166 .into_group_map();
21167
21168 cx.spawn_in(window, async move |_, cx| {
21169 workspace.update_in(cx, |workspace, window, cx| {
21170 Self::open_locations_in_multibuffer(
21171 workspace,
21172 locations,
21173 format!("Selections for '{title}'"),
21174 false,
21175 false,
21176 MultibufferSelectionMode::All,
21177 window,
21178 cx,
21179 );
21180 })
21181 })
21182 .detach();
21183 }
21184
21185 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21186 /// last highlight added will be used.
21187 ///
21188 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21189 pub fn highlight_rows<T: 'static>(
21190 &mut self,
21191 range: Range<Anchor>,
21192 color: Hsla,
21193 options: RowHighlightOptions,
21194 cx: &mut Context<Self>,
21195 ) {
21196 let snapshot = self.buffer().read(cx).snapshot(cx);
21197 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21198 let ix = row_highlights.binary_search_by(|highlight| {
21199 Ordering::Equal
21200 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21201 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21202 });
21203
21204 if let Err(mut ix) = ix {
21205 let index = post_inc(&mut self.highlight_order);
21206
21207 // If this range intersects with the preceding highlight, then merge it with
21208 // the preceding highlight. Otherwise insert a new highlight.
21209 let mut merged = false;
21210 if ix > 0 {
21211 let prev_highlight = &mut row_highlights[ix - 1];
21212 if prev_highlight
21213 .range
21214 .end
21215 .cmp(&range.start, &snapshot)
21216 .is_ge()
21217 {
21218 ix -= 1;
21219 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21220 prev_highlight.range.end = range.end;
21221 }
21222 merged = true;
21223 prev_highlight.index = index;
21224 prev_highlight.color = color;
21225 prev_highlight.options = options;
21226 }
21227 }
21228
21229 if !merged {
21230 row_highlights.insert(
21231 ix,
21232 RowHighlight {
21233 range,
21234 index,
21235 color,
21236 options,
21237 type_id: TypeId::of::<T>(),
21238 },
21239 );
21240 }
21241
21242 // If any of the following highlights intersect with this one, merge them.
21243 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21244 let highlight = &row_highlights[ix];
21245 if next_highlight
21246 .range
21247 .start
21248 .cmp(&highlight.range.end, &snapshot)
21249 .is_le()
21250 {
21251 if next_highlight
21252 .range
21253 .end
21254 .cmp(&highlight.range.end, &snapshot)
21255 .is_gt()
21256 {
21257 row_highlights[ix].range.end = next_highlight.range.end;
21258 }
21259 row_highlights.remove(ix + 1);
21260 } else {
21261 break;
21262 }
21263 }
21264 }
21265 }
21266
21267 /// Remove any highlighted row ranges of the given type that intersect the
21268 /// given ranges.
21269 pub fn remove_highlighted_rows<T: 'static>(
21270 &mut self,
21271 ranges_to_remove: Vec<Range<Anchor>>,
21272 cx: &mut Context<Self>,
21273 ) {
21274 let snapshot = self.buffer().read(cx).snapshot(cx);
21275 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21276 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21277 row_highlights.retain(|highlight| {
21278 while let Some(range_to_remove) = ranges_to_remove.peek() {
21279 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21280 Ordering::Less | Ordering::Equal => {
21281 ranges_to_remove.next();
21282 }
21283 Ordering::Greater => {
21284 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21285 Ordering::Less | Ordering::Equal => {
21286 return false;
21287 }
21288 Ordering::Greater => break,
21289 }
21290 }
21291 }
21292 }
21293
21294 true
21295 })
21296 }
21297
21298 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21299 pub fn clear_row_highlights<T: 'static>(&mut self) {
21300 self.highlighted_rows.remove(&TypeId::of::<T>());
21301 }
21302
21303 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21304 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21305 self.highlighted_rows
21306 .get(&TypeId::of::<T>())
21307 .map_or(&[] as &[_], |vec| vec.as_slice())
21308 .iter()
21309 .map(|highlight| (highlight.range.clone(), highlight.color))
21310 }
21311
21312 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21313 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21314 /// Allows to ignore certain kinds of highlights.
21315 pub fn highlighted_display_rows(
21316 &self,
21317 window: &mut Window,
21318 cx: &mut App,
21319 ) -> BTreeMap<DisplayRow, LineHighlight> {
21320 let snapshot = self.snapshot(window, cx);
21321 let mut used_highlight_orders = HashMap::default();
21322 self.highlighted_rows
21323 .iter()
21324 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21325 .fold(
21326 BTreeMap::<DisplayRow, LineHighlight>::new(),
21327 |mut unique_rows, highlight| {
21328 let start = highlight.range.start.to_display_point(&snapshot);
21329 let end = highlight.range.end.to_display_point(&snapshot);
21330 let start_row = start.row().0;
21331 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21332 {
21333 end.row().0.saturating_sub(1)
21334 } else {
21335 end.row().0
21336 };
21337 for row in start_row..=end_row {
21338 let used_index =
21339 used_highlight_orders.entry(row).or_insert(highlight.index);
21340 if highlight.index >= *used_index {
21341 *used_index = highlight.index;
21342 unique_rows.insert(
21343 DisplayRow(row),
21344 LineHighlight {
21345 include_gutter: highlight.options.include_gutter,
21346 border: None,
21347 background: highlight.color.into(),
21348 type_id: Some(highlight.type_id),
21349 },
21350 );
21351 }
21352 }
21353 unique_rows
21354 },
21355 )
21356 }
21357
21358 pub fn highlighted_display_row_for_autoscroll(
21359 &self,
21360 snapshot: &DisplaySnapshot,
21361 ) -> Option<DisplayRow> {
21362 self.highlighted_rows
21363 .values()
21364 .flat_map(|highlighted_rows| highlighted_rows.iter())
21365 .filter_map(|highlight| {
21366 if highlight.options.autoscroll {
21367 Some(highlight.range.start.to_display_point(snapshot).row())
21368 } else {
21369 None
21370 }
21371 })
21372 .min()
21373 }
21374
21375 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21376 self.highlight_background::<SearchWithinRange>(
21377 ranges,
21378 |_, colors| colors.colors().editor_document_highlight_read_background,
21379 cx,
21380 )
21381 }
21382
21383 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21384 self.breadcrumb_header = Some(new_header);
21385 }
21386
21387 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21388 self.clear_background_highlights::<SearchWithinRange>(cx);
21389 }
21390
21391 pub fn highlight_background<T: 'static>(
21392 &mut self,
21393 ranges: &[Range<Anchor>],
21394 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21395 cx: &mut Context<Self>,
21396 ) {
21397 self.background_highlights.insert(
21398 HighlightKey::Type(TypeId::of::<T>()),
21399 (Arc::new(color_fetcher), Arc::from(ranges)),
21400 );
21401 self.scrollbar_marker_state.dirty = true;
21402 cx.notify();
21403 }
21404
21405 pub fn highlight_background_key<T: 'static>(
21406 &mut self,
21407 key: usize,
21408 ranges: &[Range<Anchor>],
21409 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21410 cx: &mut Context<Self>,
21411 ) {
21412 self.background_highlights.insert(
21413 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21414 (Arc::new(color_fetcher), Arc::from(ranges)),
21415 );
21416 self.scrollbar_marker_state.dirty = true;
21417 cx.notify();
21418 }
21419
21420 pub fn clear_background_highlights<T: 'static>(
21421 &mut self,
21422 cx: &mut Context<Self>,
21423 ) -> Option<BackgroundHighlight> {
21424 let text_highlights = self
21425 .background_highlights
21426 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21427 if !text_highlights.1.is_empty() {
21428 self.scrollbar_marker_state.dirty = true;
21429 cx.notify();
21430 }
21431 Some(text_highlights)
21432 }
21433
21434 pub fn highlight_gutter<T: 'static>(
21435 &mut self,
21436 ranges: impl Into<Vec<Range<Anchor>>>,
21437 color_fetcher: fn(&App) -> Hsla,
21438 cx: &mut Context<Self>,
21439 ) {
21440 self.gutter_highlights
21441 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21442 cx.notify();
21443 }
21444
21445 pub fn clear_gutter_highlights<T: 'static>(
21446 &mut self,
21447 cx: &mut Context<Self>,
21448 ) -> Option<GutterHighlight> {
21449 cx.notify();
21450 self.gutter_highlights.remove(&TypeId::of::<T>())
21451 }
21452
21453 pub fn insert_gutter_highlight<T: 'static>(
21454 &mut self,
21455 range: Range<Anchor>,
21456 color_fetcher: fn(&App) -> Hsla,
21457 cx: &mut Context<Self>,
21458 ) {
21459 let snapshot = self.buffer().read(cx).snapshot(cx);
21460 let mut highlights = self
21461 .gutter_highlights
21462 .remove(&TypeId::of::<T>())
21463 .map(|(_, highlights)| highlights)
21464 .unwrap_or_default();
21465 let ix = highlights.binary_search_by(|highlight| {
21466 Ordering::Equal
21467 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21468 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21469 });
21470 if let Err(ix) = ix {
21471 highlights.insert(ix, range);
21472 }
21473 self.gutter_highlights
21474 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21475 }
21476
21477 pub fn remove_gutter_highlights<T: 'static>(
21478 &mut self,
21479 ranges_to_remove: Vec<Range<Anchor>>,
21480 cx: &mut Context<Self>,
21481 ) {
21482 let snapshot = self.buffer().read(cx).snapshot(cx);
21483 let Some((color_fetcher, mut gutter_highlights)) =
21484 self.gutter_highlights.remove(&TypeId::of::<T>())
21485 else {
21486 return;
21487 };
21488 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21489 gutter_highlights.retain(|highlight| {
21490 while let Some(range_to_remove) = ranges_to_remove.peek() {
21491 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21492 Ordering::Less | Ordering::Equal => {
21493 ranges_to_remove.next();
21494 }
21495 Ordering::Greater => {
21496 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21497 Ordering::Less | Ordering::Equal => {
21498 return false;
21499 }
21500 Ordering::Greater => break,
21501 }
21502 }
21503 }
21504 }
21505
21506 true
21507 });
21508 self.gutter_highlights
21509 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21510 }
21511
21512 #[cfg(feature = "test-support")]
21513 pub fn all_text_highlights(
21514 &self,
21515 window: &mut Window,
21516 cx: &mut Context<Self>,
21517 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21518 let snapshot = self.snapshot(window, cx);
21519 self.display_map.update(cx, |display_map, _| {
21520 display_map
21521 .all_text_highlights()
21522 .map(|highlight| {
21523 let (style, ranges) = highlight.as_ref();
21524 (
21525 *style,
21526 ranges
21527 .iter()
21528 .map(|range| range.clone().to_display_points(&snapshot))
21529 .collect(),
21530 )
21531 })
21532 .collect()
21533 })
21534 }
21535
21536 #[cfg(feature = "test-support")]
21537 pub fn all_text_background_highlights(
21538 &self,
21539 window: &mut Window,
21540 cx: &mut Context<Self>,
21541 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21542 let snapshot = self.snapshot(window, cx);
21543 let buffer = &snapshot.buffer_snapshot();
21544 let start = buffer.anchor_before(MultiBufferOffset(0));
21545 let end = buffer.anchor_after(buffer.len());
21546 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21547 }
21548
21549 #[cfg(any(test, feature = "test-support"))]
21550 pub fn sorted_background_highlights_in_range(
21551 &self,
21552 search_range: Range<Anchor>,
21553 display_snapshot: &DisplaySnapshot,
21554 theme: &Theme,
21555 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21556 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21557 res.sort_by(|a, b| {
21558 a.0.start
21559 .cmp(&b.0.start)
21560 .then_with(|| a.0.end.cmp(&b.0.end))
21561 .then_with(|| a.1.cmp(&b.1))
21562 });
21563 res
21564 }
21565
21566 #[cfg(feature = "test-support")]
21567 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21568 let snapshot = self.buffer().read(cx).snapshot(cx);
21569
21570 let highlights = self
21571 .background_highlights
21572 .get(&HighlightKey::Type(TypeId::of::<
21573 items::BufferSearchHighlights,
21574 >()));
21575
21576 if let Some((_color, ranges)) = highlights {
21577 ranges
21578 .iter()
21579 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21580 .collect_vec()
21581 } else {
21582 vec![]
21583 }
21584 }
21585
21586 fn document_highlights_for_position<'a>(
21587 &'a self,
21588 position: Anchor,
21589 buffer: &'a MultiBufferSnapshot,
21590 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21591 let read_highlights = self
21592 .background_highlights
21593 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21594 .map(|h| &h.1);
21595 let write_highlights = self
21596 .background_highlights
21597 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21598 .map(|h| &h.1);
21599 let left_position = position.bias_left(buffer);
21600 let right_position = position.bias_right(buffer);
21601 read_highlights
21602 .into_iter()
21603 .chain(write_highlights)
21604 .flat_map(move |ranges| {
21605 let start_ix = match ranges.binary_search_by(|probe| {
21606 let cmp = probe.end.cmp(&left_position, buffer);
21607 if cmp.is_ge() {
21608 Ordering::Greater
21609 } else {
21610 Ordering::Less
21611 }
21612 }) {
21613 Ok(i) | Err(i) => i,
21614 };
21615
21616 ranges[start_ix..]
21617 .iter()
21618 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21619 })
21620 }
21621
21622 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21623 self.background_highlights
21624 .get(&HighlightKey::Type(TypeId::of::<T>()))
21625 .is_some_and(|(_, highlights)| !highlights.is_empty())
21626 }
21627
21628 /// Returns all background highlights for a given range.
21629 ///
21630 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21631 pub fn background_highlights_in_range(
21632 &self,
21633 search_range: Range<Anchor>,
21634 display_snapshot: &DisplaySnapshot,
21635 theme: &Theme,
21636 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21637 let mut results = Vec::new();
21638 for (color_fetcher, ranges) in self.background_highlights.values() {
21639 let start_ix = match ranges.binary_search_by(|probe| {
21640 let cmp = probe
21641 .end
21642 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21643 if cmp.is_gt() {
21644 Ordering::Greater
21645 } else {
21646 Ordering::Less
21647 }
21648 }) {
21649 Ok(i) | Err(i) => i,
21650 };
21651 for (index, range) in ranges[start_ix..].iter().enumerate() {
21652 if range
21653 .start
21654 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21655 .is_ge()
21656 {
21657 break;
21658 }
21659
21660 let color = color_fetcher(&(start_ix + index), theme);
21661 let start = range.start.to_display_point(display_snapshot);
21662 let end = range.end.to_display_point(display_snapshot);
21663 results.push((start..end, color))
21664 }
21665 }
21666 results
21667 }
21668
21669 pub fn gutter_highlights_in_range(
21670 &self,
21671 search_range: Range<Anchor>,
21672 display_snapshot: &DisplaySnapshot,
21673 cx: &App,
21674 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21675 let mut results = Vec::new();
21676 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21677 let color = color_fetcher(cx);
21678 let start_ix = match ranges.binary_search_by(|probe| {
21679 let cmp = probe
21680 .end
21681 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21682 if cmp.is_gt() {
21683 Ordering::Greater
21684 } else {
21685 Ordering::Less
21686 }
21687 }) {
21688 Ok(i) | Err(i) => i,
21689 };
21690 for range in &ranges[start_ix..] {
21691 if range
21692 .start
21693 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21694 .is_ge()
21695 {
21696 break;
21697 }
21698
21699 let start = range.start.to_display_point(display_snapshot);
21700 let end = range.end.to_display_point(display_snapshot);
21701 results.push((start..end, color))
21702 }
21703 }
21704 results
21705 }
21706
21707 /// Get the text ranges corresponding to the redaction query
21708 pub fn redacted_ranges(
21709 &self,
21710 search_range: Range<Anchor>,
21711 display_snapshot: &DisplaySnapshot,
21712 cx: &App,
21713 ) -> Vec<Range<DisplayPoint>> {
21714 display_snapshot
21715 .buffer_snapshot()
21716 .redacted_ranges(search_range, |file| {
21717 if let Some(file) = file {
21718 file.is_private()
21719 && EditorSettings::get(
21720 Some(SettingsLocation {
21721 worktree_id: file.worktree_id(cx),
21722 path: file.path().as_ref(),
21723 }),
21724 cx,
21725 )
21726 .redact_private_values
21727 } else {
21728 false
21729 }
21730 })
21731 .map(|range| {
21732 range.start.to_display_point(display_snapshot)
21733 ..range.end.to_display_point(display_snapshot)
21734 })
21735 .collect()
21736 }
21737
21738 pub fn highlight_text_key<T: 'static>(
21739 &mut self,
21740 key: usize,
21741 ranges: Vec<Range<Anchor>>,
21742 style: HighlightStyle,
21743 merge: bool,
21744 cx: &mut Context<Self>,
21745 ) {
21746 self.display_map.update(cx, |map, cx| {
21747 map.highlight_text(
21748 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21749 ranges,
21750 style,
21751 merge,
21752 cx,
21753 );
21754 });
21755 cx.notify();
21756 }
21757
21758 pub fn highlight_text<T: 'static>(
21759 &mut self,
21760 ranges: Vec<Range<Anchor>>,
21761 style: HighlightStyle,
21762 cx: &mut Context<Self>,
21763 ) {
21764 self.display_map.update(cx, |map, cx| {
21765 map.highlight_text(
21766 HighlightKey::Type(TypeId::of::<T>()),
21767 ranges,
21768 style,
21769 false,
21770 cx,
21771 )
21772 });
21773 cx.notify();
21774 }
21775
21776 pub fn text_highlights<'a, T: 'static>(
21777 &'a self,
21778 cx: &'a App,
21779 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21780 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21781 }
21782
21783 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21784 let cleared = self
21785 .display_map
21786 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21787 if cleared {
21788 cx.notify();
21789 }
21790 }
21791
21792 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21793 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21794 && self.focus_handle.is_focused(window)
21795 }
21796
21797 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21798 self.show_cursor_when_unfocused = is_enabled;
21799 cx.notify();
21800 }
21801
21802 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21803 cx.notify();
21804 }
21805
21806 fn on_debug_session_event(
21807 &mut self,
21808 _session: Entity<Session>,
21809 event: &SessionEvent,
21810 cx: &mut Context<Self>,
21811 ) {
21812 if let SessionEvent::InvalidateInlineValue = event {
21813 self.refresh_inline_values(cx);
21814 }
21815 }
21816
21817 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21818 let Some(project) = self.project.clone() else {
21819 return;
21820 };
21821
21822 if !self.inline_value_cache.enabled {
21823 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21824 self.splice_inlays(&inlays, Vec::new(), cx);
21825 return;
21826 }
21827
21828 let current_execution_position = self
21829 .highlighted_rows
21830 .get(&TypeId::of::<ActiveDebugLine>())
21831 .and_then(|lines| lines.last().map(|line| line.range.end));
21832
21833 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21834 let inline_values = editor
21835 .update(cx, |editor, cx| {
21836 let Some(current_execution_position) = current_execution_position else {
21837 return Some(Task::ready(Ok(Vec::new())));
21838 };
21839
21840 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21841 let snapshot = buffer.snapshot(cx);
21842
21843 let excerpt = snapshot.excerpt_containing(
21844 current_execution_position..current_execution_position,
21845 )?;
21846
21847 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21848 })?;
21849
21850 let range =
21851 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21852
21853 project.inline_values(buffer, range, cx)
21854 })
21855 .ok()
21856 .flatten()?
21857 .await
21858 .context("refreshing debugger inlays")
21859 .log_err()?;
21860
21861 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21862
21863 for (buffer_id, inline_value) in inline_values
21864 .into_iter()
21865 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21866 {
21867 buffer_inline_values
21868 .entry(buffer_id)
21869 .or_default()
21870 .push(inline_value);
21871 }
21872
21873 editor
21874 .update(cx, |editor, cx| {
21875 let snapshot = editor.buffer.read(cx).snapshot(cx);
21876 let mut new_inlays = Vec::default();
21877
21878 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21879 let buffer_id = buffer_snapshot.remote_id();
21880 buffer_inline_values
21881 .get(&buffer_id)
21882 .into_iter()
21883 .flatten()
21884 .for_each(|hint| {
21885 let inlay = Inlay::debugger(
21886 post_inc(&mut editor.next_inlay_id),
21887 Anchor::in_buffer(excerpt_id, hint.position),
21888 hint.text(),
21889 );
21890 if !inlay.text().chars().contains(&'\n') {
21891 new_inlays.push(inlay);
21892 }
21893 });
21894 }
21895
21896 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21897 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21898
21899 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21900 })
21901 .ok()?;
21902 Some(())
21903 });
21904 }
21905
21906 fn on_buffer_event(
21907 &mut self,
21908 multibuffer: &Entity<MultiBuffer>,
21909 event: &multi_buffer::Event,
21910 window: &mut Window,
21911 cx: &mut Context<Self>,
21912 ) {
21913 match event {
21914 multi_buffer::Event::Edited { edited_buffer } => {
21915 self.scrollbar_marker_state.dirty = true;
21916 self.active_indent_guides_state.dirty = true;
21917 self.refresh_active_diagnostics(cx);
21918 self.refresh_code_actions(window, cx);
21919 self.refresh_single_line_folds(window, cx);
21920 self.refresh_matching_bracket_highlights(window, cx);
21921 if self.has_active_edit_prediction() {
21922 self.update_visible_edit_prediction(window, cx);
21923 }
21924
21925 if let Some(buffer) = edited_buffer {
21926 if buffer.read(cx).file().is_none() {
21927 cx.emit(EditorEvent::TitleChanged);
21928 }
21929
21930 if self.project.is_some() {
21931 let buffer_id = buffer.read(cx).remote_id();
21932 self.register_buffer(buffer_id, cx);
21933 self.update_lsp_data(Some(buffer_id), window, cx);
21934 self.refresh_inlay_hints(
21935 InlayHintRefreshReason::BufferEdited(buffer_id),
21936 cx,
21937 );
21938 }
21939 }
21940
21941 cx.emit(EditorEvent::BufferEdited);
21942 cx.emit(SearchEvent::MatchesInvalidated);
21943
21944 let Some(project) = &self.project else { return };
21945 let (telemetry, is_via_ssh) = {
21946 let project = project.read(cx);
21947 let telemetry = project.client().telemetry().clone();
21948 let is_via_ssh = project.is_via_remote_server();
21949 (telemetry, is_via_ssh)
21950 };
21951 telemetry.log_edit_event("editor", is_via_ssh);
21952 }
21953 multi_buffer::Event::ExcerptsAdded {
21954 buffer,
21955 predecessor,
21956 excerpts,
21957 } => {
21958 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21959 let buffer_id = buffer.read(cx).remote_id();
21960 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21961 && let Some(project) = &self.project
21962 {
21963 update_uncommitted_diff_for_buffer(
21964 cx.entity(),
21965 project,
21966 [buffer.clone()],
21967 self.buffer.clone(),
21968 cx,
21969 )
21970 .detach();
21971 }
21972 self.update_lsp_data(Some(buffer_id), window, cx);
21973 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21974 self.colorize_brackets(false, cx);
21975 cx.emit(EditorEvent::ExcerptsAdded {
21976 buffer: buffer.clone(),
21977 predecessor: *predecessor,
21978 excerpts: excerpts.clone(),
21979 });
21980 }
21981 multi_buffer::Event::ExcerptsRemoved {
21982 ids,
21983 removed_buffer_ids,
21984 } => {
21985 if let Some(inlay_hints) = &mut self.inlay_hints {
21986 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21987 }
21988 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21989 for buffer_id in removed_buffer_ids {
21990 self.registered_buffers.remove(buffer_id);
21991 }
21992 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21993 cx.emit(EditorEvent::ExcerptsRemoved {
21994 ids: ids.clone(),
21995 removed_buffer_ids: removed_buffer_ids.clone(),
21996 });
21997 }
21998 multi_buffer::Event::ExcerptsEdited {
21999 excerpt_ids,
22000 buffer_ids,
22001 } => {
22002 self.display_map.update(cx, |map, cx| {
22003 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22004 });
22005 cx.emit(EditorEvent::ExcerptsEdited {
22006 ids: excerpt_ids.clone(),
22007 });
22008 }
22009 multi_buffer::Event::ExcerptsExpanded { ids } => {
22010 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22011 self.refresh_document_highlights(cx);
22012 for id in ids {
22013 self.fetched_tree_sitter_chunks.remove(id);
22014 }
22015 self.colorize_brackets(false, cx);
22016 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22017 }
22018 multi_buffer::Event::Reparsed(buffer_id) => {
22019 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22020 self.refresh_selected_text_highlights(true, window, cx);
22021 self.colorize_brackets(true, cx);
22022 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22023
22024 cx.emit(EditorEvent::Reparsed(*buffer_id));
22025 }
22026 multi_buffer::Event::DiffHunksToggled => {
22027 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22028 }
22029 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22030 if !is_fresh_language {
22031 self.registered_buffers.remove(&buffer_id);
22032 }
22033 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22034 cx.emit(EditorEvent::Reparsed(*buffer_id));
22035 cx.notify();
22036 }
22037 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22038 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22039 multi_buffer::Event::FileHandleChanged
22040 | multi_buffer::Event::Reloaded
22041 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22042 multi_buffer::Event::DiagnosticsUpdated => {
22043 self.update_diagnostics_state(window, cx);
22044 }
22045 _ => {}
22046 };
22047 }
22048
22049 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22050 if !self.diagnostics_enabled() {
22051 return;
22052 }
22053 self.refresh_active_diagnostics(cx);
22054 self.refresh_inline_diagnostics(true, window, cx);
22055 self.scrollbar_marker_state.dirty = true;
22056 cx.notify();
22057 }
22058
22059 pub fn start_temporary_diff_override(&mut self) {
22060 self.load_diff_task.take();
22061 self.temporary_diff_override = true;
22062 }
22063
22064 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22065 self.temporary_diff_override = false;
22066 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22067 self.buffer.update(cx, |buffer, cx| {
22068 buffer.set_all_diff_hunks_collapsed(cx);
22069 });
22070
22071 if let Some(project) = self.project.clone() {
22072 self.load_diff_task = Some(
22073 update_uncommitted_diff_for_buffer(
22074 cx.entity(),
22075 &project,
22076 self.buffer.read(cx).all_buffers(),
22077 self.buffer.clone(),
22078 cx,
22079 )
22080 .shared(),
22081 );
22082 }
22083 }
22084
22085 fn on_display_map_changed(
22086 &mut self,
22087 _: Entity<DisplayMap>,
22088 _: &mut Window,
22089 cx: &mut Context<Self>,
22090 ) {
22091 cx.notify();
22092 }
22093
22094 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22095 if !self.mode.is_full() {
22096 return None;
22097 }
22098
22099 let theme_settings = theme::ThemeSettings::get_global(cx);
22100 let theme = cx.theme();
22101 let accent_colors = theme.accents().clone();
22102
22103 let accent_overrides = theme_settings
22104 .theme_overrides
22105 .get(theme.name.as_ref())
22106 .map(|theme_style| &theme_style.accents)
22107 .into_iter()
22108 .flatten()
22109 .chain(
22110 theme_settings
22111 .experimental_theme_overrides
22112 .as_ref()
22113 .map(|overrides| &overrides.accents)
22114 .into_iter()
22115 .flatten(),
22116 )
22117 .flat_map(|accent| accent.0.clone())
22118 .collect();
22119
22120 Some(AccentData {
22121 colors: accent_colors,
22122 overrides: accent_overrides,
22123 })
22124 }
22125
22126 fn fetch_applicable_language_settings(
22127 &self,
22128 cx: &App,
22129 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22130 if !self.mode.is_full() {
22131 return HashMap::default();
22132 }
22133
22134 self.buffer().read(cx).all_buffers().into_iter().fold(
22135 HashMap::default(),
22136 |mut acc, buffer| {
22137 let buffer = buffer.read(cx);
22138 let language = buffer.language().map(|language| language.name());
22139 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22140 let file = buffer.file();
22141 v.insert(language_settings(language, file, cx).into_owned());
22142 }
22143 acc
22144 },
22145 )
22146 }
22147
22148 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22149 let new_language_settings = self.fetch_applicable_language_settings(cx);
22150 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22151 self.applicable_language_settings = new_language_settings;
22152
22153 let new_accents = self.fetch_accent_data(cx);
22154 let accents_changed = new_accents != self.accent_data;
22155 self.accent_data = new_accents;
22156
22157 if self.diagnostics_enabled() {
22158 let new_severity = EditorSettings::get_global(cx)
22159 .diagnostics_max_severity
22160 .unwrap_or(DiagnosticSeverity::Hint);
22161 self.set_max_diagnostics_severity(new_severity, cx);
22162 }
22163 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22164 self.update_edit_prediction_settings(cx);
22165 self.refresh_edit_prediction(true, false, window, cx);
22166 self.refresh_inline_values(cx);
22167 self.refresh_inlay_hints(
22168 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22169 self.selections.newest_anchor().head(),
22170 &self.buffer.read(cx).snapshot(cx),
22171 cx,
22172 )),
22173 cx,
22174 );
22175
22176 let old_cursor_shape = self.cursor_shape;
22177 let old_show_breadcrumbs = self.show_breadcrumbs;
22178
22179 {
22180 let editor_settings = EditorSettings::get_global(cx);
22181 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22182 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22183 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22184 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22185 }
22186
22187 if old_cursor_shape != self.cursor_shape {
22188 cx.emit(EditorEvent::CursorShapeChanged);
22189 }
22190
22191 if old_show_breadcrumbs != self.show_breadcrumbs {
22192 cx.emit(EditorEvent::BreadcrumbsChanged);
22193 }
22194
22195 let project_settings = ProjectSettings::get_global(cx);
22196 self.buffer_serialization = self
22197 .should_serialize_buffer()
22198 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22199
22200 if self.mode.is_full() {
22201 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22202 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22203 if self.show_inline_diagnostics != show_inline_diagnostics {
22204 self.show_inline_diagnostics = show_inline_diagnostics;
22205 self.refresh_inline_diagnostics(false, window, cx);
22206 }
22207
22208 if self.git_blame_inline_enabled != inline_blame_enabled {
22209 self.toggle_git_blame_inline_internal(false, window, cx);
22210 }
22211
22212 let minimap_settings = EditorSettings::get_global(cx).minimap;
22213 if self.minimap_visibility != MinimapVisibility::Disabled {
22214 if self.minimap_visibility.settings_visibility()
22215 != minimap_settings.minimap_enabled()
22216 {
22217 self.set_minimap_visibility(
22218 MinimapVisibility::for_mode(self.mode(), cx),
22219 window,
22220 cx,
22221 );
22222 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22223 minimap_entity.update(cx, |minimap_editor, cx| {
22224 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22225 })
22226 }
22227 }
22228
22229 if language_settings_changed || accents_changed {
22230 self.colorize_brackets(true, cx);
22231 }
22232
22233 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22234 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22235 }) {
22236 if !inlay_splice.is_empty() {
22237 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22238 }
22239 self.refresh_colors_for_visible_range(None, window, cx);
22240 }
22241 }
22242
22243 cx.notify();
22244 }
22245
22246 pub fn set_searchable(&mut self, searchable: bool) {
22247 self.searchable = searchable;
22248 }
22249
22250 pub fn searchable(&self) -> bool {
22251 self.searchable
22252 }
22253
22254 pub fn open_excerpts_in_split(
22255 &mut self,
22256 _: &OpenExcerptsSplit,
22257 window: &mut Window,
22258 cx: &mut Context<Self>,
22259 ) {
22260 self.open_excerpts_common(None, true, window, cx)
22261 }
22262
22263 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22264 self.open_excerpts_common(None, false, window, cx)
22265 }
22266
22267 fn open_excerpts_common(
22268 &mut self,
22269 jump_data: Option<JumpData>,
22270 split: bool,
22271 window: &mut Window,
22272 cx: &mut Context<Self>,
22273 ) {
22274 let Some(workspace) = self.workspace() else {
22275 cx.propagate();
22276 return;
22277 };
22278
22279 if self.buffer.read(cx).is_singleton() {
22280 cx.propagate();
22281 return;
22282 }
22283
22284 let mut new_selections_by_buffer = HashMap::default();
22285 match &jump_data {
22286 Some(JumpData::MultiBufferPoint {
22287 excerpt_id,
22288 position,
22289 anchor,
22290 line_offset_from_top,
22291 }) => {
22292 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22293 if let Some(buffer) = multi_buffer_snapshot
22294 .buffer_id_for_excerpt(*excerpt_id)
22295 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22296 {
22297 let buffer_snapshot = buffer.read(cx).snapshot();
22298 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22299 language::ToPoint::to_point(anchor, &buffer_snapshot)
22300 } else {
22301 buffer_snapshot.clip_point(*position, Bias::Left)
22302 };
22303 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22304 new_selections_by_buffer.insert(
22305 buffer,
22306 (
22307 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22308 Some(*line_offset_from_top),
22309 ),
22310 );
22311 }
22312 }
22313 Some(JumpData::MultiBufferRow {
22314 row,
22315 line_offset_from_top,
22316 }) => {
22317 let point = MultiBufferPoint::new(row.0, 0);
22318 if let Some((buffer, buffer_point, _)) =
22319 self.buffer.read(cx).point_to_buffer_point(point, cx)
22320 {
22321 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22322 new_selections_by_buffer
22323 .entry(buffer)
22324 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22325 .0
22326 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22327 }
22328 }
22329 None => {
22330 let selections = self
22331 .selections
22332 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22333 let multi_buffer = self.buffer.read(cx);
22334 for selection in selections {
22335 for (snapshot, range, _, anchor) in multi_buffer
22336 .snapshot(cx)
22337 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22338 {
22339 if let Some(anchor) = anchor {
22340 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22341 else {
22342 continue;
22343 };
22344 let offset = text::ToOffset::to_offset(
22345 &anchor.text_anchor,
22346 &buffer_handle.read(cx).snapshot(),
22347 );
22348 let range = BufferOffset(offset)..BufferOffset(offset);
22349 new_selections_by_buffer
22350 .entry(buffer_handle)
22351 .or_insert((Vec::new(), None))
22352 .0
22353 .push(range)
22354 } else {
22355 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22356 else {
22357 continue;
22358 };
22359 new_selections_by_buffer
22360 .entry(buffer_handle)
22361 .or_insert((Vec::new(), None))
22362 .0
22363 .push(range)
22364 }
22365 }
22366 }
22367 }
22368 }
22369
22370 new_selections_by_buffer
22371 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22372
22373 if new_selections_by_buffer.is_empty() {
22374 return;
22375 }
22376
22377 // We defer the pane interaction because we ourselves are a workspace item
22378 // and activating a new item causes the pane to call a method on us reentrantly,
22379 // which panics if we're on the stack.
22380 window.defer(cx, move |window, cx| {
22381 workspace.update(cx, |workspace, cx| {
22382 let pane = if split {
22383 workspace.adjacent_pane(window, cx)
22384 } else {
22385 workspace.active_pane().clone()
22386 };
22387
22388 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22389 let buffer_read = buffer.read(cx);
22390 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22391 (true, project::File::from_dyn(Some(file)).is_some())
22392 } else {
22393 (false, false)
22394 };
22395
22396 // If project file is none workspace.open_project_item will fail to open the excerpt
22397 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22398 // so we check if there's a tab match in that case first
22399 let editor = (!has_file || !is_project_file)
22400 .then(|| {
22401 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22402 // so `workspace.open_project_item` will never find them, always opening a new editor.
22403 // Instead, we try to activate the existing editor in the pane first.
22404 let (editor, pane_item_index, pane_item_id) =
22405 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22406 let editor = item.downcast::<Editor>()?;
22407 let singleton_buffer =
22408 editor.read(cx).buffer().read(cx).as_singleton()?;
22409 if singleton_buffer == buffer {
22410 Some((editor, i, item.item_id()))
22411 } else {
22412 None
22413 }
22414 })?;
22415 pane.update(cx, |pane, cx| {
22416 pane.activate_item(pane_item_index, true, true, window, cx);
22417 if !PreviewTabsSettings::get_global(cx)
22418 .enable_preview_from_multibuffer
22419 {
22420 pane.unpreview_item_if_preview(pane_item_id);
22421 }
22422 });
22423 Some(editor)
22424 })
22425 .flatten()
22426 .unwrap_or_else(|| {
22427 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22428 .enable_keep_preview_on_code_navigation;
22429 let allow_new_preview =
22430 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22431 workspace.open_project_item::<Self>(
22432 pane.clone(),
22433 buffer,
22434 true,
22435 true,
22436 keep_old_preview,
22437 allow_new_preview,
22438 window,
22439 cx,
22440 )
22441 });
22442
22443 editor.update(cx, |editor, cx| {
22444 if has_file && !is_project_file {
22445 editor.set_read_only(true);
22446 }
22447 let autoscroll = match scroll_offset {
22448 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22449 None => Autoscroll::newest(),
22450 };
22451 let nav_history = editor.nav_history.take();
22452 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22453 let Some((&excerpt_id, _, buffer_snapshot)) =
22454 multibuffer_snapshot.as_singleton()
22455 else {
22456 return;
22457 };
22458 editor.change_selections(
22459 SelectionEffects::scroll(autoscroll),
22460 window,
22461 cx,
22462 |s| {
22463 s.select_ranges(ranges.into_iter().map(|range| {
22464 let range = buffer_snapshot.anchor_before(range.start)
22465 ..buffer_snapshot.anchor_after(range.end);
22466 multibuffer_snapshot
22467 .anchor_range_in_excerpt(excerpt_id, range)
22468 .unwrap()
22469 }));
22470 },
22471 );
22472 editor.nav_history = nav_history;
22473 });
22474 }
22475 })
22476 });
22477 }
22478
22479 // Allow opening excerpts for buffers that either belong to the current project
22480 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22481 // are also supported so tests and other in-memory views keep working.
22482 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22483 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22484 }
22485
22486 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22487 let snapshot = self.buffer.read(cx).read(cx);
22488 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22489 Some(
22490 ranges
22491 .iter()
22492 .map(move |range| {
22493 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22494 })
22495 .collect(),
22496 )
22497 }
22498
22499 fn selection_replacement_ranges(
22500 &self,
22501 range: Range<MultiBufferOffsetUtf16>,
22502 cx: &mut App,
22503 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22504 let selections = self
22505 .selections
22506 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22507 let newest_selection = selections
22508 .iter()
22509 .max_by_key(|selection| selection.id)
22510 .unwrap();
22511 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22512 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22513 let snapshot = self.buffer.read(cx).read(cx);
22514 selections
22515 .into_iter()
22516 .map(|mut selection| {
22517 selection.start.0.0 =
22518 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22519 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22520 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22521 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22522 })
22523 .collect()
22524 }
22525
22526 fn report_editor_event(
22527 &self,
22528 reported_event: ReportEditorEvent,
22529 file_extension: Option<String>,
22530 cx: &App,
22531 ) {
22532 if cfg!(any(test, feature = "test-support")) {
22533 return;
22534 }
22535
22536 let Some(project) = &self.project else { return };
22537
22538 // If None, we are in a file without an extension
22539 let file = self
22540 .buffer
22541 .read(cx)
22542 .as_singleton()
22543 .and_then(|b| b.read(cx).file());
22544 let file_extension = file_extension.or(file
22545 .as_ref()
22546 .and_then(|file| Path::new(file.file_name(cx)).extension())
22547 .and_then(|e| e.to_str())
22548 .map(|a| a.to_string()));
22549
22550 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22551 .map(|vim_mode| vim_mode.0)
22552 .unwrap_or(false);
22553
22554 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22555 let copilot_enabled = edit_predictions_provider
22556 == language::language_settings::EditPredictionProvider::Copilot;
22557 let copilot_enabled_for_language = self
22558 .buffer
22559 .read(cx)
22560 .language_settings(cx)
22561 .show_edit_predictions;
22562
22563 let project = project.read(cx);
22564 let event_type = reported_event.event_type();
22565
22566 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22567 telemetry::event!(
22568 event_type,
22569 type = if auto_saved {"autosave"} else {"manual"},
22570 file_extension,
22571 vim_mode,
22572 copilot_enabled,
22573 copilot_enabled_for_language,
22574 edit_predictions_provider,
22575 is_via_ssh = project.is_via_remote_server(),
22576 );
22577 } else {
22578 telemetry::event!(
22579 event_type,
22580 file_extension,
22581 vim_mode,
22582 copilot_enabled,
22583 copilot_enabled_for_language,
22584 edit_predictions_provider,
22585 is_via_ssh = project.is_via_remote_server(),
22586 );
22587 };
22588 }
22589
22590 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22591 /// with each line being an array of {text, highlight} objects.
22592 fn copy_highlight_json(
22593 &mut self,
22594 _: &CopyHighlightJson,
22595 window: &mut Window,
22596 cx: &mut Context<Self>,
22597 ) {
22598 #[derive(Serialize)]
22599 struct Chunk<'a> {
22600 text: String,
22601 highlight: Option<&'a str>,
22602 }
22603
22604 let snapshot = self.buffer.read(cx).snapshot(cx);
22605 let range = self
22606 .selected_text_range(false, window, cx)
22607 .and_then(|selection| {
22608 if selection.range.is_empty() {
22609 None
22610 } else {
22611 Some(
22612 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22613 selection.range.start,
22614 )))
22615 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22616 selection.range.end,
22617 ))),
22618 )
22619 }
22620 })
22621 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22622
22623 let chunks = snapshot.chunks(range, true);
22624 let mut lines = Vec::new();
22625 let mut line: VecDeque<Chunk> = VecDeque::new();
22626
22627 let Some(style) = self.style.as_ref() else {
22628 return;
22629 };
22630
22631 for chunk in chunks {
22632 let highlight = chunk
22633 .syntax_highlight_id
22634 .and_then(|id| id.name(&style.syntax));
22635 let mut chunk_lines = chunk.text.split('\n').peekable();
22636 while let Some(text) = chunk_lines.next() {
22637 let mut merged_with_last_token = false;
22638 if let Some(last_token) = line.back_mut()
22639 && last_token.highlight == highlight
22640 {
22641 last_token.text.push_str(text);
22642 merged_with_last_token = true;
22643 }
22644
22645 if !merged_with_last_token {
22646 line.push_back(Chunk {
22647 text: text.into(),
22648 highlight,
22649 });
22650 }
22651
22652 if chunk_lines.peek().is_some() {
22653 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22654 line.pop_front();
22655 }
22656 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22657 line.pop_back();
22658 }
22659
22660 lines.push(mem::take(&mut line));
22661 }
22662 }
22663 }
22664
22665 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22666 return;
22667 };
22668 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22669 }
22670
22671 pub fn open_context_menu(
22672 &mut self,
22673 _: &OpenContextMenu,
22674 window: &mut Window,
22675 cx: &mut Context<Self>,
22676 ) {
22677 self.request_autoscroll(Autoscroll::newest(), cx);
22678 let position = self
22679 .selections
22680 .newest_display(&self.display_snapshot(cx))
22681 .start;
22682 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22683 }
22684
22685 pub fn replay_insert_event(
22686 &mut self,
22687 text: &str,
22688 relative_utf16_range: Option<Range<isize>>,
22689 window: &mut Window,
22690 cx: &mut Context<Self>,
22691 ) {
22692 if !self.input_enabled {
22693 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22694 return;
22695 }
22696 if let Some(relative_utf16_range) = relative_utf16_range {
22697 let selections = self
22698 .selections
22699 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22700 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22701 let new_ranges = selections.into_iter().map(|range| {
22702 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22703 range
22704 .head()
22705 .0
22706 .0
22707 .saturating_add_signed(relative_utf16_range.start),
22708 ));
22709 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22710 range
22711 .head()
22712 .0
22713 .0
22714 .saturating_add_signed(relative_utf16_range.end),
22715 ));
22716 start..end
22717 });
22718 s.select_ranges(new_ranges);
22719 });
22720 }
22721
22722 self.handle_input(text, window, cx);
22723 }
22724
22725 pub fn is_focused(&self, window: &Window) -> bool {
22726 self.focus_handle.is_focused(window)
22727 }
22728
22729 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22730 cx.emit(EditorEvent::Focused);
22731
22732 if let Some(descendant) = self
22733 .last_focused_descendant
22734 .take()
22735 .and_then(|descendant| descendant.upgrade())
22736 {
22737 window.focus(&descendant);
22738 } else {
22739 if let Some(blame) = self.blame.as_ref() {
22740 blame.update(cx, GitBlame::focus)
22741 }
22742
22743 self.blink_manager.update(cx, BlinkManager::enable);
22744 self.show_cursor_names(window, cx);
22745 self.buffer.update(cx, |buffer, cx| {
22746 buffer.finalize_last_transaction(cx);
22747 if self.leader_id.is_none() {
22748 buffer.set_active_selections(
22749 &self.selections.disjoint_anchors_arc(),
22750 self.selections.line_mode(),
22751 self.cursor_shape,
22752 cx,
22753 );
22754 }
22755 });
22756
22757 if let Some(position_map) = self.last_position_map.clone() {
22758 EditorElement::mouse_moved(
22759 self,
22760 &MouseMoveEvent {
22761 position: window.mouse_position(),
22762 pressed_button: None,
22763 modifiers: window.modifiers(),
22764 },
22765 &position_map,
22766 window,
22767 cx,
22768 );
22769 }
22770 }
22771 }
22772
22773 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22774 cx.emit(EditorEvent::FocusedIn)
22775 }
22776
22777 fn handle_focus_out(
22778 &mut self,
22779 event: FocusOutEvent,
22780 _window: &mut Window,
22781 cx: &mut Context<Self>,
22782 ) {
22783 if event.blurred != self.focus_handle {
22784 self.last_focused_descendant = Some(event.blurred);
22785 }
22786 self.selection_drag_state = SelectionDragState::None;
22787 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22788 }
22789
22790 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22791 self.blink_manager.update(cx, BlinkManager::disable);
22792 self.buffer
22793 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22794
22795 if let Some(blame) = self.blame.as_ref() {
22796 blame.update(cx, GitBlame::blur)
22797 }
22798 if !self.hover_state.focused(window, cx) {
22799 hide_hover(self, cx);
22800 }
22801 if !self
22802 .context_menu
22803 .borrow()
22804 .as_ref()
22805 .is_some_and(|context_menu| context_menu.focused(window, cx))
22806 {
22807 self.hide_context_menu(window, cx);
22808 }
22809 self.take_active_edit_prediction(cx);
22810 cx.emit(EditorEvent::Blurred);
22811 cx.notify();
22812 }
22813
22814 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22815 let mut pending: String = window
22816 .pending_input_keystrokes()
22817 .into_iter()
22818 .flatten()
22819 .filter_map(|keystroke| keystroke.key_char.clone())
22820 .collect();
22821
22822 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22823 pending = "".to_string();
22824 }
22825
22826 let existing_pending = self
22827 .text_highlights::<PendingInput>(cx)
22828 .map(|(_, ranges)| ranges.to_vec());
22829 if existing_pending.is_none() && pending.is_empty() {
22830 return;
22831 }
22832 let transaction =
22833 self.transact(window, cx, |this, window, cx| {
22834 let selections = this
22835 .selections
22836 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22837 let edits = selections
22838 .iter()
22839 .map(|selection| (selection.end..selection.end, pending.clone()));
22840 this.edit(edits, cx);
22841 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22842 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22843 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22844 }));
22845 });
22846 if let Some(existing_ranges) = existing_pending {
22847 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22848 this.edit(edits, cx);
22849 }
22850 });
22851
22852 let snapshot = self.snapshot(window, cx);
22853 let ranges = self
22854 .selections
22855 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22856 .into_iter()
22857 .map(|selection| {
22858 snapshot.buffer_snapshot().anchor_after(selection.end)
22859 ..snapshot
22860 .buffer_snapshot()
22861 .anchor_before(selection.end + pending.len())
22862 })
22863 .collect();
22864
22865 if pending.is_empty() {
22866 self.clear_highlights::<PendingInput>(cx);
22867 } else {
22868 self.highlight_text::<PendingInput>(
22869 ranges,
22870 HighlightStyle {
22871 underline: Some(UnderlineStyle {
22872 thickness: px(1.),
22873 color: None,
22874 wavy: false,
22875 }),
22876 ..Default::default()
22877 },
22878 cx,
22879 );
22880 }
22881
22882 self.ime_transaction = self.ime_transaction.or(transaction);
22883 if let Some(transaction) = self.ime_transaction {
22884 self.buffer.update(cx, |buffer, cx| {
22885 buffer.group_until_transaction(transaction, cx);
22886 });
22887 }
22888
22889 if self.text_highlights::<PendingInput>(cx).is_none() {
22890 self.ime_transaction.take();
22891 }
22892 }
22893
22894 pub fn register_action_renderer(
22895 &mut self,
22896 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22897 ) -> Subscription {
22898 let id = self.next_editor_action_id.post_inc();
22899 self.editor_actions
22900 .borrow_mut()
22901 .insert(id, Box::new(listener));
22902
22903 let editor_actions = self.editor_actions.clone();
22904 Subscription::new(move || {
22905 editor_actions.borrow_mut().remove(&id);
22906 })
22907 }
22908
22909 pub fn register_action<A: Action>(
22910 &mut self,
22911 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22912 ) -> Subscription {
22913 let id = self.next_editor_action_id.post_inc();
22914 let listener = Arc::new(listener);
22915 self.editor_actions.borrow_mut().insert(
22916 id,
22917 Box::new(move |_, window, _| {
22918 let listener = listener.clone();
22919 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22920 let action = action.downcast_ref().unwrap();
22921 if phase == DispatchPhase::Bubble {
22922 listener(action, window, cx)
22923 }
22924 })
22925 }),
22926 );
22927
22928 let editor_actions = self.editor_actions.clone();
22929 Subscription::new(move || {
22930 editor_actions.borrow_mut().remove(&id);
22931 })
22932 }
22933
22934 pub fn file_header_size(&self) -> u32 {
22935 FILE_HEADER_HEIGHT
22936 }
22937
22938 pub fn restore(
22939 &mut self,
22940 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22941 window: &mut Window,
22942 cx: &mut Context<Self>,
22943 ) {
22944 let workspace = self.workspace();
22945 let project = self.project();
22946 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22947 let mut tasks = Vec::new();
22948 for (buffer_id, changes) in revert_changes {
22949 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22950 buffer.update(cx, |buffer, cx| {
22951 buffer.edit(
22952 changes
22953 .into_iter()
22954 .map(|(range, text)| (range, text.to_string())),
22955 None,
22956 cx,
22957 );
22958 });
22959
22960 if let Some(project) =
22961 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22962 {
22963 project.update(cx, |project, cx| {
22964 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22965 })
22966 }
22967 }
22968 }
22969 tasks
22970 });
22971 cx.spawn_in(window, async move |_, cx| {
22972 for (buffer, task) in save_tasks {
22973 let result = task.await;
22974 if result.is_err() {
22975 let Some(path) = buffer
22976 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22977 .ok()
22978 else {
22979 continue;
22980 };
22981 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22982 let Some(task) = cx
22983 .update_window_entity(workspace, |workspace, window, cx| {
22984 workspace
22985 .open_path_preview(path, None, false, false, false, window, cx)
22986 })
22987 .ok()
22988 else {
22989 continue;
22990 };
22991 task.await.log_err();
22992 }
22993 }
22994 }
22995 })
22996 .detach();
22997 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22998 selections.refresh()
22999 });
23000 }
23001
23002 pub fn to_pixel_point(
23003 &mut self,
23004 source: multi_buffer::Anchor,
23005 editor_snapshot: &EditorSnapshot,
23006 window: &mut Window,
23007 cx: &App,
23008 ) -> Option<gpui::Point<Pixels>> {
23009 let source_point = source.to_display_point(editor_snapshot);
23010 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23011 }
23012
23013 pub fn display_to_pixel_point(
23014 &mut self,
23015 source: DisplayPoint,
23016 editor_snapshot: &EditorSnapshot,
23017 window: &mut Window,
23018 cx: &App,
23019 ) -> Option<gpui::Point<Pixels>> {
23020 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23021 let text_layout_details = self.text_layout_details(window);
23022 let scroll_top = text_layout_details
23023 .scroll_anchor
23024 .scroll_position(editor_snapshot)
23025 .y;
23026
23027 if source.row().as_f64() < scroll_top.floor() {
23028 return None;
23029 }
23030 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23031 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23032 Some(gpui::Point::new(source_x, source_y))
23033 }
23034
23035 pub fn has_visible_completions_menu(&self) -> bool {
23036 !self.edit_prediction_preview_is_active()
23037 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23038 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23039 })
23040 }
23041
23042 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23043 if self.mode.is_minimap() {
23044 return;
23045 }
23046 self.addons
23047 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23048 }
23049
23050 pub fn unregister_addon<T: Addon>(&mut self) {
23051 self.addons.remove(&std::any::TypeId::of::<T>());
23052 }
23053
23054 pub fn addon<T: Addon>(&self) -> Option<&T> {
23055 let type_id = std::any::TypeId::of::<T>();
23056 self.addons
23057 .get(&type_id)
23058 .and_then(|item| item.to_any().downcast_ref::<T>())
23059 }
23060
23061 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23062 let type_id = std::any::TypeId::of::<T>();
23063 self.addons
23064 .get_mut(&type_id)
23065 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23066 }
23067
23068 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23069 let text_layout_details = self.text_layout_details(window);
23070 let style = &text_layout_details.editor_style;
23071 let font_id = window.text_system().resolve_font(&style.text.font());
23072 let font_size = style.text.font_size.to_pixels(window.rem_size());
23073 let line_height = style.text.line_height_in_pixels(window.rem_size());
23074 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23075 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23076
23077 CharacterDimensions {
23078 em_width,
23079 em_advance,
23080 line_height,
23081 }
23082 }
23083
23084 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23085 self.load_diff_task.clone()
23086 }
23087
23088 fn read_metadata_from_db(
23089 &mut self,
23090 item_id: u64,
23091 workspace_id: WorkspaceId,
23092 window: &mut Window,
23093 cx: &mut Context<Editor>,
23094 ) {
23095 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23096 && !self.mode.is_minimap()
23097 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
23098 {
23099 let buffer_snapshot = OnceCell::new();
23100
23101 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23102 && !folds.is_empty()
23103 {
23104 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23105 self.fold_ranges(
23106 folds
23107 .into_iter()
23108 .map(|(start, end)| {
23109 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23110 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23111 })
23112 .collect(),
23113 false,
23114 window,
23115 cx,
23116 );
23117 }
23118
23119 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23120 && !selections.is_empty()
23121 {
23122 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23123 // skip adding the initial selection to selection history
23124 self.selection_history.mode = SelectionHistoryMode::Skipping;
23125 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23126 s.select_ranges(selections.into_iter().map(|(start, end)| {
23127 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23128 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23129 }));
23130 });
23131 self.selection_history.mode = SelectionHistoryMode::Normal;
23132 };
23133 }
23134
23135 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23136 }
23137
23138 fn update_lsp_data(
23139 &mut self,
23140 for_buffer: Option<BufferId>,
23141 window: &mut Window,
23142 cx: &mut Context<'_, Self>,
23143 ) {
23144 self.pull_diagnostics(for_buffer, window, cx);
23145 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23146 }
23147
23148 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23149 if self.ignore_lsp_data() {
23150 return;
23151 }
23152 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23153 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23154 }
23155 }
23156
23157 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23158 if self.ignore_lsp_data() {
23159 return;
23160 }
23161
23162 if !self.registered_buffers.contains_key(&buffer_id)
23163 && let Some(project) = self.project.as_ref()
23164 {
23165 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23166 project.update(cx, |project, cx| {
23167 self.registered_buffers.insert(
23168 buffer_id,
23169 project.register_buffer_with_language_servers(&buffer, cx),
23170 );
23171 });
23172 } else {
23173 self.registered_buffers.remove(&buffer_id);
23174 }
23175 }
23176 }
23177
23178 fn ignore_lsp_data(&self) -> bool {
23179 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23180 // skip any LSP updates for it.
23181 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23182 }
23183
23184 fn create_style(&self, cx: &App) -> EditorStyle {
23185 let settings = ThemeSettings::get_global(cx);
23186
23187 let mut text_style = match self.mode {
23188 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23189 color: cx.theme().colors().editor_foreground,
23190 font_family: settings.ui_font.family.clone(),
23191 font_features: settings.ui_font.features.clone(),
23192 font_fallbacks: settings.ui_font.fallbacks.clone(),
23193 font_size: rems(0.875).into(),
23194 font_weight: settings.ui_font.weight,
23195 line_height: relative(settings.buffer_line_height.value()),
23196 ..Default::default()
23197 },
23198 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23199 color: cx.theme().colors().editor_foreground,
23200 font_family: settings.buffer_font.family.clone(),
23201 font_features: settings.buffer_font.features.clone(),
23202 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23203 font_size: settings.buffer_font_size(cx).into(),
23204 font_weight: settings.buffer_font.weight,
23205 line_height: relative(settings.buffer_line_height.value()),
23206 ..Default::default()
23207 },
23208 };
23209 if let Some(text_style_refinement) = &self.text_style_refinement {
23210 text_style.refine(text_style_refinement)
23211 }
23212
23213 let background = match self.mode {
23214 EditorMode::SingleLine => cx.theme().system().transparent,
23215 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23216 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23217 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23218 };
23219
23220 EditorStyle {
23221 background,
23222 border: cx.theme().colors().border,
23223 local_player: cx.theme().players().local(),
23224 text: text_style,
23225 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23226 syntax: cx.theme().syntax().clone(),
23227 status: cx.theme().status().clone(),
23228 inlay_hints_style: make_inlay_hints_style(cx),
23229 edit_prediction_styles: make_suggestion_styles(cx),
23230 unnecessary_code_fade: settings.unnecessary_code_fade,
23231 show_underlines: self.diagnostics_enabled(),
23232 }
23233 }
23234}
23235
23236fn edit_for_markdown_paste<'a>(
23237 buffer: &MultiBufferSnapshot,
23238 range: Range<MultiBufferOffset>,
23239 to_insert: &'a str,
23240 url: Option<url::Url>,
23241) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23242 if url.is_none() {
23243 return (range, Cow::Borrowed(to_insert));
23244 };
23245
23246 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23247
23248 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23249 Cow::Borrowed(to_insert)
23250 } else {
23251 Cow::Owned(format!("[{old_text}]({to_insert})"))
23252 };
23253 (range, new_text)
23254}
23255
23256fn process_completion_for_edit(
23257 completion: &Completion,
23258 intent: CompletionIntent,
23259 buffer: &Entity<Buffer>,
23260 cursor_position: &text::Anchor,
23261 cx: &mut Context<Editor>,
23262) -> CompletionEdit {
23263 let buffer = buffer.read(cx);
23264 let buffer_snapshot = buffer.snapshot();
23265 let (snippet, new_text) = if completion.is_snippet() {
23266 let mut snippet_source = completion.new_text.clone();
23267 // Workaround for typescript language server issues so that methods don't expand within
23268 // strings and functions with type expressions. The previous point is used because the query
23269 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23270 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23271 let previous_point = if previous_point.column > 0 {
23272 cursor_position.to_previous_offset(&buffer_snapshot)
23273 } else {
23274 cursor_position.to_offset(&buffer_snapshot)
23275 };
23276 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23277 && scope.prefers_label_for_snippet_in_completion()
23278 && let Some(label) = completion.label()
23279 && matches!(
23280 completion.kind(),
23281 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23282 )
23283 {
23284 snippet_source = label;
23285 }
23286 match Snippet::parse(&snippet_source).log_err() {
23287 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23288 None => (None, completion.new_text.clone()),
23289 }
23290 } else {
23291 (None, completion.new_text.clone())
23292 };
23293
23294 let mut range_to_replace = {
23295 let replace_range = &completion.replace_range;
23296 if let CompletionSource::Lsp {
23297 insert_range: Some(insert_range),
23298 ..
23299 } = &completion.source
23300 {
23301 debug_assert_eq!(
23302 insert_range.start, replace_range.start,
23303 "insert_range and replace_range should start at the same position"
23304 );
23305 debug_assert!(
23306 insert_range
23307 .start
23308 .cmp(cursor_position, &buffer_snapshot)
23309 .is_le(),
23310 "insert_range should start before or at cursor position"
23311 );
23312 debug_assert!(
23313 replace_range
23314 .start
23315 .cmp(cursor_position, &buffer_snapshot)
23316 .is_le(),
23317 "replace_range should start before or at cursor position"
23318 );
23319
23320 let should_replace = match intent {
23321 CompletionIntent::CompleteWithInsert => false,
23322 CompletionIntent::CompleteWithReplace => true,
23323 CompletionIntent::Complete | CompletionIntent::Compose => {
23324 let insert_mode =
23325 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23326 .completions
23327 .lsp_insert_mode;
23328 match insert_mode {
23329 LspInsertMode::Insert => false,
23330 LspInsertMode::Replace => true,
23331 LspInsertMode::ReplaceSubsequence => {
23332 let mut text_to_replace = buffer.chars_for_range(
23333 buffer.anchor_before(replace_range.start)
23334 ..buffer.anchor_after(replace_range.end),
23335 );
23336 let mut current_needle = text_to_replace.next();
23337 for haystack_ch in completion.label.text.chars() {
23338 if let Some(needle_ch) = current_needle
23339 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23340 {
23341 current_needle = text_to_replace.next();
23342 }
23343 }
23344 current_needle.is_none()
23345 }
23346 LspInsertMode::ReplaceSuffix => {
23347 if replace_range
23348 .end
23349 .cmp(cursor_position, &buffer_snapshot)
23350 .is_gt()
23351 {
23352 let range_after_cursor = *cursor_position..replace_range.end;
23353 let text_after_cursor = buffer
23354 .text_for_range(
23355 buffer.anchor_before(range_after_cursor.start)
23356 ..buffer.anchor_after(range_after_cursor.end),
23357 )
23358 .collect::<String>()
23359 .to_ascii_lowercase();
23360 completion
23361 .label
23362 .text
23363 .to_ascii_lowercase()
23364 .ends_with(&text_after_cursor)
23365 } else {
23366 true
23367 }
23368 }
23369 }
23370 }
23371 };
23372
23373 if should_replace {
23374 replace_range.clone()
23375 } else {
23376 insert_range.clone()
23377 }
23378 } else {
23379 replace_range.clone()
23380 }
23381 };
23382
23383 if range_to_replace
23384 .end
23385 .cmp(cursor_position, &buffer_snapshot)
23386 .is_lt()
23387 {
23388 range_to_replace.end = *cursor_position;
23389 }
23390
23391 let replace_range = range_to_replace.to_offset(buffer);
23392 CompletionEdit {
23393 new_text,
23394 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23395 snippet,
23396 }
23397}
23398
23399struct CompletionEdit {
23400 new_text: String,
23401 replace_range: Range<BufferOffset>,
23402 snippet: Option<Snippet>,
23403}
23404
23405fn insert_extra_newline_brackets(
23406 buffer: &MultiBufferSnapshot,
23407 range: Range<MultiBufferOffset>,
23408 language: &language::LanguageScope,
23409) -> bool {
23410 let leading_whitespace_len = buffer
23411 .reversed_chars_at(range.start)
23412 .take_while(|c| c.is_whitespace() && *c != '\n')
23413 .map(|c| c.len_utf8())
23414 .sum::<usize>();
23415 let trailing_whitespace_len = buffer
23416 .chars_at(range.end)
23417 .take_while(|c| c.is_whitespace() && *c != '\n')
23418 .map(|c| c.len_utf8())
23419 .sum::<usize>();
23420 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23421
23422 language.brackets().any(|(pair, enabled)| {
23423 let pair_start = pair.start.trim_end();
23424 let pair_end = pair.end.trim_start();
23425
23426 enabled
23427 && pair.newline
23428 && buffer.contains_str_at(range.end, pair_end)
23429 && buffer.contains_str_at(
23430 range.start.saturating_sub_usize(pair_start.len()),
23431 pair_start,
23432 )
23433 })
23434}
23435
23436fn insert_extra_newline_tree_sitter(
23437 buffer: &MultiBufferSnapshot,
23438 range: Range<MultiBufferOffset>,
23439) -> bool {
23440 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23441 [(buffer, range, _)] => (*buffer, range.clone()),
23442 _ => return false,
23443 };
23444 let pair = {
23445 let mut result: Option<BracketMatch<usize>> = None;
23446
23447 for pair in buffer
23448 .all_bracket_ranges(range.start.0..range.end.0)
23449 .filter(move |pair| {
23450 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23451 })
23452 {
23453 let len = pair.close_range.end - pair.open_range.start;
23454
23455 if let Some(existing) = &result {
23456 let existing_len = existing.close_range.end - existing.open_range.start;
23457 if len > existing_len {
23458 continue;
23459 }
23460 }
23461
23462 result = Some(pair);
23463 }
23464
23465 result
23466 };
23467 let Some(pair) = pair else {
23468 return false;
23469 };
23470 pair.newline_only
23471 && buffer
23472 .chars_for_range(pair.open_range.end..range.start.0)
23473 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23474 .all(|c| c.is_whitespace() && c != '\n')
23475}
23476
23477fn update_uncommitted_diff_for_buffer(
23478 editor: Entity<Editor>,
23479 project: &Entity<Project>,
23480 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23481 buffer: Entity<MultiBuffer>,
23482 cx: &mut App,
23483) -> Task<()> {
23484 let mut tasks = Vec::new();
23485 project.update(cx, |project, cx| {
23486 for buffer in buffers {
23487 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23488 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23489 }
23490 }
23491 });
23492 cx.spawn(async move |cx| {
23493 let diffs = future::join_all(tasks).await;
23494 if editor
23495 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23496 .unwrap_or(false)
23497 {
23498 return;
23499 }
23500
23501 buffer
23502 .update(cx, |buffer, cx| {
23503 for diff in diffs.into_iter().flatten() {
23504 buffer.add_diff(diff, cx);
23505 }
23506 })
23507 .ok();
23508 })
23509}
23510
23511fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23512 let tab_size = tab_size.get() as usize;
23513 let mut width = offset;
23514
23515 for ch in text.chars() {
23516 width += if ch == '\t' {
23517 tab_size - (width % tab_size)
23518 } else {
23519 1
23520 };
23521 }
23522
23523 width - offset
23524}
23525
23526#[cfg(test)]
23527mod tests {
23528 use super::*;
23529
23530 #[test]
23531 fn test_string_size_with_expanded_tabs() {
23532 let nz = |val| NonZeroU32::new(val).unwrap();
23533 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23534 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23535 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23536 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23537 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23538 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23539 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23540 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23541 }
23542}
23543
23544/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23545struct WordBreakingTokenizer<'a> {
23546 input: &'a str,
23547}
23548
23549impl<'a> WordBreakingTokenizer<'a> {
23550 fn new(input: &'a str) -> Self {
23551 Self { input }
23552 }
23553}
23554
23555fn is_char_ideographic(ch: char) -> bool {
23556 use unicode_script::Script::*;
23557 use unicode_script::UnicodeScript;
23558 matches!(ch.script(), Han | Tangut | Yi)
23559}
23560
23561fn is_grapheme_ideographic(text: &str) -> bool {
23562 text.chars().any(is_char_ideographic)
23563}
23564
23565fn is_grapheme_whitespace(text: &str) -> bool {
23566 text.chars().any(|x| x.is_whitespace())
23567}
23568
23569fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23570 text.chars()
23571 .next()
23572 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23573}
23574
23575#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23576enum WordBreakToken<'a> {
23577 Word { token: &'a str, grapheme_len: usize },
23578 InlineWhitespace { token: &'a str, grapheme_len: usize },
23579 Newline,
23580}
23581
23582impl<'a> Iterator for WordBreakingTokenizer<'a> {
23583 /// Yields a span, the count of graphemes in the token, and whether it was
23584 /// whitespace. Note that it also breaks at word boundaries.
23585 type Item = WordBreakToken<'a>;
23586
23587 fn next(&mut self) -> Option<Self::Item> {
23588 use unicode_segmentation::UnicodeSegmentation;
23589 if self.input.is_empty() {
23590 return None;
23591 }
23592
23593 let mut iter = self.input.graphemes(true).peekable();
23594 let mut offset = 0;
23595 let mut grapheme_len = 0;
23596 if let Some(first_grapheme) = iter.next() {
23597 let is_newline = first_grapheme == "\n";
23598 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23599 offset += first_grapheme.len();
23600 grapheme_len += 1;
23601 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23602 if let Some(grapheme) = iter.peek().copied()
23603 && should_stay_with_preceding_ideograph(grapheme)
23604 {
23605 offset += grapheme.len();
23606 grapheme_len += 1;
23607 }
23608 } else {
23609 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23610 let mut next_word_bound = words.peek().copied();
23611 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23612 next_word_bound = words.next();
23613 }
23614 while let Some(grapheme) = iter.peek().copied() {
23615 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23616 break;
23617 };
23618 if is_grapheme_whitespace(grapheme) != is_whitespace
23619 || (grapheme == "\n") != is_newline
23620 {
23621 break;
23622 };
23623 offset += grapheme.len();
23624 grapheme_len += 1;
23625 iter.next();
23626 }
23627 }
23628 let token = &self.input[..offset];
23629 self.input = &self.input[offset..];
23630 if token == "\n" {
23631 Some(WordBreakToken::Newline)
23632 } else if is_whitespace {
23633 Some(WordBreakToken::InlineWhitespace {
23634 token,
23635 grapheme_len,
23636 })
23637 } else {
23638 Some(WordBreakToken::Word {
23639 token,
23640 grapheme_len,
23641 })
23642 }
23643 } else {
23644 None
23645 }
23646 }
23647}
23648
23649#[test]
23650fn test_word_breaking_tokenizer() {
23651 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23652 ("", &[]),
23653 (" ", &[whitespace(" ", 2)]),
23654 ("Ʒ", &[word("Ʒ", 1)]),
23655 ("Ǽ", &[word("Ǽ", 1)]),
23656 ("⋑", &[word("⋑", 1)]),
23657 ("⋑⋑", &[word("⋑⋑", 2)]),
23658 (
23659 "原理,进而",
23660 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23661 ),
23662 (
23663 "hello world",
23664 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23665 ),
23666 (
23667 "hello, world",
23668 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23669 ),
23670 (
23671 " hello world",
23672 &[
23673 whitespace(" ", 2),
23674 word("hello", 5),
23675 whitespace(" ", 1),
23676 word("world", 5),
23677 ],
23678 ),
23679 (
23680 "这是什么 \n 钢笔",
23681 &[
23682 word("这", 1),
23683 word("是", 1),
23684 word("什", 1),
23685 word("么", 1),
23686 whitespace(" ", 1),
23687 newline(),
23688 whitespace(" ", 1),
23689 word("钢", 1),
23690 word("笔", 1),
23691 ],
23692 ),
23693 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23694 ];
23695
23696 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23697 WordBreakToken::Word {
23698 token,
23699 grapheme_len,
23700 }
23701 }
23702
23703 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23704 WordBreakToken::InlineWhitespace {
23705 token,
23706 grapheme_len,
23707 }
23708 }
23709
23710 fn newline() -> WordBreakToken<'static> {
23711 WordBreakToken::Newline
23712 }
23713
23714 for (input, result) in tests {
23715 assert_eq!(
23716 WordBreakingTokenizer::new(input)
23717 .collect::<Vec<_>>()
23718 .as_slice(),
23719 *result,
23720 );
23721 }
23722}
23723
23724fn wrap_with_prefix(
23725 first_line_prefix: String,
23726 subsequent_lines_prefix: String,
23727 unwrapped_text: String,
23728 wrap_column: usize,
23729 tab_size: NonZeroU32,
23730 preserve_existing_whitespace: bool,
23731) -> String {
23732 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23733 let subsequent_lines_prefix_len =
23734 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23735 let mut wrapped_text = String::new();
23736 let mut current_line = first_line_prefix;
23737 let mut is_first_line = true;
23738
23739 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23740 let mut current_line_len = first_line_prefix_len;
23741 let mut in_whitespace = false;
23742 for token in tokenizer {
23743 let have_preceding_whitespace = in_whitespace;
23744 match token {
23745 WordBreakToken::Word {
23746 token,
23747 grapheme_len,
23748 } => {
23749 in_whitespace = false;
23750 let current_prefix_len = if is_first_line {
23751 first_line_prefix_len
23752 } else {
23753 subsequent_lines_prefix_len
23754 };
23755 if current_line_len + grapheme_len > wrap_column
23756 && current_line_len != current_prefix_len
23757 {
23758 wrapped_text.push_str(current_line.trim_end());
23759 wrapped_text.push('\n');
23760 is_first_line = false;
23761 current_line = subsequent_lines_prefix.clone();
23762 current_line_len = subsequent_lines_prefix_len;
23763 }
23764 current_line.push_str(token);
23765 current_line_len += grapheme_len;
23766 }
23767 WordBreakToken::InlineWhitespace {
23768 mut token,
23769 mut grapheme_len,
23770 } => {
23771 in_whitespace = true;
23772 if have_preceding_whitespace && !preserve_existing_whitespace {
23773 continue;
23774 }
23775 if !preserve_existing_whitespace {
23776 // Keep a single whitespace grapheme as-is
23777 if let Some(first) =
23778 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23779 {
23780 token = first;
23781 } else {
23782 token = " ";
23783 }
23784 grapheme_len = 1;
23785 }
23786 let current_prefix_len = if is_first_line {
23787 first_line_prefix_len
23788 } else {
23789 subsequent_lines_prefix_len
23790 };
23791 if current_line_len + grapheme_len > wrap_column {
23792 wrapped_text.push_str(current_line.trim_end());
23793 wrapped_text.push('\n');
23794 is_first_line = false;
23795 current_line = subsequent_lines_prefix.clone();
23796 current_line_len = subsequent_lines_prefix_len;
23797 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23798 current_line.push_str(token);
23799 current_line_len += grapheme_len;
23800 }
23801 }
23802 WordBreakToken::Newline => {
23803 in_whitespace = true;
23804 let current_prefix_len = if is_first_line {
23805 first_line_prefix_len
23806 } else {
23807 subsequent_lines_prefix_len
23808 };
23809 if preserve_existing_whitespace {
23810 wrapped_text.push_str(current_line.trim_end());
23811 wrapped_text.push('\n');
23812 is_first_line = false;
23813 current_line = subsequent_lines_prefix.clone();
23814 current_line_len = subsequent_lines_prefix_len;
23815 } else if have_preceding_whitespace {
23816 continue;
23817 } else if current_line_len + 1 > wrap_column
23818 && current_line_len != current_prefix_len
23819 {
23820 wrapped_text.push_str(current_line.trim_end());
23821 wrapped_text.push('\n');
23822 is_first_line = false;
23823 current_line = subsequent_lines_prefix.clone();
23824 current_line_len = subsequent_lines_prefix_len;
23825 } else if current_line_len != current_prefix_len {
23826 current_line.push(' ');
23827 current_line_len += 1;
23828 }
23829 }
23830 }
23831 }
23832
23833 if !current_line.is_empty() {
23834 wrapped_text.push_str(¤t_line);
23835 }
23836 wrapped_text
23837}
23838
23839#[test]
23840fn test_wrap_with_prefix() {
23841 assert_eq!(
23842 wrap_with_prefix(
23843 "# ".to_string(),
23844 "# ".to_string(),
23845 "abcdefg".to_string(),
23846 4,
23847 NonZeroU32::new(4).unwrap(),
23848 false,
23849 ),
23850 "# abcdefg"
23851 );
23852 assert_eq!(
23853 wrap_with_prefix(
23854 "".to_string(),
23855 "".to_string(),
23856 "\thello world".to_string(),
23857 8,
23858 NonZeroU32::new(4).unwrap(),
23859 false,
23860 ),
23861 "hello\nworld"
23862 );
23863 assert_eq!(
23864 wrap_with_prefix(
23865 "// ".to_string(),
23866 "// ".to_string(),
23867 "xx \nyy zz aa bb cc".to_string(),
23868 12,
23869 NonZeroU32::new(4).unwrap(),
23870 false,
23871 ),
23872 "// xx yy zz\n// aa bb cc"
23873 );
23874 assert_eq!(
23875 wrap_with_prefix(
23876 String::new(),
23877 String::new(),
23878 "这是什么 \n 钢笔".to_string(),
23879 3,
23880 NonZeroU32::new(4).unwrap(),
23881 false,
23882 ),
23883 "这是什\n么 钢\n笔"
23884 );
23885 assert_eq!(
23886 wrap_with_prefix(
23887 String::new(),
23888 String::new(),
23889 format!("foo{}bar", '\u{2009}'), // thin space
23890 80,
23891 NonZeroU32::new(4).unwrap(),
23892 false,
23893 ),
23894 format!("foo{}bar", '\u{2009}')
23895 );
23896}
23897
23898pub trait CollaborationHub {
23899 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23900 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23901 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23902}
23903
23904impl CollaborationHub for Entity<Project> {
23905 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23906 self.read(cx).collaborators()
23907 }
23908
23909 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23910 self.read(cx).user_store().read(cx).participant_indices()
23911 }
23912
23913 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23914 let this = self.read(cx);
23915 let user_ids = this.collaborators().values().map(|c| c.user_id);
23916 this.user_store().read(cx).participant_names(user_ids, cx)
23917 }
23918}
23919
23920pub trait SemanticsProvider {
23921 fn hover(
23922 &self,
23923 buffer: &Entity<Buffer>,
23924 position: text::Anchor,
23925 cx: &mut App,
23926 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23927
23928 fn inline_values(
23929 &self,
23930 buffer_handle: Entity<Buffer>,
23931 range: Range<text::Anchor>,
23932 cx: &mut App,
23933 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23934
23935 fn applicable_inlay_chunks(
23936 &self,
23937 buffer: &Entity<Buffer>,
23938 ranges: &[Range<text::Anchor>],
23939 cx: &mut App,
23940 ) -> Vec<Range<BufferRow>>;
23941
23942 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23943
23944 fn inlay_hints(
23945 &self,
23946 invalidate: InvalidationStrategy,
23947 buffer: Entity<Buffer>,
23948 ranges: Vec<Range<text::Anchor>>,
23949 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23950 cx: &mut App,
23951 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23952
23953 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23954
23955 fn document_highlights(
23956 &self,
23957 buffer: &Entity<Buffer>,
23958 position: text::Anchor,
23959 cx: &mut App,
23960 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23961
23962 fn definitions(
23963 &self,
23964 buffer: &Entity<Buffer>,
23965 position: text::Anchor,
23966 kind: GotoDefinitionKind,
23967 cx: &mut App,
23968 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23969
23970 fn range_for_rename(
23971 &self,
23972 buffer: &Entity<Buffer>,
23973 position: text::Anchor,
23974 cx: &mut App,
23975 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23976
23977 fn perform_rename(
23978 &self,
23979 buffer: &Entity<Buffer>,
23980 position: text::Anchor,
23981 new_name: String,
23982 cx: &mut App,
23983 ) -> Option<Task<Result<ProjectTransaction>>>;
23984}
23985
23986pub trait CompletionProvider {
23987 fn completions(
23988 &self,
23989 excerpt_id: ExcerptId,
23990 buffer: &Entity<Buffer>,
23991 buffer_position: text::Anchor,
23992 trigger: CompletionContext,
23993 window: &mut Window,
23994 cx: &mut Context<Editor>,
23995 ) -> Task<Result<Vec<CompletionResponse>>>;
23996
23997 fn resolve_completions(
23998 &self,
23999 _buffer: Entity<Buffer>,
24000 _completion_indices: Vec<usize>,
24001 _completions: Rc<RefCell<Box<[Completion]>>>,
24002 _cx: &mut Context<Editor>,
24003 ) -> Task<Result<bool>> {
24004 Task::ready(Ok(false))
24005 }
24006
24007 fn apply_additional_edits_for_completion(
24008 &self,
24009 _buffer: Entity<Buffer>,
24010 _completions: Rc<RefCell<Box<[Completion]>>>,
24011 _completion_index: usize,
24012 _push_to_history: bool,
24013 _cx: &mut Context<Editor>,
24014 ) -> Task<Result<Option<language::Transaction>>> {
24015 Task::ready(Ok(None))
24016 }
24017
24018 fn is_completion_trigger(
24019 &self,
24020 buffer: &Entity<Buffer>,
24021 position: language::Anchor,
24022 text: &str,
24023 trigger_in_words: bool,
24024 cx: &mut Context<Editor>,
24025 ) -> bool;
24026
24027 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24028
24029 fn sort_completions(&self) -> bool {
24030 true
24031 }
24032
24033 fn filter_completions(&self) -> bool {
24034 true
24035 }
24036
24037 fn show_snippets(&self) -> bool {
24038 false
24039 }
24040}
24041
24042pub trait CodeActionProvider {
24043 fn id(&self) -> Arc<str>;
24044
24045 fn code_actions(
24046 &self,
24047 buffer: &Entity<Buffer>,
24048 range: Range<text::Anchor>,
24049 window: &mut Window,
24050 cx: &mut App,
24051 ) -> Task<Result<Vec<CodeAction>>>;
24052
24053 fn apply_code_action(
24054 &self,
24055 buffer_handle: Entity<Buffer>,
24056 action: CodeAction,
24057 excerpt_id: ExcerptId,
24058 push_to_history: bool,
24059 window: &mut Window,
24060 cx: &mut App,
24061 ) -> Task<Result<ProjectTransaction>>;
24062}
24063
24064impl CodeActionProvider for Entity<Project> {
24065 fn id(&self) -> Arc<str> {
24066 "project".into()
24067 }
24068
24069 fn code_actions(
24070 &self,
24071 buffer: &Entity<Buffer>,
24072 range: Range<text::Anchor>,
24073 _window: &mut Window,
24074 cx: &mut App,
24075 ) -> Task<Result<Vec<CodeAction>>> {
24076 self.update(cx, |project, cx| {
24077 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24078 let code_actions = project.code_actions(buffer, range, None, cx);
24079 cx.background_spawn(async move {
24080 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24081 Ok(code_lens_actions
24082 .context("code lens fetch")?
24083 .into_iter()
24084 .flatten()
24085 .chain(
24086 code_actions
24087 .context("code action fetch")?
24088 .into_iter()
24089 .flatten(),
24090 )
24091 .collect())
24092 })
24093 })
24094 }
24095
24096 fn apply_code_action(
24097 &self,
24098 buffer_handle: Entity<Buffer>,
24099 action: CodeAction,
24100 _excerpt_id: ExcerptId,
24101 push_to_history: bool,
24102 _window: &mut Window,
24103 cx: &mut App,
24104 ) -> Task<Result<ProjectTransaction>> {
24105 self.update(cx, |project, cx| {
24106 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24107 })
24108 }
24109}
24110
24111fn snippet_completions(
24112 project: &Project,
24113 buffer: &Entity<Buffer>,
24114 buffer_anchor: text::Anchor,
24115 classifier: CharClassifier,
24116 cx: &mut App,
24117) -> Task<Result<CompletionResponse>> {
24118 let languages = buffer.read(cx).languages_at(buffer_anchor);
24119 let snippet_store = project.snippets().read(cx);
24120
24121 let scopes: Vec<_> = languages
24122 .iter()
24123 .filter_map(|language| {
24124 let language_name = language.lsp_id();
24125 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24126
24127 if snippets.is_empty() {
24128 None
24129 } else {
24130 Some((language.default_scope(), snippets))
24131 }
24132 })
24133 .collect();
24134
24135 if scopes.is_empty() {
24136 return Task::ready(Ok(CompletionResponse {
24137 completions: vec![],
24138 display_options: CompletionDisplayOptions::default(),
24139 is_incomplete: false,
24140 }));
24141 }
24142
24143 let snapshot = buffer.read(cx).text_snapshot();
24144 let executor = cx.background_executor().clone();
24145
24146 cx.background_spawn(async move {
24147 let is_word_char = |c| classifier.is_word(c);
24148
24149 let mut is_incomplete = false;
24150 let mut completions: Vec<Completion> = Vec::new();
24151
24152 const MAX_PREFIX_LEN: usize = 128;
24153 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24154 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24155 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24156
24157 let max_buffer_window: String = snapshot
24158 .text_for_range(window_start..buffer_offset)
24159 .collect();
24160
24161 if max_buffer_window.is_empty() {
24162 return Ok(CompletionResponse {
24163 completions: vec![],
24164 display_options: CompletionDisplayOptions::default(),
24165 is_incomplete: true,
24166 });
24167 }
24168
24169 for (_scope, snippets) in scopes.into_iter() {
24170 // Sort snippets by word count to match longer snippet prefixes first.
24171 let mut sorted_snippet_candidates = snippets
24172 .iter()
24173 .enumerate()
24174 .flat_map(|(snippet_ix, snippet)| {
24175 snippet
24176 .prefix
24177 .iter()
24178 .enumerate()
24179 .map(move |(prefix_ix, prefix)| {
24180 let word_count =
24181 snippet_candidate_suffixes(prefix, is_word_char).count();
24182 ((snippet_ix, prefix_ix), prefix, word_count)
24183 })
24184 })
24185 .collect_vec();
24186 sorted_snippet_candidates
24187 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24188
24189 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24190
24191 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24192 .take(
24193 sorted_snippet_candidates
24194 .first()
24195 .map(|(_, _, word_count)| *word_count)
24196 .unwrap_or_default(),
24197 )
24198 .collect_vec();
24199
24200 const MAX_RESULTS: usize = 100;
24201 // Each match also remembers how many characters from the buffer it consumed
24202 let mut matches: Vec<(StringMatch, usize)> = vec![];
24203
24204 let mut snippet_list_cutoff_index = 0;
24205 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24206 let word_count = buffer_index + 1;
24207 // Increase `snippet_list_cutoff_index` until we have all of the
24208 // snippets with sufficiently many words.
24209 while sorted_snippet_candidates
24210 .get(snippet_list_cutoff_index)
24211 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24212 *snippet_word_count >= word_count
24213 })
24214 {
24215 snippet_list_cutoff_index += 1;
24216 }
24217
24218 // Take only the candidates with at least `word_count` many words
24219 let snippet_candidates_at_word_len =
24220 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24221
24222 let candidates = snippet_candidates_at_word_len
24223 .iter()
24224 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24225 .enumerate() // index in `sorted_snippet_candidates`
24226 // First char must match
24227 .filter(|(_ix, prefix)| {
24228 itertools::equal(
24229 prefix
24230 .chars()
24231 .next()
24232 .into_iter()
24233 .flat_map(|c| c.to_lowercase()),
24234 buffer_window
24235 .chars()
24236 .next()
24237 .into_iter()
24238 .flat_map(|c| c.to_lowercase()),
24239 )
24240 })
24241 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24242 .collect::<Vec<StringMatchCandidate>>();
24243
24244 matches.extend(
24245 fuzzy::match_strings(
24246 &candidates,
24247 &buffer_window,
24248 buffer_window.chars().any(|c| c.is_uppercase()),
24249 true,
24250 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24251 &Default::default(),
24252 executor.clone(),
24253 )
24254 .await
24255 .into_iter()
24256 .map(|string_match| (string_match, buffer_window.len())),
24257 );
24258
24259 if matches.len() >= MAX_RESULTS {
24260 break;
24261 }
24262 }
24263
24264 let to_lsp = |point: &text::Anchor| {
24265 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24266 point_to_lsp(end)
24267 };
24268 let lsp_end = to_lsp(&buffer_anchor);
24269
24270 if matches.len() >= MAX_RESULTS {
24271 is_incomplete = true;
24272 }
24273
24274 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24275 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24276 sorted_snippet_candidates[string_match.candidate_id];
24277 let snippet = &snippets[snippet_index];
24278 let start = buffer_offset - buffer_window_len;
24279 let start = snapshot.anchor_before(start);
24280 let range = start..buffer_anchor;
24281 let lsp_start = to_lsp(&start);
24282 let lsp_range = lsp::Range {
24283 start: lsp_start,
24284 end: lsp_end,
24285 };
24286 Completion {
24287 replace_range: range,
24288 new_text: snippet.body.clone(),
24289 source: CompletionSource::Lsp {
24290 insert_range: None,
24291 server_id: LanguageServerId(usize::MAX),
24292 resolved: true,
24293 lsp_completion: Box::new(lsp::CompletionItem {
24294 label: snippet.prefix.first().unwrap().clone(),
24295 kind: Some(CompletionItemKind::SNIPPET),
24296 label_details: snippet.description.as_ref().map(|description| {
24297 lsp::CompletionItemLabelDetails {
24298 detail: Some(description.clone()),
24299 description: None,
24300 }
24301 }),
24302 insert_text_format: Some(InsertTextFormat::SNIPPET),
24303 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24304 lsp::InsertReplaceEdit {
24305 new_text: snippet.body.clone(),
24306 insert: lsp_range,
24307 replace: lsp_range,
24308 },
24309 )),
24310 filter_text: Some(snippet.body.clone()),
24311 sort_text: Some(char::MAX.to_string()),
24312 ..lsp::CompletionItem::default()
24313 }),
24314 lsp_defaults: None,
24315 },
24316 label: CodeLabel {
24317 text: matching_prefix.clone(),
24318 runs: Vec::new(),
24319 filter_range: 0..matching_prefix.len(),
24320 },
24321 icon_path: None,
24322 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24323 single_line: snippet.name.clone().into(),
24324 plain_text: snippet
24325 .description
24326 .clone()
24327 .map(|description| description.into()),
24328 }),
24329 insert_text_mode: None,
24330 confirm: None,
24331 match_start: Some(start),
24332 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24333 }
24334 }));
24335 }
24336
24337 Ok(CompletionResponse {
24338 completions,
24339 display_options: CompletionDisplayOptions::default(),
24340 is_incomplete,
24341 })
24342 })
24343}
24344
24345impl CompletionProvider for Entity<Project> {
24346 fn completions(
24347 &self,
24348 _excerpt_id: ExcerptId,
24349 buffer: &Entity<Buffer>,
24350 buffer_position: text::Anchor,
24351 options: CompletionContext,
24352 _window: &mut Window,
24353 cx: &mut Context<Editor>,
24354 ) -> Task<Result<Vec<CompletionResponse>>> {
24355 self.update(cx, |project, cx| {
24356 let task = project.completions(buffer, buffer_position, options, cx);
24357 cx.background_spawn(task)
24358 })
24359 }
24360
24361 fn resolve_completions(
24362 &self,
24363 buffer: Entity<Buffer>,
24364 completion_indices: Vec<usize>,
24365 completions: Rc<RefCell<Box<[Completion]>>>,
24366 cx: &mut Context<Editor>,
24367 ) -> Task<Result<bool>> {
24368 self.update(cx, |project, cx| {
24369 project.lsp_store().update(cx, |lsp_store, cx| {
24370 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24371 })
24372 })
24373 }
24374
24375 fn apply_additional_edits_for_completion(
24376 &self,
24377 buffer: Entity<Buffer>,
24378 completions: Rc<RefCell<Box<[Completion]>>>,
24379 completion_index: usize,
24380 push_to_history: bool,
24381 cx: &mut Context<Editor>,
24382 ) -> Task<Result<Option<language::Transaction>>> {
24383 self.update(cx, |project, cx| {
24384 project.lsp_store().update(cx, |lsp_store, cx| {
24385 lsp_store.apply_additional_edits_for_completion(
24386 buffer,
24387 completions,
24388 completion_index,
24389 push_to_history,
24390 cx,
24391 )
24392 })
24393 })
24394 }
24395
24396 fn is_completion_trigger(
24397 &self,
24398 buffer: &Entity<Buffer>,
24399 position: language::Anchor,
24400 text: &str,
24401 trigger_in_words: bool,
24402 cx: &mut Context<Editor>,
24403 ) -> bool {
24404 let mut chars = text.chars();
24405 let char = if let Some(char) = chars.next() {
24406 char
24407 } else {
24408 return false;
24409 };
24410 if chars.next().is_some() {
24411 return false;
24412 }
24413
24414 let buffer = buffer.read(cx);
24415 let snapshot = buffer.snapshot();
24416 let classifier = snapshot
24417 .char_classifier_at(position)
24418 .scope_context(Some(CharScopeContext::Completion));
24419 if trigger_in_words && classifier.is_word(char) {
24420 return true;
24421 }
24422
24423 buffer.completion_triggers().contains(text)
24424 }
24425
24426 fn show_snippets(&self) -> bool {
24427 true
24428 }
24429}
24430
24431impl SemanticsProvider for Entity<Project> {
24432 fn hover(
24433 &self,
24434 buffer: &Entity<Buffer>,
24435 position: text::Anchor,
24436 cx: &mut App,
24437 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24438 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24439 }
24440
24441 fn document_highlights(
24442 &self,
24443 buffer: &Entity<Buffer>,
24444 position: text::Anchor,
24445 cx: &mut App,
24446 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24447 Some(self.update(cx, |project, cx| {
24448 project.document_highlights(buffer, position, cx)
24449 }))
24450 }
24451
24452 fn definitions(
24453 &self,
24454 buffer: &Entity<Buffer>,
24455 position: text::Anchor,
24456 kind: GotoDefinitionKind,
24457 cx: &mut App,
24458 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24459 Some(self.update(cx, |project, cx| match kind {
24460 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24461 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24462 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24463 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24464 }))
24465 }
24466
24467 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24468 self.update(cx, |project, cx| {
24469 if project
24470 .active_debug_session(cx)
24471 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24472 {
24473 return true;
24474 }
24475
24476 buffer.update(cx, |buffer, cx| {
24477 project.any_language_server_supports_inlay_hints(buffer, cx)
24478 })
24479 })
24480 }
24481
24482 fn inline_values(
24483 &self,
24484 buffer_handle: Entity<Buffer>,
24485 range: Range<text::Anchor>,
24486 cx: &mut App,
24487 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24488 self.update(cx, |project, cx| {
24489 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24490
24491 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24492 })
24493 }
24494
24495 fn applicable_inlay_chunks(
24496 &self,
24497 buffer: &Entity<Buffer>,
24498 ranges: &[Range<text::Anchor>],
24499 cx: &mut App,
24500 ) -> Vec<Range<BufferRow>> {
24501 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24502 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24503 })
24504 }
24505
24506 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24507 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24508 lsp_store.invalidate_inlay_hints(for_buffers)
24509 });
24510 }
24511
24512 fn inlay_hints(
24513 &self,
24514 invalidate: InvalidationStrategy,
24515 buffer: Entity<Buffer>,
24516 ranges: Vec<Range<text::Anchor>>,
24517 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24518 cx: &mut App,
24519 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24520 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24521 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24522 }))
24523 }
24524
24525 fn range_for_rename(
24526 &self,
24527 buffer: &Entity<Buffer>,
24528 position: text::Anchor,
24529 cx: &mut App,
24530 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24531 Some(self.update(cx, |project, cx| {
24532 let buffer = buffer.clone();
24533 let task = project.prepare_rename(buffer.clone(), position, cx);
24534 cx.spawn(async move |_, cx| {
24535 Ok(match task.await? {
24536 PrepareRenameResponse::Success(range) => Some(range),
24537 PrepareRenameResponse::InvalidPosition => None,
24538 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24539 // Fallback on using TreeSitter info to determine identifier range
24540 buffer.read_with(cx, |buffer, _| {
24541 let snapshot = buffer.snapshot();
24542 let (range, kind) = snapshot.surrounding_word(position, None);
24543 if kind != Some(CharKind::Word) {
24544 return None;
24545 }
24546 Some(
24547 snapshot.anchor_before(range.start)
24548 ..snapshot.anchor_after(range.end),
24549 )
24550 })?
24551 }
24552 })
24553 })
24554 }))
24555 }
24556
24557 fn perform_rename(
24558 &self,
24559 buffer: &Entity<Buffer>,
24560 position: text::Anchor,
24561 new_name: String,
24562 cx: &mut App,
24563 ) -> Option<Task<Result<ProjectTransaction>>> {
24564 Some(self.update(cx, |project, cx| {
24565 project.perform_rename(buffer.clone(), position, new_name, cx)
24566 }))
24567 }
24568}
24569
24570fn consume_contiguous_rows(
24571 contiguous_row_selections: &mut Vec<Selection<Point>>,
24572 selection: &Selection<Point>,
24573 display_map: &DisplaySnapshot,
24574 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24575) -> (MultiBufferRow, MultiBufferRow) {
24576 contiguous_row_selections.push(selection.clone());
24577 let start_row = starting_row(selection, display_map);
24578 let mut end_row = ending_row(selection, display_map);
24579
24580 while let Some(next_selection) = selections.peek() {
24581 if next_selection.start.row <= end_row.0 {
24582 end_row = ending_row(next_selection, display_map);
24583 contiguous_row_selections.push(selections.next().unwrap().clone());
24584 } else {
24585 break;
24586 }
24587 }
24588 (start_row, end_row)
24589}
24590
24591fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24592 if selection.start.column > 0 {
24593 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24594 } else {
24595 MultiBufferRow(selection.start.row)
24596 }
24597}
24598
24599fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24600 if next_selection.end.column > 0 || next_selection.is_empty() {
24601 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24602 } else {
24603 MultiBufferRow(next_selection.end.row)
24604 }
24605}
24606
24607impl EditorSnapshot {
24608 pub fn remote_selections_in_range<'a>(
24609 &'a self,
24610 range: &'a Range<Anchor>,
24611 collaboration_hub: &dyn CollaborationHub,
24612 cx: &'a App,
24613 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24614 let participant_names = collaboration_hub.user_names(cx);
24615 let participant_indices = collaboration_hub.user_participant_indices(cx);
24616 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24617 let collaborators_by_replica_id = collaborators_by_peer_id
24618 .values()
24619 .map(|collaborator| (collaborator.replica_id, collaborator))
24620 .collect::<HashMap<_, _>>();
24621 self.buffer_snapshot()
24622 .selections_in_range(range, false)
24623 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24624 if replica_id == ReplicaId::AGENT {
24625 Some(RemoteSelection {
24626 replica_id,
24627 selection,
24628 cursor_shape,
24629 line_mode,
24630 collaborator_id: CollaboratorId::Agent,
24631 user_name: Some("Agent".into()),
24632 color: cx.theme().players().agent(),
24633 })
24634 } else {
24635 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24636 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24637 let user_name = participant_names.get(&collaborator.user_id).cloned();
24638 Some(RemoteSelection {
24639 replica_id,
24640 selection,
24641 cursor_shape,
24642 line_mode,
24643 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24644 user_name,
24645 color: if let Some(index) = participant_index {
24646 cx.theme().players().color_for_participant(index.0)
24647 } else {
24648 cx.theme().players().absent()
24649 },
24650 })
24651 }
24652 })
24653 }
24654
24655 pub fn hunks_for_ranges(
24656 &self,
24657 ranges: impl IntoIterator<Item = Range<Point>>,
24658 ) -> Vec<MultiBufferDiffHunk> {
24659 let mut hunks = Vec::new();
24660 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24661 HashMap::default();
24662 for query_range in ranges {
24663 let query_rows =
24664 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24665 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24666 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24667 ) {
24668 // Include deleted hunks that are adjacent to the query range, because
24669 // otherwise they would be missed.
24670 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24671 if hunk.status().is_deleted() {
24672 intersects_range |= hunk.row_range.start == query_rows.end;
24673 intersects_range |= hunk.row_range.end == query_rows.start;
24674 }
24675 if intersects_range {
24676 if !processed_buffer_rows
24677 .entry(hunk.buffer_id)
24678 .or_default()
24679 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24680 {
24681 continue;
24682 }
24683 hunks.push(hunk);
24684 }
24685 }
24686 }
24687
24688 hunks
24689 }
24690
24691 fn display_diff_hunks_for_rows<'a>(
24692 &'a self,
24693 display_rows: Range<DisplayRow>,
24694 folded_buffers: &'a HashSet<BufferId>,
24695 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24696 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24697 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24698
24699 self.buffer_snapshot()
24700 .diff_hunks_in_range(buffer_start..buffer_end)
24701 .filter_map(|hunk| {
24702 if folded_buffers.contains(&hunk.buffer_id) {
24703 return None;
24704 }
24705
24706 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24707 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24708
24709 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24710 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24711
24712 let display_hunk = if hunk_display_start.column() != 0 {
24713 DisplayDiffHunk::Folded {
24714 display_row: hunk_display_start.row(),
24715 }
24716 } else {
24717 let mut end_row = hunk_display_end.row();
24718 if hunk_display_end.column() > 0 {
24719 end_row.0 += 1;
24720 }
24721 let is_created_file = hunk.is_created_file();
24722
24723 DisplayDiffHunk::Unfolded {
24724 status: hunk.status(),
24725 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24726 ..hunk.diff_base_byte_range.end.0,
24727 word_diffs: hunk.word_diffs,
24728 display_row_range: hunk_display_start.row()..end_row,
24729 multi_buffer_range: Anchor::range_in_buffer(
24730 hunk.excerpt_id,
24731 hunk.buffer_range,
24732 ),
24733 is_created_file,
24734 }
24735 };
24736
24737 Some(display_hunk)
24738 })
24739 }
24740
24741 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24742 self.display_snapshot
24743 .buffer_snapshot()
24744 .language_at(position)
24745 }
24746
24747 pub fn is_focused(&self) -> bool {
24748 self.is_focused
24749 }
24750
24751 pub fn placeholder_text(&self) -> Option<String> {
24752 self.placeholder_display_snapshot
24753 .as_ref()
24754 .map(|display_map| display_map.text())
24755 }
24756
24757 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24758 self.scroll_anchor.scroll_position(&self.display_snapshot)
24759 }
24760
24761 pub fn gutter_dimensions(
24762 &self,
24763 font_id: FontId,
24764 font_size: Pixels,
24765 style: &EditorStyle,
24766 window: &mut Window,
24767 cx: &App,
24768 ) -> GutterDimensions {
24769 if self.show_gutter
24770 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24771 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24772 {
24773 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24774 matches!(
24775 ProjectSettings::get_global(cx).git.git_gutter,
24776 GitGutterSetting::TrackedFiles
24777 )
24778 });
24779 let gutter_settings = EditorSettings::get_global(cx).gutter;
24780 let show_line_numbers = self
24781 .show_line_numbers
24782 .unwrap_or(gutter_settings.line_numbers);
24783 let line_gutter_width = if show_line_numbers {
24784 // Avoid flicker-like gutter resizes when the line number gains another digit by
24785 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24786 let min_width_for_number_on_gutter =
24787 ch_advance * gutter_settings.min_line_number_digits as f32;
24788 self.max_line_number_width(style, window)
24789 .max(min_width_for_number_on_gutter)
24790 } else {
24791 0.0.into()
24792 };
24793
24794 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24795 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24796
24797 let git_blame_entries_width =
24798 self.git_blame_gutter_max_author_length
24799 .map(|max_author_length| {
24800 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24801 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24802
24803 /// The number of characters to dedicate to gaps and margins.
24804 const SPACING_WIDTH: usize = 4;
24805
24806 let max_char_count = max_author_length.min(renderer.max_author_length())
24807 + ::git::SHORT_SHA_LENGTH
24808 + MAX_RELATIVE_TIMESTAMP.len()
24809 + SPACING_WIDTH;
24810
24811 ch_advance * max_char_count
24812 });
24813
24814 let is_singleton = self.buffer_snapshot().is_singleton();
24815
24816 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24817 left_padding += if !is_singleton {
24818 ch_width * 4.0
24819 } else if show_runnables || show_breakpoints {
24820 ch_width * 3.0
24821 } else if show_git_gutter && show_line_numbers {
24822 ch_width * 2.0
24823 } else if show_git_gutter || show_line_numbers {
24824 ch_width
24825 } else {
24826 px(0.)
24827 };
24828
24829 let shows_folds = is_singleton && gutter_settings.folds;
24830
24831 let right_padding = if shows_folds && show_line_numbers {
24832 ch_width * 4.0
24833 } else if shows_folds || (!is_singleton && show_line_numbers) {
24834 ch_width * 3.0
24835 } else if show_line_numbers {
24836 ch_width
24837 } else {
24838 px(0.)
24839 };
24840
24841 GutterDimensions {
24842 left_padding,
24843 right_padding,
24844 width: line_gutter_width + left_padding + right_padding,
24845 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24846 git_blame_entries_width,
24847 }
24848 } else if self.offset_content {
24849 GutterDimensions::default_with_margin(font_id, font_size, cx)
24850 } else {
24851 GutterDimensions::default()
24852 }
24853 }
24854
24855 pub fn render_crease_toggle(
24856 &self,
24857 buffer_row: MultiBufferRow,
24858 row_contains_cursor: bool,
24859 editor: Entity<Editor>,
24860 window: &mut Window,
24861 cx: &mut App,
24862 ) -> Option<AnyElement> {
24863 let folded = self.is_line_folded(buffer_row);
24864 let mut is_foldable = false;
24865
24866 if let Some(crease) = self
24867 .crease_snapshot
24868 .query_row(buffer_row, self.buffer_snapshot())
24869 {
24870 is_foldable = true;
24871 match crease {
24872 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24873 if let Some(render_toggle) = render_toggle {
24874 let toggle_callback =
24875 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24876 if folded {
24877 editor.update(cx, |editor, cx| {
24878 editor.fold_at(buffer_row, window, cx)
24879 });
24880 } else {
24881 editor.update(cx, |editor, cx| {
24882 editor.unfold_at(buffer_row, window, cx)
24883 });
24884 }
24885 });
24886 return Some((render_toggle)(
24887 buffer_row,
24888 folded,
24889 toggle_callback,
24890 window,
24891 cx,
24892 ));
24893 }
24894 }
24895 }
24896 }
24897
24898 is_foldable |= self.starts_indent(buffer_row);
24899
24900 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24901 Some(
24902 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24903 .toggle_state(folded)
24904 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24905 if folded {
24906 this.unfold_at(buffer_row, window, cx);
24907 } else {
24908 this.fold_at(buffer_row, window, cx);
24909 }
24910 }))
24911 .into_any_element(),
24912 )
24913 } else {
24914 None
24915 }
24916 }
24917
24918 pub fn render_crease_trailer(
24919 &self,
24920 buffer_row: MultiBufferRow,
24921 window: &mut Window,
24922 cx: &mut App,
24923 ) -> Option<AnyElement> {
24924 let folded = self.is_line_folded(buffer_row);
24925 if let Crease::Inline { render_trailer, .. } = self
24926 .crease_snapshot
24927 .query_row(buffer_row, self.buffer_snapshot())?
24928 {
24929 let render_trailer = render_trailer.as_ref()?;
24930 Some(render_trailer(buffer_row, folded, window, cx))
24931 } else {
24932 None
24933 }
24934 }
24935
24936 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
24937 let digit_count = self.widest_line_number().ilog10() + 1;
24938 column_pixels(style, digit_count as usize, window)
24939 }
24940}
24941
24942pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
24943 let font_size = style.text.font_size.to_pixels(window.rem_size());
24944 let layout = window.text_system().shape_line(
24945 SharedString::from(" ".repeat(column)),
24946 font_size,
24947 &[TextRun {
24948 len: column,
24949 font: style.text.font(),
24950 color: Hsla::default(),
24951 ..Default::default()
24952 }],
24953 None,
24954 );
24955
24956 layout.width
24957}
24958
24959impl Deref for EditorSnapshot {
24960 type Target = DisplaySnapshot;
24961
24962 fn deref(&self) -> &Self::Target {
24963 &self.display_snapshot
24964 }
24965}
24966
24967#[derive(Clone, Debug, PartialEq, Eq)]
24968pub enum EditorEvent {
24969 InputIgnored {
24970 text: Arc<str>,
24971 },
24972 InputHandled {
24973 utf16_range_to_replace: Option<Range<isize>>,
24974 text: Arc<str>,
24975 },
24976 ExcerptsAdded {
24977 buffer: Entity<Buffer>,
24978 predecessor: ExcerptId,
24979 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24980 },
24981 ExcerptsRemoved {
24982 ids: Vec<ExcerptId>,
24983 removed_buffer_ids: Vec<BufferId>,
24984 },
24985 BufferFoldToggled {
24986 ids: Vec<ExcerptId>,
24987 folded: bool,
24988 },
24989 ExcerptsEdited {
24990 ids: Vec<ExcerptId>,
24991 },
24992 ExcerptsExpanded {
24993 ids: Vec<ExcerptId>,
24994 },
24995 BufferEdited,
24996 Edited {
24997 transaction_id: clock::Lamport,
24998 },
24999 Reparsed(BufferId),
25000 Focused,
25001 FocusedIn,
25002 Blurred,
25003 DirtyChanged,
25004 Saved,
25005 TitleChanged,
25006 SelectionsChanged {
25007 local: bool,
25008 },
25009 ScrollPositionChanged {
25010 local: bool,
25011 autoscroll: bool,
25012 },
25013 TransactionUndone {
25014 transaction_id: clock::Lamport,
25015 },
25016 TransactionBegun {
25017 transaction_id: clock::Lamport,
25018 },
25019 CursorShapeChanged,
25020 BreadcrumbsChanged,
25021 PushedToNavHistory {
25022 anchor: Anchor,
25023 is_deactivate: bool,
25024 },
25025}
25026
25027impl EventEmitter<EditorEvent> for Editor {}
25028
25029impl Focusable for Editor {
25030 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25031 self.focus_handle.clone()
25032 }
25033}
25034
25035impl Render for Editor {
25036 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25037 EditorElement::new(&cx.entity(), self.create_style(cx))
25038 }
25039}
25040
25041impl EntityInputHandler for Editor {
25042 fn text_for_range(
25043 &mut self,
25044 range_utf16: Range<usize>,
25045 adjusted_range: &mut Option<Range<usize>>,
25046 _: &mut Window,
25047 cx: &mut Context<Self>,
25048 ) -> Option<String> {
25049 let snapshot = self.buffer.read(cx).read(cx);
25050 let start = snapshot.clip_offset_utf16(
25051 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25052 Bias::Left,
25053 );
25054 let end = snapshot.clip_offset_utf16(
25055 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25056 Bias::Right,
25057 );
25058 if (start.0.0..end.0.0) != range_utf16 {
25059 adjusted_range.replace(start.0.0..end.0.0);
25060 }
25061 Some(snapshot.text_for_range(start..end).collect())
25062 }
25063
25064 fn selected_text_range(
25065 &mut self,
25066 ignore_disabled_input: bool,
25067 _: &mut Window,
25068 cx: &mut Context<Self>,
25069 ) -> Option<UTF16Selection> {
25070 // Prevent the IME menu from appearing when holding down an alphabetic key
25071 // while input is disabled.
25072 if !ignore_disabled_input && !self.input_enabled {
25073 return None;
25074 }
25075
25076 let selection = self
25077 .selections
25078 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25079 let range = selection.range();
25080
25081 Some(UTF16Selection {
25082 range: range.start.0.0..range.end.0.0,
25083 reversed: selection.reversed,
25084 })
25085 }
25086
25087 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25088 let snapshot = self.buffer.read(cx).read(cx);
25089 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25090 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25091 }
25092
25093 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25094 self.clear_highlights::<InputComposition>(cx);
25095 self.ime_transaction.take();
25096 }
25097
25098 fn replace_text_in_range(
25099 &mut self,
25100 range_utf16: Option<Range<usize>>,
25101 text: &str,
25102 window: &mut Window,
25103 cx: &mut Context<Self>,
25104 ) {
25105 if !self.input_enabled {
25106 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25107 return;
25108 }
25109
25110 self.transact(window, cx, |this, window, cx| {
25111 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25112 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25113 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25114 Some(this.selection_replacement_ranges(range_utf16, cx))
25115 } else {
25116 this.marked_text_ranges(cx)
25117 };
25118
25119 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25120 let newest_selection_id = this.selections.newest_anchor().id;
25121 this.selections
25122 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25123 .iter()
25124 .zip(ranges_to_replace.iter())
25125 .find_map(|(selection, range)| {
25126 if selection.id == newest_selection_id {
25127 Some(
25128 (range.start.0.0 as isize - selection.head().0.0 as isize)
25129 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25130 )
25131 } else {
25132 None
25133 }
25134 })
25135 });
25136
25137 cx.emit(EditorEvent::InputHandled {
25138 utf16_range_to_replace: range_to_replace,
25139 text: text.into(),
25140 });
25141
25142 if let Some(new_selected_ranges) = new_selected_ranges {
25143 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25144 selections.select_ranges(new_selected_ranges)
25145 });
25146 this.backspace(&Default::default(), window, cx);
25147 }
25148
25149 this.handle_input(text, window, cx);
25150 });
25151
25152 if let Some(transaction) = self.ime_transaction {
25153 self.buffer.update(cx, |buffer, cx| {
25154 buffer.group_until_transaction(transaction, cx);
25155 });
25156 }
25157
25158 self.unmark_text(window, cx);
25159 }
25160
25161 fn replace_and_mark_text_in_range(
25162 &mut self,
25163 range_utf16: Option<Range<usize>>,
25164 text: &str,
25165 new_selected_range_utf16: Option<Range<usize>>,
25166 window: &mut Window,
25167 cx: &mut Context<Self>,
25168 ) {
25169 if !self.input_enabled {
25170 return;
25171 }
25172
25173 let transaction = self.transact(window, cx, |this, window, cx| {
25174 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25175 let snapshot = this.buffer.read(cx).read(cx);
25176 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25177 for marked_range in &mut marked_ranges {
25178 marked_range.end = marked_range.start + relative_range_utf16.end;
25179 marked_range.start += relative_range_utf16.start;
25180 marked_range.start =
25181 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25182 marked_range.end =
25183 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25184 }
25185 }
25186 Some(marked_ranges)
25187 } else if let Some(range_utf16) = range_utf16 {
25188 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25189 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25190 Some(this.selection_replacement_ranges(range_utf16, cx))
25191 } else {
25192 None
25193 };
25194
25195 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25196 let newest_selection_id = this.selections.newest_anchor().id;
25197 this.selections
25198 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25199 .iter()
25200 .zip(ranges_to_replace.iter())
25201 .find_map(|(selection, range)| {
25202 if selection.id == newest_selection_id {
25203 Some(
25204 (range.start.0.0 as isize - selection.head().0.0 as isize)
25205 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25206 )
25207 } else {
25208 None
25209 }
25210 })
25211 });
25212
25213 cx.emit(EditorEvent::InputHandled {
25214 utf16_range_to_replace: range_to_replace,
25215 text: text.into(),
25216 });
25217
25218 if let Some(ranges) = ranges_to_replace {
25219 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25220 s.select_ranges(ranges)
25221 });
25222 }
25223
25224 let marked_ranges = {
25225 let snapshot = this.buffer.read(cx).read(cx);
25226 this.selections
25227 .disjoint_anchors_arc()
25228 .iter()
25229 .map(|selection| {
25230 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25231 })
25232 .collect::<Vec<_>>()
25233 };
25234
25235 if text.is_empty() {
25236 this.unmark_text(window, cx);
25237 } else {
25238 this.highlight_text::<InputComposition>(
25239 marked_ranges.clone(),
25240 HighlightStyle {
25241 underline: Some(UnderlineStyle {
25242 thickness: px(1.),
25243 color: None,
25244 wavy: false,
25245 }),
25246 ..Default::default()
25247 },
25248 cx,
25249 );
25250 }
25251
25252 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25253 let use_autoclose = this.use_autoclose;
25254 let use_auto_surround = this.use_auto_surround;
25255 this.set_use_autoclose(false);
25256 this.set_use_auto_surround(false);
25257 this.handle_input(text, window, cx);
25258 this.set_use_autoclose(use_autoclose);
25259 this.set_use_auto_surround(use_auto_surround);
25260
25261 if let Some(new_selected_range) = new_selected_range_utf16 {
25262 let snapshot = this.buffer.read(cx).read(cx);
25263 let new_selected_ranges = marked_ranges
25264 .into_iter()
25265 .map(|marked_range| {
25266 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25267 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25268 insertion_start.0 + new_selected_range.start,
25269 ));
25270 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25271 insertion_start.0 + new_selected_range.end,
25272 ));
25273 snapshot.clip_offset_utf16(new_start, Bias::Left)
25274 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25275 })
25276 .collect::<Vec<_>>();
25277
25278 drop(snapshot);
25279 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25280 selections.select_ranges(new_selected_ranges)
25281 });
25282 }
25283 });
25284
25285 self.ime_transaction = self.ime_transaction.or(transaction);
25286 if let Some(transaction) = self.ime_transaction {
25287 self.buffer.update(cx, |buffer, cx| {
25288 buffer.group_until_transaction(transaction, cx);
25289 });
25290 }
25291
25292 if self.text_highlights::<InputComposition>(cx).is_none() {
25293 self.ime_transaction.take();
25294 }
25295 }
25296
25297 fn bounds_for_range(
25298 &mut self,
25299 range_utf16: Range<usize>,
25300 element_bounds: gpui::Bounds<Pixels>,
25301 window: &mut Window,
25302 cx: &mut Context<Self>,
25303 ) -> Option<gpui::Bounds<Pixels>> {
25304 let text_layout_details = self.text_layout_details(window);
25305 let CharacterDimensions {
25306 em_width,
25307 em_advance,
25308 line_height,
25309 } = self.character_dimensions(window);
25310
25311 let snapshot = self.snapshot(window, cx);
25312 let scroll_position = snapshot.scroll_position();
25313 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25314
25315 let start =
25316 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25317 let x = Pixels::from(
25318 ScrollOffset::from(
25319 snapshot.x_for_display_point(start, &text_layout_details)
25320 + self.gutter_dimensions.full_width(),
25321 ) - scroll_left,
25322 );
25323 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25324
25325 Some(Bounds {
25326 origin: element_bounds.origin + point(x, y),
25327 size: size(em_width, line_height),
25328 })
25329 }
25330
25331 fn character_index_for_point(
25332 &mut self,
25333 point: gpui::Point<Pixels>,
25334 _window: &mut Window,
25335 _cx: &mut Context<Self>,
25336 ) -> Option<usize> {
25337 let position_map = self.last_position_map.as_ref()?;
25338 if !position_map.text_hitbox.contains(&point) {
25339 return None;
25340 }
25341 let display_point = position_map.point_for_position(point).previous_valid;
25342 let anchor = position_map
25343 .snapshot
25344 .display_point_to_anchor(display_point, Bias::Left);
25345 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25346 Some(utf16_offset.0.0)
25347 }
25348
25349 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25350 self.input_enabled
25351 }
25352}
25353
25354trait SelectionExt {
25355 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25356 fn spanned_rows(
25357 &self,
25358 include_end_if_at_line_start: bool,
25359 map: &DisplaySnapshot,
25360 ) -> Range<MultiBufferRow>;
25361}
25362
25363impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25364 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25365 let start = self
25366 .start
25367 .to_point(map.buffer_snapshot())
25368 .to_display_point(map);
25369 let end = self
25370 .end
25371 .to_point(map.buffer_snapshot())
25372 .to_display_point(map);
25373 if self.reversed {
25374 end..start
25375 } else {
25376 start..end
25377 }
25378 }
25379
25380 fn spanned_rows(
25381 &self,
25382 include_end_if_at_line_start: bool,
25383 map: &DisplaySnapshot,
25384 ) -> Range<MultiBufferRow> {
25385 let start = self.start.to_point(map.buffer_snapshot());
25386 let mut end = self.end.to_point(map.buffer_snapshot());
25387 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25388 end.row -= 1;
25389 }
25390
25391 let buffer_start = map.prev_line_boundary(start).0;
25392 let buffer_end = map.next_line_boundary(end).0;
25393 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25394 }
25395}
25396
25397impl<T: InvalidationRegion> InvalidationStack<T> {
25398 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25399 where
25400 S: Clone + ToOffset,
25401 {
25402 while let Some(region) = self.last() {
25403 let all_selections_inside_invalidation_ranges =
25404 if selections.len() == region.ranges().len() {
25405 selections
25406 .iter()
25407 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25408 .all(|(selection, invalidation_range)| {
25409 let head = selection.head().to_offset(buffer);
25410 invalidation_range.start <= head && invalidation_range.end >= head
25411 })
25412 } else {
25413 false
25414 };
25415
25416 if all_selections_inside_invalidation_ranges {
25417 break;
25418 } else {
25419 self.pop();
25420 }
25421 }
25422 }
25423}
25424
25425impl<T> Default for InvalidationStack<T> {
25426 fn default() -> Self {
25427 Self(Default::default())
25428 }
25429}
25430
25431impl<T> Deref for InvalidationStack<T> {
25432 type Target = Vec<T>;
25433
25434 fn deref(&self) -> &Self::Target {
25435 &self.0
25436 }
25437}
25438
25439impl<T> DerefMut for InvalidationStack<T> {
25440 fn deref_mut(&mut self) -> &mut Self::Target {
25441 &mut self.0
25442 }
25443}
25444
25445impl InvalidationRegion for SnippetState {
25446 fn ranges(&self) -> &[Range<Anchor>] {
25447 &self.ranges[self.active_index]
25448 }
25449}
25450
25451fn edit_prediction_edit_text(
25452 current_snapshot: &BufferSnapshot,
25453 edits: &[(Range<Anchor>, impl AsRef<str>)],
25454 edit_preview: &EditPreview,
25455 include_deletions: bool,
25456 cx: &App,
25457) -> HighlightedText {
25458 let edits = edits
25459 .iter()
25460 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25461 .collect::<Vec<_>>();
25462
25463 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25464}
25465
25466fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25467 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25468 // Just show the raw edit text with basic styling
25469 let mut text = String::new();
25470 let mut highlights = Vec::new();
25471
25472 let insertion_highlight_style = HighlightStyle {
25473 color: Some(cx.theme().colors().text),
25474 ..Default::default()
25475 };
25476
25477 for (_, edit_text) in edits {
25478 let start_offset = text.len();
25479 text.push_str(edit_text);
25480 let end_offset = text.len();
25481
25482 if start_offset < end_offset {
25483 highlights.push((start_offset..end_offset, insertion_highlight_style));
25484 }
25485 }
25486
25487 HighlightedText {
25488 text: text.into(),
25489 highlights,
25490 }
25491}
25492
25493pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25494 match severity {
25495 lsp::DiagnosticSeverity::ERROR => colors.error,
25496 lsp::DiagnosticSeverity::WARNING => colors.warning,
25497 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25498 lsp::DiagnosticSeverity::HINT => colors.info,
25499 _ => colors.ignored,
25500 }
25501}
25502
25503pub fn styled_runs_for_code_label<'a>(
25504 label: &'a CodeLabel,
25505 syntax_theme: &'a theme::SyntaxTheme,
25506 local_player: &'a theme::PlayerColor,
25507) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25508 let fade_out = HighlightStyle {
25509 fade_out: Some(0.35),
25510 ..Default::default()
25511 };
25512
25513 let mut prev_end = label.filter_range.end;
25514 label
25515 .runs
25516 .iter()
25517 .enumerate()
25518 .flat_map(move |(ix, (range, highlight_id))| {
25519 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25520 HighlightStyle {
25521 color: Some(local_player.cursor),
25522 ..Default::default()
25523 }
25524 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25525 HighlightStyle {
25526 background_color: Some(local_player.selection),
25527 ..Default::default()
25528 }
25529 } else if let Some(style) = highlight_id.style(syntax_theme) {
25530 style
25531 } else {
25532 return Default::default();
25533 };
25534 let muted_style = style.highlight(fade_out);
25535
25536 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25537 if range.start >= label.filter_range.end {
25538 if range.start > prev_end {
25539 runs.push((prev_end..range.start, fade_out));
25540 }
25541 runs.push((range.clone(), muted_style));
25542 } else if range.end <= label.filter_range.end {
25543 runs.push((range.clone(), style));
25544 } else {
25545 runs.push((range.start..label.filter_range.end, style));
25546 runs.push((label.filter_range.end..range.end, muted_style));
25547 }
25548 prev_end = cmp::max(prev_end, range.end);
25549
25550 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25551 runs.push((prev_end..label.text.len(), fade_out));
25552 }
25553
25554 runs
25555 })
25556}
25557
25558pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25559 let mut prev_index = 0;
25560 let mut prev_codepoint: Option<char> = None;
25561 text.char_indices()
25562 .chain([(text.len(), '\0')])
25563 .filter_map(move |(index, codepoint)| {
25564 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25565 let is_boundary = index == text.len()
25566 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25567 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25568 if is_boundary {
25569 let chunk = &text[prev_index..index];
25570 prev_index = index;
25571 Some(chunk)
25572 } else {
25573 None
25574 }
25575 })
25576}
25577
25578/// Given a string of text immediately before the cursor, iterates over possible
25579/// strings a snippet could match to. More precisely: returns an iterator over
25580/// suffixes of `text` created by splitting at word boundaries (before & after
25581/// every non-word character).
25582///
25583/// Shorter suffixes are returned first.
25584pub(crate) fn snippet_candidate_suffixes(
25585 text: &str,
25586 is_word_char: impl Fn(char) -> bool,
25587) -> impl std::iter::Iterator<Item = &str> {
25588 let mut prev_index = text.len();
25589 let mut prev_codepoint = None;
25590 text.char_indices()
25591 .rev()
25592 .chain([(0, '\0')])
25593 .filter_map(move |(index, codepoint)| {
25594 let prev_index = std::mem::replace(&mut prev_index, index);
25595 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25596 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25597 None
25598 } else {
25599 let chunk = &text[prev_index..]; // go to end of string
25600 Some(chunk)
25601 }
25602 })
25603}
25604
25605pub trait RangeToAnchorExt: Sized {
25606 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25607
25608 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25609 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25610 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25611 }
25612}
25613
25614impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25615 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25616 let start_offset = self.start.to_offset(snapshot);
25617 let end_offset = self.end.to_offset(snapshot);
25618 if start_offset == end_offset {
25619 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25620 } else {
25621 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25622 }
25623 }
25624}
25625
25626pub trait RowExt {
25627 fn as_f64(&self) -> f64;
25628
25629 fn next_row(&self) -> Self;
25630
25631 fn previous_row(&self) -> Self;
25632
25633 fn minus(&self, other: Self) -> u32;
25634}
25635
25636impl RowExt for DisplayRow {
25637 fn as_f64(&self) -> f64 {
25638 self.0 as _
25639 }
25640
25641 fn next_row(&self) -> Self {
25642 Self(self.0 + 1)
25643 }
25644
25645 fn previous_row(&self) -> Self {
25646 Self(self.0.saturating_sub(1))
25647 }
25648
25649 fn minus(&self, other: Self) -> u32 {
25650 self.0 - other.0
25651 }
25652}
25653
25654impl RowExt for MultiBufferRow {
25655 fn as_f64(&self) -> f64 {
25656 self.0 as _
25657 }
25658
25659 fn next_row(&self) -> Self {
25660 Self(self.0 + 1)
25661 }
25662
25663 fn previous_row(&self) -> Self {
25664 Self(self.0.saturating_sub(1))
25665 }
25666
25667 fn minus(&self, other: Self) -> u32 {
25668 self.0 - other.0
25669 }
25670}
25671
25672trait RowRangeExt {
25673 type Row;
25674
25675 fn len(&self) -> usize;
25676
25677 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25678}
25679
25680impl RowRangeExt for Range<MultiBufferRow> {
25681 type Row = MultiBufferRow;
25682
25683 fn len(&self) -> usize {
25684 (self.end.0 - self.start.0) as usize
25685 }
25686
25687 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25688 (self.start.0..self.end.0).map(MultiBufferRow)
25689 }
25690}
25691
25692impl RowRangeExt for Range<DisplayRow> {
25693 type Row = DisplayRow;
25694
25695 fn len(&self) -> usize {
25696 (self.end.0 - self.start.0) as usize
25697 }
25698
25699 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25700 (self.start.0..self.end.0).map(DisplayRow)
25701 }
25702}
25703
25704/// If select range has more than one line, we
25705/// just point the cursor to range.start.
25706fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25707 if range.start.row == range.end.row {
25708 range
25709 } else {
25710 range.start..range.start
25711 }
25712}
25713pub struct KillRing(ClipboardItem);
25714impl Global for KillRing {}
25715
25716const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25717
25718enum BreakpointPromptEditAction {
25719 Log,
25720 Condition,
25721 HitCondition,
25722}
25723
25724struct BreakpointPromptEditor {
25725 pub(crate) prompt: Entity<Editor>,
25726 editor: WeakEntity<Editor>,
25727 breakpoint_anchor: Anchor,
25728 breakpoint: Breakpoint,
25729 edit_action: BreakpointPromptEditAction,
25730 block_ids: HashSet<CustomBlockId>,
25731 editor_margins: Arc<Mutex<EditorMargins>>,
25732 _subscriptions: Vec<Subscription>,
25733}
25734
25735impl BreakpointPromptEditor {
25736 const MAX_LINES: u8 = 4;
25737
25738 fn new(
25739 editor: WeakEntity<Editor>,
25740 breakpoint_anchor: Anchor,
25741 breakpoint: Breakpoint,
25742 edit_action: BreakpointPromptEditAction,
25743 window: &mut Window,
25744 cx: &mut Context<Self>,
25745 ) -> Self {
25746 let base_text = match edit_action {
25747 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25748 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25749 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25750 }
25751 .map(|msg| msg.to_string())
25752 .unwrap_or_default();
25753
25754 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25755 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25756
25757 let prompt = cx.new(|cx| {
25758 let mut prompt = Editor::new(
25759 EditorMode::AutoHeight {
25760 min_lines: 1,
25761 max_lines: Some(Self::MAX_LINES as usize),
25762 },
25763 buffer,
25764 None,
25765 window,
25766 cx,
25767 );
25768 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25769 prompt.set_show_cursor_when_unfocused(false, cx);
25770 prompt.set_placeholder_text(
25771 match edit_action {
25772 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25773 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25774 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25775 },
25776 window,
25777 cx,
25778 );
25779
25780 prompt
25781 });
25782
25783 Self {
25784 prompt,
25785 editor,
25786 breakpoint_anchor,
25787 breakpoint,
25788 edit_action,
25789 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25790 block_ids: Default::default(),
25791 _subscriptions: vec![],
25792 }
25793 }
25794
25795 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25796 self.block_ids.extend(block_ids)
25797 }
25798
25799 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25800 if let Some(editor) = self.editor.upgrade() {
25801 let message = self
25802 .prompt
25803 .read(cx)
25804 .buffer
25805 .read(cx)
25806 .as_singleton()
25807 .expect("A multi buffer in breakpoint prompt isn't possible")
25808 .read(cx)
25809 .as_rope()
25810 .to_string();
25811
25812 editor.update(cx, |editor, cx| {
25813 editor.edit_breakpoint_at_anchor(
25814 self.breakpoint_anchor,
25815 self.breakpoint.clone(),
25816 match self.edit_action {
25817 BreakpointPromptEditAction::Log => {
25818 BreakpointEditAction::EditLogMessage(message.into())
25819 }
25820 BreakpointPromptEditAction::Condition => {
25821 BreakpointEditAction::EditCondition(message.into())
25822 }
25823 BreakpointPromptEditAction::HitCondition => {
25824 BreakpointEditAction::EditHitCondition(message.into())
25825 }
25826 },
25827 cx,
25828 );
25829
25830 editor.remove_blocks(self.block_ids.clone(), None, cx);
25831 cx.focus_self(window);
25832 });
25833 }
25834 }
25835
25836 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25837 self.editor
25838 .update(cx, |editor, cx| {
25839 editor.remove_blocks(self.block_ids.clone(), None, cx);
25840 window.focus(&editor.focus_handle);
25841 })
25842 .log_err();
25843 }
25844
25845 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25846 let settings = ThemeSettings::get_global(cx);
25847 let text_style = TextStyle {
25848 color: if self.prompt.read(cx).read_only(cx) {
25849 cx.theme().colors().text_disabled
25850 } else {
25851 cx.theme().colors().text
25852 },
25853 font_family: settings.buffer_font.family.clone(),
25854 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25855 font_size: settings.buffer_font_size(cx).into(),
25856 font_weight: settings.buffer_font.weight,
25857 line_height: relative(settings.buffer_line_height.value()),
25858 ..Default::default()
25859 };
25860 EditorElement::new(
25861 &self.prompt,
25862 EditorStyle {
25863 background: cx.theme().colors().editor_background,
25864 local_player: cx.theme().players().local(),
25865 text: text_style,
25866 ..Default::default()
25867 },
25868 )
25869 }
25870}
25871
25872impl Render for BreakpointPromptEditor {
25873 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25874 let editor_margins = *self.editor_margins.lock();
25875 let gutter_dimensions = editor_margins.gutter;
25876 h_flex()
25877 .key_context("Editor")
25878 .bg(cx.theme().colors().editor_background)
25879 .border_y_1()
25880 .border_color(cx.theme().status().info_border)
25881 .size_full()
25882 .py(window.line_height() / 2.5)
25883 .on_action(cx.listener(Self::confirm))
25884 .on_action(cx.listener(Self::cancel))
25885 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25886 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25887 }
25888}
25889
25890impl Focusable for BreakpointPromptEditor {
25891 fn focus_handle(&self, cx: &App) -> FocusHandle {
25892 self.prompt.focus_handle(cx)
25893 }
25894}
25895
25896fn all_edits_insertions_or_deletions(
25897 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25898 snapshot: &MultiBufferSnapshot,
25899) -> bool {
25900 let mut all_insertions = true;
25901 let mut all_deletions = true;
25902
25903 for (range, new_text) in edits.iter() {
25904 let range_is_empty = range.to_offset(snapshot).is_empty();
25905 let text_is_empty = new_text.is_empty();
25906
25907 if range_is_empty != text_is_empty {
25908 if range_is_empty {
25909 all_deletions = false;
25910 } else {
25911 all_insertions = false;
25912 }
25913 } else {
25914 return false;
25915 }
25916
25917 if !all_insertions && !all_deletions {
25918 return false;
25919 }
25920 }
25921 all_insertions || all_deletions
25922}
25923
25924struct MissingEditPredictionKeybindingTooltip;
25925
25926impl Render for MissingEditPredictionKeybindingTooltip {
25927 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25928 ui::tooltip_container(cx, |container, cx| {
25929 container
25930 .flex_shrink_0()
25931 .max_w_80()
25932 .min_h(rems_from_px(124.))
25933 .justify_between()
25934 .child(
25935 v_flex()
25936 .flex_1()
25937 .text_ui_sm(cx)
25938 .child(Label::new("Conflict with Accept Keybinding"))
25939 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25940 )
25941 .child(
25942 h_flex()
25943 .pb_1()
25944 .gap_1()
25945 .items_end()
25946 .w_full()
25947 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25948 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25949 }))
25950 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25951 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25952 })),
25953 )
25954 })
25955 }
25956}
25957
25958#[derive(Debug, Clone, Copy, PartialEq)]
25959pub struct LineHighlight {
25960 pub background: Background,
25961 pub border: Option<gpui::Hsla>,
25962 pub include_gutter: bool,
25963 pub type_id: Option<TypeId>,
25964}
25965
25966struct LineManipulationResult {
25967 pub new_text: String,
25968 pub line_count_before: usize,
25969 pub line_count_after: usize,
25970}
25971
25972fn render_diff_hunk_controls(
25973 row: u32,
25974 status: &DiffHunkStatus,
25975 hunk_range: Range<Anchor>,
25976 is_created_file: bool,
25977 line_height: Pixels,
25978 editor: &Entity<Editor>,
25979 _window: &mut Window,
25980 cx: &mut App,
25981) -> AnyElement {
25982 h_flex()
25983 .h(line_height)
25984 .mr_1()
25985 .gap_1()
25986 .px_0p5()
25987 .pb_1()
25988 .border_x_1()
25989 .border_b_1()
25990 .border_color(cx.theme().colors().border_variant)
25991 .rounded_b_lg()
25992 .bg(cx.theme().colors().editor_background)
25993 .gap_1()
25994 .block_mouse_except_scroll()
25995 .shadow_md()
25996 .child(if status.has_secondary_hunk() {
25997 Button::new(("stage", row as u64), "Stage")
25998 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25999 .tooltip({
26000 let focus_handle = editor.focus_handle(cx);
26001 move |_window, cx| {
26002 Tooltip::for_action_in(
26003 "Stage Hunk",
26004 &::git::ToggleStaged,
26005 &focus_handle,
26006 cx,
26007 )
26008 }
26009 })
26010 .on_click({
26011 let editor = editor.clone();
26012 move |_event, _window, cx| {
26013 editor.update(cx, |editor, cx| {
26014 editor.stage_or_unstage_diff_hunks(
26015 true,
26016 vec![hunk_range.start..hunk_range.start],
26017 cx,
26018 );
26019 });
26020 }
26021 })
26022 } else {
26023 Button::new(("unstage", row as u64), "Unstage")
26024 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26025 .tooltip({
26026 let focus_handle = editor.focus_handle(cx);
26027 move |_window, cx| {
26028 Tooltip::for_action_in(
26029 "Unstage Hunk",
26030 &::git::ToggleStaged,
26031 &focus_handle,
26032 cx,
26033 )
26034 }
26035 })
26036 .on_click({
26037 let editor = editor.clone();
26038 move |_event, _window, cx| {
26039 editor.update(cx, |editor, cx| {
26040 editor.stage_or_unstage_diff_hunks(
26041 false,
26042 vec![hunk_range.start..hunk_range.start],
26043 cx,
26044 );
26045 });
26046 }
26047 })
26048 })
26049 .child(
26050 Button::new(("restore", row as u64), "Restore")
26051 .tooltip({
26052 let focus_handle = editor.focus_handle(cx);
26053 move |_window, cx| {
26054 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26055 }
26056 })
26057 .on_click({
26058 let editor = editor.clone();
26059 move |_event, window, cx| {
26060 editor.update(cx, |editor, cx| {
26061 let snapshot = editor.snapshot(window, cx);
26062 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26063 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26064 });
26065 }
26066 })
26067 .disabled(is_created_file),
26068 )
26069 .when(
26070 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26071 |el| {
26072 el.child(
26073 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26074 .shape(IconButtonShape::Square)
26075 .icon_size(IconSize::Small)
26076 // .disabled(!has_multiple_hunks)
26077 .tooltip({
26078 let focus_handle = editor.focus_handle(cx);
26079 move |_window, cx| {
26080 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26081 }
26082 })
26083 .on_click({
26084 let editor = editor.clone();
26085 move |_event, window, cx| {
26086 editor.update(cx, |editor, cx| {
26087 let snapshot = editor.snapshot(window, cx);
26088 let position =
26089 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26090 editor.go_to_hunk_before_or_after_position(
26091 &snapshot,
26092 position,
26093 Direction::Next,
26094 window,
26095 cx,
26096 );
26097 editor.expand_selected_diff_hunks(cx);
26098 });
26099 }
26100 }),
26101 )
26102 .child(
26103 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26104 .shape(IconButtonShape::Square)
26105 .icon_size(IconSize::Small)
26106 // .disabled(!has_multiple_hunks)
26107 .tooltip({
26108 let focus_handle = editor.focus_handle(cx);
26109 move |_window, cx| {
26110 Tooltip::for_action_in(
26111 "Previous Hunk",
26112 &GoToPreviousHunk,
26113 &focus_handle,
26114 cx,
26115 )
26116 }
26117 })
26118 .on_click({
26119 let editor = editor.clone();
26120 move |_event, window, cx| {
26121 editor.update(cx, |editor, cx| {
26122 let snapshot = editor.snapshot(window, cx);
26123 let point =
26124 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26125 editor.go_to_hunk_before_or_after_position(
26126 &snapshot,
26127 point,
26128 Direction::Prev,
26129 window,
26130 cx,
26131 );
26132 editor.expand_selected_diff_hunks(cx);
26133 });
26134 }
26135 }),
26136 )
26137 },
26138 )
26139 .into_any_element()
26140}
26141
26142pub fn multibuffer_context_lines(cx: &App) -> u32 {
26143 EditorSettings::try_get(cx)
26144 .map(|settings| settings.excerpt_context_lines)
26145 .unwrap_or(2)
26146 .min(32)
26147}