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 document_colors;
21mod editor_settings;
22mod element;
23mod folding_ranges;
24mod git;
25mod highlight_matching_bracket;
26mod hover_links;
27pub mod hover_popover;
28mod indent_guides;
29mod inlays;
30pub mod items;
31mod jsx_tag_auto_close;
32mod linked_editing_ranges;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod semantic_tokens;
41mod split;
42pub mod split_editor_view;
43pub mod tasks;
44
45#[cfg(test)]
46mod code_completion_tests;
47#[cfg(test)]
48mod edit_prediction_tests;
49#[cfg(test)]
50mod editor_tests;
51mod signature_help;
52#[cfg(any(test, feature = "test-support"))]
53pub mod test;
54
55pub(crate) use actions::*;
56pub use display_map::{
57 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
58 SemanticTokenHighlight,
59};
60pub use edit_prediction_types::Direction;
61pub use editor_settings::{
62 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
63 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
64 ShowMinimap,
65};
66pub use element::{
67 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
68 render_breadcrumb_text,
69};
70pub use git::blame::BlameRenderer;
71pub use hover_popover::hover_markdown_style;
72pub use inlays::Inlay;
73pub use items::MAX_TAB_TITLE_LEN;
74pub use lsp::CompletionContext;
75pub use lsp_ext::lsp_tasks;
76pub use multi_buffer::{
77 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
78 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
79 ToPoint,
80};
81pub use split::{SplittableEditor, ToggleSplitDiff};
82pub use split_editor_view::SplitEditorView;
83pub use text::Bias;
84
85use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
86use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
87use anyhow::{Context as _, Result, anyhow, bail};
88use blink_manager::BlinkManager;
89use buffer_diff::DiffHunkStatus;
90use client::{Collaborator, ParticipantIndex, parse_zed_link};
91use clock::ReplicaId;
92use code_context_menus::{
93 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
94 CompletionsMenu, ContextMenuOrigin,
95};
96use collections::{BTreeMap, HashMap, HashSet, VecDeque};
97use convert_case::{Case, Casing};
98use dap::TelemetrySpawnLocation;
99use display_map::*;
100use document_colors::LspColorData;
101use edit_prediction_types::{
102 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
103 EditPredictionGranularity, SuggestionDisplayType,
104};
105use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
106use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
107use futures::{
108 FutureExt,
109 future::{self, Shared, join},
110};
111use fuzzy::{StringMatch, StringMatchCandidate};
112use git::blame::{GitBlame, GlobalBlameRenderer};
113use gpui::{
114 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
115 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
116 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
117 Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
118 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
119 Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
120 TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
121 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
122 pulsating_between, px, relative, size,
123};
124use hover_links::{HoverLink, HoveredLinkState, find_file};
125use hover_popover::{HoverState, hide_hover};
126use indent_guides::ActiveIndentGuidesState;
127use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
128use itertools::{Either, Itertools};
129use language::{
130 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
131 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
132 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
133 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
134 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
135 TreeSitterOptions, WordsQuery,
136 language_settings::{
137 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
138 all_language_settings, language_settings,
139 },
140 point_from_lsp, point_to_lsp, text_diff_with_options,
141};
142use linked_editing_ranges::refresh_linked_ranges;
143use lsp::{
144 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
145 LanguageServerId,
146};
147use markdown::Markdown;
148use mouse_context_menu::MouseContextMenu;
149use movement::TextLayoutDetails;
150use multi_buffer::{
151 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
152};
153use parking_lot::Mutex;
154use persistence::DB;
155use project::{
156 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
157 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
158 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
159 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
160 debugger::{
161 breakpoint_store::{
162 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
163 BreakpointStore, BreakpointStoreEvent,
164 },
165 session::{Session, SessionEvent},
166 },
167 git_store::GitStoreEvent,
168 lsp_store::{
169 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
170 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
171 },
172 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
173};
174use rand::seq::SliceRandom;
175use regex::Regex;
176use rpc::{ErrorCode, ErrorExt, proto::PeerId};
177use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
178use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
179use serde::{Deserialize, Serialize};
180use settings::{
181 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
182 update_settings_file,
183};
184use smallvec::{SmallVec, smallvec};
185use snippet::Snippet;
186use std::{
187 any::{Any, TypeId},
188 borrow::Cow,
189 cell::{OnceCell, RefCell},
190 cmp::{self, Ordering, Reverse},
191 collections::hash_map,
192 iter::{self, Peekable},
193 mem,
194 num::NonZeroU32,
195 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
196 path::{Path, PathBuf},
197 rc::Rc,
198 sync::Arc,
199 time::{Duration, Instant},
200};
201use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
202use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
203use theme::{
204 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
205 ThemeSettings, observe_buffer_font_size_adjustment,
206};
207use ui::{
208 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
209 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
210};
211use ui_input::ErasedEditor;
212use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
213use workspace::{
214 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
215 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
216 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
217 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
218 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
219 searchable::{CollapseDirection, SearchEvent},
220};
221use zed_actions::editor::{MoveDown, MoveUp};
222
223use crate::{
224 code_context_menus::CompletionsMenuSource,
225 editor_settings::MultiCursorModifier,
226 hover_links::{find_url, find_url_from_range},
227 inlays::{
228 InlineValueCache,
229 inlay_hints::{LspInlayHintData, inlay_hint_settings},
230 },
231 scroll::{ScrollOffset, ScrollPixelOffset},
232 selections_collection::resolve_selections_wrapping_blocks,
233 semantic_tokens::SemanticTokenState,
234 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
235};
236
237pub const FILE_HEADER_HEIGHT: u32 = 2;
238pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
239const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
240const MAX_LINE_LEN: usize = 1024;
241const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
242const MAX_SELECTION_HISTORY_LEN: usize = 1024;
243pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
244#[doc(hidden)]
245pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
246pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
247
248pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
249pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
250pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
251pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
252
253pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
254pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
255pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
256
257pub type RenderDiffHunkControlsFn = Arc<
258 dyn Fn(
259 u32,
260 &DiffHunkStatus,
261 Range<Anchor>,
262 bool,
263 Pixels,
264 &Entity<Editor>,
265 &mut Window,
266 &mut App,
267 ) -> AnyElement,
268>;
269
270enum ReportEditorEvent {
271 Saved { auto_saved: bool },
272 EditorOpened,
273 Closed,
274}
275
276impl ReportEditorEvent {
277 pub fn event_type(&self) -> &'static str {
278 match self {
279 Self::Saved { .. } => "Editor Saved",
280 Self::EditorOpened => "Editor Opened",
281 Self::Closed => "Editor Closed",
282 }
283 }
284}
285
286pub enum ActiveDebugLine {}
287pub enum DebugStackFrameLine {}
288
289pub enum ConflictsOuter {}
290pub enum ConflictsOurs {}
291pub enum ConflictsTheirs {}
292pub enum ConflictsOursMarker {}
293pub enum ConflictsTheirsMarker {}
294
295pub struct HunkAddedColor;
296pub struct HunkRemovedColor;
297
298#[derive(Debug, Copy, Clone, PartialEq, Eq)]
299pub enum Navigated {
300 Yes,
301 No,
302}
303
304impl Navigated {
305 pub fn from_bool(yes: bool) -> Navigated {
306 if yes { Navigated::Yes } else { Navigated::No }
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq)]
311enum DisplayDiffHunk {
312 Folded {
313 display_row: DisplayRow,
314 },
315 Unfolded {
316 is_created_file: bool,
317 diff_base_byte_range: Range<usize>,
318 display_row_range: Range<DisplayRow>,
319 multi_buffer_range: Range<Anchor>,
320 status: DiffHunkStatus,
321 word_diffs: Vec<Range<MultiBufferOffset>>,
322 },
323}
324
325pub enum HideMouseCursorOrigin {
326 TypingAction,
327 MovementAction,
328}
329
330pub fn init(cx: &mut App) {
331 cx.set_global(GlobalBlameRenderer(Arc::new(())));
332 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
333
334 workspace::register_project_item::<Editor>(cx);
335 workspace::FollowableViewRegistry::register::<Editor>(cx);
336 workspace::register_serializable_item::<Editor>(cx);
337
338 cx.observe_new(
339 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
340 workspace.register_action(Editor::new_file);
341 workspace.register_action(Editor::new_file_split);
342 workspace.register_action(Editor::new_file_vertical);
343 workspace.register_action(Editor::new_file_horizontal);
344 workspace.register_action(Editor::cancel_language_server_work);
345 workspace.register_action(Editor::toggle_focus);
346 },
347 )
348 .detach();
349
350 cx.on_action(move |_: &workspace::NewFile, cx| {
351 let app_state = workspace::AppState::global(cx);
352 if let Some(app_state) = app_state.upgrade() {
353 workspace::open_new(
354 Default::default(),
355 app_state,
356 cx,
357 |workspace, window, cx| {
358 Editor::new_file(workspace, &Default::default(), window, cx)
359 },
360 )
361 .detach();
362 }
363 })
364 .on_action(move |_: &workspace::NewWindow, cx| {
365 let app_state = workspace::AppState::global(cx);
366 if let Some(app_state) = app_state.upgrade() {
367 workspace::open_new(
368 Default::default(),
369 app_state,
370 cx,
371 |workspace, window, cx| {
372 cx.activate(true);
373 Editor::new_file(workspace, &Default::default(), window, cx)
374 },
375 )
376 .detach();
377 }
378 });
379 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
380 Arc::new(ErasedEditorImpl(
381 cx.new(|cx| Editor::single_line(window, cx)),
382 )) as Arc<dyn ErasedEditor>
383 });
384 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
385}
386
387pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
388 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
389}
390
391pub trait DiagnosticRenderer {
392 fn render_group(
393 &self,
394 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
395 buffer_id: BufferId,
396 snapshot: EditorSnapshot,
397 editor: WeakEntity<Editor>,
398 language_registry: Option<Arc<LanguageRegistry>>,
399 cx: &mut App,
400 ) -> Vec<BlockProperties<Anchor>>;
401
402 fn render_hover(
403 &self,
404 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
405 range: Range<Point>,
406 buffer_id: BufferId,
407 language_registry: Option<Arc<LanguageRegistry>>,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
479pub enum SizingBehavior {
480 /// The editor will layout itself using `size_full` and will include the vertical
481 /// scroll margin as requested by user settings.
482 #[default]
483 Default,
484 /// The editor will layout itself using `size_full`, but will not have any
485 /// vertical overscroll.
486 ExcludeOverscrollMargin,
487 /// The editor will request a vertical size according to its content and will be
488 /// layouted without a vertical scroll margin.
489 SizeByContent,
490}
491
492#[derive(Clone, PartialEq, Eq, Debug)]
493pub enum EditorMode {
494 SingleLine,
495 AutoHeight {
496 min_lines: usize,
497 max_lines: Option<usize>,
498 },
499 Full {
500 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
501 scale_ui_elements_with_buffer_font_size: bool,
502 /// When set to `true`, the editor will render a background for the active line.
503 show_active_line_background: bool,
504 /// Determines the sizing behavior for this editor
505 sizing_behavior: SizingBehavior,
506 },
507 Minimap {
508 parent: WeakEntity<Editor>,
509 },
510}
511
512impl EditorMode {
513 pub fn full() -> Self {
514 Self::Full {
515 scale_ui_elements_with_buffer_font_size: true,
516 show_active_line_background: true,
517 sizing_behavior: SizingBehavior::Default,
518 }
519 }
520
521 #[inline]
522 pub fn is_full(&self) -> bool {
523 matches!(self, Self::Full { .. })
524 }
525
526 #[inline]
527 pub fn is_single_line(&self) -> bool {
528 matches!(self, Self::SingleLine { .. })
529 }
530
531 #[inline]
532 fn is_minimap(&self) -> bool {
533 matches!(self, Self::Minimap { .. })
534 }
535}
536
537#[derive(Copy, Clone, Debug)]
538pub enum SoftWrap {
539 /// Prefer not to wrap at all.
540 ///
541 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
542 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
543 GitDiff,
544 /// Prefer a single line generally, unless an overly long line is encountered.
545 None,
546 /// Soft wrap lines that exceed the editor width.
547 EditorWidth,
548 /// Soft wrap lines at the preferred line length.
549 Column(u32),
550 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
551 Bounded(u32),
552}
553
554#[derive(Clone)]
555pub struct EditorStyle {
556 pub background: Hsla,
557 pub border: Hsla,
558 pub local_player: PlayerColor,
559 pub text: TextStyle,
560 pub scrollbar_width: Pixels,
561 pub syntax: Arc<SyntaxTheme>,
562 pub status: StatusColors,
563 pub inlay_hints_style: HighlightStyle,
564 pub edit_prediction_styles: EditPredictionStyles,
565 pub unnecessary_code_fade: f32,
566 pub show_underlines: bool,
567}
568
569impl Default for EditorStyle {
570 fn default() -> Self {
571 Self {
572 background: Hsla::default(),
573 border: Hsla::default(),
574 local_player: PlayerColor::default(),
575 text: TextStyle::default(),
576 scrollbar_width: Pixels::default(),
577 syntax: Default::default(),
578 // HACK: Status colors don't have a real default.
579 // We should look into removing the status colors from the editor
580 // style and retrieve them directly from the theme.
581 status: StatusColors::dark(),
582 inlay_hints_style: HighlightStyle::default(),
583 edit_prediction_styles: EditPredictionStyles {
584 insertion: HighlightStyle::default(),
585 whitespace: HighlightStyle::default(),
586 },
587 unnecessary_code_fade: Default::default(),
588 show_underlines: true,
589 }
590 }
591}
592
593pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
594 let show_background = language_settings::language_settings(None, None, cx)
595 .inlay_hints
596 .show_background;
597
598 let mut style = cx.theme().syntax().get("hint");
599
600 if style.color.is_none() {
601 style.color = Some(cx.theme().status().hint);
602 }
603
604 if !show_background {
605 style.background_color = None;
606 return style;
607 }
608
609 if style.background_color.is_none() {
610 style.background_color = Some(cx.theme().status().hint_background);
611 }
612
613 style
614}
615
616pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
617 EditPredictionStyles {
618 insertion: HighlightStyle {
619 color: Some(cx.theme().status().predictive),
620 ..HighlightStyle::default()
621 },
622 whitespace: HighlightStyle {
623 background_color: Some(cx.theme().status().created_background),
624 ..HighlightStyle::default()
625 },
626 }
627}
628
629type CompletionId = usize;
630
631pub(crate) enum EditDisplayMode {
632 TabAccept,
633 DiffPopover,
634 Inline,
635}
636
637enum EditPrediction {
638 Edit {
639 edits: Vec<(Range<Anchor>, Arc<str>)>,
640 /// Predicted cursor position as (anchor, offset_from_anchor).
641 /// The anchor is in multibuffer coordinates; after applying edits,
642 /// resolve the anchor and add the offset to get the final cursor position.
643 cursor_position: Option<(Anchor, usize)>,
644 edit_preview: Option<EditPreview>,
645 display_mode: EditDisplayMode,
646 snapshot: BufferSnapshot,
647 },
648 /// Move to a specific location in the active editor
649 MoveWithin {
650 target: Anchor,
651 snapshot: BufferSnapshot,
652 },
653 /// Move to a specific location in a different editor (not the active one)
654 MoveOutside {
655 target: language::Anchor,
656 snapshot: BufferSnapshot,
657 },
658}
659
660struct EditPredictionState {
661 inlay_ids: Vec<InlayId>,
662 completion: EditPrediction,
663 completion_id: Option<SharedString>,
664 invalidation_range: Option<Range<Anchor>>,
665}
666
667enum EditPredictionSettings {
668 Disabled,
669 Enabled {
670 show_in_menu: bool,
671 preview_requires_modifier: bool,
672 },
673}
674
675#[derive(Debug, Clone)]
676struct InlineDiagnostic {
677 message: SharedString,
678 group_id: usize,
679 is_primary: bool,
680 start: Point,
681 severity: lsp::DiagnosticSeverity,
682}
683
684pub enum MenuEditPredictionsPolicy {
685 Never,
686 ByProvider,
687}
688
689pub enum EditPredictionPreview {
690 /// Modifier is not pressed
691 Inactive { released_too_fast: bool },
692 /// Modifier pressed
693 Active {
694 since: Instant,
695 previous_scroll_position: Option<SharedScrollAnchor>,
696 },
697}
698
699impl EditPredictionPreview {
700 pub fn released_too_fast(&self) -> bool {
701 match self {
702 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
703 EditPredictionPreview::Active { .. } => false,
704 }
705 }
706
707 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
708 if let EditPredictionPreview::Active {
709 previous_scroll_position,
710 ..
711 } = self
712 {
713 *previous_scroll_position = scroll_position;
714 }
715 }
716}
717
718pub struct ContextMenuOptions {
719 pub min_entries_visible: usize,
720 pub max_entries_visible: usize,
721 pub placement: Option<ContextMenuPlacement>,
722}
723
724#[derive(Debug, Clone, PartialEq, Eq)]
725pub enum ContextMenuPlacement {
726 Above,
727 Below,
728}
729
730#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
731struct EditorActionId(usize);
732
733impl EditorActionId {
734 pub fn post_inc(&mut self) -> Self {
735 let answer = self.0;
736
737 *self = Self(answer + 1);
738
739 Self(answer)
740 }
741}
742
743// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
744// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
745
746type BackgroundHighlight = (
747 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
748 Arc<[Range<Anchor>]>,
749);
750type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
751
752#[derive(Default)]
753struct ScrollbarMarkerState {
754 scrollbar_size: Size<Pixels>,
755 dirty: bool,
756 markers: Arc<[PaintQuad]>,
757 pending_refresh: Option<Task<Result<()>>>,
758}
759
760impl ScrollbarMarkerState {
761 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
762 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
763 }
764}
765
766#[derive(Clone, Copy, PartialEq, Eq)]
767pub enum MinimapVisibility {
768 Disabled,
769 Enabled {
770 /// The configuration currently present in the users settings.
771 setting_configuration: bool,
772 /// Whether to override the currently set visibility from the users setting.
773 toggle_override: bool,
774 },
775}
776
777impl MinimapVisibility {
778 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
779 if mode.is_full() {
780 Self::Enabled {
781 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
782 toggle_override: false,
783 }
784 } else {
785 Self::Disabled
786 }
787 }
788
789 fn hidden(&self) -> Self {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 ..
794 } => Self::Enabled {
795 setting_configuration,
796 toggle_override: setting_configuration,
797 },
798 Self::Disabled => Self::Disabled,
799 }
800 }
801
802 fn disabled(&self) -> bool {
803 matches!(*self, Self::Disabled)
804 }
805
806 fn settings_visibility(&self) -> bool {
807 match *self {
808 Self::Enabled {
809 setting_configuration,
810 ..
811 } => setting_configuration,
812 _ => false,
813 }
814 }
815
816 fn visible(&self) -> bool {
817 match *self {
818 Self::Enabled {
819 setting_configuration,
820 toggle_override,
821 } => setting_configuration ^ toggle_override,
822 _ => false,
823 }
824 }
825
826 fn toggle_visibility(&self) -> Self {
827 match *self {
828 Self::Enabled {
829 toggle_override,
830 setting_configuration,
831 } => Self::Enabled {
832 setting_configuration,
833 toggle_override: !toggle_override,
834 },
835 Self::Disabled => Self::Disabled,
836 }
837 }
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq)]
841pub enum BufferSerialization {
842 All,
843 NonDirtyBuffers,
844}
845
846impl BufferSerialization {
847 fn new(restore_unsaved_buffers: bool) -> Self {
848 if restore_unsaved_buffers {
849 Self::All
850 } else {
851 Self::NonDirtyBuffers
852 }
853 }
854}
855
856#[derive(Clone, Debug)]
857struct RunnableTasks {
858 templates: Vec<(TaskSourceKind, TaskTemplate)>,
859 offset: multi_buffer::Anchor,
860 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
861 column: u32,
862 // Values of all named captures, including those starting with '_'
863 extra_variables: HashMap<String, String>,
864 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
865 context_range: Range<BufferOffset>,
866}
867
868impl RunnableTasks {
869 fn resolve<'a>(
870 &'a self,
871 cx: &'a task::TaskContext,
872 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
873 self.templates.iter().filter_map(|(kind, template)| {
874 template
875 .resolve_task(&kind.to_id_base(), cx)
876 .map(|task| (kind.clone(), task))
877 })
878 }
879}
880
881#[derive(Clone)]
882pub struct ResolvedTasks {
883 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
884 position: Anchor,
885}
886
887/// Addons allow storing per-editor state in other crates (e.g. Vim)
888pub trait Addon: 'static {
889 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
890
891 fn render_buffer_header_controls(
892 &self,
893 _: &ExcerptInfo,
894 _: &Window,
895 _: &App,
896 ) -> Option<AnyElement> {
897 None
898 }
899
900 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
901 None
902 }
903
904 fn to_any(&self) -> &dyn std::any::Any;
905
906 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
907 None
908 }
909}
910
911struct ChangeLocation {
912 current: Option<Vec<Anchor>>,
913 original: Vec<Anchor>,
914}
915impl ChangeLocation {
916 fn locations(&self) -> &[Anchor] {
917 self.current.as_ref().unwrap_or(&self.original)
918 }
919}
920
921/// A set of caret positions, registered when the editor was edited.
922pub struct ChangeList {
923 changes: Vec<ChangeLocation>,
924 /// Currently "selected" change.
925 position: Option<usize>,
926}
927
928impl ChangeList {
929 pub fn new() -> Self {
930 Self {
931 changes: Vec::new(),
932 position: None,
933 }
934 }
935
936 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
937 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
938 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
939 if self.changes.is_empty() {
940 return None;
941 }
942
943 let prev = self.position.unwrap_or(self.changes.len());
944 let next = if direction == Direction::Prev {
945 prev.saturating_sub(count)
946 } else {
947 (prev + count).min(self.changes.len() - 1)
948 };
949 self.position = Some(next);
950 self.changes.get(next).map(|change| change.locations())
951 }
952
953 /// Adds a new change to the list, resetting the change list position.
954 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
955 self.position.take();
956 if let Some(last) = self.changes.last_mut()
957 && group
958 {
959 last.current = Some(new_positions)
960 } else {
961 self.changes.push(ChangeLocation {
962 original: new_positions,
963 current: None,
964 });
965 }
966 }
967
968 pub fn last(&self) -> Option<&[Anchor]> {
969 self.changes.last().map(|change| change.locations())
970 }
971
972 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
973 self.changes.last().map(|change| change.original.as_slice())
974 }
975
976 pub fn invert_last_group(&mut self) {
977 if let Some(last) = self.changes.last_mut()
978 && let Some(current) = last.current.as_mut()
979 {
980 mem::swap(&mut last.original, current);
981 }
982 }
983}
984
985#[derive(Clone)]
986struct InlineBlamePopoverState {
987 scroll_handle: ScrollHandle,
988 commit_message: Option<ParsedCommitMessage>,
989 markdown: Entity<Markdown>,
990}
991
992struct InlineBlamePopover {
993 position: gpui::Point<Pixels>,
994 hide_task: Option<Task<()>>,
995 popover_bounds: Option<Bounds<Pixels>>,
996 popover_state: InlineBlamePopoverState,
997 keyboard_grace: bool,
998}
999
1000enum SelectionDragState {
1001 /// State when no drag related activity is detected.
1002 None,
1003 /// State when the mouse is down on a selection that is about to be dragged.
1004 ReadyToDrag {
1005 selection: Selection<Anchor>,
1006 click_position: gpui::Point<Pixels>,
1007 mouse_down_time: Instant,
1008 },
1009 /// State when the mouse is dragging the selection in the editor.
1010 Dragging {
1011 selection: Selection<Anchor>,
1012 drop_cursor: Selection<Anchor>,
1013 hide_drop_cursor: bool,
1014 },
1015}
1016
1017enum ColumnarSelectionState {
1018 FromMouse {
1019 selection_tail: Anchor,
1020 display_point: Option<DisplayPoint>,
1021 },
1022 FromSelection {
1023 selection_tail: Anchor,
1024 },
1025}
1026
1027/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1028/// a breakpoint on them.
1029#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1030struct PhantomBreakpointIndicator {
1031 display_row: DisplayRow,
1032 /// There's a small debounce between hovering over the line and showing the indicator.
1033 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1034 is_active: bool,
1035 collides_with_existing_breakpoint: bool,
1036}
1037
1038/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1039/// in diff view mode.
1040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1041pub(crate) struct PhantomDiffReviewIndicator {
1042 /// The starting anchor of the selection (or the only row if not dragging).
1043 pub start: Anchor,
1044 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1045 pub end: Anchor,
1046 /// There's a small debounce between hovering over the line and showing the indicator.
1047 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1048 pub is_active: bool,
1049}
1050
1051#[derive(Clone, Debug)]
1052pub(crate) struct DiffReviewDragState {
1053 pub start_anchor: Anchor,
1054 pub current_anchor: Anchor,
1055}
1056
1057impl DiffReviewDragState {
1058 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1059 let start = self.start_anchor.to_display_point(snapshot).row();
1060 let current = self.current_anchor.to_display_point(snapshot).row();
1061
1062 (start..=current).sorted()
1063 }
1064}
1065
1066/// Identifies a specific hunk in the diff buffer.
1067/// Used as a key to group comments by their location.
1068#[derive(Clone, Debug)]
1069pub struct DiffHunkKey {
1070 /// The file path (relative to worktree) this hunk belongs to.
1071 pub file_path: Arc<util::rel_path::RelPath>,
1072 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1073 pub hunk_start_anchor: Anchor,
1074}
1075
1076/// A review comment stored locally before being sent to the Agent panel.
1077#[derive(Clone)]
1078pub struct StoredReviewComment {
1079 /// Unique identifier for this comment (for edit/delete operations).
1080 pub id: usize,
1081 /// The comment text entered by the user.
1082 pub comment: String,
1083 /// Anchors for the code range being reviewed.
1084 pub range: Range<Anchor>,
1085 /// Timestamp when the comment was created (for chronological ordering).
1086 pub created_at: Instant,
1087 /// Whether this comment is currently being edited inline.
1088 pub is_editing: bool,
1089}
1090
1091impl StoredReviewComment {
1092 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1093 Self {
1094 id,
1095 comment,
1096 range: anchor_range,
1097 created_at: Instant::now(),
1098 is_editing: false,
1099 }
1100 }
1101}
1102
1103/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1104pub(crate) struct DiffReviewOverlay {
1105 pub anchor_range: Range<Anchor>,
1106 /// The block ID for the overlay.
1107 pub block_id: CustomBlockId,
1108 /// The editor entity for the review input.
1109 pub prompt_editor: Entity<Editor>,
1110 /// The hunk key this overlay belongs to.
1111 pub hunk_key: DiffHunkKey,
1112 /// Whether the comments section is expanded.
1113 pub comments_expanded: bool,
1114 /// Editors for comments currently being edited inline.
1115 /// Key: comment ID, Value: Editor entity for inline editing.
1116 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1117 /// Subscriptions for inline edit editors' action handlers.
1118 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1119 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1120 /// The current user's avatar URI for display in comment rows.
1121 pub user_avatar_uri: Option<SharedUri>,
1122 /// Subscription to keep the action handler alive.
1123 _subscription: Subscription,
1124}
1125
1126/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1127///
1128/// See the [module level documentation](self) for more information.
1129pub struct Editor {
1130 focus_handle: FocusHandle,
1131 last_focused_descendant: Option<WeakFocusHandle>,
1132 /// The text buffer being edited
1133 buffer: Entity<MultiBuffer>,
1134 /// Map of how text in the buffer should be displayed.
1135 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1136 pub display_map: Entity<DisplayMap>,
1137 placeholder_display_map: Option<Entity<DisplayMap>>,
1138 pub selections: SelectionsCollection,
1139 pub scroll_manager: ScrollManager,
1140 /// When inline assist editors are linked, they all render cursors because
1141 /// typing enters text into each of them, even the ones that aren't focused.
1142 pub(crate) show_cursor_when_unfocused: bool,
1143 columnar_selection_state: Option<ColumnarSelectionState>,
1144 add_selections_state: Option<AddSelectionsState>,
1145 select_next_state: Option<SelectNextState>,
1146 select_prev_state: Option<SelectNextState>,
1147 selection_history: SelectionHistory,
1148 defer_selection_effects: bool,
1149 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1150 autoclose_regions: Vec<AutocloseRegion>,
1151 snippet_stack: InvalidationStack<SnippetState>,
1152 select_syntax_node_history: SelectSyntaxNodeHistory,
1153 ime_transaction: Option<TransactionId>,
1154 pub diagnostics_max_severity: DiagnosticSeverity,
1155 active_diagnostics: ActiveDiagnostic,
1156 show_inline_diagnostics: bool,
1157 inline_diagnostics_update: Task<()>,
1158 inline_diagnostics_enabled: bool,
1159 diagnostics_enabled: bool,
1160 word_completions_enabled: bool,
1161 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1162 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1163 hard_wrap: Option<usize>,
1164 project: Option<Entity<Project>>,
1165 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1166 completion_provider: Option<Rc<dyn CompletionProvider>>,
1167 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1168 blink_manager: Entity<BlinkManager>,
1169 show_cursor_names: bool,
1170 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1171 pub show_local_selections: bool,
1172 mode: EditorMode,
1173 show_breadcrumbs: bool,
1174 show_gutter: bool,
1175 show_scrollbars: ScrollbarAxes,
1176 minimap_visibility: MinimapVisibility,
1177 offset_content: bool,
1178 disable_expand_excerpt_buttons: bool,
1179 delegate_expand_excerpts: bool,
1180 delegate_stage_and_restore: bool,
1181 delegate_open_excerpts: bool,
1182 enable_lsp_data: bool,
1183 enable_runnables: bool,
1184 show_line_numbers: Option<bool>,
1185 use_relative_line_numbers: Option<bool>,
1186 show_git_diff_gutter: Option<bool>,
1187 show_code_actions: Option<bool>,
1188 show_runnables: Option<bool>,
1189 show_breakpoints: Option<bool>,
1190 show_diff_review_button: bool,
1191 show_wrap_guides: Option<bool>,
1192 show_indent_guides: Option<bool>,
1193 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1194 highlight_order: usize,
1195 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1196 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1197 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1198 scrollbar_marker_state: ScrollbarMarkerState,
1199 active_indent_guides_state: ActiveIndentGuidesState,
1200 nav_history: Option<ItemNavHistory>,
1201 context_menu: RefCell<Option<CodeContextMenu>>,
1202 context_menu_options: Option<ContextMenuOptions>,
1203 mouse_context_menu: Option<MouseContextMenu>,
1204 completion_tasks: Vec<(CompletionId, Task<()>)>,
1205 inline_blame_popover: Option<InlineBlamePopover>,
1206 inline_blame_popover_show_task: Option<Task<()>>,
1207 signature_help_state: SignatureHelpState,
1208 auto_signature_help: Option<bool>,
1209 find_all_references_task_sources: Vec<Anchor>,
1210 next_completion_id: CompletionId,
1211 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1212 code_actions_task: Option<Task<Result<()>>>,
1213 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1214 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1215 debounced_selection_highlight_complete: bool,
1216 document_highlights_task: Option<Task<()>>,
1217 linked_editing_range_task: Option<Task<Option<()>>>,
1218 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1219 pending_rename: Option<RenameState>,
1220 searchable: bool,
1221 cursor_shape: CursorShape,
1222 /// Whether the cursor is offset one character to the left when something is
1223 /// selected (needed for vim visual mode)
1224 cursor_offset_on_selection: bool,
1225 current_line_highlight: Option<CurrentLineHighlight>,
1226 /// Whether to collapse search match ranges to just their start position.
1227 /// When true, navigating to a match positions the cursor at the match
1228 /// without selecting the matched text.
1229 collapse_matches: bool,
1230 autoindent_mode: Option<AutoindentMode>,
1231 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1232 input_enabled: bool,
1233 use_modal_editing: bool,
1234 read_only: bool,
1235 leader_id: Option<CollaboratorId>,
1236 remote_id: Option<ViewId>,
1237 pub hover_state: HoverState,
1238 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1239 prev_pressure_stage: Option<PressureStage>,
1240 gutter_hovered: bool,
1241 hovered_link_state: Option<HoveredLinkState>,
1242 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1243 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1244 active_edit_prediction: Option<EditPredictionState>,
1245 /// Used to prevent flickering as the user types while the menu is open
1246 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1247 edit_prediction_settings: EditPredictionSettings,
1248 edit_predictions_hidden_for_vim_mode: bool,
1249 show_edit_predictions_override: Option<bool>,
1250 show_completions_on_input_override: Option<bool>,
1251 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1252 edit_prediction_preview: EditPredictionPreview,
1253 edit_prediction_indent_conflict: bool,
1254 edit_prediction_requires_modifier_in_indent_conflict: bool,
1255 next_inlay_id: usize,
1256 next_color_inlay_id: usize,
1257 _subscriptions: Vec<Subscription>,
1258 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1259 gutter_dimensions: GutterDimensions,
1260 style: Option<EditorStyle>,
1261 text_style_refinement: Option<TextStyleRefinement>,
1262 next_editor_action_id: EditorActionId,
1263 editor_actions: Rc<
1264 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1265 >,
1266 use_autoclose: bool,
1267 use_auto_surround: bool,
1268 auto_replace_emoji_shortcode: bool,
1269 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1270 show_git_blame_gutter: bool,
1271 show_git_blame_inline: bool,
1272 show_git_blame_inline_delay_task: Option<Task<()>>,
1273 git_blame_inline_enabled: bool,
1274 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1275 buffer_serialization: Option<BufferSerialization>,
1276 show_selection_menu: Option<bool>,
1277 blame: Option<Entity<GitBlame>>,
1278 blame_subscription: Option<Subscription>,
1279 custom_context_menu: Option<
1280 Box<
1281 dyn 'static
1282 + Fn(
1283 &mut Self,
1284 DisplayPoint,
1285 &mut Window,
1286 &mut Context<Self>,
1287 ) -> Option<Entity<ui::ContextMenu>>,
1288 >,
1289 >,
1290 last_bounds: Option<Bounds<Pixels>>,
1291 last_position_map: Option<Rc<PositionMap>>,
1292 expect_bounds_change: Option<Bounds<Pixels>>,
1293 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1294 tasks_update_task: Option<Task<()>>,
1295 breakpoint_store: Option<Entity<BreakpointStore>>,
1296 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1297 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1298 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1299 /// Active diff review overlays. Multiple overlays can be open simultaneously
1300 /// when hunks have comments stored.
1301 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1302 /// Stored review comments grouped by hunk.
1303 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1304 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1305 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1306 /// Counter for generating unique comment IDs.
1307 next_review_comment_id: usize,
1308 hovered_diff_hunk_row: Option<DisplayRow>,
1309 pull_diagnostics_task: Task<()>,
1310 in_project_search: bool,
1311 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1312 breadcrumb_header: Option<String>,
1313 focused_block: Option<FocusedBlock>,
1314 next_scroll_position: NextScrollCursorCenterTopBottom,
1315 addons: HashMap<TypeId, Box<dyn Addon>>,
1316 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1317 load_diff_task: Option<Shared<Task<()>>>,
1318 /// Whether we are temporarily displaying a diff other than git's
1319 temporary_diff_override: bool,
1320 selection_mark_mode: bool,
1321 toggle_fold_multiple_buffers: Task<()>,
1322 _scroll_cursor_center_top_bottom_task: Task<()>,
1323 serialize_selections: Task<()>,
1324 serialize_folds: Task<()>,
1325 mouse_cursor_hidden: bool,
1326 minimap: Option<Entity<Self>>,
1327 hide_mouse_mode: HideMouseMode,
1328 pub change_list: ChangeList,
1329 inline_value_cache: InlineValueCache,
1330 number_deleted_lines: bool,
1331
1332 selection_drag_state: SelectionDragState,
1333 colors: Option<LspColorData>,
1334 post_scroll_update: Task<()>,
1335 refresh_colors_task: Task<()>,
1336 use_document_folding_ranges: bool,
1337 refresh_folding_ranges_task: Task<()>,
1338 inlay_hints: Option<LspInlayHintData>,
1339 folding_newlines: Task<()>,
1340 select_next_is_case_sensitive: Option<bool>,
1341 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1342 on_local_selections_changed:
1343 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1344 suppress_selection_callback: bool,
1345 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1346 accent_data: Option<AccentData>,
1347 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1348 semantic_token_state: SemanticTokenState,
1349 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1350 refresh_outline_symbols_task: Task<()>,
1351 outline_symbols: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1352 sticky_headers_task: Task<()>,
1353 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1354}
1355
1356#[derive(Debug, PartialEq)]
1357struct AccentData {
1358 colors: AccentColors,
1359 overrides: Vec<SharedString>,
1360}
1361
1362fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1363 if debounce_ms > 0 {
1364 Some(Duration::from_millis(debounce_ms))
1365 } else {
1366 None
1367 }
1368}
1369
1370#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1371enum NextScrollCursorCenterTopBottom {
1372 #[default]
1373 Center,
1374 Top,
1375 Bottom,
1376}
1377
1378impl NextScrollCursorCenterTopBottom {
1379 fn next(&self) -> Self {
1380 match self {
1381 Self::Center => Self::Top,
1382 Self::Top => Self::Bottom,
1383 Self::Bottom => Self::Center,
1384 }
1385 }
1386}
1387
1388#[derive(Clone)]
1389pub struct EditorSnapshot {
1390 pub mode: EditorMode,
1391 show_gutter: bool,
1392 offset_content: bool,
1393 show_line_numbers: Option<bool>,
1394 number_deleted_lines: bool,
1395 show_git_diff_gutter: Option<bool>,
1396 show_code_actions: Option<bool>,
1397 show_runnables: Option<bool>,
1398 show_breakpoints: Option<bool>,
1399 git_blame_gutter_max_author_length: Option<usize>,
1400 pub display_snapshot: DisplaySnapshot,
1401 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1402 is_focused: bool,
1403 scroll_anchor: SharedScrollAnchor,
1404 ongoing_scroll: OngoingScroll,
1405 current_line_highlight: CurrentLineHighlight,
1406 gutter_hovered: bool,
1407 semantic_tokens_enabled: bool,
1408}
1409
1410#[derive(Default, Debug, Clone, Copy)]
1411pub struct GutterDimensions {
1412 pub left_padding: Pixels,
1413 pub right_padding: Pixels,
1414 pub width: Pixels,
1415 pub margin: Pixels,
1416 pub git_blame_entries_width: Option<Pixels>,
1417}
1418
1419impl GutterDimensions {
1420 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1421 Self {
1422 margin: Self::default_gutter_margin(font_id, font_size, cx),
1423 ..Default::default()
1424 }
1425 }
1426
1427 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1428 -cx.text_system().descent(font_id, font_size)
1429 }
1430 /// The full width of the space taken up by the gutter.
1431 pub fn full_width(&self) -> Pixels {
1432 self.margin + self.width
1433 }
1434
1435 /// The width of the space reserved for the fold indicators,
1436 /// use alongside 'justify_end' and `gutter_width` to
1437 /// right align content with the line numbers
1438 pub fn fold_area_width(&self) -> Pixels {
1439 self.margin + self.right_padding
1440 }
1441}
1442
1443struct CharacterDimensions {
1444 em_width: Pixels,
1445 em_advance: Pixels,
1446 line_height: Pixels,
1447}
1448
1449#[derive(Debug)]
1450pub struct RemoteSelection {
1451 pub replica_id: ReplicaId,
1452 pub selection: Selection<Anchor>,
1453 pub cursor_shape: CursorShape,
1454 pub collaborator_id: CollaboratorId,
1455 pub line_mode: bool,
1456 pub user_name: Option<SharedString>,
1457 pub color: PlayerColor,
1458}
1459
1460#[derive(Clone, Debug)]
1461struct SelectionHistoryEntry {
1462 selections: Arc<[Selection<Anchor>]>,
1463 select_next_state: Option<SelectNextState>,
1464 select_prev_state: Option<SelectNextState>,
1465 add_selections_state: Option<AddSelectionsState>,
1466}
1467
1468#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1469enum SelectionHistoryMode {
1470 #[default]
1471 Normal,
1472 Undoing,
1473 Redoing,
1474 Skipping,
1475}
1476
1477#[derive(Clone, PartialEq, Eq, Hash)]
1478struct HoveredCursor {
1479 replica_id: ReplicaId,
1480 selection_id: usize,
1481}
1482
1483#[derive(Debug)]
1484/// SelectionEffects controls the side-effects of updating the selection.
1485///
1486/// The default behaviour does "what you mostly want":
1487/// - it pushes to the nav history if the cursor moved by >10 lines
1488/// - it re-triggers completion requests
1489/// - it scrolls to fit
1490///
1491/// You might want to modify these behaviours. For example when doing a "jump"
1492/// like go to definition, we always want to add to nav history; but when scrolling
1493/// in vim mode we never do.
1494///
1495/// Similarly, you might want to disable scrolling if you don't want the viewport to
1496/// move.
1497#[derive(Clone)]
1498pub struct SelectionEffects {
1499 nav_history: Option<bool>,
1500 completions: bool,
1501 scroll: Option<Autoscroll>,
1502}
1503
1504impl Default for SelectionEffects {
1505 fn default() -> Self {
1506 Self {
1507 nav_history: None,
1508 completions: true,
1509 scroll: Some(Autoscroll::fit()),
1510 }
1511 }
1512}
1513impl SelectionEffects {
1514 pub fn scroll(scroll: Autoscroll) -> Self {
1515 Self {
1516 scroll: Some(scroll),
1517 ..Default::default()
1518 }
1519 }
1520
1521 pub fn no_scroll() -> Self {
1522 Self {
1523 scroll: None,
1524 ..Default::default()
1525 }
1526 }
1527
1528 pub fn completions(self, completions: bool) -> Self {
1529 Self {
1530 completions,
1531 ..self
1532 }
1533 }
1534
1535 pub fn nav_history(self, nav_history: bool) -> Self {
1536 Self {
1537 nav_history: Some(nav_history),
1538 ..self
1539 }
1540 }
1541}
1542
1543struct DeferredSelectionEffectsState {
1544 changed: bool,
1545 effects: SelectionEffects,
1546 old_cursor_position: Anchor,
1547 history_entry: SelectionHistoryEntry,
1548}
1549
1550#[derive(Default)]
1551struct SelectionHistory {
1552 #[allow(clippy::type_complexity)]
1553 selections_by_transaction:
1554 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1555 mode: SelectionHistoryMode,
1556 undo_stack: VecDeque<SelectionHistoryEntry>,
1557 redo_stack: VecDeque<SelectionHistoryEntry>,
1558}
1559
1560impl SelectionHistory {
1561 #[track_caller]
1562 fn insert_transaction(
1563 &mut self,
1564 transaction_id: TransactionId,
1565 selections: Arc<[Selection<Anchor>]>,
1566 ) {
1567 if selections.is_empty() {
1568 log::error!(
1569 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1570 std::panic::Location::caller()
1571 );
1572 return;
1573 }
1574 self.selections_by_transaction
1575 .insert(transaction_id, (selections, None));
1576 }
1577
1578 #[allow(clippy::type_complexity)]
1579 fn transaction(
1580 &self,
1581 transaction_id: TransactionId,
1582 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1583 self.selections_by_transaction.get(&transaction_id)
1584 }
1585
1586 #[allow(clippy::type_complexity)]
1587 fn transaction_mut(
1588 &mut self,
1589 transaction_id: TransactionId,
1590 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1591 self.selections_by_transaction.get_mut(&transaction_id)
1592 }
1593
1594 fn push(&mut self, entry: SelectionHistoryEntry) {
1595 if !entry.selections.is_empty() {
1596 match self.mode {
1597 SelectionHistoryMode::Normal => {
1598 self.push_undo(entry);
1599 self.redo_stack.clear();
1600 }
1601 SelectionHistoryMode::Undoing => self.push_redo(entry),
1602 SelectionHistoryMode::Redoing => self.push_undo(entry),
1603 SelectionHistoryMode::Skipping => {}
1604 }
1605 }
1606 }
1607
1608 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1609 if self
1610 .undo_stack
1611 .back()
1612 .is_none_or(|e| e.selections != entry.selections)
1613 {
1614 self.undo_stack.push_back(entry);
1615 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1616 self.undo_stack.pop_front();
1617 }
1618 }
1619 }
1620
1621 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1622 if self
1623 .redo_stack
1624 .back()
1625 .is_none_or(|e| e.selections != entry.selections)
1626 {
1627 self.redo_stack.push_back(entry);
1628 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1629 self.redo_stack.pop_front();
1630 }
1631 }
1632 }
1633}
1634
1635#[derive(Clone, Copy)]
1636pub struct RowHighlightOptions {
1637 pub autoscroll: bool,
1638 pub include_gutter: bool,
1639}
1640
1641impl Default for RowHighlightOptions {
1642 fn default() -> Self {
1643 Self {
1644 autoscroll: Default::default(),
1645 include_gutter: true,
1646 }
1647 }
1648}
1649
1650struct RowHighlight {
1651 index: usize,
1652 range: Range<Anchor>,
1653 color: Hsla,
1654 options: RowHighlightOptions,
1655 type_id: TypeId,
1656}
1657
1658#[derive(Clone, Debug)]
1659struct AddSelectionsState {
1660 groups: Vec<AddSelectionsGroup>,
1661}
1662
1663#[derive(Clone, Debug)]
1664struct AddSelectionsGroup {
1665 above: bool,
1666 stack: Vec<usize>,
1667}
1668
1669#[derive(Clone)]
1670struct SelectNextState {
1671 query: AhoCorasick,
1672 wordwise: bool,
1673 done: bool,
1674}
1675
1676impl std::fmt::Debug for SelectNextState {
1677 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1678 f.debug_struct(std::any::type_name::<Self>())
1679 .field("wordwise", &self.wordwise)
1680 .field("done", &self.done)
1681 .finish()
1682 }
1683}
1684
1685#[derive(Debug)]
1686struct AutocloseRegion {
1687 selection_id: usize,
1688 range: Range<Anchor>,
1689 pair: BracketPair,
1690}
1691
1692#[derive(Debug)]
1693struct SnippetState {
1694 ranges: Vec<Vec<Range<Anchor>>>,
1695 active_index: usize,
1696 choices: Vec<Option<Vec<String>>>,
1697}
1698
1699#[doc(hidden)]
1700pub struct RenameState {
1701 pub range: Range<Anchor>,
1702 pub old_name: Arc<str>,
1703 pub editor: Entity<Editor>,
1704 block_id: CustomBlockId,
1705}
1706
1707struct InvalidationStack<T>(Vec<T>);
1708
1709struct RegisteredEditPredictionDelegate {
1710 provider: Arc<dyn EditPredictionDelegateHandle>,
1711 _subscription: Subscription,
1712}
1713
1714#[derive(Debug, PartialEq, Eq)]
1715pub struct ActiveDiagnosticGroup {
1716 pub active_range: Range<Anchor>,
1717 pub active_message: String,
1718 pub group_id: usize,
1719 pub blocks: HashSet<CustomBlockId>,
1720}
1721
1722#[derive(Debug, PartialEq, Eq)]
1723
1724pub(crate) enum ActiveDiagnostic {
1725 None,
1726 All,
1727 Group(ActiveDiagnosticGroup),
1728}
1729
1730#[derive(Serialize, Deserialize, Clone, Debug)]
1731pub struct ClipboardSelection {
1732 /// The number of bytes in this selection.
1733 pub len: usize,
1734 /// Whether this was a full-line selection.
1735 pub is_entire_line: bool,
1736 /// The indentation of the first line when this content was originally copied.
1737 pub first_line_indent: u32,
1738 #[serde(default)]
1739 pub file_path: Option<PathBuf>,
1740 #[serde(default)]
1741 pub line_range: Option<RangeInclusive<u32>>,
1742}
1743
1744impl ClipboardSelection {
1745 pub fn for_buffer(
1746 len: usize,
1747 is_entire_line: bool,
1748 range: Range<Point>,
1749 buffer: &MultiBufferSnapshot,
1750 project: Option<&Entity<Project>>,
1751 cx: &App,
1752 ) -> Self {
1753 let first_line_indent = buffer
1754 .indent_size_for_line(MultiBufferRow(range.start.row))
1755 .len;
1756
1757 let file_path = util::maybe!({
1758 let project = project?.read(cx);
1759 let file = buffer.file_at(range.start)?;
1760 let project_path = ProjectPath {
1761 worktree_id: file.worktree_id(cx),
1762 path: file.path().clone(),
1763 };
1764 project.absolute_path(&project_path, cx)
1765 });
1766
1767 let line_range = file_path.as_ref().and_then(|_| {
1768 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1769 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1770 if start_excerpt_id == end_excerpt_id {
1771 Some(start_point.row..=end_point.row)
1772 } else {
1773 None
1774 }
1775 });
1776
1777 Self {
1778 len,
1779 is_entire_line,
1780 first_line_indent,
1781 file_path,
1782 line_range,
1783 }
1784 }
1785}
1786
1787// selections, scroll behavior, was newest selection reversed
1788type SelectSyntaxNodeHistoryState = (
1789 Box<[Selection<MultiBufferOffset>]>,
1790 SelectSyntaxNodeScrollBehavior,
1791 bool,
1792);
1793
1794#[derive(Default)]
1795struct SelectSyntaxNodeHistory {
1796 stack: Vec<SelectSyntaxNodeHistoryState>,
1797 // disable temporarily to allow changing selections without losing the stack
1798 pub disable_clearing: bool,
1799}
1800
1801impl SelectSyntaxNodeHistory {
1802 pub fn try_clear(&mut self) {
1803 if !self.disable_clearing {
1804 self.stack.clear();
1805 }
1806 }
1807
1808 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1809 self.stack.push(selection);
1810 }
1811
1812 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1813 self.stack.pop()
1814 }
1815}
1816
1817enum SelectSyntaxNodeScrollBehavior {
1818 CursorTop,
1819 FitSelection,
1820 CursorBottom,
1821}
1822
1823#[derive(Debug, Clone, Copy)]
1824pub(crate) struct NavigationData {
1825 cursor_anchor: Anchor,
1826 cursor_position: Point,
1827 scroll_anchor: ScrollAnchor,
1828 scroll_top_row: u32,
1829}
1830
1831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1832pub enum GotoDefinitionKind {
1833 Symbol,
1834 Declaration,
1835 Type,
1836 Implementation,
1837}
1838
1839pub enum FormatTarget {
1840 Buffers(HashSet<Entity<Buffer>>),
1841 Ranges(Vec<Range<MultiBufferPoint>>),
1842}
1843
1844pub(crate) struct FocusedBlock {
1845 id: BlockId,
1846 focus_handle: WeakFocusHandle,
1847}
1848
1849#[derive(Clone, Debug)]
1850pub enum JumpData {
1851 MultiBufferRow {
1852 row: MultiBufferRow,
1853 line_offset_from_top: u32,
1854 },
1855 MultiBufferPoint {
1856 excerpt_id: ExcerptId,
1857 position: Point,
1858 anchor: text::Anchor,
1859 line_offset_from_top: u32,
1860 },
1861}
1862
1863pub enum MultibufferSelectionMode {
1864 First,
1865 All,
1866}
1867
1868#[derive(Clone, Copy, Debug, Default)]
1869pub struct RewrapOptions {
1870 pub override_language_settings: bool,
1871 pub preserve_existing_whitespace: bool,
1872}
1873
1874impl Editor {
1875 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1876 let buffer = cx.new(|cx| Buffer::local("", cx));
1877 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1878 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1879 }
1880
1881 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1882 let buffer = cx.new(|cx| Buffer::local("", cx));
1883 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1884 Self::new(EditorMode::full(), buffer, None, window, cx)
1885 }
1886
1887 pub fn auto_height(
1888 min_lines: usize,
1889 max_lines: usize,
1890 window: &mut Window,
1891 cx: &mut Context<Self>,
1892 ) -> Self {
1893 let buffer = cx.new(|cx| Buffer::local("", cx));
1894 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1895 Self::new(
1896 EditorMode::AutoHeight {
1897 min_lines,
1898 max_lines: Some(max_lines),
1899 },
1900 buffer,
1901 None,
1902 window,
1903 cx,
1904 )
1905 }
1906
1907 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1908 /// The editor grows as tall as needed to fit its content.
1909 pub fn auto_height_unbounded(
1910 min_lines: usize,
1911 window: &mut Window,
1912 cx: &mut Context<Self>,
1913 ) -> Self {
1914 let buffer = cx.new(|cx| Buffer::local("", cx));
1915 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1916 Self::new(
1917 EditorMode::AutoHeight {
1918 min_lines,
1919 max_lines: None,
1920 },
1921 buffer,
1922 None,
1923 window,
1924 cx,
1925 )
1926 }
1927
1928 pub fn for_buffer(
1929 buffer: Entity<Buffer>,
1930 project: Option<Entity<Project>>,
1931 window: &mut Window,
1932 cx: &mut Context<Self>,
1933 ) -> Self {
1934 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1935 Self::new(EditorMode::full(), buffer, project, window, cx)
1936 }
1937
1938 pub fn for_multibuffer(
1939 buffer: Entity<MultiBuffer>,
1940 project: Option<Entity<Project>>,
1941 window: &mut Window,
1942 cx: &mut Context<Self>,
1943 ) -> Self {
1944 Self::new(EditorMode::full(), buffer, project, window, cx)
1945 }
1946
1947 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1948 let mut clone = Self::new(
1949 self.mode.clone(),
1950 self.buffer.clone(),
1951 self.project.clone(),
1952 window,
1953 cx,
1954 );
1955 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1956 let snapshot = display_map.snapshot(cx);
1957 clone.display_map.update(cx, |display_map, cx| {
1958 display_map.set_state(&snapshot, cx);
1959 });
1960 snapshot
1961 });
1962 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1963 clone.folds_did_change(cx);
1964 clone.selections.clone_state(&self.selections);
1965 clone
1966 .scroll_manager
1967 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1968 clone.searchable = self.searchable;
1969 clone.read_only = self.read_only;
1970 clone
1971 }
1972
1973 pub fn new(
1974 mode: EditorMode,
1975 buffer: Entity<MultiBuffer>,
1976 project: Option<Entity<Project>>,
1977 window: &mut Window,
1978 cx: &mut Context<Self>,
1979 ) -> Self {
1980 Editor::new_internal(mode, buffer, project, None, window, cx)
1981 }
1982
1983 pub fn refresh_sticky_headers(
1984 &mut self,
1985 display_snapshot: &DisplaySnapshot,
1986 cx: &mut Context<Editor>,
1987 ) {
1988 if !self.mode.is_full() {
1989 return;
1990 }
1991 let multi_buffer = display_snapshot.buffer_snapshot();
1992 let multi_buffer_visible_start = self
1993 .scroll_manager
1994 .native_anchor(display_snapshot, cx)
1995 .anchor
1996 .to_point(&multi_buffer);
1997 let max_row = multi_buffer.max_point().row;
1998
1999 let start_row = (multi_buffer_visible_start.row).min(max_row);
2000 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
2001 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2002 return;
2003 };
2004 let buffer = buffer.clone();
2005 let &excerpt_id = excerpt_id;
2006 let syntax = self.style(cx).syntax.clone();
2007 let background_task = cx.background_spawn(async move {
2008 buffer
2009 .outline_items_containing(
2010 Point::new(start_row, 0)..Point::new(end_row, 0),
2011 true,
2012 Some(syntax.as_ref()),
2013 )
2014 .into_iter()
2015 .map(|outline_item| OutlineItem {
2016 depth: outline_item.depth,
2017 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2018 source_range_for_text: Anchor::range_in_buffer(
2019 excerpt_id,
2020 outline_item.source_range_for_text,
2021 ),
2022 text: outline_item.text,
2023 highlight_ranges: outline_item.highlight_ranges,
2024 name_ranges: outline_item.name_ranges,
2025 body_range: outline_item
2026 .body_range
2027 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2028 annotation_range: outline_item
2029 .annotation_range
2030 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2031 })
2032 .collect()
2033 });
2034 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2035 let sticky_headers = background_task.await;
2036 this.update(cx, |this, cx| {
2037 this.sticky_headers = Some(sticky_headers);
2038 cx.notify();
2039 })
2040 .ok();
2041 });
2042 }
2043
2044 fn new_internal(
2045 mode: EditorMode,
2046 multi_buffer: Entity<MultiBuffer>,
2047 project: Option<Entity<Project>>,
2048 display_map: Option<Entity<DisplayMap>>,
2049 window: &mut Window,
2050 cx: &mut Context<Self>,
2051 ) -> Self {
2052 debug_assert!(
2053 display_map.is_none() || mode.is_minimap(),
2054 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2055 );
2056
2057 let full_mode = mode.is_full();
2058 let is_minimap = mode.is_minimap();
2059 let diagnostics_max_severity = if full_mode {
2060 EditorSettings::get_global(cx)
2061 .diagnostics_max_severity
2062 .unwrap_or(DiagnosticSeverity::Hint)
2063 } else {
2064 DiagnosticSeverity::Off
2065 };
2066 let style = window.text_style();
2067 let font_size = style.font_size.to_pixels(window.rem_size());
2068 let editor = cx.entity().downgrade();
2069 let fold_placeholder = FoldPlaceholder {
2070 constrain_width: false,
2071 render: Arc::new(move |fold_id, fold_range, cx| {
2072 let editor = editor.clone();
2073 FoldPlaceholder::fold_element(fold_id, cx)
2074 .cursor_pointer()
2075 .child("⋯")
2076 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2077 .on_click(move |_, _window, cx| {
2078 editor
2079 .update(cx, |editor, cx| {
2080 editor.unfold_ranges(
2081 &[fold_range.start..fold_range.end],
2082 true,
2083 false,
2084 cx,
2085 );
2086 cx.stop_propagation();
2087 })
2088 .ok();
2089 })
2090 .into_any()
2091 }),
2092 merge_adjacent: true,
2093 ..FoldPlaceholder::default()
2094 };
2095 let display_map = display_map.unwrap_or_else(|| {
2096 cx.new(|cx| {
2097 DisplayMap::new(
2098 multi_buffer.clone(),
2099 style.font(),
2100 font_size,
2101 None,
2102 FILE_HEADER_HEIGHT,
2103 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2104 fold_placeholder,
2105 diagnostics_max_severity,
2106 cx,
2107 )
2108 })
2109 });
2110
2111 let selections = SelectionsCollection::new();
2112
2113 let blink_manager = cx.new(|cx| {
2114 let mut blink_manager = BlinkManager::new(
2115 CURSOR_BLINK_INTERVAL,
2116 |cx| EditorSettings::get_global(cx).cursor_blink,
2117 cx,
2118 );
2119 if is_minimap {
2120 blink_manager.disable(cx);
2121 }
2122 blink_manager
2123 });
2124
2125 let soft_wrap_mode_override =
2126 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2127
2128 let mut project_subscriptions = Vec::new();
2129 if full_mode && let Some(project) = project.as_ref() {
2130 project_subscriptions.push(cx.subscribe_in(
2131 project,
2132 window,
2133 |editor, _, event, window, cx| match event {
2134 project::Event::RefreshCodeLens => {
2135 // we always query lens with actions, without storing them, always refreshing them
2136 }
2137 project::Event::RefreshInlayHints {
2138 server_id,
2139 request_id,
2140 } => {
2141 editor.refresh_inlay_hints(
2142 InlayHintRefreshReason::RefreshRequested {
2143 server_id: *server_id,
2144 request_id: *request_id,
2145 },
2146 cx,
2147 );
2148 }
2149 project::Event::RefreshSemanticTokens {
2150 server_id,
2151 request_id,
2152 } => {
2153 editor.update_semantic_tokens(
2154 None,
2155 Some(RefreshForServer {
2156 server_id: *server_id,
2157 request_id: *request_id,
2158 }),
2159 cx,
2160 );
2161 }
2162 project::Event::LanguageServerRemoved(_server_id) => {
2163 editor.registered_buffers.clear();
2164 editor.register_visible_buffers(cx);
2165 editor.update_lsp_data(None, window, cx);
2166 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2167 if editor.tasks_update_task.is_none() {
2168 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2169 }
2170 }
2171 project::Event::LanguageServerAdded(..) => {
2172 if editor.tasks_update_task.is_none() {
2173 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2174 }
2175 }
2176 project::Event::SnippetEdit(id, snippet_edits) => {
2177 // todo(lw): Non singletons
2178 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2179 let snapshot = buffer.read(cx).snapshot();
2180 let focus_handle = editor.focus_handle(cx);
2181 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2182 for (range, snippet) in snippet_edits {
2183 let buffer_range =
2184 language::range_from_lsp(*range).to_offset(&snapshot);
2185 editor
2186 .insert_snippet(
2187 &[MultiBufferOffset(buffer_range.start)
2188 ..MultiBufferOffset(buffer_range.end)],
2189 snippet.clone(),
2190 window,
2191 cx,
2192 )
2193 .ok();
2194 }
2195 }
2196 }
2197 }
2198 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2199 let buffer_id = *buffer_id;
2200 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2201 editor.register_buffer(buffer_id, cx);
2202 editor.update_lsp_data(Some(buffer_id), window, cx);
2203 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2204 refresh_linked_ranges(editor, window, cx);
2205 editor.refresh_code_actions(window, cx);
2206 editor.refresh_document_highlights(cx);
2207 }
2208 }
2209
2210 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2211 let Some(workspace) = editor.workspace() else {
2212 return;
2213 };
2214 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2215 else {
2216 return;
2217 };
2218
2219 if active_editor.entity_id() == cx.entity_id() {
2220 let entity_id = cx.entity_id();
2221 workspace.update(cx, |this, cx| {
2222 this.panes_mut()
2223 .iter_mut()
2224 .filter(|pane| pane.entity_id() != entity_id)
2225 .for_each(|p| {
2226 p.update(cx, |pane, _| {
2227 pane.nav_history_mut().rename_item(
2228 entity_id,
2229 project_path.clone(),
2230 abs_path.clone().into(),
2231 );
2232 })
2233 });
2234 });
2235
2236 Self::open_transaction_for_hidden_buffers(
2237 workspace,
2238 transaction.clone(),
2239 "Rename".to_string(),
2240 window,
2241 cx,
2242 );
2243 }
2244 }
2245
2246 project::Event::WorkspaceEditApplied(transaction) => {
2247 let Some(workspace) = editor.workspace() else {
2248 return;
2249 };
2250 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2251 else {
2252 return;
2253 };
2254
2255 if active_editor.entity_id() == cx.entity_id() {
2256 Self::open_transaction_for_hidden_buffers(
2257 workspace,
2258 transaction.clone(),
2259 "LSP Edit".to_string(),
2260 window,
2261 cx,
2262 );
2263 }
2264 }
2265
2266 _ => {}
2267 },
2268 ));
2269 if let Some(task_inventory) = project
2270 .read(cx)
2271 .task_store()
2272 .read(cx)
2273 .task_inventory()
2274 .cloned()
2275 {
2276 project_subscriptions.push(cx.observe_in(
2277 &task_inventory,
2278 window,
2279 |editor, _, window, cx| {
2280 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2281 },
2282 ));
2283 };
2284
2285 project_subscriptions.push(cx.subscribe_in(
2286 &project.read(cx).breakpoint_store(),
2287 window,
2288 |editor, _, event, window, cx| match event {
2289 BreakpointStoreEvent::ClearDebugLines => {
2290 editor.clear_row_highlights::<ActiveDebugLine>();
2291 editor.refresh_inline_values(cx);
2292 }
2293 BreakpointStoreEvent::SetDebugLine => {
2294 if editor.go_to_active_debug_line(window, cx) {
2295 cx.stop_propagation();
2296 }
2297
2298 editor.refresh_inline_values(cx);
2299 }
2300 _ => {}
2301 },
2302 ));
2303 let git_store = project.read(cx).git_store().clone();
2304 let project = project.clone();
2305 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2306 if let GitStoreEvent::RepositoryAdded = event {
2307 this.load_diff_task = Some(
2308 update_uncommitted_diff_for_buffer(
2309 cx.entity(),
2310 &project,
2311 this.buffer.read(cx).all_buffers(),
2312 this.buffer.clone(),
2313 cx,
2314 )
2315 .shared(),
2316 );
2317 }
2318 }));
2319 }
2320
2321 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2322
2323 let inlay_hint_settings =
2324 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2325 let focus_handle = cx.focus_handle();
2326 if !is_minimap {
2327 cx.on_focus(&focus_handle, window, Self::handle_focus)
2328 .detach();
2329 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2330 .detach();
2331 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2332 .detach();
2333 cx.on_blur(&focus_handle, window, Self::handle_blur)
2334 .detach();
2335 cx.observe_pending_input(window, Self::observe_pending_input)
2336 .detach();
2337 }
2338
2339 let show_indent_guides =
2340 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2341 Some(false)
2342 } else {
2343 None
2344 };
2345
2346 let breakpoint_store = match (&mode, project.as_ref()) {
2347 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2348 _ => None,
2349 };
2350
2351 let mut code_action_providers = Vec::new();
2352 let mut load_uncommitted_diff = None;
2353 if let Some(project) = project.clone() {
2354 load_uncommitted_diff = Some(
2355 update_uncommitted_diff_for_buffer(
2356 cx.entity(),
2357 &project,
2358 multi_buffer.read(cx).all_buffers(),
2359 multi_buffer.clone(),
2360 cx,
2361 )
2362 .shared(),
2363 );
2364 code_action_providers.push(Rc::new(project) as Rc<_>);
2365 }
2366
2367 let mut editor = Self {
2368 focus_handle,
2369 show_cursor_when_unfocused: false,
2370 last_focused_descendant: None,
2371 buffer: multi_buffer.clone(),
2372 display_map: display_map.clone(),
2373 placeholder_display_map: None,
2374 selections,
2375 scroll_manager: ScrollManager::new(cx),
2376 columnar_selection_state: None,
2377 add_selections_state: None,
2378 select_next_state: None,
2379 select_prev_state: None,
2380 selection_history: SelectionHistory::default(),
2381 defer_selection_effects: false,
2382 deferred_selection_effects_state: None,
2383 autoclose_regions: Vec::new(),
2384 snippet_stack: InvalidationStack::default(),
2385 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2386 ime_transaction: None,
2387 active_diagnostics: ActiveDiagnostic::None,
2388 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2389 inline_diagnostics_update: Task::ready(()),
2390 inline_diagnostics: Vec::new(),
2391 soft_wrap_mode_override,
2392 diagnostics_max_severity,
2393 hard_wrap: None,
2394 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2395 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2396 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2397 project,
2398 blink_manager: blink_manager.clone(),
2399 show_local_selections: true,
2400 show_scrollbars: ScrollbarAxes {
2401 horizontal: full_mode,
2402 vertical: full_mode,
2403 },
2404 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2405 offset_content: !matches!(mode, EditorMode::SingleLine),
2406 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2407 show_gutter: full_mode,
2408 show_line_numbers: (!full_mode).then_some(false),
2409 use_relative_line_numbers: None,
2410 disable_expand_excerpt_buttons: !full_mode,
2411 delegate_expand_excerpts: false,
2412 delegate_stage_and_restore: false,
2413 delegate_open_excerpts: false,
2414 enable_lsp_data: true,
2415 enable_runnables: true,
2416 show_git_diff_gutter: None,
2417 show_code_actions: None,
2418 show_runnables: None,
2419 show_breakpoints: None,
2420 show_diff_review_button: false,
2421 show_wrap_guides: None,
2422 show_indent_guides,
2423 buffers_with_disabled_indent_guides: HashSet::default(),
2424 highlight_order: 0,
2425 highlighted_rows: HashMap::default(),
2426 background_highlights: HashMap::default(),
2427 gutter_highlights: HashMap::default(),
2428 scrollbar_marker_state: ScrollbarMarkerState::default(),
2429 active_indent_guides_state: ActiveIndentGuidesState::default(),
2430 nav_history: None,
2431 context_menu: RefCell::new(None),
2432 context_menu_options: None,
2433 mouse_context_menu: None,
2434 completion_tasks: Vec::new(),
2435 inline_blame_popover: None,
2436 inline_blame_popover_show_task: None,
2437 signature_help_state: SignatureHelpState::default(),
2438 auto_signature_help: None,
2439 find_all_references_task_sources: Vec::new(),
2440 next_completion_id: 0,
2441 next_inlay_id: 0,
2442 code_action_providers,
2443 available_code_actions: None,
2444 code_actions_task: None,
2445 quick_selection_highlight_task: None,
2446 debounced_selection_highlight_task: None,
2447 debounced_selection_highlight_complete: false,
2448 document_highlights_task: None,
2449 linked_editing_range_task: None,
2450 pending_rename: None,
2451 searchable: !is_minimap,
2452 cursor_shape: EditorSettings::get_global(cx)
2453 .cursor_shape
2454 .unwrap_or_default(),
2455 cursor_offset_on_selection: false,
2456 current_line_highlight: None,
2457 autoindent_mode: Some(AutoindentMode::EachLine),
2458 collapse_matches: false,
2459 workspace: None,
2460 input_enabled: !is_minimap,
2461 use_modal_editing: full_mode,
2462 read_only: is_minimap,
2463 use_autoclose: true,
2464 use_auto_surround: true,
2465 auto_replace_emoji_shortcode: false,
2466 jsx_tag_auto_close_enabled_in_any_buffer: false,
2467 leader_id: None,
2468 remote_id: None,
2469 hover_state: HoverState::default(),
2470 pending_mouse_down: None,
2471 prev_pressure_stage: None,
2472 hovered_link_state: None,
2473 edit_prediction_provider: None,
2474 active_edit_prediction: None,
2475 stale_edit_prediction_in_menu: None,
2476 edit_prediction_preview: EditPredictionPreview::Inactive {
2477 released_too_fast: false,
2478 },
2479 inline_diagnostics_enabled: full_mode,
2480 diagnostics_enabled: full_mode,
2481 word_completions_enabled: full_mode,
2482 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2483 gutter_hovered: false,
2484 pixel_position_of_newest_cursor: None,
2485 last_bounds: None,
2486 last_position_map: None,
2487 expect_bounds_change: None,
2488 gutter_dimensions: GutterDimensions::default(),
2489 style: None,
2490 show_cursor_names: false,
2491 hovered_cursors: HashMap::default(),
2492 next_editor_action_id: EditorActionId::default(),
2493 editor_actions: Rc::default(),
2494 edit_predictions_hidden_for_vim_mode: false,
2495 show_edit_predictions_override: None,
2496 show_completions_on_input_override: None,
2497 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2498 edit_prediction_settings: EditPredictionSettings::Disabled,
2499 edit_prediction_indent_conflict: false,
2500 edit_prediction_requires_modifier_in_indent_conflict: true,
2501 custom_context_menu: None,
2502 show_git_blame_gutter: false,
2503 show_git_blame_inline: false,
2504 show_selection_menu: None,
2505 show_git_blame_inline_delay_task: None,
2506 git_blame_inline_enabled: full_mode
2507 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2508 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2509 buffer_serialization: is_minimap.not().then(|| {
2510 BufferSerialization::new(
2511 ProjectSettings::get_global(cx)
2512 .session
2513 .restore_unsaved_buffers,
2514 )
2515 }),
2516 blame: None,
2517 blame_subscription: None,
2518 tasks: BTreeMap::default(),
2519
2520 breakpoint_store,
2521 gutter_breakpoint_indicator: (None, None),
2522 gutter_diff_review_indicator: (None, None),
2523 diff_review_drag_state: None,
2524 diff_review_overlays: Vec::new(),
2525 stored_review_comments: Vec::new(),
2526 next_review_comment_id: 0,
2527 hovered_diff_hunk_row: None,
2528 _subscriptions: (!is_minimap)
2529 .then(|| {
2530 vec![
2531 cx.observe(&multi_buffer, Self::on_buffer_changed),
2532 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2533 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2534 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2535 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2536 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2537 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2538 cx.observe_window_activation(window, |editor, window, cx| {
2539 let active = window.is_window_active();
2540 editor.blink_manager.update(cx, |blink_manager, cx| {
2541 if active {
2542 blink_manager.enable(cx);
2543 } else {
2544 blink_manager.disable(cx);
2545 }
2546 });
2547 if active {
2548 editor.show_mouse_cursor(cx);
2549 }
2550 }),
2551 ]
2552 })
2553 .unwrap_or_default(),
2554 tasks_update_task: None,
2555 pull_diagnostics_task: Task::ready(()),
2556 colors: None,
2557 refresh_colors_task: Task::ready(()),
2558 use_document_folding_ranges: false,
2559 refresh_folding_ranges_task: Task::ready(()),
2560 inlay_hints: None,
2561 next_color_inlay_id: 0,
2562 post_scroll_update: Task::ready(()),
2563 linked_edit_ranges: Default::default(),
2564 in_project_search: false,
2565 previous_search_ranges: None,
2566 breadcrumb_header: None,
2567 focused_block: None,
2568 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2569 addons: HashMap::default(),
2570 registered_buffers: HashMap::default(),
2571 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2572 selection_mark_mode: false,
2573 toggle_fold_multiple_buffers: Task::ready(()),
2574 serialize_selections: Task::ready(()),
2575 serialize_folds: Task::ready(()),
2576 text_style_refinement: None,
2577 load_diff_task: load_uncommitted_diff,
2578 temporary_diff_override: false,
2579 mouse_cursor_hidden: false,
2580 minimap: None,
2581 hide_mouse_mode: EditorSettings::get_global(cx)
2582 .hide_mouse
2583 .unwrap_or_default(),
2584 change_list: ChangeList::new(),
2585 mode,
2586 selection_drag_state: SelectionDragState::None,
2587 folding_newlines: Task::ready(()),
2588 lookup_key: None,
2589 select_next_is_case_sensitive: None,
2590 on_local_selections_changed: None,
2591 suppress_selection_callback: false,
2592 applicable_language_settings: HashMap::default(),
2593 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2594 accent_data: None,
2595 fetched_tree_sitter_chunks: HashMap::default(),
2596 number_deleted_lines: false,
2597 refresh_matching_bracket_highlights_task: Task::ready(()),
2598 refresh_outline_symbols_task: Task::ready(()),
2599 outline_symbols: None,
2600 sticky_headers_task: Task::ready(()),
2601 sticky_headers: None,
2602 };
2603
2604 if is_minimap {
2605 return editor;
2606 }
2607
2608 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2609 editor.accent_data = editor.fetch_accent_data(cx);
2610
2611 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2612 editor
2613 ._subscriptions
2614 .push(cx.observe(breakpoints, |_, _, cx| {
2615 cx.notify();
2616 }));
2617 }
2618 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2619 editor._subscriptions.extend(project_subscriptions);
2620
2621 editor._subscriptions.push(cx.subscribe_in(
2622 &cx.entity(),
2623 window,
2624 |editor, _, e: &EditorEvent, window, cx| match e {
2625 EditorEvent::ScrollPositionChanged { local, .. } => {
2626 if *local {
2627 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2628 editor.inline_blame_popover.take();
2629 let snapshot = editor.snapshot(window, cx);
2630 let new_anchor = editor
2631 .scroll_manager
2632 .native_anchor(&snapshot.display_snapshot, cx);
2633 editor.update_restoration_data(cx, move |data| {
2634 data.scroll_position = (
2635 new_anchor.top_row(snapshot.buffer_snapshot()),
2636 new_anchor.offset,
2637 );
2638 });
2639
2640 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2641 cx.background_executor()
2642 .timer(Duration::from_millis(50))
2643 .await;
2644 editor
2645 .update_in(cx, |editor, window, cx| {
2646 editor.register_visible_buffers(cx);
2647 editor.refresh_colors_for_visible_range(None, window, cx);
2648 editor.refresh_folding_ranges(None, window, cx);
2649 editor.refresh_inlay_hints(
2650 InlayHintRefreshReason::NewLinesShown,
2651 cx,
2652 );
2653 editor.colorize_brackets(false, cx);
2654 })
2655 .ok();
2656 });
2657 }
2658 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2659 }
2660 EditorEvent::Edited { .. } => {
2661 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2662 .map(|vim_mode| vim_mode.0)
2663 .unwrap_or(false);
2664 if !vim_mode {
2665 let display_map = editor.display_snapshot(cx);
2666 let selections = editor.selections.all_adjusted_display(&display_map);
2667 let pop_state = editor
2668 .change_list
2669 .last()
2670 .map(|previous| {
2671 previous.len() == selections.len()
2672 && previous.iter().enumerate().all(|(ix, p)| {
2673 p.to_display_point(&display_map).row()
2674 == selections[ix].head().row()
2675 })
2676 })
2677 .unwrap_or(false);
2678 let new_positions = selections
2679 .into_iter()
2680 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2681 .collect();
2682 editor
2683 .change_list
2684 .push_to_change_list(pop_state, new_positions);
2685 }
2686 }
2687 _ => (),
2688 },
2689 ));
2690
2691 if let Some(dap_store) = editor
2692 .project
2693 .as_ref()
2694 .map(|project| project.read(cx).dap_store())
2695 {
2696 let weak_editor = cx.weak_entity();
2697
2698 editor
2699 ._subscriptions
2700 .push(
2701 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2702 let session_entity = cx.entity();
2703 weak_editor
2704 .update(cx, |editor, cx| {
2705 editor._subscriptions.push(
2706 cx.subscribe(&session_entity, Self::on_debug_session_event),
2707 );
2708 })
2709 .ok();
2710 }),
2711 );
2712
2713 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2714 editor
2715 ._subscriptions
2716 .push(cx.subscribe(&session, Self::on_debug_session_event));
2717 }
2718 }
2719
2720 // skip adding the initial selection to selection history
2721 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2722 editor.end_selection(window, cx);
2723 editor.selection_history.mode = SelectionHistoryMode::Normal;
2724
2725 editor.scroll_manager.show_scrollbars(window, cx);
2726 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2727
2728 if full_mode {
2729 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2730 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2731
2732 if editor.git_blame_inline_enabled {
2733 editor.start_git_blame_inline(false, window, cx);
2734 }
2735
2736 editor.go_to_active_debug_line(window, cx);
2737
2738 editor.minimap =
2739 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2740 editor.colors = Some(LspColorData::new(cx));
2741 editor.use_document_folding_ranges = true;
2742 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2743
2744 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2745 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2746 }
2747 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2748 }
2749
2750 editor
2751 }
2752
2753 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2754 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2755 }
2756
2757 pub fn deploy_mouse_context_menu(
2758 &mut self,
2759 position: gpui::Point<Pixels>,
2760 context_menu: Entity<ContextMenu>,
2761 window: &mut Window,
2762 cx: &mut Context<Self>,
2763 ) {
2764 self.mouse_context_menu = Some(MouseContextMenu::new(
2765 self,
2766 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2767 context_menu,
2768 window,
2769 cx,
2770 ));
2771 }
2772
2773 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2774 self.mouse_context_menu
2775 .as_ref()
2776 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2777 }
2778
2779 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2780 if self
2781 .selections
2782 .pending_anchor()
2783 .is_some_and(|pending_selection| {
2784 let snapshot = self.buffer().read(cx).snapshot(cx);
2785 pending_selection.range().includes(range, &snapshot)
2786 })
2787 {
2788 return true;
2789 }
2790
2791 self.selections
2792 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2793 .into_iter()
2794 .any(|selection| {
2795 // This is needed to cover a corner case, if we just check for an existing
2796 // selection in the fold range, having a cursor at the start of the fold
2797 // marks it as selected. Non-empty selections don't cause this.
2798 let length = selection.end - selection.start;
2799 length > 0
2800 })
2801 }
2802
2803 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2804 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2805 }
2806
2807 fn key_context_internal(
2808 &self,
2809 has_active_edit_prediction: bool,
2810 window: &mut Window,
2811 cx: &mut App,
2812 ) -> KeyContext {
2813 let mut key_context = KeyContext::new_with_defaults();
2814 key_context.add("Editor");
2815 let mode = match self.mode {
2816 EditorMode::SingleLine => "single_line",
2817 EditorMode::AutoHeight { .. } => "auto_height",
2818 EditorMode::Minimap { .. } => "minimap",
2819 EditorMode::Full { .. } => "full",
2820 };
2821
2822 if EditorSettings::jupyter_enabled(cx) {
2823 key_context.add("jupyter");
2824 }
2825
2826 key_context.set("mode", mode);
2827 if self.pending_rename.is_some() {
2828 key_context.add("renaming");
2829 }
2830
2831 if let Some(snippet_stack) = self.snippet_stack.last() {
2832 key_context.add("in_snippet");
2833
2834 if snippet_stack.active_index > 0 {
2835 key_context.add("has_previous_tabstop");
2836 }
2837
2838 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2839 key_context.add("has_next_tabstop");
2840 }
2841 }
2842
2843 match self.context_menu.borrow().as_ref() {
2844 Some(CodeContextMenu::Completions(menu)) => {
2845 if menu.visible() {
2846 key_context.add("menu");
2847 key_context.add("showing_completions");
2848 }
2849 }
2850 Some(CodeContextMenu::CodeActions(menu)) => {
2851 if menu.visible() {
2852 key_context.add("menu");
2853 key_context.add("showing_code_actions")
2854 }
2855 }
2856 None => {}
2857 }
2858
2859 if self.signature_help_state.has_multiple_signatures() {
2860 key_context.add("showing_signature_help");
2861 }
2862
2863 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2864 if !self.focus_handle(cx).contains_focused(window, cx)
2865 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2866 {
2867 for addon in self.addons.values() {
2868 addon.extend_key_context(&mut key_context, cx)
2869 }
2870 }
2871
2872 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2873 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2874 Some(
2875 file.full_path(cx)
2876 .extension()?
2877 .to_string_lossy()
2878 .to_lowercase(),
2879 )
2880 }) {
2881 key_context.set("extension", extension);
2882 }
2883 } else {
2884 key_context.add("multibuffer");
2885 }
2886
2887 if has_active_edit_prediction {
2888 if self.edit_prediction_in_conflict() {
2889 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2890 } else {
2891 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2892 key_context.add("copilot_suggestion");
2893 }
2894 }
2895
2896 if self.selection_mark_mode {
2897 key_context.add("selection_mode");
2898 }
2899
2900 let disjoint = self.selections.disjoint_anchors();
2901 let snapshot = self.snapshot(window, cx);
2902 let snapshot = snapshot.buffer_snapshot();
2903 if self.mode == EditorMode::SingleLine
2904 && let [selection] = disjoint
2905 && selection.start == selection.end
2906 && selection.end.to_offset(snapshot) == snapshot.len()
2907 {
2908 key_context.add("end_of_input");
2909 }
2910
2911 if self.has_any_expanded_diff_hunks(cx) {
2912 key_context.add("diffs_expanded");
2913 }
2914
2915 key_context
2916 }
2917
2918 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2919 self.last_bounds.as_ref()
2920 }
2921
2922 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2923 if self.mouse_cursor_hidden {
2924 self.mouse_cursor_hidden = false;
2925 cx.notify();
2926 }
2927 }
2928
2929 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2930 let hide_mouse_cursor = match origin {
2931 HideMouseCursorOrigin::TypingAction => {
2932 matches!(
2933 self.hide_mouse_mode,
2934 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2935 )
2936 }
2937 HideMouseCursorOrigin::MovementAction => {
2938 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2939 }
2940 };
2941 if self.mouse_cursor_hidden != hide_mouse_cursor {
2942 self.mouse_cursor_hidden = hide_mouse_cursor;
2943 cx.notify();
2944 }
2945 }
2946
2947 pub fn edit_prediction_in_conflict(&self) -> bool {
2948 if !self.show_edit_predictions_in_menu() {
2949 return false;
2950 }
2951
2952 let showing_completions = self
2953 .context_menu
2954 .borrow()
2955 .as_ref()
2956 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2957
2958 showing_completions
2959 || self.edit_prediction_requires_modifier()
2960 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2961 // bindings to insert tab characters.
2962 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2963 }
2964
2965 pub fn accept_edit_prediction_keybind(
2966 &self,
2967 granularity: EditPredictionGranularity,
2968 window: &mut Window,
2969 cx: &mut App,
2970 ) -> AcceptEditPredictionBinding {
2971 let key_context = self.key_context_internal(true, window, cx);
2972 let in_conflict = self.edit_prediction_in_conflict();
2973
2974 let bindings =
2975 match granularity {
2976 EditPredictionGranularity::Word => window
2977 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2978 EditPredictionGranularity::Line => window
2979 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2980 EditPredictionGranularity::Full => {
2981 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2982 }
2983 };
2984
2985 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2986 !in_conflict
2987 || binding
2988 .keystrokes()
2989 .first()
2990 .is_some_and(|keystroke| keystroke.modifiers().modified())
2991 }))
2992 }
2993
2994 pub fn new_file(
2995 workspace: &mut Workspace,
2996 _: &workspace::NewFile,
2997 window: &mut Window,
2998 cx: &mut Context<Workspace>,
2999 ) {
3000 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3001 "Failed to create buffer",
3002 window,
3003 cx,
3004 |e, _, _| match e.error_code() {
3005 ErrorCode::RemoteUpgradeRequired => Some(format!(
3006 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3007 e.error_tag("required").unwrap_or("the latest version")
3008 )),
3009 _ => None,
3010 },
3011 );
3012 }
3013
3014 pub fn new_in_workspace(
3015 workspace: &mut Workspace,
3016 window: &mut Window,
3017 cx: &mut Context<Workspace>,
3018 ) -> Task<Result<Entity<Editor>>> {
3019 let project = workspace.project().clone();
3020 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3021
3022 cx.spawn_in(window, async move |workspace, cx| {
3023 let buffer = create.await?;
3024 workspace.update_in(cx, |workspace, window, cx| {
3025 let editor =
3026 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3027 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3028 editor
3029 })
3030 })
3031 }
3032
3033 fn new_file_vertical(
3034 workspace: &mut Workspace,
3035 _: &workspace::NewFileSplitVertical,
3036 window: &mut Window,
3037 cx: &mut Context<Workspace>,
3038 ) {
3039 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3040 }
3041
3042 fn new_file_horizontal(
3043 workspace: &mut Workspace,
3044 _: &workspace::NewFileSplitHorizontal,
3045 window: &mut Window,
3046 cx: &mut Context<Workspace>,
3047 ) {
3048 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3049 }
3050
3051 fn new_file_split(
3052 workspace: &mut Workspace,
3053 action: &workspace::NewFileSplit,
3054 window: &mut Window,
3055 cx: &mut Context<Workspace>,
3056 ) {
3057 Self::new_file_in_direction(workspace, action.0, window, cx)
3058 }
3059
3060 fn new_file_in_direction(
3061 workspace: &mut Workspace,
3062 direction: SplitDirection,
3063 window: &mut Window,
3064 cx: &mut Context<Workspace>,
3065 ) {
3066 let project = workspace.project().clone();
3067 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3068
3069 cx.spawn_in(window, async move |workspace, cx| {
3070 let buffer = create.await?;
3071 workspace.update_in(cx, move |workspace, window, cx| {
3072 workspace.split_item(
3073 direction,
3074 Box::new(
3075 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3076 ),
3077 window,
3078 cx,
3079 )
3080 })?;
3081 anyhow::Ok(())
3082 })
3083 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3084 match e.error_code() {
3085 ErrorCode::RemoteUpgradeRequired => Some(format!(
3086 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3087 e.error_tag("required").unwrap_or("the latest version")
3088 )),
3089 _ => None,
3090 }
3091 });
3092 }
3093
3094 pub fn leader_id(&self) -> Option<CollaboratorId> {
3095 self.leader_id
3096 }
3097
3098 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3099 &self.buffer
3100 }
3101
3102 pub fn project(&self) -> Option<&Entity<Project>> {
3103 self.project.as_ref()
3104 }
3105
3106 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3107 self.workspace.as_ref()?.0.upgrade()
3108 }
3109
3110 /// Returns the workspace serialization ID if this editor should be serialized.
3111 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3112 self.workspace
3113 .as_ref()
3114 .filter(|_| self.should_serialize_buffer())
3115 .and_then(|workspace| workspace.1)
3116 }
3117
3118 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3119 self.buffer().read(cx).title(cx)
3120 }
3121
3122 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3123 let git_blame_gutter_max_author_length = self
3124 .render_git_blame_gutter(cx)
3125 .then(|| {
3126 if let Some(blame) = self.blame.as_ref() {
3127 let max_author_length =
3128 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3129 Some(max_author_length)
3130 } else {
3131 None
3132 }
3133 })
3134 .flatten();
3135
3136 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3137
3138 EditorSnapshot {
3139 mode: self.mode.clone(),
3140 show_gutter: self.show_gutter,
3141 offset_content: self.offset_content,
3142 show_line_numbers: self.show_line_numbers,
3143 number_deleted_lines: self.number_deleted_lines,
3144 show_git_diff_gutter: self.show_git_diff_gutter,
3145 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3146 show_code_actions: self.show_code_actions,
3147 show_runnables: self.show_runnables,
3148 show_breakpoints: self.show_breakpoints,
3149 git_blame_gutter_max_author_length,
3150 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3151 display_snapshot,
3152 placeholder_display_snapshot: self
3153 .placeholder_display_map
3154 .as_ref()
3155 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3156 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3157 is_focused: self.focus_handle.is_focused(window),
3158 current_line_highlight: self
3159 .current_line_highlight
3160 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3161 gutter_hovered: self.gutter_hovered,
3162 }
3163 }
3164
3165 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3166 self.buffer.read(cx).language_at(point, cx)
3167 }
3168
3169 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3170 self.buffer.read(cx).read(cx).file_at(point).cloned()
3171 }
3172
3173 pub fn active_excerpt(
3174 &self,
3175 cx: &App,
3176 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3177 self.buffer
3178 .read(cx)
3179 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3180 }
3181
3182 pub fn mode(&self) -> &EditorMode {
3183 &self.mode
3184 }
3185
3186 pub fn set_mode(&mut self, mode: EditorMode) {
3187 self.mode = mode;
3188 }
3189
3190 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3191 self.collaboration_hub.as_deref()
3192 }
3193
3194 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3195 self.collaboration_hub = Some(hub);
3196 }
3197
3198 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3199 self.in_project_search = in_project_search;
3200 }
3201
3202 pub fn set_custom_context_menu(
3203 &mut self,
3204 f: impl 'static
3205 + Fn(
3206 &mut Self,
3207 DisplayPoint,
3208 &mut Window,
3209 &mut Context<Self>,
3210 ) -> Option<Entity<ui::ContextMenu>>,
3211 ) {
3212 self.custom_context_menu = Some(Box::new(f))
3213 }
3214
3215 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3216 self.completion_provider = provider;
3217 }
3218
3219 #[cfg(any(test, feature = "test-support"))]
3220 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3221 self.completion_provider.clone()
3222 }
3223
3224 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3225 self.semantics_provider.clone()
3226 }
3227
3228 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3229 self.semantics_provider = provider;
3230 }
3231
3232 pub fn set_edit_prediction_provider<T>(
3233 &mut self,
3234 provider: Option<Entity<T>>,
3235 window: &mut Window,
3236 cx: &mut Context<Self>,
3237 ) where
3238 T: EditPredictionDelegate,
3239 {
3240 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3241 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3242 if this.focus_handle.is_focused(window) {
3243 this.update_visible_edit_prediction(window, cx);
3244 }
3245 }),
3246 provider: Arc::new(provider),
3247 });
3248 self.update_edit_prediction_settings(cx);
3249 self.refresh_edit_prediction(false, false, window, cx);
3250 }
3251
3252 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3253 self.placeholder_display_map
3254 .as_ref()
3255 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3256 }
3257
3258 pub fn set_placeholder_text(
3259 &mut self,
3260 placeholder_text: &str,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 ) {
3264 let multibuffer = cx
3265 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3266
3267 let style = window.text_style();
3268
3269 self.placeholder_display_map = Some(cx.new(|cx| {
3270 DisplayMap::new(
3271 multibuffer,
3272 style.font(),
3273 style.font_size.to_pixels(window.rem_size()),
3274 None,
3275 FILE_HEADER_HEIGHT,
3276 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3277 Default::default(),
3278 DiagnosticSeverity::Off,
3279 cx,
3280 )
3281 }));
3282 cx.notify();
3283 }
3284
3285 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3286 self.cursor_shape = cursor_shape;
3287
3288 // Disrupt blink for immediate user feedback that the cursor shape has changed
3289 self.blink_manager.update(cx, BlinkManager::show_cursor);
3290
3291 cx.notify();
3292 }
3293
3294 pub fn cursor_shape(&self) -> CursorShape {
3295 self.cursor_shape
3296 }
3297
3298 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3299 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3300 }
3301
3302 pub fn set_current_line_highlight(
3303 &mut self,
3304 current_line_highlight: Option<CurrentLineHighlight>,
3305 ) {
3306 self.current_line_highlight = current_line_highlight;
3307 }
3308
3309 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3310 self.collapse_matches = collapse_matches;
3311 }
3312
3313 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3314 if self.collapse_matches {
3315 return range.start..range.start;
3316 }
3317 range.clone()
3318 }
3319
3320 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3321 self.display_map.read(cx).clip_at_line_ends
3322 }
3323
3324 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3325 if self.display_map.read(cx).clip_at_line_ends != clip {
3326 self.display_map
3327 .update(cx, |map, _| map.clip_at_line_ends = clip);
3328 }
3329 }
3330
3331 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3332 self.input_enabled = input_enabled;
3333 }
3334
3335 pub fn set_edit_predictions_hidden_for_vim_mode(
3336 &mut self,
3337 hidden: bool,
3338 window: &mut Window,
3339 cx: &mut Context<Self>,
3340 ) {
3341 if hidden != self.edit_predictions_hidden_for_vim_mode {
3342 self.edit_predictions_hidden_for_vim_mode = hidden;
3343 if hidden {
3344 self.update_visible_edit_prediction(window, cx);
3345 } else {
3346 self.refresh_edit_prediction(true, false, window, cx);
3347 }
3348 }
3349 }
3350
3351 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3352 self.menu_edit_predictions_policy = value;
3353 }
3354
3355 pub fn set_autoindent(&mut self, autoindent: bool) {
3356 if autoindent {
3357 self.autoindent_mode = Some(AutoindentMode::EachLine);
3358 } else {
3359 self.autoindent_mode = None;
3360 }
3361 }
3362
3363 pub fn capability(&self, cx: &App) -> Capability {
3364 if self.read_only {
3365 Capability::ReadOnly
3366 } else {
3367 self.buffer.read(cx).capability()
3368 }
3369 }
3370
3371 pub fn read_only(&self, cx: &App) -> bool {
3372 self.read_only || self.buffer.read(cx).read_only()
3373 }
3374
3375 pub fn set_read_only(&mut self, read_only: bool) {
3376 self.read_only = read_only;
3377 }
3378
3379 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3380 self.use_autoclose = autoclose;
3381 }
3382
3383 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3384 self.use_auto_surround = auto_surround;
3385 }
3386
3387 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3388 self.auto_replace_emoji_shortcode = auto_replace;
3389 }
3390
3391 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3392 self.buffer_serialization = should_serialize.then(|| {
3393 BufferSerialization::new(
3394 ProjectSettings::get_global(cx)
3395 .session
3396 .restore_unsaved_buffers,
3397 )
3398 })
3399 }
3400
3401 fn should_serialize_buffer(&self) -> bool {
3402 self.buffer_serialization.is_some()
3403 }
3404
3405 pub fn toggle_edit_predictions(
3406 &mut self,
3407 _: &ToggleEditPrediction,
3408 window: &mut Window,
3409 cx: &mut Context<Self>,
3410 ) {
3411 if self.show_edit_predictions_override.is_some() {
3412 self.set_show_edit_predictions(None, window, cx);
3413 } else {
3414 let show_edit_predictions = !self.edit_predictions_enabled();
3415 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3416 }
3417 }
3418
3419 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3420 self.show_completions_on_input_override = show_completions_on_input;
3421 }
3422
3423 pub fn set_show_edit_predictions(
3424 &mut self,
3425 show_edit_predictions: Option<bool>,
3426 window: &mut Window,
3427 cx: &mut Context<Self>,
3428 ) {
3429 self.show_edit_predictions_override = show_edit_predictions;
3430 self.update_edit_prediction_settings(cx);
3431
3432 if let Some(false) = show_edit_predictions {
3433 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3434 } else {
3435 self.refresh_edit_prediction(false, true, window, cx);
3436 }
3437 }
3438
3439 fn edit_predictions_disabled_in_scope(
3440 &self,
3441 buffer: &Entity<Buffer>,
3442 buffer_position: language::Anchor,
3443 cx: &App,
3444 ) -> bool {
3445 let snapshot = buffer.read(cx).snapshot();
3446 let settings = snapshot.settings_at(buffer_position, cx);
3447
3448 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3449 return false;
3450 };
3451
3452 scope.override_name().is_some_and(|scope_name| {
3453 settings
3454 .edit_predictions_disabled_in
3455 .iter()
3456 .any(|s| s == scope_name)
3457 })
3458 }
3459
3460 pub fn set_use_modal_editing(&mut self, to: bool) {
3461 self.use_modal_editing = to;
3462 }
3463
3464 pub fn use_modal_editing(&self) -> bool {
3465 self.use_modal_editing
3466 }
3467
3468 fn selections_did_change(
3469 &mut self,
3470 local: bool,
3471 old_cursor_position: &Anchor,
3472 effects: SelectionEffects,
3473 window: &mut Window,
3474 cx: &mut Context<Self>,
3475 ) {
3476 window.invalidate_character_coordinates();
3477
3478 // Copy selections to primary selection buffer
3479 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3480 if local {
3481 let selections = self
3482 .selections
3483 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3484 let buffer_handle = self.buffer.read(cx).read(cx);
3485
3486 let mut text = String::new();
3487 for (index, selection) in selections.iter().enumerate() {
3488 let text_for_selection = buffer_handle
3489 .text_for_range(selection.start..selection.end)
3490 .collect::<String>();
3491
3492 text.push_str(&text_for_selection);
3493 if index != selections.len() - 1 {
3494 text.push('\n');
3495 }
3496 }
3497
3498 if !text.is_empty() {
3499 cx.write_to_primary(ClipboardItem::new_string(text));
3500 }
3501 }
3502
3503 let selection_anchors = self.selections.disjoint_anchors_arc();
3504
3505 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3506 self.buffer.update(cx, |buffer, cx| {
3507 buffer.set_active_selections(
3508 &selection_anchors,
3509 self.selections.line_mode(),
3510 self.cursor_shape,
3511 cx,
3512 )
3513 });
3514 }
3515 let display_map = self
3516 .display_map
3517 .update(cx, |display_map, cx| display_map.snapshot(cx));
3518 let buffer = display_map.buffer_snapshot();
3519 if self.selections.count() == 1 {
3520 self.add_selections_state = None;
3521 }
3522 self.select_next_state = None;
3523 self.select_prev_state = None;
3524 self.select_syntax_node_history.try_clear();
3525 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3526 self.snippet_stack.invalidate(&selection_anchors, buffer);
3527 self.take_rename(false, window, cx);
3528
3529 let newest_selection = self.selections.newest_anchor();
3530 let new_cursor_position = newest_selection.head();
3531 let selection_start = newest_selection.start;
3532
3533 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3534 self.push_to_nav_history(
3535 *old_cursor_position,
3536 Some(new_cursor_position.to_point(buffer)),
3537 false,
3538 effects.nav_history == Some(true),
3539 cx,
3540 );
3541 }
3542
3543 if local {
3544 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3545 self.register_buffer(buffer_id, cx);
3546 }
3547
3548 let mut context_menu = self.context_menu.borrow_mut();
3549 let completion_menu = match context_menu.as_ref() {
3550 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3551 Some(CodeContextMenu::CodeActions(_)) => {
3552 *context_menu = None;
3553 None
3554 }
3555 None => None,
3556 };
3557 let completion_position = completion_menu.map(|menu| menu.initial_position);
3558 drop(context_menu);
3559
3560 if effects.completions
3561 && let Some(completion_position) = completion_position
3562 {
3563 let start_offset = selection_start.to_offset(buffer);
3564 let position_matches = start_offset == completion_position.to_offset(buffer);
3565 let continue_showing = if let Some((snap, ..)) =
3566 buffer.point_to_buffer_offset(completion_position)
3567 && !snap.capability.editable()
3568 {
3569 false
3570 } else if position_matches {
3571 if self.snippet_stack.is_empty() {
3572 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3573 == Some(CharKind::Word)
3574 } else {
3575 // Snippet choices can be shown even when the cursor is in whitespace.
3576 // Dismissing the menu with actions like backspace is handled by
3577 // invalidation regions.
3578 true
3579 }
3580 } else {
3581 false
3582 };
3583
3584 if continue_showing {
3585 self.open_or_update_completions_menu(None, None, false, window, cx);
3586 } else {
3587 self.hide_context_menu(window, cx);
3588 }
3589 }
3590
3591 hide_hover(self, cx);
3592
3593 if old_cursor_position.to_display_point(&display_map).row()
3594 != new_cursor_position.to_display_point(&display_map).row()
3595 {
3596 self.available_code_actions.take();
3597 }
3598 self.refresh_code_actions(window, cx);
3599 self.refresh_document_highlights(cx);
3600 refresh_linked_ranges(self, window, cx);
3601
3602 self.refresh_selected_text_highlights(false, window, cx);
3603 self.refresh_matching_bracket_highlights(&display_map, cx);
3604 self.refresh_outline_symbols(cx);
3605 self.update_visible_edit_prediction(window, cx);
3606 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3607 self.inline_blame_popover.take();
3608 if self.git_blame_inline_enabled {
3609 self.start_inline_blame_timer(window, cx);
3610 }
3611 }
3612
3613 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3614
3615 if local && !self.suppress_selection_callback {
3616 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3617 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3618 callback(cursor_position, window, cx);
3619 }
3620 }
3621
3622 cx.emit(EditorEvent::SelectionsChanged { local });
3623
3624 let selections = &self.selections.disjoint_anchors_arc();
3625 if selections.len() == 1 {
3626 cx.emit(SearchEvent::ActiveMatchChanged)
3627 }
3628 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3629 let inmemory_selections = selections
3630 .iter()
3631 .map(|s| {
3632 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3633 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3634 })
3635 .collect();
3636 self.update_restoration_data(cx, |data| {
3637 data.selections = inmemory_selections;
3638 });
3639
3640 if WorkspaceSettings::get(None, cx).restore_on_startup
3641 != RestoreOnStartupBehavior::EmptyTab
3642 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3643 {
3644 let snapshot = self.buffer().read(cx).snapshot(cx);
3645 let selections = selections.clone();
3646 let background_executor = cx.background_executor().clone();
3647 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3648 self.serialize_selections = cx.background_spawn(async move {
3649 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3650 let db_selections = selections
3651 .iter()
3652 .map(|selection| {
3653 (
3654 selection.start.to_offset(&snapshot).0,
3655 selection.end.to_offset(&snapshot).0,
3656 )
3657 })
3658 .collect();
3659
3660 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3661 .await
3662 .with_context(|| {
3663 format!(
3664 "persisting editor selections for editor {editor_id}, \
3665 workspace {workspace_id:?}"
3666 )
3667 })
3668 .log_err();
3669 });
3670 }
3671 }
3672
3673 cx.notify();
3674 }
3675
3676 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3677 use text::ToOffset as _;
3678 use text::ToPoint as _;
3679
3680 if self.mode.is_minimap()
3681 || WorkspaceSettings::get(None, cx).restore_on_startup
3682 == RestoreOnStartupBehavior::EmptyTab
3683 {
3684 return;
3685 }
3686
3687 if !self.buffer().read(cx).is_singleton() {
3688 return;
3689 }
3690
3691 let display_snapshot = self
3692 .display_map
3693 .update(cx, |display_map, cx| display_map.snapshot(cx));
3694 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3695 return;
3696 };
3697 let inmemory_folds = display_snapshot
3698 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3699 .map(|fold| {
3700 fold.range.start.text_anchor.to_point(&snapshot)
3701 ..fold.range.end.text_anchor.to_point(&snapshot)
3702 })
3703 .collect();
3704 self.update_restoration_data(cx, |data| {
3705 data.folds = inmemory_folds;
3706 });
3707
3708 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3709 return;
3710 };
3711 let background_executor = cx.background_executor().clone();
3712 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3713 const FINGERPRINT_LEN: usize = 32;
3714 let db_folds = display_snapshot
3715 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3716 .map(|fold| {
3717 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3718 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3719
3720 // Extract fingerprints - content at fold boundaries for validation on restore
3721 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3722 // content that might change independently.
3723 // start_fp: first min(32, fold_len) bytes of fold content
3724 // end_fp: last min(32, fold_len) bytes of fold content
3725 // Clip to character boundaries to handle multibyte UTF-8 characters.
3726 let fold_len = end - start;
3727 let start_fp_end = snapshot
3728 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3729 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3730 let end_fp_start = snapshot
3731 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3732 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3733
3734 (start, end, start_fp, end_fp)
3735 })
3736 .collect::<Vec<_>>();
3737 self.serialize_folds = cx.background_spawn(async move {
3738 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3739 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3740 .await
3741 .with_context(|| {
3742 format!(
3743 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3744 )
3745 })
3746 .log_err();
3747 });
3748 }
3749
3750 pub fn sync_selections(
3751 &mut self,
3752 other: Entity<Editor>,
3753 cx: &mut Context<Self>,
3754 ) -> gpui::Subscription {
3755 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3756 if !other_selections.is_empty() {
3757 self.selections
3758 .change_with(&self.display_snapshot(cx), |selections| {
3759 selections.select_anchors(other_selections);
3760 });
3761 }
3762
3763 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3764 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3765 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3766 if other_selections.is_empty() {
3767 return;
3768 }
3769 let snapshot = this.display_snapshot(cx);
3770 this.selections.change_with(&snapshot, |selections| {
3771 selections.select_anchors(other_selections);
3772 });
3773 }
3774 });
3775
3776 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3777 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3778 let these_selections = this.selections.disjoint_anchors().to_vec();
3779 if these_selections.is_empty() {
3780 return;
3781 }
3782 other.update(cx, |other_editor, cx| {
3783 let snapshot = other_editor.display_snapshot(cx);
3784 other_editor
3785 .selections
3786 .change_with(&snapshot, |selections| {
3787 selections.select_anchors(these_selections);
3788 })
3789 });
3790 }
3791 });
3792
3793 Subscription::join(other_subscription, this_subscription)
3794 }
3795
3796 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3797 if self.buffer().read(cx).is_singleton() {
3798 return;
3799 }
3800 let snapshot = self.buffer.read(cx).snapshot(cx);
3801 let buffer_ids: HashSet<BufferId> = self
3802 .selections
3803 .disjoint_anchor_ranges()
3804 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3805 .collect();
3806 for buffer_id in buffer_ids {
3807 self.unfold_buffer(buffer_id, cx);
3808 }
3809 }
3810
3811 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3812 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3813 /// effects of selection change occur at the end of the transaction.
3814 pub fn change_selections<R>(
3815 &mut self,
3816 effects: SelectionEffects,
3817 window: &mut Window,
3818 cx: &mut Context<Self>,
3819 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3820 ) -> R {
3821 let snapshot = self.display_snapshot(cx);
3822 if let Some(state) = &mut self.deferred_selection_effects_state {
3823 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3824 state.effects.completions = effects.completions;
3825 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3826 let (changed, result) = self.selections.change_with(&snapshot, change);
3827 state.changed |= changed;
3828 return result;
3829 }
3830 let mut state = DeferredSelectionEffectsState {
3831 changed: false,
3832 effects,
3833 old_cursor_position: self.selections.newest_anchor().head(),
3834 history_entry: SelectionHistoryEntry {
3835 selections: self.selections.disjoint_anchors_arc(),
3836 select_next_state: self.select_next_state.clone(),
3837 select_prev_state: self.select_prev_state.clone(),
3838 add_selections_state: self.add_selections_state.clone(),
3839 },
3840 };
3841 let (changed, result) = self.selections.change_with(&snapshot, change);
3842 state.changed = state.changed || changed;
3843 if self.defer_selection_effects {
3844 self.deferred_selection_effects_state = Some(state);
3845 } else {
3846 self.apply_selection_effects(state, window, cx);
3847 }
3848 result
3849 }
3850
3851 /// Defers the effects of selection change, so that the effects of multiple calls to
3852 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3853 /// to selection history and the state of popovers based on selection position aren't
3854 /// erroneously updated.
3855 pub fn with_selection_effects_deferred<R>(
3856 &mut self,
3857 window: &mut Window,
3858 cx: &mut Context<Self>,
3859 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3860 ) -> R {
3861 let already_deferred = self.defer_selection_effects;
3862 self.defer_selection_effects = true;
3863 let result = update(self, window, cx);
3864 if !already_deferred {
3865 self.defer_selection_effects = false;
3866 if let Some(state) = self.deferred_selection_effects_state.take() {
3867 self.apply_selection_effects(state, window, cx);
3868 }
3869 }
3870 result
3871 }
3872
3873 fn apply_selection_effects(
3874 &mut self,
3875 state: DeferredSelectionEffectsState,
3876 window: &mut Window,
3877 cx: &mut Context<Self>,
3878 ) {
3879 if state.changed {
3880 self.selection_history.push(state.history_entry);
3881
3882 if let Some(autoscroll) = state.effects.scroll {
3883 self.request_autoscroll(autoscroll, cx);
3884 }
3885
3886 let old_cursor_position = &state.old_cursor_position;
3887
3888 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3889
3890 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3891 self.show_signature_help_auto(window, cx);
3892 }
3893 }
3894 }
3895
3896 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3897 where
3898 I: IntoIterator<Item = (Range<S>, T)>,
3899 S: ToOffset,
3900 T: Into<Arc<str>>,
3901 {
3902 if self.read_only(cx) {
3903 return;
3904 }
3905
3906 self.buffer
3907 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3908 }
3909
3910 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3911 where
3912 I: IntoIterator<Item = (Range<S>, T)>,
3913 S: ToOffset,
3914 T: Into<Arc<str>>,
3915 {
3916 if self.read_only(cx) {
3917 return;
3918 }
3919
3920 self.buffer.update(cx, |buffer, cx| {
3921 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3922 });
3923 }
3924
3925 pub fn edit_with_block_indent<I, S, T>(
3926 &mut self,
3927 edits: I,
3928 original_indent_columns: Vec<Option<u32>>,
3929 cx: &mut Context<Self>,
3930 ) where
3931 I: IntoIterator<Item = (Range<S>, T)>,
3932 S: ToOffset,
3933 T: Into<Arc<str>>,
3934 {
3935 if self.read_only(cx) {
3936 return;
3937 }
3938
3939 self.buffer.update(cx, |buffer, cx| {
3940 buffer.edit(
3941 edits,
3942 Some(AutoindentMode::Block {
3943 original_indent_columns,
3944 }),
3945 cx,
3946 )
3947 });
3948 }
3949
3950 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3951 self.hide_context_menu(window, cx);
3952
3953 match phase {
3954 SelectPhase::Begin {
3955 position,
3956 add,
3957 click_count,
3958 } => self.begin_selection(position, add, click_count, window, cx),
3959 SelectPhase::BeginColumnar {
3960 position,
3961 goal_column,
3962 reset,
3963 mode,
3964 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3965 SelectPhase::Extend {
3966 position,
3967 click_count,
3968 } => self.extend_selection(position, click_count, window, cx),
3969 SelectPhase::Update {
3970 position,
3971 goal_column,
3972 scroll_delta,
3973 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3974 SelectPhase::End => self.end_selection(window, cx),
3975 }
3976 }
3977
3978 fn extend_selection(
3979 &mut self,
3980 position: DisplayPoint,
3981 click_count: usize,
3982 window: &mut Window,
3983 cx: &mut Context<Self>,
3984 ) {
3985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3986 let tail = self
3987 .selections
3988 .newest::<MultiBufferOffset>(&display_map)
3989 .tail();
3990 let click_count = click_count.max(match self.selections.select_mode() {
3991 SelectMode::Character => 1,
3992 SelectMode::Word(_) => 2,
3993 SelectMode::Line(_) => 3,
3994 SelectMode::All => 4,
3995 });
3996 self.begin_selection(position, false, click_count, window, cx);
3997
3998 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3999
4000 let current_selection = match self.selections.select_mode() {
4001 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4002 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4003 };
4004
4005 let mut pending_selection = self
4006 .selections
4007 .pending_anchor()
4008 .cloned()
4009 .expect("extend_selection not called with pending selection");
4010
4011 if pending_selection
4012 .start
4013 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4014 == Ordering::Greater
4015 {
4016 pending_selection.start = current_selection.start;
4017 }
4018 if pending_selection
4019 .end
4020 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4021 == Ordering::Less
4022 {
4023 pending_selection.end = current_selection.end;
4024 pending_selection.reversed = true;
4025 }
4026
4027 let mut pending_mode = self.selections.pending_mode().unwrap();
4028 match &mut pending_mode {
4029 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4030 _ => {}
4031 }
4032
4033 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4034 SelectionEffects::scroll(Autoscroll::fit())
4035 } else {
4036 SelectionEffects::no_scroll()
4037 };
4038
4039 self.change_selections(effects, window, cx, |s| {
4040 s.set_pending(pending_selection.clone(), pending_mode);
4041 s.set_is_extending(true);
4042 });
4043 }
4044
4045 fn begin_selection(
4046 &mut self,
4047 position: DisplayPoint,
4048 add: bool,
4049 click_count: usize,
4050 window: &mut Window,
4051 cx: &mut Context<Self>,
4052 ) {
4053 if !self.focus_handle.is_focused(window) {
4054 self.last_focused_descendant = None;
4055 window.focus(&self.focus_handle, cx);
4056 }
4057
4058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4059 let buffer = display_map.buffer_snapshot();
4060 let position = display_map.clip_point(position, Bias::Left);
4061
4062 let start;
4063 let end;
4064 let mode;
4065 let mut auto_scroll;
4066 match click_count {
4067 1 => {
4068 start = buffer.anchor_before(position.to_point(&display_map));
4069 end = start;
4070 mode = SelectMode::Character;
4071 auto_scroll = true;
4072 }
4073 2 => {
4074 let position = display_map
4075 .clip_point(position, Bias::Left)
4076 .to_offset(&display_map, Bias::Left);
4077 let (range, _) = buffer.surrounding_word(position, None);
4078 start = buffer.anchor_before(range.start);
4079 end = buffer.anchor_before(range.end);
4080 mode = SelectMode::Word(start..end);
4081 auto_scroll = true;
4082 }
4083 3 => {
4084 let position = display_map
4085 .clip_point(position, Bias::Left)
4086 .to_point(&display_map);
4087 let line_start = display_map.prev_line_boundary(position).0;
4088 let next_line_start = buffer.clip_point(
4089 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4090 Bias::Left,
4091 );
4092 start = buffer.anchor_before(line_start);
4093 end = buffer.anchor_before(next_line_start);
4094 mode = SelectMode::Line(start..end);
4095 auto_scroll = true;
4096 }
4097 _ => {
4098 start = buffer.anchor_before(MultiBufferOffset(0));
4099 end = buffer.anchor_before(buffer.len());
4100 mode = SelectMode::All;
4101 auto_scroll = false;
4102 }
4103 }
4104 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4105
4106 let point_to_delete: Option<usize> = {
4107 let selected_points: Vec<Selection<Point>> =
4108 self.selections.disjoint_in_range(start..end, &display_map);
4109
4110 if !add || click_count > 1 {
4111 None
4112 } else if !selected_points.is_empty() {
4113 Some(selected_points[0].id)
4114 } else {
4115 let clicked_point_already_selected =
4116 self.selections.disjoint_anchors().iter().find(|selection| {
4117 selection.start.to_point(buffer) == start.to_point(buffer)
4118 || selection.end.to_point(buffer) == end.to_point(buffer)
4119 });
4120
4121 clicked_point_already_selected.map(|selection| selection.id)
4122 }
4123 };
4124
4125 let selections_count = self.selections.count();
4126 let effects = if auto_scroll {
4127 SelectionEffects::default()
4128 } else {
4129 SelectionEffects::no_scroll()
4130 };
4131
4132 self.change_selections(effects, window, cx, |s| {
4133 if let Some(point_to_delete) = point_to_delete {
4134 s.delete(point_to_delete);
4135
4136 if selections_count == 1 {
4137 s.set_pending_anchor_range(start..end, mode);
4138 }
4139 } else {
4140 if !add {
4141 s.clear_disjoint();
4142 }
4143
4144 s.set_pending_anchor_range(start..end, mode);
4145 }
4146 });
4147 }
4148
4149 fn begin_columnar_selection(
4150 &mut self,
4151 position: DisplayPoint,
4152 goal_column: u32,
4153 reset: bool,
4154 mode: ColumnarMode,
4155 window: &mut Window,
4156 cx: &mut Context<Self>,
4157 ) {
4158 if !self.focus_handle.is_focused(window) {
4159 self.last_focused_descendant = None;
4160 window.focus(&self.focus_handle, cx);
4161 }
4162
4163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4164
4165 if reset {
4166 let pointer_position = display_map
4167 .buffer_snapshot()
4168 .anchor_before(position.to_point(&display_map));
4169
4170 self.change_selections(
4171 SelectionEffects::scroll(Autoscroll::newest()),
4172 window,
4173 cx,
4174 |s| {
4175 s.clear_disjoint();
4176 s.set_pending_anchor_range(
4177 pointer_position..pointer_position,
4178 SelectMode::Character,
4179 );
4180 },
4181 );
4182 };
4183
4184 let tail = self.selections.newest::<Point>(&display_map).tail();
4185 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4186 self.columnar_selection_state = match mode {
4187 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4188 selection_tail: selection_anchor,
4189 display_point: if reset {
4190 if position.column() != goal_column {
4191 Some(DisplayPoint::new(position.row(), goal_column))
4192 } else {
4193 None
4194 }
4195 } else {
4196 None
4197 },
4198 }),
4199 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4200 selection_tail: selection_anchor,
4201 }),
4202 };
4203
4204 if !reset {
4205 self.select_columns(position, goal_column, &display_map, window, cx);
4206 }
4207 }
4208
4209 fn update_selection(
4210 &mut self,
4211 position: DisplayPoint,
4212 goal_column: u32,
4213 scroll_delta: gpui::Point<f32>,
4214 window: &mut Window,
4215 cx: &mut Context<Self>,
4216 ) {
4217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4218
4219 if self.columnar_selection_state.is_some() {
4220 self.select_columns(position, goal_column, &display_map, window, cx);
4221 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4222 let buffer = display_map.buffer_snapshot();
4223 let head;
4224 let tail;
4225 let mode = self.selections.pending_mode().unwrap();
4226 match &mode {
4227 SelectMode::Character => {
4228 head = position.to_point(&display_map);
4229 tail = pending.tail().to_point(buffer);
4230 }
4231 SelectMode::Word(original_range) => {
4232 let offset = display_map
4233 .clip_point(position, Bias::Left)
4234 .to_offset(&display_map, Bias::Left);
4235 let original_range = original_range.to_offset(buffer);
4236
4237 let head_offset = if buffer.is_inside_word(offset, None)
4238 || original_range.contains(&offset)
4239 {
4240 let (word_range, _) = buffer.surrounding_word(offset, None);
4241 if word_range.start < original_range.start {
4242 word_range.start
4243 } else {
4244 word_range.end
4245 }
4246 } else {
4247 offset
4248 };
4249
4250 head = head_offset.to_point(buffer);
4251 if head_offset <= original_range.start {
4252 tail = original_range.end.to_point(buffer);
4253 } else {
4254 tail = original_range.start.to_point(buffer);
4255 }
4256 }
4257 SelectMode::Line(original_range) => {
4258 let original_range = original_range.to_point(display_map.buffer_snapshot());
4259
4260 let position = display_map
4261 .clip_point(position, Bias::Left)
4262 .to_point(&display_map);
4263 let line_start = display_map.prev_line_boundary(position).0;
4264 let next_line_start = buffer.clip_point(
4265 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4266 Bias::Left,
4267 );
4268
4269 if line_start < original_range.start {
4270 head = line_start
4271 } else {
4272 head = next_line_start
4273 }
4274
4275 if head <= original_range.start {
4276 tail = original_range.end;
4277 } else {
4278 tail = original_range.start;
4279 }
4280 }
4281 SelectMode::All => {
4282 return;
4283 }
4284 };
4285
4286 if head < tail {
4287 pending.start = buffer.anchor_before(head);
4288 pending.end = buffer.anchor_before(tail);
4289 pending.reversed = true;
4290 } else {
4291 pending.start = buffer.anchor_before(tail);
4292 pending.end = buffer.anchor_before(head);
4293 pending.reversed = false;
4294 }
4295
4296 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4297 s.set_pending(pending.clone(), mode);
4298 });
4299 } else {
4300 log::error!("update_selection dispatched with no pending selection");
4301 return;
4302 }
4303
4304 self.apply_scroll_delta(scroll_delta, window, cx);
4305 cx.notify();
4306 }
4307
4308 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4309 self.columnar_selection_state.take();
4310 if let Some(pending_mode) = self.selections.pending_mode() {
4311 let selections = self
4312 .selections
4313 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4314 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4315 s.select(selections);
4316 s.clear_pending();
4317 if s.is_extending() {
4318 s.set_is_extending(false);
4319 } else {
4320 s.set_select_mode(pending_mode);
4321 }
4322 });
4323 }
4324 }
4325
4326 fn select_columns(
4327 &mut self,
4328 head: DisplayPoint,
4329 goal_column: u32,
4330 display_map: &DisplaySnapshot,
4331 window: &mut Window,
4332 cx: &mut Context<Self>,
4333 ) {
4334 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4335 return;
4336 };
4337
4338 let tail = match columnar_state {
4339 ColumnarSelectionState::FromMouse {
4340 selection_tail,
4341 display_point,
4342 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4343 ColumnarSelectionState::FromSelection { selection_tail } => {
4344 selection_tail.to_display_point(display_map)
4345 }
4346 };
4347
4348 let start_row = cmp::min(tail.row(), head.row());
4349 let end_row = cmp::max(tail.row(), head.row());
4350 let start_column = cmp::min(tail.column(), goal_column);
4351 let end_column = cmp::max(tail.column(), goal_column);
4352 let reversed = start_column < tail.column();
4353
4354 let selection_ranges = (start_row.0..=end_row.0)
4355 .map(DisplayRow)
4356 .filter_map(|row| {
4357 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4358 || start_column <= display_map.line_len(row))
4359 && !display_map.is_block_line(row)
4360 {
4361 let start = display_map
4362 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4363 .to_point(display_map);
4364 let end = display_map
4365 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4366 .to_point(display_map);
4367 if reversed {
4368 Some(end..start)
4369 } else {
4370 Some(start..end)
4371 }
4372 } else {
4373 None
4374 }
4375 })
4376 .collect::<Vec<_>>();
4377 if selection_ranges.is_empty() {
4378 return;
4379 }
4380
4381 let ranges = match columnar_state {
4382 ColumnarSelectionState::FromMouse { .. } => {
4383 let mut non_empty_ranges = selection_ranges
4384 .iter()
4385 .filter(|selection_range| selection_range.start != selection_range.end)
4386 .peekable();
4387 if non_empty_ranges.peek().is_some() {
4388 non_empty_ranges.cloned().collect()
4389 } else {
4390 selection_ranges
4391 }
4392 }
4393 _ => selection_ranges,
4394 };
4395
4396 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4397 s.select_ranges(ranges);
4398 });
4399 cx.notify();
4400 }
4401
4402 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4403 self.selections
4404 .all_adjusted(snapshot)
4405 .iter()
4406 .any(|selection| !selection.is_empty())
4407 }
4408
4409 pub fn has_pending_nonempty_selection(&self) -> bool {
4410 let pending_nonempty_selection = match self.selections.pending_anchor() {
4411 Some(Selection { start, end, .. }) => start != end,
4412 None => false,
4413 };
4414
4415 pending_nonempty_selection
4416 || (self.columnar_selection_state.is_some()
4417 && self.selections.disjoint_anchors().len() > 1)
4418 }
4419
4420 pub fn has_pending_selection(&self) -> bool {
4421 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4422 }
4423
4424 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4425 self.selection_mark_mode = false;
4426 self.selection_drag_state = SelectionDragState::None;
4427
4428 if self.dismiss_menus_and_popups(true, window, cx) {
4429 cx.notify();
4430 return;
4431 }
4432 if self.clear_expanded_diff_hunks(cx) {
4433 cx.notify();
4434 return;
4435 }
4436 if self.show_git_blame_gutter {
4437 self.show_git_blame_gutter = false;
4438 cx.notify();
4439 return;
4440 }
4441
4442 if self.mode.is_full()
4443 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4444 {
4445 cx.notify();
4446 return;
4447 }
4448
4449 cx.propagate();
4450 }
4451
4452 pub fn dismiss_menus_and_popups(
4453 &mut self,
4454 is_user_requested: bool,
4455 window: &mut Window,
4456 cx: &mut Context<Self>,
4457 ) -> bool {
4458 let mut dismissed = false;
4459
4460 dismissed |= self.take_rename(false, window, cx).is_some();
4461 dismissed |= self.hide_blame_popover(true, cx);
4462 dismissed |= hide_hover(self, cx);
4463 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4464 dismissed |= self.hide_context_menu(window, cx).is_some();
4465 dismissed |= self.mouse_context_menu.take().is_some();
4466 dismissed |= is_user_requested
4467 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4468 dismissed |= self.snippet_stack.pop().is_some();
4469 if self.diff_review_drag_state.is_some() {
4470 self.cancel_diff_review_drag(cx);
4471 dismissed = true;
4472 }
4473 if !self.diff_review_overlays.is_empty() {
4474 self.dismiss_all_diff_review_overlays(cx);
4475 dismissed = true;
4476 }
4477
4478 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4479 self.dismiss_diagnostics(cx);
4480 dismissed = true;
4481 }
4482
4483 dismissed
4484 }
4485
4486 fn linked_editing_ranges_for(
4487 &self,
4488 selection: Range<text::Anchor>,
4489 cx: &App,
4490 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4491 if self.linked_edit_ranges.is_empty() {
4492 return None;
4493 }
4494 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4495 selection.end.buffer_id.and_then(|end_buffer_id| {
4496 if selection.start.buffer_id != Some(end_buffer_id) {
4497 return None;
4498 }
4499 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4500 let snapshot = buffer.read(cx).snapshot();
4501 self.linked_edit_ranges
4502 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4503 .map(|ranges| (ranges, snapshot, buffer))
4504 })?;
4505 use text::ToOffset as TO;
4506 // find offset from the start of current range to current cursor position
4507 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4508
4509 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4510 let start_difference = start_offset - start_byte_offset;
4511 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4512 let end_difference = end_offset - start_byte_offset;
4513 // Current range has associated linked ranges.
4514 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4515 for range in linked_ranges.iter() {
4516 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4517 let end_offset = start_offset + end_difference;
4518 let start_offset = start_offset + start_difference;
4519 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4520 continue;
4521 }
4522 if self.selections.disjoint_anchor_ranges().any(|s| {
4523 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4524 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4525 {
4526 return false;
4527 }
4528 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4529 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4530 }) {
4531 continue;
4532 }
4533 let start = buffer_snapshot.anchor_after(start_offset);
4534 let end = buffer_snapshot.anchor_after(end_offset);
4535 linked_edits
4536 .entry(buffer.clone())
4537 .or_default()
4538 .push(start..end);
4539 }
4540 Some(linked_edits)
4541 }
4542
4543 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4544 let text: Arc<str> = text.into();
4545
4546 if self.read_only(cx) {
4547 return;
4548 }
4549
4550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4551
4552 self.unfold_buffers_with_selections(cx);
4553
4554 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4555 let mut bracket_inserted = false;
4556 let mut edits = Vec::new();
4557 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4558 let mut new_selections = Vec::with_capacity(selections.len());
4559 let mut new_autoclose_regions = Vec::new();
4560 let snapshot = self.buffer.read(cx).read(cx);
4561 let mut clear_linked_edit_ranges = false;
4562 let mut all_selections_read_only = true;
4563 let mut has_adjacent_edits = false;
4564 let mut in_adjacent_group = false;
4565
4566 let mut regions = self
4567 .selections_with_autoclose_regions(selections, &snapshot)
4568 .peekable();
4569
4570 while let Some((selection, autoclose_region)) = regions.next() {
4571 if snapshot
4572 .point_to_buffer_point(selection.head())
4573 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4574 {
4575 continue;
4576 }
4577 if snapshot
4578 .point_to_buffer_point(selection.tail())
4579 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4580 {
4581 // note, ideally we'd clip the tail to the closest writeable region towards the head
4582 continue;
4583 }
4584 all_selections_read_only = false;
4585
4586 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4587 // Determine if the inserted text matches the opening or closing
4588 // bracket of any of this language's bracket pairs.
4589 let mut bracket_pair = None;
4590 let mut is_bracket_pair_start = false;
4591 let mut is_bracket_pair_end = false;
4592 if !text.is_empty() {
4593 let mut bracket_pair_matching_end = None;
4594 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4595 // and they are removing the character that triggered IME popup.
4596 for (pair, enabled) in scope.brackets() {
4597 if !pair.close && !pair.surround {
4598 continue;
4599 }
4600
4601 if enabled && pair.start.ends_with(text.as_ref()) {
4602 let prefix_len = pair.start.len() - text.len();
4603 let preceding_text_matches_prefix = prefix_len == 0
4604 || (selection.start.column >= (prefix_len as u32)
4605 && snapshot.contains_str_at(
4606 Point::new(
4607 selection.start.row,
4608 selection.start.column - (prefix_len as u32),
4609 ),
4610 &pair.start[..prefix_len],
4611 ));
4612 if preceding_text_matches_prefix {
4613 bracket_pair = Some(pair.clone());
4614 is_bracket_pair_start = true;
4615 break;
4616 }
4617 }
4618 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4619 {
4620 // take first bracket pair matching end, but don't break in case a later bracket
4621 // pair matches start
4622 bracket_pair_matching_end = Some(pair.clone());
4623 }
4624 }
4625 if let Some(end) = bracket_pair_matching_end
4626 && bracket_pair.is_none()
4627 {
4628 bracket_pair = Some(end);
4629 is_bracket_pair_end = true;
4630 }
4631 }
4632
4633 if let Some(bracket_pair) = bracket_pair {
4634 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4635 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4636 let auto_surround =
4637 self.use_auto_surround && snapshot_settings.use_auto_surround;
4638 if selection.is_empty() {
4639 if is_bracket_pair_start {
4640 // If the inserted text is a suffix of an opening bracket and the
4641 // selection is preceded by the rest of the opening bracket, then
4642 // insert the closing bracket.
4643 let following_text_allows_autoclose = snapshot
4644 .chars_at(selection.start)
4645 .next()
4646 .is_none_or(|c| scope.should_autoclose_before(c));
4647
4648 let preceding_text_allows_autoclose = selection.start.column == 0
4649 || snapshot
4650 .reversed_chars_at(selection.start)
4651 .next()
4652 .is_none_or(|c| {
4653 bracket_pair.start != bracket_pair.end
4654 || !snapshot
4655 .char_classifier_at(selection.start)
4656 .is_word(c)
4657 });
4658
4659 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4660 && bracket_pair.start.len() == 1
4661 {
4662 let target = bracket_pair.start.chars().next().unwrap();
4663 let mut byte_offset = 0u32;
4664 let current_line_count = snapshot
4665 .reversed_chars_at(selection.start)
4666 .take_while(|&c| c != '\n')
4667 .filter(|c| {
4668 byte_offset += c.len_utf8() as u32;
4669 if *c != target {
4670 return false;
4671 }
4672
4673 let point = Point::new(
4674 selection.start.row,
4675 selection.start.column.saturating_sub(byte_offset),
4676 );
4677
4678 let is_enabled = snapshot
4679 .language_scope_at(point)
4680 .and_then(|scope| {
4681 scope
4682 .brackets()
4683 .find(|(pair, _)| {
4684 pair.start == bracket_pair.start
4685 })
4686 .map(|(_, enabled)| enabled)
4687 })
4688 .unwrap_or(true);
4689
4690 let is_delimiter = snapshot
4691 .language_scope_at(Point::new(
4692 point.row,
4693 point.column + 1,
4694 ))
4695 .and_then(|scope| {
4696 scope
4697 .brackets()
4698 .find(|(pair, _)| {
4699 pair.start == bracket_pair.start
4700 })
4701 .map(|(_, enabled)| !enabled)
4702 })
4703 .unwrap_or(false);
4704
4705 is_enabled && !is_delimiter
4706 })
4707 .count();
4708 current_line_count % 2 == 1
4709 } else {
4710 false
4711 };
4712
4713 if autoclose
4714 && bracket_pair.close
4715 && following_text_allows_autoclose
4716 && preceding_text_allows_autoclose
4717 && !is_closing_quote
4718 {
4719 let anchor = snapshot.anchor_before(selection.end);
4720 new_selections.push((selection.map(|_| anchor), text.len()));
4721 new_autoclose_regions.push((
4722 anchor,
4723 text.len(),
4724 selection.id,
4725 bracket_pair.clone(),
4726 ));
4727 edits.push((
4728 selection.range(),
4729 format!("{}{}", text, bracket_pair.end).into(),
4730 ));
4731 bracket_inserted = true;
4732 continue;
4733 }
4734 }
4735
4736 if let Some(region) = autoclose_region {
4737 // If the selection is followed by an auto-inserted closing bracket,
4738 // then don't insert that closing bracket again; just move the selection
4739 // past the closing bracket.
4740 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4741 && text.as_ref() == region.pair.end.as_str()
4742 && snapshot.contains_str_at(region.range.end, text.as_ref());
4743 if should_skip {
4744 let anchor = snapshot.anchor_after(selection.end);
4745 new_selections
4746 .push((selection.map(|_| anchor), region.pair.end.len()));
4747 continue;
4748 }
4749 }
4750
4751 let always_treat_brackets_as_autoclosed = snapshot
4752 .language_settings_at(selection.start, cx)
4753 .always_treat_brackets_as_autoclosed;
4754 if always_treat_brackets_as_autoclosed
4755 && is_bracket_pair_end
4756 && snapshot.contains_str_at(selection.end, text.as_ref())
4757 {
4758 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4759 // and the inserted text is a closing bracket and the selection is followed
4760 // by the closing bracket then move the selection past the closing bracket.
4761 let anchor = snapshot.anchor_after(selection.end);
4762 new_selections.push((selection.map(|_| anchor), text.len()));
4763 continue;
4764 }
4765 }
4766 // If an opening bracket is 1 character long and is typed while
4767 // text is selected, then surround that text with the bracket pair.
4768 else if auto_surround
4769 && bracket_pair.surround
4770 && is_bracket_pair_start
4771 && bracket_pair.start.chars().count() == 1
4772 {
4773 edits.push((selection.start..selection.start, text.clone()));
4774 edits.push((
4775 selection.end..selection.end,
4776 bracket_pair.end.as_str().into(),
4777 ));
4778 bracket_inserted = true;
4779 new_selections.push((
4780 Selection {
4781 id: selection.id,
4782 start: snapshot.anchor_after(selection.start),
4783 end: snapshot.anchor_before(selection.end),
4784 reversed: selection.reversed,
4785 goal: selection.goal,
4786 },
4787 0,
4788 ));
4789 continue;
4790 }
4791 }
4792 }
4793
4794 if self.auto_replace_emoji_shortcode
4795 && selection.is_empty()
4796 && text.as_ref().ends_with(':')
4797 && let Some(possible_emoji_short_code) =
4798 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4799 && !possible_emoji_short_code.is_empty()
4800 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4801 {
4802 let emoji_shortcode_start = Point::new(
4803 selection.start.row,
4804 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4805 );
4806
4807 // Remove shortcode from buffer
4808 edits.push((
4809 emoji_shortcode_start..selection.start,
4810 "".to_string().into(),
4811 ));
4812 new_selections.push((
4813 Selection {
4814 id: selection.id,
4815 start: snapshot.anchor_after(emoji_shortcode_start),
4816 end: snapshot.anchor_before(selection.start),
4817 reversed: selection.reversed,
4818 goal: selection.goal,
4819 },
4820 0,
4821 ));
4822
4823 // Insert emoji
4824 let selection_start_anchor = snapshot.anchor_after(selection.start);
4825 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4826 edits.push((selection.start..selection.end, emoji.to_string().into()));
4827
4828 continue;
4829 }
4830
4831 let next_is_adjacent = regions
4832 .peek()
4833 .is_some_and(|(next, _)| selection.end == next.start);
4834
4835 // If not handling any auto-close operation, then just replace the selected
4836 // text with the given input and move the selection to the end of the
4837 // newly inserted text.
4838 let anchor = if in_adjacent_group || next_is_adjacent {
4839 // After edits the right bias would shift those anchor to the next visible fragment
4840 // but we want to resolve to the previous one
4841 snapshot.anchor_before(selection.end)
4842 } else {
4843 snapshot.anchor_after(selection.end)
4844 };
4845
4846 if !self.linked_edit_ranges.is_empty() {
4847 let start_anchor = snapshot.anchor_before(selection.start);
4848
4849 let is_word_char = text.chars().next().is_none_or(|char| {
4850 let classifier = snapshot
4851 .char_classifier_at(start_anchor.to_offset(&snapshot))
4852 .scope_context(Some(CharScopeContext::LinkedEdit));
4853 classifier.is_word(char)
4854 });
4855
4856 if is_word_char {
4857 if let Some(ranges) = self
4858 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4859 {
4860 for (buffer, edits) in ranges {
4861 linked_edits
4862 .entry(buffer.clone())
4863 .or_default()
4864 .extend(edits.into_iter().map(|range| (range, text.clone())));
4865 }
4866 }
4867 } else {
4868 clear_linked_edit_ranges = true;
4869 }
4870 }
4871
4872 new_selections.push((selection.map(|_| anchor), 0));
4873 edits.push((selection.start..selection.end, text.clone()));
4874
4875 has_adjacent_edits |= next_is_adjacent;
4876 in_adjacent_group = next_is_adjacent;
4877 }
4878
4879 if all_selections_read_only {
4880 return;
4881 }
4882
4883 drop(regions);
4884 drop(snapshot);
4885
4886 self.transact(window, cx, |this, window, cx| {
4887 if clear_linked_edit_ranges {
4888 this.linked_edit_ranges.clear();
4889 }
4890 let initial_buffer_versions =
4891 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4892
4893 this.buffer.update(cx, |buffer, cx| {
4894 if has_adjacent_edits {
4895 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4896 } else {
4897 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4898 }
4899 });
4900 for (buffer, edits) in linked_edits {
4901 buffer.update(cx, |buffer, cx| {
4902 let snapshot = buffer.snapshot();
4903 let edits = edits
4904 .into_iter()
4905 .map(|(range, text)| {
4906 use text::ToPoint as TP;
4907 let end_point = TP::to_point(&range.end, &snapshot);
4908 let start_point = TP::to_point(&range.start, &snapshot);
4909 (start_point..end_point, text)
4910 })
4911 .sorted_by_key(|(range, _)| range.start);
4912 buffer.edit(edits, None, cx);
4913 })
4914 }
4915 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4916 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4917 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4918 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4919 new_anchor_selections,
4920 &map,
4921 )
4922 .zip(new_selection_deltas)
4923 .map(|(selection, delta)| Selection {
4924 id: selection.id,
4925 start: selection.start + delta,
4926 end: selection.end + delta,
4927 reversed: selection.reversed,
4928 goal: SelectionGoal::None,
4929 })
4930 .collect::<Vec<_>>();
4931
4932 let mut i = 0;
4933 for (position, delta, selection_id, pair) in new_autoclose_regions {
4934 let position = position.to_offset(map.buffer_snapshot()) + delta;
4935 let start = map.buffer_snapshot().anchor_before(position);
4936 let end = map.buffer_snapshot().anchor_after(position);
4937 while let Some(existing_state) = this.autoclose_regions.get(i) {
4938 match existing_state
4939 .range
4940 .start
4941 .cmp(&start, map.buffer_snapshot())
4942 {
4943 Ordering::Less => i += 1,
4944 Ordering::Greater => break,
4945 Ordering::Equal => {
4946 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4947 Ordering::Less => i += 1,
4948 Ordering::Equal => break,
4949 Ordering::Greater => break,
4950 }
4951 }
4952 }
4953 }
4954 this.autoclose_regions.insert(
4955 i,
4956 AutocloseRegion {
4957 selection_id,
4958 range: start..end,
4959 pair,
4960 },
4961 );
4962 }
4963
4964 let had_active_edit_prediction = this.has_active_edit_prediction();
4965 this.change_selections(
4966 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4967 window,
4968 cx,
4969 |s| s.select(new_selections),
4970 );
4971
4972 if !bracket_inserted
4973 && let Some(on_type_format_task) =
4974 this.trigger_on_type_formatting(text.to_string(), window, cx)
4975 {
4976 on_type_format_task.detach_and_log_err(cx);
4977 }
4978
4979 let editor_settings = EditorSettings::get_global(cx);
4980 if bracket_inserted
4981 && (editor_settings.auto_signature_help
4982 || editor_settings.show_signature_help_after_edits)
4983 {
4984 this.show_signature_help(&ShowSignatureHelp, window, cx);
4985 }
4986
4987 let trigger_in_words =
4988 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4989 if this.hard_wrap.is_some() {
4990 let latest: Range<Point> = this.selections.newest(&map).range();
4991 if latest.is_empty()
4992 && this
4993 .buffer()
4994 .read(cx)
4995 .snapshot(cx)
4996 .line_len(MultiBufferRow(latest.start.row))
4997 == latest.start.column
4998 {
4999 this.rewrap_impl(
5000 RewrapOptions {
5001 override_language_settings: true,
5002 preserve_existing_whitespace: true,
5003 },
5004 cx,
5005 )
5006 }
5007 }
5008 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5009 refresh_linked_ranges(this, window, cx);
5010 this.refresh_edit_prediction(true, false, window, cx);
5011 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5012 });
5013 }
5014
5015 fn find_possible_emoji_shortcode_at_position(
5016 snapshot: &MultiBufferSnapshot,
5017 position: Point,
5018 ) -> Option<String> {
5019 let mut chars = Vec::new();
5020 let mut found_colon = false;
5021 for char in snapshot.reversed_chars_at(position).take(100) {
5022 // Found a possible emoji shortcode in the middle of the buffer
5023 if found_colon {
5024 if char.is_whitespace() {
5025 chars.reverse();
5026 return Some(chars.iter().collect());
5027 }
5028 // If the previous character is not a whitespace, we are in the middle of a word
5029 // and we only want to complete the shortcode if the word is made up of other emojis
5030 let mut containing_word = String::new();
5031 for ch in snapshot
5032 .reversed_chars_at(position)
5033 .skip(chars.len() + 1)
5034 .take(100)
5035 {
5036 if ch.is_whitespace() {
5037 break;
5038 }
5039 containing_word.push(ch);
5040 }
5041 let containing_word = containing_word.chars().rev().collect::<String>();
5042 if util::word_consists_of_emojis(containing_word.as_str()) {
5043 chars.reverse();
5044 return Some(chars.iter().collect());
5045 }
5046 }
5047
5048 if char.is_whitespace() || !char.is_ascii() {
5049 return None;
5050 }
5051 if char == ':' {
5052 found_colon = true;
5053 } else {
5054 chars.push(char);
5055 }
5056 }
5057 // Found a possible emoji shortcode at the beginning of the buffer
5058 chars.reverse();
5059 Some(chars.iter().collect())
5060 }
5061
5062 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5064 self.transact(window, cx, |this, window, cx| {
5065 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5066 let selections = this
5067 .selections
5068 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5069 let multi_buffer = this.buffer.read(cx);
5070 let buffer = multi_buffer.snapshot(cx);
5071 selections
5072 .iter()
5073 .map(|selection| {
5074 let start_point = selection.start.to_point(&buffer);
5075 let mut existing_indent =
5076 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5077 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5078 let start = selection.start;
5079 let end = selection.end;
5080 let selection_is_empty = start == end;
5081 let language_scope = buffer.language_scope_at(start);
5082 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5083 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5084 &buffer,
5085 start..end,
5086 language,
5087 )
5088 || NewlineConfig::insert_extra_newline_tree_sitter(
5089 &buffer,
5090 start..end,
5091 );
5092
5093 let mut newline_config = NewlineConfig::Newline {
5094 additional_indent: IndentSize::spaces(0),
5095 extra_line_additional_indent: if needs_extra_newline {
5096 Some(IndentSize::spaces(0))
5097 } else {
5098 None
5099 },
5100 prevent_auto_indent: false,
5101 };
5102
5103 let comment_delimiter = maybe!({
5104 if !selection_is_empty {
5105 return None;
5106 }
5107
5108 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5109 return None;
5110 }
5111
5112 return comment_delimiter_for_newline(
5113 &start_point,
5114 &buffer,
5115 language,
5116 );
5117 });
5118
5119 let doc_delimiter = maybe!({
5120 if !selection_is_empty {
5121 return None;
5122 }
5123
5124 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5125 return None;
5126 }
5127
5128 return documentation_delimiter_for_newline(
5129 &start_point,
5130 &buffer,
5131 language,
5132 &mut newline_config,
5133 );
5134 });
5135
5136 let list_delimiter = maybe!({
5137 if !selection_is_empty {
5138 return None;
5139 }
5140
5141 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5142 return None;
5143 }
5144
5145 return list_delimiter_for_newline(
5146 &start_point,
5147 &buffer,
5148 language,
5149 &mut newline_config,
5150 );
5151 });
5152
5153 (
5154 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5155 newline_config,
5156 )
5157 } else {
5158 (
5159 None,
5160 NewlineConfig::Newline {
5161 additional_indent: IndentSize::spaces(0),
5162 extra_line_additional_indent: None,
5163 prevent_auto_indent: false,
5164 },
5165 )
5166 };
5167
5168 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5169 NewlineConfig::ClearCurrentLine => {
5170 let row_start =
5171 buffer.point_to_offset(Point::new(start_point.row, 0));
5172 (row_start, String::new(), false)
5173 }
5174 NewlineConfig::UnindentCurrentLine { continuation } => {
5175 let row_start =
5176 buffer.point_to_offset(Point::new(start_point.row, 0));
5177 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5178 let tab_size_indent = IndentSize::spaces(tab_size.get());
5179 let reduced_indent =
5180 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5181 let mut new_text = String::new();
5182 new_text.extend(reduced_indent.chars());
5183 new_text.push_str(continuation);
5184 (row_start, new_text, true)
5185 }
5186 NewlineConfig::Newline {
5187 additional_indent,
5188 extra_line_additional_indent,
5189 prevent_auto_indent,
5190 } => {
5191 let capacity_for_delimiter =
5192 delimiter.as_deref().map(str::len).unwrap_or_default();
5193 let extra_line_len = extra_line_additional_indent
5194 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5195 .unwrap_or(0);
5196 let mut new_text = String::with_capacity(
5197 1 + capacity_for_delimiter
5198 + existing_indent.len as usize
5199 + additional_indent.len as usize
5200 + extra_line_len,
5201 );
5202 new_text.push('\n');
5203 new_text.extend(existing_indent.chars());
5204 new_text.extend(additional_indent.chars());
5205 if let Some(delimiter) = &delimiter {
5206 new_text.push_str(delimiter);
5207 }
5208 if let Some(extra_indent) = extra_line_additional_indent {
5209 new_text.push('\n');
5210 new_text.extend(existing_indent.chars());
5211 new_text.extend(extra_indent.chars());
5212 }
5213 (start, new_text, *prevent_auto_indent)
5214 }
5215 };
5216
5217 let anchor = buffer.anchor_after(end);
5218 let new_selection = selection.map(|_| anchor);
5219 (
5220 ((edit_start..end, new_text), prevent_auto_indent),
5221 (newline_config.has_extra_line(), new_selection),
5222 )
5223 })
5224 .unzip()
5225 };
5226
5227 let mut auto_indent_edits = Vec::new();
5228 let mut edits = Vec::new();
5229 for (edit, prevent_auto_indent) in edits_with_flags {
5230 if prevent_auto_indent {
5231 edits.push(edit);
5232 } else {
5233 auto_indent_edits.push(edit);
5234 }
5235 }
5236 if !edits.is_empty() {
5237 this.edit(edits, cx);
5238 }
5239 if !auto_indent_edits.is_empty() {
5240 this.edit_with_autoindent(auto_indent_edits, cx);
5241 }
5242
5243 let buffer = this.buffer.read(cx).snapshot(cx);
5244 let new_selections = selection_info
5245 .into_iter()
5246 .map(|(extra_newline_inserted, new_selection)| {
5247 let mut cursor = new_selection.end.to_point(&buffer);
5248 if extra_newline_inserted {
5249 cursor.row -= 1;
5250 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5251 }
5252 new_selection.map(|_| cursor)
5253 })
5254 .collect();
5255
5256 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5257 this.refresh_edit_prediction(true, false, window, cx);
5258 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5259 task.detach_and_log_err(cx);
5260 }
5261 });
5262 }
5263
5264 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5266
5267 let buffer = self.buffer.read(cx);
5268 let snapshot = buffer.snapshot(cx);
5269
5270 let mut edits = Vec::new();
5271 let mut rows = Vec::new();
5272
5273 for (rows_inserted, selection) in self
5274 .selections
5275 .all_adjusted(&self.display_snapshot(cx))
5276 .into_iter()
5277 .enumerate()
5278 {
5279 let cursor = selection.head();
5280 let row = cursor.row;
5281
5282 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5283
5284 let newline = "\n".to_string();
5285 edits.push((start_of_line..start_of_line, newline));
5286
5287 rows.push(row + rows_inserted as u32);
5288 }
5289
5290 self.transact(window, cx, |editor, window, cx| {
5291 editor.edit(edits, cx);
5292
5293 editor.change_selections(Default::default(), window, cx, |s| {
5294 let mut index = 0;
5295 s.move_cursors_with(|map, _, _| {
5296 let row = rows[index];
5297 index += 1;
5298
5299 let point = Point::new(row, 0);
5300 let boundary = map.next_line_boundary(point).1;
5301 let clipped = map.clip_point(boundary, Bias::Left);
5302
5303 (clipped, SelectionGoal::None)
5304 });
5305 });
5306
5307 let mut indent_edits = Vec::new();
5308 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5309 for row in rows {
5310 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5311 for (row, indent) in indents {
5312 if indent.len == 0 {
5313 continue;
5314 }
5315
5316 let text = match indent.kind {
5317 IndentKind::Space => " ".repeat(indent.len as usize),
5318 IndentKind::Tab => "\t".repeat(indent.len as usize),
5319 };
5320 let point = Point::new(row.0, 0);
5321 indent_edits.push((point..point, text));
5322 }
5323 }
5324 editor.edit(indent_edits, cx);
5325 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5326 format.detach_and_log_err(cx);
5327 }
5328 });
5329 }
5330
5331 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5332 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5333
5334 let buffer = self.buffer.read(cx);
5335 let snapshot = buffer.snapshot(cx);
5336
5337 let mut edits = Vec::new();
5338 let mut rows = Vec::new();
5339 let mut rows_inserted = 0;
5340
5341 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5342 let cursor = selection.head();
5343 let row = cursor.row;
5344
5345 let point = Point::new(row + 1, 0);
5346 let start_of_line = snapshot.clip_point(point, Bias::Left);
5347
5348 let newline = "\n".to_string();
5349 edits.push((start_of_line..start_of_line, newline));
5350
5351 rows_inserted += 1;
5352 rows.push(row + rows_inserted);
5353 }
5354
5355 self.transact(window, cx, |editor, window, cx| {
5356 editor.edit(edits, cx);
5357
5358 editor.change_selections(Default::default(), window, cx, |s| {
5359 let mut index = 0;
5360 s.move_cursors_with(|map, _, _| {
5361 let row = rows[index];
5362 index += 1;
5363
5364 let point = Point::new(row, 0);
5365 let boundary = map.next_line_boundary(point).1;
5366 let clipped = map.clip_point(boundary, Bias::Left);
5367
5368 (clipped, SelectionGoal::None)
5369 });
5370 });
5371
5372 let mut indent_edits = Vec::new();
5373 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5374 for row in rows {
5375 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5376 for (row, indent) in indents {
5377 if indent.len == 0 {
5378 continue;
5379 }
5380
5381 let text = match indent.kind {
5382 IndentKind::Space => " ".repeat(indent.len as usize),
5383 IndentKind::Tab => "\t".repeat(indent.len as usize),
5384 };
5385 let point = Point::new(row.0, 0);
5386 indent_edits.push((point..point, text));
5387 }
5388 }
5389 editor.edit(indent_edits, cx);
5390 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5391 format.detach_and_log_err(cx);
5392 }
5393 });
5394 }
5395
5396 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5397 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5398 original_indent_columns: Vec::new(),
5399 });
5400 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5401 }
5402
5403 fn insert_with_autoindent_mode(
5404 &mut self,
5405 text: &str,
5406 autoindent_mode: Option<AutoindentMode>,
5407 window: &mut Window,
5408 cx: &mut Context<Self>,
5409 ) {
5410 if self.read_only(cx) {
5411 return;
5412 }
5413
5414 let text: Arc<str> = text.into();
5415 self.transact(window, cx, |this, window, cx| {
5416 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5417 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5418 let anchors = {
5419 let snapshot = buffer.read(cx);
5420 old_selections
5421 .iter()
5422 .map(|s| {
5423 let anchor = snapshot.anchor_after(s.head());
5424 s.map(|_| anchor)
5425 })
5426 .collect::<Vec<_>>()
5427 };
5428 buffer.edit(
5429 old_selections
5430 .iter()
5431 .map(|s| (s.start..s.end, text.clone())),
5432 autoindent_mode,
5433 cx,
5434 );
5435 anchors
5436 });
5437
5438 this.change_selections(Default::default(), window, cx, |s| {
5439 s.select_anchors(selection_anchors);
5440 });
5441
5442 cx.notify();
5443 });
5444 }
5445
5446 fn trigger_completion_on_input(
5447 &mut self,
5448 text: &str,
5449 trigger_in_words: bool,
5450 window: &mut Window,
5451 cx: &mut Context<Self>,
5452 ) {
5453 let completions_source = self
5454 .context_menu
5455 .borrow()
5456 .as_ref()
5457 .and_then(|menu| match menu {
5458 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5459 CodeContextMenu::CodeActions(_) => None,
5460 });
5461
5462 match completions_source {
5463 Some(CompletionsMenuSource::Words { .. }) => {
5464 self.open_or_update_completions_menu(
5465 Some(CompletionsMenuSource::Words {
5466 ignore_threshold: false,
5467 }),
5468 None,
5469 trigger_in_words,
5470 window,
5471 cx,
5472 );
5473 }
5474 _ => self.open_or_update_completions_menu(
5475 None,
5476 Some(text.to_owned()).filter(|x| !x.is_empty()),
5477 true,
5478 window,
5479 cx,
5480 ),
5481 }
5482 }
5483
5484 /// If any empty selections is touching the start of its innermost containing autoclose
5485 /// region, expand it to select the brackets.
5486 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5487 let selections = self
5488 .selections
5489 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5490 let buffer = self.buffer.read(cx).read(cx);
5491 let new_selections = self
5492 .selections_with_autoclose_regions(selections, &buffer)
5493 .map(|(mut selection, region)| {
5494 if !selection.is_empty() {
5495 return selection;
5496 }
5497
5498 if let Some(region) = region {
5499 let mut range = region.range.to_offset(&buffer);
5500 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5501 range.start -= region.pair.start.len();
5502 if buffer.contains_str_at(range.start, ®ion.pair.start)
5503 && buffer.contains_str_at(range.end, ®ion.pair.end)
5504 {
5505 range.end += region.pair.end.len();
5506 selection.start = range.start;
5507 selection.end = range.end;
5508
5509 return selection;
5510 }
5511 }
5512 }
5513
5514 let always_treat_brackets_as_autoclosed = buffer
5515 .language_settings_at(selection.start, cx)
5516 .always_treat_brackets_as_autoclosed;
5517
5518 if !always_treat_brackets_as_autoclosed {
5519 return selection;
5520 }
5521
5522 if let Some(scope) = buffer.language_scope_at(selection.start) {
5523 for (pair, enabled) in scope.brackets() {
5524 if !enabled || !pair.close {
5525 continue;
5526 }
5527
5528 if buffer.contains_str_at(selection.start, &pair.end) {
5529 let pair_start_len = pair.start.len();
5530 if buffer.contains_str_at(
5531 selection.start.saturating_sub_usize(pair_start_len),
5532 &pair.start,
5533 ) {
5534 selection.start -= pair_start_len;
5535 selection.end += pair.end.len();
5536
5537 return selection;
5538 }
5539 }
5540 }
5541 }
5542
5543 selection
5544 })
5545 .collect();
5546
5547 drop(buffer);
5548 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5549 selections.select(new_selections)
5550 });
5551 }
5552
5553 /// Iterate the given selections, and for each one, find the smallest surrounding
5554 /// autoclose region. This uses the ordering of the selections and the autoclose
5555 /// regions to avoid repeated comparisons.
5556 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5557 &'a self,
5558 selections: impl IntoIterator<Item = Selection<D>>,
5559 buffer: &'a MultiBufferSnapshot,
5560 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5561 let mut i = 0;
5562 let mut regions = self.autoclose_regions.as_slice();
5563 selections.into_iter().map(move |selection| {
5564 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5565
5566 let mut enclosing = None;
5567 while let Some(pair_state) = regions.get(i) {
5568 if pair_state.range.end.to_offset(buffer) < range.start {
5569 regions = ®ions[i + 1..];
5570 i = 0;
5571 } else if pair_state.range.start.to_offset(buffer) > range.end {
5572 break;
5573 } else {
5574 if pair_state.selection_id == selection.id {
5575 enclosing = Some(pair_state);
5576 }
5577 i += 1;
5578 }
5579 }
5580
5581 (selection, enclosing)
5582 })
5583 }
5584
5585 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5586 fn invalidate_autoclose_regions(
5587 &mut self,
5588 mut selections: &[Selection<Anchor>],
5589 buffer: &MultiBufferSnapshot,
5590 ) {
5591 self.autoclose_regions.retain(|state| {
5592 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5593 return false;
5594 }
5595
5596 let mut i = 0;
5597 while let Some(selection) = selections.get(i) {
5598 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5599 selections = &selections[1..];
5600 continue;
5601 }
5602 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5603 break;
5604 }
5605 if selection.id == state.selection_id {
5606 return true;
5607 } else {
5608 i += 1;
5609 }
5610 }
5611 false
5612 });
5613 }
5614
5615 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5616 let offset = position.to_offset(buffer);
5617 let (word_range, kind) =
5618 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5619 if offset > word_range.start && kind == Some(CharKind::Word) {
5620 Some(
5621 buffer
5622 .text_for_range(word_range.start..offset)
5623 .collect::<String>(),
5624 )
5625 } else {
5626 None
5627 }
5628 }
5629
5630 pub fn visible_excerpts(
5631 &self,
5632 lsp_related_only: bool,
5633 cx: &mut Context<Editor>,
5634 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5635 let project = self.project().cloned();
5636 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5637 let multi_buffer = self.buffer().read(cx);
5638 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5639 let multi_buffer_visible_start = self
5640 .scroll_manager
5641 .native_anchor(&display_snapshot, cx)
5642 .anchor
5643 .to_point(&multi_buffer_snapshot);
5644 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5645 multi_buffer_visible_start
5646 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5647 Bias::Left,
5648 );
5649 multi_buffer_snapshot
5650 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5651 .into_iter()
5652 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5653 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5654 if !lsp_related_only {
5655 return Some((
5656 excerpt_id,
5657 (
5658 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5659 buffer.version().clone(),
5660 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5661 ),
5662 ));
5663 }
5664
5665 let project = project.as_ref()?.read(cx);
5666 let buffer_file = project::File::from_dyn(buffer.file())?;
5667 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5668 let worktree_entry = buffer_worktree
5669 .read(cx)
5670 .entry_for_id(buffer_file.project_entry_id()?)?;
5671 if worktree_entry.is_ignored {
5672 None
5673 } else {
5674 Some((
5675 excerpt_id,
5676 (
5677 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5678 buffer.version().clone(),
5679 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5680 ),
5681 ))
5682 }
5683 })
5684 .collect()
5685 }
5686
5687 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5688 TextLayoutDetails {
5689 text_system: window.text_system().clone(),
5690 editor_style: self.style.clone().unwrap(),
5691 rem_size: window.rem_size(),
5692 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5693 visible_rows: self.visible_line_count(),
5694 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5695 }
5696 }
5697
5698 fn trigger_on_type_formatting(
5699 &self,
5700 input: String,
5701 window: &mut Window,
5702 cx: &mut Context<Self>,
5703 ) -> Option<Task<Result<()>>> {
5704 if input.chars().count() != 1 {
5705 return None;
5706 }
5707
5708 let project = self.project()?;
5709 let position = self.selections.newest_anchor().head();
5710 let (buffer, buffer_position) = self
5711 .buffer
5712 .read(cx)
5713 .text_anchor_for_position(position, cx)?;
5714
5715 let settings = language_settings::language_settings(
5716 buffer
5717 .read(cx)
5718 .language_at(buffer_position)
5719 .map(|l| l.name()),
5720 buffer.read(cx).file(),
5721 cx,
5722 );
5723 if !settings.use_on_type_format {
5724 return None;
5725 }
5726
5727 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5728 // hence we do LSP request & edit on host side only — add formats to host's history.
5729 let push_to_lsp_host_history = true;
5730 // If this is not the host, append its history with new edits.
5731 let push_to_client_history = project.read(cx).is_via_collab();
5732
5733 let on_type_formatting = project.update(cx, |project, cx| {
5734 project.on_type_format(
5735 buffer.clone(),
5736 buffer_position,
5737 input,
5738 push_to_lsp_host_history,
5739 cx,
5740 )
5741 });
5742 Some(cx.spawn_in(window, async move |editor, cx| {
5743 if let Some(transaction) = on_type_formatting.await? {
5744 if push_to_client_history {
5745 buffer.update(cx, |buffer, _| {
5746 buffer.push_transaction(transaction, Instant::now());
5747 buffer.finalize_last_transaction();
5748 });
5749 }
5750 editor.update(cx, |editor, cx| {
5751 editor.refresh_document_highlights(cx);
5752 })?;
5753 }
5754 Ok(())
5755 }))
5756 }
5757
5758 pub fn show_word_completions(
5759 &mut self,
5760 _: &ShowWordCompletions,
5761 window: &mut Window,
5762 cx: &mut Context<Self>,
5763 ) {
5764 self.open_or_update_completions_menu(
5765 Some(CompletionsMenuSource::Words {
5766 ignore_threshold: true,
5767 }),
5768 None,
5769 false,
5770 window,
5771 cx,
5772 );
5773 }
5774
5775 pub fn show_completions(
5776 &mut self,
5777 _: &ShowCompletions,
5778 window: &mut Window,
5779 cx: &mut Context<Self>,
5780 ) {
5781 self.open_or_update_completions_menu(None, None, false, window, cx);
5782 }
5783
5784 fn open_or_update_completions_menu(
5785 &mut self,
5786 requested_source: Option<CompletionsMenuSource>,
5787 trigger: Option<String>,
5788 trigger_in_words: bool,
5789 window: &mut Window,
5790 cx: &mut Context<Self>,
5791 ) {
5792 if self.pending_rename.is_some() {
5793 return;
5794 }
5795
5796 let completions_source = self
5797 .context_menu
5798 .borrow()
5799 .as_ref()
5800 .and_then(|menu| match menu {
5801 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5802 CodeContextMenu::CodeActions(_) => None,
5803 });
5804
5805 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5806
5807 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5808 // inserted and selected. To handle that case, the start of the selection is used so that
5809 // the menu starts with all choices.
5810 let position = self
5811 .selections
5812 .newest_anchor()
5813 .start
5814 .bias_right(&multibuffer_snapshot);
5815 if position.diff_base_anchor.is_some() {
5816 return;
5817 }
5818 let buffer_position = multibuffer_snapshot.anchor_before(position);
5819 let Some(buffer) = buffer_position
5820 .text_anchor
5821 .buffer_id
5822 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5823 else {
5824 return;
5825 };
5826 let buffer_snapshot = buffer.read(cx).snapshot();
5827
5828 let menu_is_open = matches!(
5829 self.context_menu.borrow().as_ref(),
5830 Some(CodeContextMenu::Completions(_))
5831 );
5832
5833 let language = buffer_snapshot
5834 .language_at(buffer_position.text_anchor)
5835 .map(|language| language.name());
5836
5837 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5838 let completion_settings = language_settings.completions.clone();
5839
5840 let show_completions_on_input = self
5841 .show_completions_on_input_override
5842 .unwrap_or(language_settings.show_completions_on_input);
5843 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5844 return;
5845 }
5846
5847 let query: Option<Arc<String>> =
5848 Self::completion_query(&multibuffer_snapshot, buffer_position)
5849 .map(|query| query.into());
5850
5851 drop(multibuffer_snapshot);
5852
5853 // Hide the current completions menu when query is empty. Without this, cached
5854 // completions from before the trigger char may be reused (#32774).
5855 if query.is_none() && menu_is_open {
5856 self.hide_context_menu(window, cx);
5857 }
5858
5859 let mut ignore_word_threshold = false;
5860 let provider = match requested_source {
5861 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5862 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5863 ignore_word_threshold = ignore_threshold;
5864 None
5865 }
5866 Some(CompletionsMenuSource::SnippetChoices)
5867 | Some(CompletionsMenuSource::SnippetsOnly) => {
5868 log::error!("bug: SnippetChoices requested_source is not handled");
5869 None
5870 }
5871 };
5872
5873 let sort_completions = provider
5874 .as_ref()
5875 .is_some_and(|provider| provider.sort_completions());
5876
5877 let filter_completions = provider
5878 .as_ref()
5879 .is_none_or(|provider| provider.filter_completions());
5880
5881 let was_snippets_only = matches!(
5882 completions_source,
5883 Some(CompletionsMenuSource::SnippetsOnly)
5884 );
5885
5886 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5887 if filter_completions {
5888 menu.filter(
5889 query.clone().unwrap_or_default(),
5890 buffer_position.text_anchor,
5891 &buffer,
5892 provider.clone(),
5893 window,
5894 cx,
5895 );
5896 }
5897 // When `is_incomplete` is false, no need to re-query completions when the current query
5898 // is a suffix of the initial query.
5899 let was_complete = !menu.is_incomplete;
5900 if was_complete && !was_snippets_only {
5901 // If the new query is a suffix of the old query (typing more characters) and
5902 // the previous result was complete, the existing completions can be filtered.
5903 //
5904 // Note that snippet completions are always complete.
5905 let query_matches = match (&menu.initial_query, &query) {
5906 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5907 (None, _) => true,
5908 _ => false,
5909 };
5910 if query_matches {
5911 let position_matches = if menu.initial_position == position {
5912 true
5913 } else {
5914 let snapshot = self.buffer.read(cx).read(cx);
5915 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5916 };
5917 if position_matches {
5918 return;
5919 }
5920 }
5921 }
5922 };
5923
5924 let Anchor {
5925 excerpt_id: buffer_excerpt_id,
5926 text_anchor: buffer_position,
5927 ..
5928 } = buffer_position;
5929
5930 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5931 buffer_snapshot.surrounding_word(buffer_position, None)
5932 {
5933 let word_to_exclude = buffer_snapshot
5934 .text_for_range(word_range.clone())
5935 .collect::<String>();
5936 (
5937 buffer_snapshot.anchor_before(word_range.start)
5938 ..buffer_snapshot.anchor_after(buffer_position),
5939 Some(word_to_exclude),
5940 )
5941 } else {
5942 (buffer_position..buffer_position, None)
5943 };
5944
5945 let show_completion_documentation = buffer_snapshot
5946 .settings_at(buffer_position, cx)
5947 .show_completion_documentation;
5948
5949 // The document can be large, so stay in reasonable bounds when searching for words,
5950 // otherwise completion pop-up might be slow to appear.
5951 const WORD_LOOKUP_ROWS: u32 = 5_000;
5952 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5953 let min_word_search = buffer_snapshot.clip_point(
5954 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5955 Bias::Left,
5956 );
5957 let max_word_search = buffer_snapshot.clip_point(
5958 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5959 Bias::Right,
5960 );
5961 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5962 ..buffer_snapshot.point_to_offset(max_word_search);
5963
5964 let skip_digits = query
5965 .as_ref()
5966 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5967
5968 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5969 trigger.as_ref().is_none_or(|trigger| {
5970 provider.is_completion_trigger(
5971 &buffer,
5972 position.text_anchor,
5973 trigger,
5974 trigger_in_words,
5975 cx,
5976 )
5977 })
5978 });
5979
5980 let provider_responses = if let Some(provider) = &provider
5981 && load_provider_completions
5982 {
5983 let trigger_character =
5984 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5985 let completion_context = CompletionContext {
5986 trigger_kind: match &trigger_character {
5987 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5988 None => CompletionTriggerKind::INVOKED,
5989 },
5990 trigger_character,
5991 };
5992
5993 provider.completions(
5994 buffer_excerpt_id,
5995 &buffer,
5996 buffer_position,
5997 completion_context,
5998 window,
5999 cx,
6000 )
6001 } else {
6002 Task::ready(Ok(Vec::new()))
6003 };
6004
6005 let load_word_completions = if !self.word_completions_enabled {
6006 false
6007 } else if requested_source
6008 == Some(CompletionsMenuSource::Words {
6009 ignore_threshold: true,
6010 })
6011 {
6012 true
6013 } else {
6014 load_provider_completions
6015 && completion_settings.words != WordsCompletionMode::Disabled
6016 && (ignore_word_threshold || {
6017 let words_min_length = completion_settings.words_min_length;
6018 // check whether word has at least `words_min_length` characters
6019 let query_chars = query.iter().flat_map(|q| q.chars());
6020 query_chars.take(words_min_length).count() == words_min_length
6021 })
6022 };
6023
6024 let mut words = if load_word_completions {
6025 cx.background_spawn({
6026 let buffer_snapshot = buffer_snapshot.clone();
6027 async move {
6028 buffer_snapshot.words_in_range(WordsQuery {
6029 fuzzy_contents: None,
6030 range: word_search_range,
6031 skip_digits,
6032 })
6033 }
6034 })
6035 } else {
6036 Task::ready(BTreeMap::default())
6037 };
6038
6039 let snippets = if let Some(provider) = &provider
6040 && provider.show_snippets()
6041 && let Some(project) = self.project()
6042 {
6043 let char_classifier = buffer_snapshot
6044 .char_classifier_at(buffer_position)
6045 .scope_context(Some(CharScopeContext::Completion));
6046 project.update(cx, |project, cx| {
6047 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6048 })
6049 } else {
6050 Task::ready(Ok(CompletionResponse {
6051 completions: Vec::new(),
6052 display_options: Default::default(),
6053 is_incomplete: false,
6054 }))
6055 };
6056
6057 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6058
6059 let id = post_inc(&mut self.next_completion_id);
6060 let task = cx.spawn_in(window, async move |editor, cx| {
6061 let Ok(()) = editor.update(cx, |this, _| {
6062 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6063 }) else {
6064 return;
6065 };
6066
6067 // TODO: Ideally completions from different sources would be selectively re-queried, so
6068 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6069 let mut completions = Vec::new();
6070 let mut is_incomplete = false;
6071 let mut display_options: Option<CompletionDisplayOptions> = None;
6072 if let Some(provider_responses) = provider_responses.await.log_err()
6073 && !provider_responses.is_empty()
6074 {
6075 for response in provider_responses {
6076 completions.extend(response.completions);
6077 is_incomplete = is_incomplete || response.is_incomplete;
6078 match display_options.as_mut() {
6079 None => {
6080 display_options = Some(response.display_options);
6081 }
6082 Some(options) => options.merge(&response.display_options),
6083 }
6084 }
6085 if completion_settings.words == WordsCompletionMode::Fallback {
6086 words = Task::ready(BTreeMap::default());
6087 }
6088 }
6089 let display_options = display_options.unwrap_or_default();
6090
6091 let mut words = words.await;
6092 if let Some(word_to_exclude) = &word_to_exclude {
6093 words.remove(word_to_exclude);
6094 }
6095 for lsp_completion in &completions {
6096 words.remove(&lsp_completion.new_text);
6097 }
6098 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6099 replace_range: word_replace_range.clone(),
6100 new_text: word.clone(),
6101 label: CodeLabel::plain(word, None),
6102 match_start: None,
6103 snippet_deduplication_key: None,
6104 icon_path: None,
6105 documentation: None,
6106 source: CompletionSource::BufferWord {
6107 word_range,
6108 resolved: false,
6109 },
6110 insert_text_mode: Some(InsertTextMode::AS_IS),
6111 confirm: None,
6112 }));
6113
6114 completions.extend(
6115 snippets
6116 .await
6117 .into_iter()
6118 .flat_map(|response| response.completions),
6119 );
6120
6121 let menu = if completions.is_empty() {
6122 None
6123 } else {
6124 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6125 let languages = editor
6126 .workspace
6127 .as_ref()
6128 .and_then(|(workspace, _)| workspace.upgrade())
6129 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6130 let menu = CompletionsMenu::new(
6131 id,
6132 requested_source.unwrap_or(if load_provider_completions {
6133 CompletionsMenuSource::Normal
6134 } else {
6135 CompletionsMenuSource::SnippetsOnly
6136 }),
6137 sort_completions,
6138 show_completion_documentation,
6139 position,
6140 query.clone(),
6141 is_incomplete,
6142 buffer.clone(),
6143 completions.into(),
6144 editor
6145 .context_menu()
6146 .borrow_mut()
6147 .as_ref()
6148 .map(|menu| menu.primary_scroll_handle()),
6149 display_options,
6150 snippet_sort_order,
6151 languages,
6152 language,
6153 cx,
6154 );
6155
6156 let query = if filter_completions { query } else { None };
6157 let matches_task = menu.do_async_filtering(
6158 query.unwrap_or_default(),
6159 buffer_position,
6160 &buffer,
6161 cx,
6162 );
6163 (menu, matches_task)
6164 }) else {
6165 return;
6166 };
6167
6168 let matches = matches_task.await;
6169
6170 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6171 // Newer menu already set, so exit.
6172 if let Some(CodeContextMenu::Completions(prev_menu)) =
6173 editor.context_menu.borrow().as_ref()
6174 && prev_menu.id > id
6175 {
6176 return;
6177 };
6178
6179 // Only valid to take prev_menu because either the new menu is immediately set
6180 // below, or the menu is hidden.
6181 if let Some(CodeContextMenu::Completions(prev_menu)) =
6182 editor.context_menu.borrow_mut().take()
6183 {
6184 let position_matches =
6185 if prev_menu.initial_position == menu.initial_position {
6186 true
6187 } else {
6188 let snapshot = editor.buffer.read(cx).read(cx);
6189 prev_menu.initial_position.to_offset(&snapshot)
6190 == menu.initial_position.to_offset(&snapshot)
6191 };
6192 if position_matches {
6193 // Preserve markdown cache before `set_filter_results` because it will
6194 // try to populate the documentation cache.
6195 menu.preserve_markdown_cache(prev_menu);
6196 }
6197 };
6198
6199 menu.set_filter_results(matches, provider, window, cx);
6200 }) else {
6201 return;
6202 };
6203
6204 menu.visible().then_some(menu)
6205 };
6206
6207 editor
6208 .update_in(cx, |editor, window, cx| {
6209 if editor.focus_handle.is_focused(window)
6210 && let Some(menu) = menu
6211 {
6212 *editor.context_menu.borrow_mut() =
6213 Some(CodeContextMenu::Completions(menu));
6214
6215 crate::hover_popover::hide_hover(editor, cx);
6216 if editor.show_edit_predictions_in_menu() {
6217 editor.update_visible_edit_prediction(window, cx);
6218 } else {
6219 editor
6220 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6221 }
6222
6223 cx.notify();
6224 return;
6225 }
6226
6227 if editor.completion_tasks.len() <= 1 {
6228 // If there are no more completion tasks and the last menu was empty, we should hide it.
6229 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6230 // If it was already hidden and we don't show edit predictions in the menu,
6231 // we should also show the edit prediction when available.
6232 if was_hidden && editor.show_edit_predictions_in_menu() {
6233 editor.update_visible_edit_prediction(window, cx);
6234 }
6235 }
6236 })
6237 .ok();
6238 });
6239
6240 self.completion_tasks.push((id, task));
6241 }
6242
6243 #[cfg(feature = "test-support")]
6244 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6245 let menu = self.context_menu.borrow();
6246 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6247 let completions = menu.completions.borrow();
6248 Some(completions.to_vec())
6249 } else {
6250 None
6251 }
6252 }
6253
6254 pub fn with_completions_menu_matching_id<R>(
6255 &self,
6256 id: CompletionId,
6257 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6258 ) -> R {
6259 let mut context_menu = self.context_menu.borrow_mut();
6260 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6261 return f(None);
6262 };
6263 if completions_menu.id != id {
6264 return f(None);
6265 }
6266 f(Some(completions_menu))
6267 }
6268
6269 pub fn confirm_completion(
6270 &mut self,
6271 action: &ConfirmCompletion,
6272 window: &mut Window,
6273 cx: &mut Context<Self>,
6274 ) -> Option<Task<Result<()>>> {
6275 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6276 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6277 }
6278
6279 pub fn confirm_completion_insert(
6280 &mut self,
6281 _: &ConfirmCompletionInsert,
6282 window: &mut Window,
6283 cx: &mut Context<Self>,
6284 ) -> Option<Task<Result<()>>> {
6285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6286 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6287 }
6288
6289 pub fn confirm_completion_replace(
6290 &mut self,
6291 _: &ConfirmCompletionReplace,
6292 window: &mut Window,
6293 cx: &mut Context<Self>,
6294 ) -> Option<Task<Result<()>>> {
6295 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6296 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6297 }
6298
6299 pub fn compose_completion(
6300 &mut self,
6301 action: &ComposeCompletion,
6302 window: &mut Window,
6303 cx: &mut Context<Self>,
6304 ) -> Option<Task<Result<()>>> {
6305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6306 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6307 }
6308
6309 fn do_completion(
6310 &mut self,
6311 item_ix: Option<usize>,
6312 intent: CompletionIntent,
6313 window: &mut Window,
6314 cx: &mut Context<Editor>,
6315 ) -> Option<Task<Result<()>>> {
6316 use language::ToOffset as _;
6317
6318 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6319 else {
6320 return None;
6321 };
6322
6323 let candidate_id = {
6324 let entries = completions_menu.entries.borrow();
6325 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6326 if self.show_edit_predictions_in_menu() {
6327 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6328 }
6329 mat.candidate_id
6330 };
6331
6332 let completion = completions_menu
6333 .completions
6334 .borrow()
6335 .get(candidate_id)?
6336 .clone();
6337 cx.stop_propagation();
6338
6339 let buffer_handle = completions_menu.buffer.clone();
6340
6341 let CompletionEdit {
6342 new_text,
6343 snippet,
6344 replace_range,
6345 } = process_completion_for_edit(
6346 &completion,
6347 intent,
6348 &buffer_handle,
6349 &completions_menu.initial_position.text_anchor,
6350 cx,
6351 );
6352
6353 let buffer = buffer_handle.read(cx);
6354 let snapshot = self.buffer.read(cx).snapshot(cx);
6355 let newest_anchor = self.selections.newest_anchor();
6356 let replace_range_multibuffer = {
6357 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6358 excerpt.map_range_from_buffer(replace_range.clone())
6359 };
6360 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6361 return None;
6362 }
6363
6364 let old_text = buffer
6365 .text_for_range(replace_range.clone())
6366 .collect::<String>();
6367 let lookbehind = newest_anchor
6368 .start
6369 .text_anchor
6370 .to_offset(buffer)
6371 .saturating_sub(replace_range.start.0);
6372 let lookahead = replace_range
6373 .end
6374 .0
6375 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6376 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6377 let suffix = &old_text[lookbehind.min(old_text.len())..];
6378
6379 let selections = self
6380 .selections
6381 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6382 let mut ranges = Vec::new();
6383 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6384
6385 for selection in &selections {
6386 let range = if selection.id == newest_anchor.id {
6387 replace_range_multibuffer.clone()
6388 } else {
6389 let mut range = selection.range();
6390
6391 // if prefix is present, don't duplicate it
6392 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6393 range.start = range.start.saturating_sub_usize(lookbehind);
6394
6395 // if suffix is also present, mimic the newest cursor and replace it
6396 if selection.id != newest_anchor.id
6397 && snapshot.contains_str_at(range.end, suffix)
6398 {
6399 range.end += lookahead;
6400 }
6401 }
6402 range
6403 };
6404
6405 ranges.push(range.clone());
6406
6407 if !self.linked_edit_ranges.is_empty() {
6408 let start_anchor = snapshot.anchor_before(range.start);
6409 let end_anchor = snapshot.anchor_after(range.end);
6410 if let Some(ranges) = self
6411 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6412 {
6413 for (buffer, edits) in ranges {
6414 linked_edits
6415 .entry(buffer.clone())
6416 .or_default()
6417 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6418 }
6419 }
6420 }
6421 }
6422
6423 let common_prefix_len = old_text
6424 .chars()
6425 .zip(new_text.chars())
6426 .take_while(|(a, b)| a == b)
6427 .map(|(a, _)| a.len_utf8())
6428 .sum::<usize>();
6429
6430 cx.emit(EditorEvent::InputHandled {
6431 utf16_range_to_replace: None,
6432 text: new_text[common_prefix_len..].into(),
6433 });
6434
6435 self.transact(window, cx, |editor, window, cx| {
6436 if let Some(mut snippet) = snippet {
6437 snippet.text = new_text.to_string();
6438 editor
6439 .insert_snippet(&ranges, snippet, window, cx)
6440 .log_err();
6441 } else {
6442 editor.buffer.update(cx, |multi_buffer, cx| {
6443 let auto_indent = match completion.insert_text_mode {
6444 Some(InsertTextMode::AS_IS) => None,
6445 _ => editor.autoindent_mode.clone(),
6446 };
6447 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6448 multi_buffer.edit(edits, auto_indent, cx);
6449 });
6450 }
6451 for (buffer, edits) in linked_edits {
6452 buffer.update(cx, |buffer, cx| {
6453 let snapshot = buffer.snapshot();
6454 let edits = edits
6455 .into_iter()
6456 .map(|(range, text)| {
6457 use text::ToPoint as TP;
6458 let end_point = TP::to_point(&range.end, &snapshot);
6459 let start_point = TP::to_point(&range.start, &snapshot);
6460 (start_point..end_point, text)
6461 })
6462 .sorted_by_key(|(range, _)| range.start);
6463 buffer.edit(edits, None, cx);
6464 })
6465 }
6466
6467 editor.refresh_edit_prediction(true, false, window, cx);
6468 });
6469 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6470
6471 let show_new_completions_on_confirm = completion
6472 .confirm
6473 .as_ref()
6474 .is_some_and(|confirm| confirm(intent, window, cx));
6475 if show_new_completions_on_confirm {
6476 self.open_or_update_completions_menu(None, None, false, window, cx);
6477 }
6478
6479 let provider = self.completion_provider.as_ref()?;
6480
6481 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6482 let command = lsp_store.as_ref().and_then(|lsp_store| {
6483 let CompletionSource::Lsp {
6484 lsp_completion,
6485 server_id,
6486 ..
6487 } = &completion.source
6488 else {
6489 return None;
6490 };
6491 let lsp_command = lsp_completion.command.as_ref()?;
6492 let available_commands = lsp_store
6493 .read(cx)
6494 .lsp_server_capabilities
6495 .get(server_id)
6496 .and_then(|server_capabilities| {
6497 server_capabilities
6498 .execute_command_provider
6499 .as_ref()
6500 .map(|options| options.commands.as_slice())
6501 })?;
6502 if available_commands.contains(&lsp_command.command) {
6503 Some(CodeAction {
6504 server_id: *server_id,
6505 range: language::Anchor::MIN..language::Anchor::MIN,
6506 lsp_action: LspAction::Command(lsp_command.clone()),
6507 resolved: false,
6508 })
6509 } else {
6510 None
6511 }
6512 });
6513
6514 drop(completion);
6515 let apply_edits = provider.apply_additional_edits_for_completion(
6516 buffer_handle.clone(),
6517 completions_menu.completions.clone(),
6518 candidate_id,
6519 true,
6520 cx,
6521 );
6522
6523 let editor_settings = EditorSettings::get_global(cx);
6524 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6525 // After the code completion is finished, users often want to know what signatures are needed.
6526 // so we should automatically call signature_help
6527 self.show_signature_help(&ShowSignatureHelp, window, cx);
6528 }
6529
6530 Some(cx.spawn_in(window, async move |editor, cx| {
6531 apply_edits.await?;
6532
6533 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6534 let title = command.lsp_action.title().to_owned();
6535 let project_transaction = lsp_store
6536 .update(cx, |lsp_store, cx| {
6537 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6538 })
6539 .await
6540 .context("applying post-completion command")?;
6541 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6542 Self::open_project_transaction(
6543 &editor,
6544 workspace.downgrade(),
6545 project_transaction,
6546 title,
6547 cx,
6548 )
6549 .await?;
6550 }
6551 }
6552
6553 Ok(())
6554 }))
6555 }
6556
6557 pub fn toggle_code_actions(
6558 &mut self,
6559 action: &ToggleCodeActions,
6560 window: &mut Window,
6561 cx: &mut Context<Self>,
6562 ) {
6563 let quick_launch = action.quick_launch;
6564 let mut context_menu = self.context_menu.borrow_mut();
6565 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6566 if code_actions.deployed_from == action.deployed_from {
6567 // Toggle if we're selecting the same one
6568 *context_menu = None;
6569 cx.notify();
6570 return;
6571 } else {
6572 // Otherwise, clear it and start a new one
6573 *context_menu = None;
6574 cx.notify();
6575 }
6576 }
6577 drop(context_menu);
6578 let snapshot = self.snapshot(window, cx);
6579 let deployed_from = action.deployed_from.clone();
6580 let action = action.clone();
6581 self.completion_tasks.clear();
6582 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6583
6584 let multibuffer_point = match &action.deployed_from {
6585 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6586 DisplayPoint::new(*row, 0).to_point(&snapshot)
6587 }
6588 _ => self
6589 .selections
6590 .newest::<Point>(&snapshot.display_snapshot)
6591 .head(),
6592 };
6593 let Some((buffer, buffer_row)) = snapshot
6594 .buffer_snapshot()
6595 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6596 .and_then(|(buffer_snapshot, range)| {
6597 self.buffer()
6598 .read(cx)
6599 .buffer(buffer_snapshot.remote_id())
6600 .map(|buffer| (buffer, range.start.row))
6601 })
6602 else {
6603 return;
6604 };
6605 let buffer_id = buffer.read(cx).remote_id();
6606 let tasks = self
6607 .tasks
6608 .get(&(buffer_id, buffer_row))
6609 .map(|t| Arc::new(t.to_owned()));
6610
6611 if !self.focus_handle.is_focused(window) {
6612 return;
6613 }
6614 let project = self.project.clone();
6615
6616 let code_actions_task = match deployed_from {
6617 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6618 _ => self.code_actions(buffer_row, window, cx),
6619 };
6620
6621 let runnable_task = match deployed_from {
6622 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6623 _ => {
6624 let mut task_context_task = Task::ready(None);
6625 if let Some(tasks) = &tasks
6626 && let Some(project) = project
6627 {
6628 task_context_task =
6629 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6630 }
6631
6632 cx.spawn_in(window, {
6633 let buffer = buffer.clone();
6634 async move |editor, cx| {
6635 let task_context = task_context_task.await;
6636
6637 let resolved_tasks =
6638 tasks
6639 .zip(task_context.clone())
6640 .map(|(tasks, task_context)| ResolvedTasks {
6641 templates: tasks.resolve(&task_context).collect(),
6642 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6643 multibuffer_point.row,
6644 tasks.column,
6645 )),
6646 });
6647 let debug_scenarios = editor
6648 .update(cx, |editor, cx| {
6649 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6650 })?
6651 .await;
6652 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6653 }
6654 })
6655 }
6656 };
6657
6658 cx.spawn_in(window, async move |editor, cx| {
6659 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6660 let code_actions = code_actions_task.await;
6661 let spawn_straight_away = quick_launch
6662 && resolved_tasks
6663 .as_ref()
6664 .is_some_and(|tasks| tasks.templates.len() == 1)
6665 && code_actions
6666 .as_ref()
6667 .is_none_or(|actions| actions.is_empty())
6668 && debug_scenarios.is_empty();
6669
6670 editor.update_in(cx, |editor, window, cx| {
6671 crate::hover_popover::hide_hover(editor, cx);
6672 let actions = CodeActionContents::new(
6673 resolved_tasks,
6674 code_actions,
6675 debug_scenarios,
6676 task_context.unwrap_or_default(),
6677 );
6678
6679 // Don't show the menu if there are no actions available
6680 if actions.is_empty() {
6681 cx.notify();
6682 return Task::ready(Ok(()));
6683 }
6684
6685 *editor.context_menu.borrow_mut() =
6686 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6687 buffer,
6688 actions,
6689 selected_item: Default::default(),
6690 scroll_handle: UniformListScrollHandle::default(),
6691 deployed_from,
6692 }));
6693 cx.notify();
6694 if spawn_straight_away
6695 && let Some(task) = editor.confirm_code_action(
6696 &ConfirmCodeAction { item_ix: Some(0) },
6697 window,
6698 cx,
6699 )
6700 {
6701 return task;
6702 }
6703
6704 Task::ready(Ok(()))
6705 })
6706 })
6707 .detach_and_log_err(cx);
6708 }
6709
6710 fn debug_scenarios(
6711 &mut self,
6712 resolved_tasks: &Option<ResolvedTasks>,
6713 buffer: &Entity<Buffer>,
6714 cx: &mut App,
6715 ) -> Task<Vec<task::DebugScenario>> {
6716 maybe!({
6717 let project = self.project()?;
6718 let dap_store = project.read(cx).dap_store();
6719 let mut scenarios = vec![];
6720 let resolved_tasks = resolved_tasks.as_ref()?;
6721 let buffer = buffer.read(cx);
6722 let language = buffer.language()?;
6723 let file = buffer.file();
6724 let debug_adapter = language_settings(language.name().into(), file, cx)
6725 .debuggers
6726 .first()
6727 .map(SharedString::from)
6728 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6729
6730 dap_store.update(cx, |dap_store, cx| {
6731 for (_, task) in &resolved_tasks.templates {
6732 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6733 task.original_task().clone(),
6734 debug_adapter.clone().into(),
6735 task.display_label().to_owned().into(),
6736 cx,
6737 );
6738 scenarios.push(maybe_scenario);
6739 }
6740 });
6741 Some(cx.background_spawn(async move {
6742 futures::future::join_all(scenarios)
6743 .await
6744 .into_iter()
6745 .flatten()
6746 .collect::<Vec<_>>()
6747 }))
6748 })
6749 .unwrap_or_else(|| Task::ready(vec![]))
6750 }
6751
6752 fn code_actions(
6753 &mut self,
6754 buffer_row: u32,
6755 window: &mut Window,
6756 cx: &mut Context<Self>,
6757 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6758 let mut task = self.code_actions_task.take();
6759 cx.spawn_in(window, async move |editor, cx| {
6760 while let Some(prev_task) = task {
6761 prev_task.await.log_err();
6762 task = editor
6763 .update(cx, |this, _| this.code_actions_task.take())
6764 .ok()?;
6765 }
6766
6767 editor
6768 .update(cx, |editor, cx| {
6769 editor
6770 .available_code_actions
6771 .clone()
6772 .and_then(|(location, code_actions)| {
6773 let snapshot = location.buffer.read(cx).snapshot();
6774 let point_range = location.range.to_point(&snapshot);
6775 let point_range = point_range.start.row..=point_range.end.row;
6776 if point_range.contains(&buffer_row) {
6777 Some(code_actions)
6778 } else {
6779 None
6780 }
6781 })
6782 })
6783 .ok()
6784 .flatten()
6785 })
6786 }
6787
6788 pub fn confirm_code_action(
6789 &mut self,
6790 action: &ConfirmCodeAction,
6791 window: &mut Window,
6792 cx: &mut Context<Self>,
6793 ) -> Option<Task<Result<()>>> {
6794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6795
6796 let actions_menu =
6797 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6798 menu
6799 } else {
6800 return None;
6801 };
6802
6803 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6804 let action = actions_menu.actions.get(action_ix)?;
6805 let title = action.label();
6806 let buffer = actions_menu.buffer;
6807 let workspace = self.workspace()?;
6808
6809 match action {
6810 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6811 workspace.update(cx, |workspace, cx| {
6812 workspace.schedule_resolved_task(
6813 task_source_kind,
6814 resolved_task,
6815 false,
6816 window,
6817 cx,
6818 );
6819
6820 Some(Task::ready(Ok(())))
6821 })
6822 }
6823 CodeActionsItem::CodeAction {
6824 excerpt_id,
6825 action,
6826 provider,
6827 } => {
6828 let apply_code_action =
6829 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6830 let workspace = workspace.downgrade();
6831 Some(cx.spawn_in(window, async move |editor, cx| {
6832 let project_transaction = apply_code_action.await?;
6833 Self::open_project_transaction(
6834 &editor,
6835 workspace,
6836 project_transaction,
6837 title,
6838 cx,
6839 )
6840 .await
6841 }))
6842 }
6843 CodeActionsItem::DebugScenario(scenario) => {
6844 let context = actions_menu.actions.context.into();
6845
6846 workspace.update(cx, |workspace, cx| {
6847 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6848 workspace.start_debug_session(
6849 scenario,
6850 context,
6851 Some(buffer),
6852 None,
6853 window,
6854 cx,
6855 );
6856 });
6857 Some(Task::ready(Ok(())))
6858 }
6859 }
6860 }
6861
6862 fn open_transaction_for_hidden_buffers(
6863 workspace: Entity<Workspace>,
6864 transaction: ProjectTransaction,
6865 title: String,
6866 window: &mut Window,
6867 cx: &mut Context<Self>,
6868 ) {
6869 if transaction.0.is_empty() {
6870 return;
6871 }
6872
6873 let edited_buffers_already_open = {
6874 let other_editors: Vec<Entity<Editor>> = workspace
6875 .read(cx)
6876 .panes()
6877 .iter()
6878 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6879 .filter(|editor| editor.entity_id() != cx.entity_id())
6880 .collect();
6881
6882 transaction.0.keys().all(|buffer| {
6883 other_editors.iter().any(|editor| {
6884 let multi_buffer = editor.read(cx).buffer();
6885 multi_buffer.read(cx).is_singleton()
6886 && multi_buffer
6887 .read(cx)
6888 .as_singleton()
6889 .map_or(false, |singleton| {
6890 singleton.entity_id() == buffer.entity_id()
6891 })
6892 })
6893 })
6894 };
6895 if !edited_buffers_already_open {
6896 let workspace = workspace.downgrade();
6897 cx.defer_in(window, move |_, window, cx| {
6898 cx.spawn_in(window, async move |editor, cx| {
6899 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6900 .await
6901 .ok()
6902 })
6903 .detach();
6904 });
6905 }
6906 }
6907
6908 pub async fn open_project_transaction(
6909 editor: &WeakEntity<Editor>,
6910 workspace: WeakEntity<Workspace>,
6911 transaction: ProjectTransaction,
6912 title: String,
6913 cx: &mut AsyncWindowContext,
6914 ) -> Result<()> {
6915 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6916 cx.update(|_, cx| {
6917 entries.sort_unstable_by_key(|(buffer, _)| {
6918 buffer.read(cx).file().map(|f| f.path().clone())
6919 });
6920 })?;
6921 if entries.is_empty() {
6922 return Ok(());
6923 }
6924
6925 // If the project transaction's edits are all contained within this editor, then
6926 // avoid opening a new editor to display them.
6927
6928 if let [(buffer, transaction)] = &*entries {
6929 let excerpt = editor.update(cx, |editor, cx| {
6930 editor
6931 .buffer()
6932 .read(cx)
6933 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6934 })?;
6935 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6936 && excerpted_buffer == *buffer
6937 {
6938 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6939 let excerpt_range = excerpt_range.to_offset(buffer);
6940 buffer
6941 .edited_ranges_for_transaction::<usize>(transaction)
6942 .all(|range| {
6943 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6944 })
6945 });
6946
6947 if all_edits_within_excerpt {
6948 return Ok(());
6949 }
6950 }
6951 }
6952
6953 let mut ranges_to_highlight = Vec::new();
6954 let excerpt_buffer = cx.new(|cx| {
6955 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6956 for (buffer_handle, transaction) in &entries {
6957 let edited_ranges = buffer_handle
6958 .read(cx)
6959 .edited_ranges_for_transaction::<Point>(transaction)
6960 .collect::<Vec<_>>();
6961 let (ranges, _) = multibuffer.set_excerpts_for_path(
6962 PathKey::for_buffer(buffer_handle, cx),
6963 buffer_handle.clone(),
6964 edited_ranges,
6965 multibuffer_context_lines(cx),
6966 cx,
6967 );
6968
6969 ranges_to_highlight.extend(ranges);
6970 }
6971 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6972 multibuffer
6973 });
6974
6975 workspace.update_in(cx, |workspace, window, cx| {
6976 let project = workspace.project().clone();
6977 let editor =
6978 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6979 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6980 editor.update(cx, |editor, cx| {
6981 editor.highlight_background(
6982 HighlightKey::Editor,
6983 &ranges_to_highlight,
6984 |_, theme| theme.colors().editor_highlighted_line_background,
6985 cx,
6986 );
6987 });
6988 })?;
6989
6990 Ok(())
6991 }
6992
6993 pub fn clear_code_action_providers(&mut self) {
6994 self.code_action_providers.clear();
6995 self.available_code_actions.take();
6996 }
6997
6998 pub fn add_code_action_provider(
6999 &mut self,
7000 provider: Rc<dyn CodeActionProvider>,
7001 window: &mut Window,
7002 cx: &mut Context<Self>,
7003 ) {
7004 if self
7005 .code_action_providers
7006 .iter()
7007 .any(|existing_provider| existing_provider.id() == provider.id())
7008 {
7009 return;
7010 }
7011
7012 self.code_action_providers.push(provider);
7013 self.refresh_code_actions(window, cx);
7014 }
7015
7016 pub fn remove_code_action_provider(
7017 &mut self,
7018 id: Arc<str>,
7019 window: &mut Window,
7020 cx: &mut Context<Self>,
7021 ) {
7022 self.code_action_providers
7023 .retain(|provider| provider.id() != id);
7024 self.refresh_code_actions(window, cx);
7025 }
7026
7027 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7028 !self.code_action_providers.is_empty()
7029 && EditorSettings::get_global(cx).toolbar.code_actions
7030 }
7031
7032 pub fn has_available_code_actions(&self) -> bool {
7033 self.available_code_actions
7034 .as_ref()
7035 .is_some_and(|(_, actions)| !actions.is_empty())
7036 }
7037
7038 fn render_inline_code_actions(
7039 &self,
7040 icon_size: ui::IconSize,
7041 display_row: DisplayRow,
7042 is_active: bool,
7043 cx: &mut Context<Self>,
7044 ) -> AnyElement {
7045 let show_tooltip = !self.context_menu_visible();
7046 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7047 .icon_size(icon_size)
7048 .shape(ui::IconButtonShape::Square)
7049 .icon_color(ui::Color::Hidden)
7050 .toggle_state(is_active)
7051 .when(show_tooltip, |this| {
7052 this.tooltip({
7053 let focus_handle = self.focus_handle.clone();
7054 move |_window, cx| {
7055 Tooltip::for_action_in(
7056 "Toggle Code Actions",
7057 &ToggleCodeActions {
7058 deployed_from: None,
7059 quick_launch: false,
7060 },
7061 &focus_handle,
7062 cx,
7063 )
7064 }
7065 })
7066 })
7067 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7068 window.focus(&editor.focus_handle(cx), cx);
7069 editor.toggle_code_actions(
7070 &crate::actions::ToggleCodeActions {
7071 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7072 display_row,
7073 )),
7074 quick_launch: false,
7075 },
7076 window,
7077 cx,
7078 );
7079 }))
7080 .into_any_element()
7081 }
7082
7083 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7084 &self.context_menu
7085 }
7086
7087 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7088 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7089 cx.background_executor()
7090 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7091 .await;
7092
7093 let (start_buffer, start, _, end, newest_selection) = this
7094 .update(cx, |this, cx| {
7095 let newest_selection = this.selections.newest_anchor().clone();
7096 if newest_selection.head().diff_base_anchor.is_some() {
7097 return None;
7098 }
7099 let display_snapshot = this.display_snapshot(cx);
7100 let newest_selection_adjusted =
7101 this.selections.newest_adjusted(&display_snapshot);
7102 let buffer = this.buffer.read(cx);
7103
7104 let (start_buffer, start) =
7105 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7106 let (end_buffer, end) =
7107 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7108
7109 Some((start_buffer, start, end_buffer, end, newest_selection))
7110 })?
7111 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7112 .context(
7113 "Expected selection to lie in a single buffer when refreshing code actions",
7114 )?;
7115 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7116 let providers = this.code_action_providers.clone();
7117 let tasks = this
7118 .code_action_providers
7119 .iter()
7120 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7121 .collect::<Vec<_>>();
7122 (providers, tasks)
7123 })?;
7124
7125 let mut actions = Vec::new();
7126 for (provider, provider_actions) in
7127 providers.into_iter().zip(future::join_all(tasks).await)
7128 {
7129 if let Some(provider_actions) = provider_actions.log_err() {
7130 actions.extend(provider_actions.into_iter().map(|action| {
7131 AvailableCodeAction {
7132 excerpt_id: newest_selection.start.excerpt_id,
7133 action,
7134 provider: provider.clone(),
7135 }
7136 }));
7137 }
7138 }
7139
7140 this.update(cx, |this, cx| {
7141 this.available_code_actions = if actions.is_empty() {
7142 None
7143 } else {
7144 Some((
7145 Location {
7146 buffer: start_buffer,
7147 range: start..end,
7148 },
7149 actions.into(),
7150 ))
7151 };
7152 cx.notify();
7153 })
7154 }));
7155 }
7156
7157 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7158 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7159 self.show_git_blame_inline = false;
7160
7161 self.show_git_blame_inline_delay_task =
7162 Some(cx.spawn_in(window, async move |this, cx| {
7163 cx.background_executor().timer(delay).await;
7164
7165 this.update(cx, |this, cx| {
7166 this.show_git_blame_inline = true;
7167 cx.notify();
7168 })
7169 .log_err();
7170 }));
7171 }
7172 }
7173
7174 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7175 let snapshot = self.snapshot(window, cx);
7176 let cursor = self
7177 .selections
7178 .newest::<Point>(&snapshot.display_snapshot)
7179 .head();
7180 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7181 else {
7182 return;
7183 };
7184
7185 if self.blame.is_none() {
7186 self.start_git_blame(true, window, cx);
7187 }
7188 let Some(blame) = self.blame.as_ref() else {
7189 return;
7190 };
7191
7192 let row_info = RowInfo {
7193 buffer_id: Some(buffer.remote_id()),
7194 buffer_row: Some(point.row),
7195 ..Default::default()
7196 };
7197 let Some((buffer, blame_entry)) = blame
7198 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7199 .flatten()
7200 else {
7201 return;
7202 };
7203
7204 let anchor = self.selections.newest_anchor().head();
7205 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7206 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7207 self.show_blame_popover(
7208 buffer,
7209 &blame_entry,
7210 position + last_bounds.origin,
7211 true,
7212 cx,
7213 );
7214 };
7215 }
7216
7217 fn show_blame_popover(
7218 &mut self,
7219 buffer: BufferId,
7220 blame_entry: &BlameEntry,
7221 position: gpui::Point<Pixels>,
7222 ignore_timeout: bool,
7223 cx: &mut Context<Self>,
7224 ) {
7225 if let Some(state) = &mut self.inline_blame_popover {
7226 state.hide_task.take();
7227 } else {
7228 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7229 let blame_entry = blame_entry.clone();
7230 let show_task = cx.spawn(async move |editor, cx| {
7231 if !ignore_timeout {
7232 cx.background_executor()
7233 .timer(std::time::Duration::from_millis(blame_popover_delay))
7234 .await;
7235 }
7236 editor
7237 .update(cx, |editor, cx| {
7238 editor.inline_blame_popover_show_task.take();
7239 let Some(blame) = editor.blame.as_ref() else {
7240 return;
7241 };
7242 let blame = blame.read(cx);
7243 let details = blame.details_for_entry(buffer, &blame_entry);
7244 let markdown = cx.new(|cx| {
7245 Markdown::new(
7246 details
7247 .as_ref()
7248 .map(|message| message.message.clone())
7249 .unwrap_or_default(),
7250 None,
7251 None,
7252 cx,
7253 )
7254 });
7255 editor.inline_blame_popover = Some(InlineBlamePopover {
7256 position,
7257 hide_task: None,
7258 popover_bounds: None,
7259 popover_state: InlineBlamePopoverState {
7260 scroll_handle: ScrollHandle::new(),
7261 commit_message: details,
7262 markdown,
7263 },
7264 keyboard_grace: ignore_timeout,
7265 });
7266 cx.notify();
7267 })
7268 .ok();
7269 });
7270 self.inline_blame_popover_show_task = Some(show_task);
7271 }
7272 }
7273
7274 pub fn has_mouse_context_menu(&self) -> bool {
7275 self.mouse_context_menu.is_some()
7276 }
7277
7278 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7279 self.inline_blame_popover_show_task.take();
7280 if let Some(state) = &mut self.inline_blame_popover {
7281 let hide_task = cx.spawn(async move |editor, cx| {
7282 if !ignore_timeout {
7283 cx.background_executor()
7284 .timer(std::time::Duration::from_millis(100))
7285 .await;
7286 }
7287 editor
7288 .update(cx, |editor, cx| {
7289 editor.inline_blame_popover.take();
7290 cx.notify();
7291 })
7292 .ok();
7293 });
7294 state.hide_task = Some(hide_task);
7295 true
7296 } else {
7297 false
7298 }
7299 }
7300
7301 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7302 if self.pending_rename.is_some() {
7303 return None;
7304 }
7305
7306 let provider = self.semantics_provider.clone()?;
7307 let buffer = self.buffer.read(cx);
7308 let newest_selection = self.selections.newest_anchor().clone();
7309 let cursor_position = newest_selection.head();
7310 let (cursor_buffer, cursor_buffer_position) =
7311 buffer.text_anchor_for_position(cursor_position, cx)?;
7312 let (tail_buffer, tail_buffer_position) =
7313 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7314 if cursor_buffer != tail_buffer {
7315 return None;
7316 }
7317
7318 let snapshot = cursor_buffer.read(cx).snapshot();
7319 let word_ranges = cx.background_spawn(async move {
7320 // this might look odd to put on the background thread, but
7321 // `surrounding_word` can be quite expensive as it calls into
7322 // tree-sitter language scopes
7323 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7324 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7325 (start_word_range, end_word_range)
7326 });
7327
7328 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7329 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7330 let (start_word_range, end_word_range) = word_ranges.await;
7331 if start_word_range != end_word_range {
7332 this.update(cx, |this, cx| {
7333 this.document_highlights_task.take();
7334 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7335 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7336 })
7337 .ok();
7338 return;
7339 }
7340 cx.background_executor()
7341 .timer(Duration::from_millis(debounce))
7342 .await;
7343
7344 let highlights = if let Some(highlights) = cx.update(|cx| {
7345 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7346 }) {
7347 highlights.await.log_err()
7348 } else {
7349 None
7350 };
7351
7352 if let Some(highlights) = highlights {
7353 this.update(cx, |this, cx| {
7354 if this.pending_rename.is_some() {
7355 return;
7356 }
7357
7358 let buffer = this.buffer.read(cx);
7359 if buffer
7360 .text_anchor_for_position(cursor_position, cx)
7361 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7362 {
7363 return;
7364 }
7365
7366 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7367 let mut write_ranges = Vec::new();
7368 let mut read_ranges = Vec::new();
7369 for highlight in highlights {
7370 let buffer_id = cursor_buffer.read(cx).remote_id();
7371 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7372 {
7373 let start = highlight
7374 .range
7375 .start
7376 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7377 let end = highlight
7378 .range
7379 .end
7380 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7381 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7382 continue;
7383 }
7384
7385 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7386 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7387 write_ranges.push(range);
7388 } else {
7389 read_ranges.push(range);
7390 }
7391 }
7392 }
7393
7394 this.highlight_background(
7395 HighlightKey::DocumentHighlightRead,
7396 &read_ranges,
7397 |_, theme| theme.colors().editor_document_highlight_read_background,
7398 cx,
7399 );
7400 this.highlight_background(
7401 HighlightKey::DocumentHighlightWrite,
7402 &write_ranges,
7403 |_, theme| theme.colors().editor_document_highlight_write_background,
7404 cx,
7405 );
7406 cx.notify();
7407 })
7408 .log_err();
7409 }
7410 }));
7411 None
7412 }
7413
7414 fn prepare_highlight_query_from_selection(
7415 &mut self,
7416 window: &Window,
7417 cx: &mut Context<Editor>,
7418 ) -> Option<(String, Range<Anchor>)> {
7419 if matches!(self.mode, EditorMode::SingleLine) {
7420 return None;
7421 }
7422 if !EditorSettings::get_global(cx).selection_highlight {
7423 return None;
7424 }
7425 if self.selections.count() != 1 || self.selections.line_mode() {
7426 return None;
7427 }
7428 let snapshot = self.snapshot(window, cx);
7429 let selection = self.selections.newest::<Point>(&snapshot);
7430 // If the selection spans multiple rows OR it is empty
7431 if selection.start.row != selection.end.row
7432 || selection.start.column == selection.end.column
7433 {
7434 return None;
7435 }
7436 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7437 let query = snapshot
7438 .buffer_snapshot()
7439 .text_for_range(selection_anchor_range.clone())
7440 .collect::<String>();
7441 if query.trim().is_empty() {
7442 return None;
7443 }
7444 Some((query, selection_anchor_range))
7445 }
7446
7447 #[ztracing::instrument(skip_all)]
7448 fn update_selection_occurrence_highlights(
7449 &mut self,
7450 query_text: String,
7451 query_range: Range<Anchor>,
7452 multi_buffer_range_to_query: Range<Point>,
7453 use_debounce: bool,
7454 window: &mut Window,
7455 cx: &mut Context<Editor>,
7456 ) -> Task<()> {
7457 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7458 cx.spawn_in(window, async move |editor, cx| {
7459 if use_debounce {
7460 cx.background_executor()
7461 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7462 .await;
7463 }
7464 let match_task = cx.background_spawn(async move {
7465 let buffer_ranges = multi_buffer_snapshot
7466 .range_to_buffer_ranges(
7467 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7468 )
7469 .into_iter()
7470 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7471 let mut match_ranges = Vec::new();
7472 let Ok(regex) = project::search::SearchQuery::text(
7473 query_text.clone(),
7474 false,
7475 false,
7476 false,
7477 Default::default(),
7478 Default::default(),
7479 false,
7480 None,
7481 ) else {
7482 return Vec::default();
7483 };
7484 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7485 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7486 match_ranges.extend(
7487 regex
7488 .search(
7489 buffer_snapshot,
7490 Some(search_range.start.0..search_range.end.0),
7491 )
7492 .await
7493 .into_iter()
7494 .filter_map(|match_range| {
7495 let match_start = buffer_snapshot
7496 .anchor_after(search_range.start + match_range.start);
7497 let match_end = buffer_snapshot
7498 .anchor_before(search_range.start + match_range.end);
7499 let match_anchor_range =
7500 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7501 (match_anchor_range != query_range).then_some(match_anchor_range)
7502 }),
7503 );
7504 }
7505 match_ranges
7506 });
7507 let match_ranges = match_task.await;
7508 editor
7509 .update_in(cx, |editor, _, cx| {
7510 if use_debounce {
7511 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7512 editor.debounced_selection_highlight_complete = true;
7513 } else if editor.debounced_selection_highlight_complete {
7514 return;
7515 }
7516 if !match_ranges.is_empty() {
7517 editor.highlight_background(
7518 HighlightKey::SelectedTextHighlight,
7519 &match_ranges,
7520 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7521 cx,
7522 )
7523 }
7524 })
7525 .log_err();
7526 })
7527 }
7528
7529 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7530 struct NewlineFold;
7531 let type_id = std::any::TypeId::of::<NewlineFold>();
7532 if !self.mode.is_single_line() {
7533 return;
7534 }
7535 let snapshot = self.snapshot(window, cx);
7536 if snapshot.buffer_snapshot().max_point().row == 0 {
7537 return;
7538 }
7539 let task = cx.background_spawn(async move {
7540 let new_newlines = snapshot
7541 .buffer_chars_at(MultiBufferOffset(0))
7542 .filter_map(|(c, i)| {
7543 if c == '\n' {
7544 Some(
7545 snapshot.buffer_snapshot().anchor_after(i)
7546 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7547 )
7548 } else {
7549 None
7550 }
7551 })
7552 .collect::<Vec<_>>();
7553 let existing_newlines = snapshot
7554 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7555 .filter_map(|fold| {
7556 if fold.placeholder.type_tag == Some(type_id) {
7557 Some(fold.range.start..fold.range.end)
7558 } else {
7559 None
7560 }
7561 })
7562 .collect::<Vec<_>>();
7563
7564 (new_newlines, existing_newlines)
7565 });
7566 self.folding_newlines = cx.spawn(async move |this, cx| {
7567 let (new_newlines, existing_newlines) = task.await;
7568 if new_newlines == existing_newlines {
7569 return;
7570 }
7571 let placeholder = FoldPlaceholder {
7572 render: Arc::new(move |_, _, cx| {
7573 div()
7574 .bg(cx.theme().status().hint_background)
7575 .border_b_1()
7576 .size_full()
7577 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7578 .border_color(cx.theme().status().hint)
7579 .child("\\n")
7580 .into_any()
7581 }),
7582 constrain_width: false,
7583 merge_adjacent: false,
7584 type_tag: Some(type_id),
7585 collapsed_text: None,
7586 };
7587 let creases = new_newlines
7588 .into_iter()
7589 .map(|range| Crease::simple(range, placeholder.clone()))
7590 .collect();
7591 this.update(cx, |this, cx| {
7592 this.display_map.update(cx, |display_map, cx| {
7593 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7594 display_map.fold(creases, cx);
7595 });
7596 })
7597 .ok();
7598 });
7599 }
7600
7601 #[ztracing::instrument(skip_all)]
7602 fn refresh_outline_symbols(&mut self, cx: &mut Context<Editor>) {
7603 if !self.mode.is_full() {
7604 return;
7605 }
7606 let cursor = self.selections.newest_anchor().head();
7607 let multibuffer = self.buffer().read(cx).snapshot(cx);
7608 let syntax = cx.theme().syntax().clone();
7609 let background_task = cx
7610 .background_spawn(async move { multibuffer.symbols_containing(cursor, Some(&syntax)) });
7611 self.refresh_outline_symbols_task = cx.spawn(async move |this, cx| {
7612 let symbols = background_task.await;
7613 this.update(cx, |this, cx| {
7614 this.outline_symbols = symbols;
7615 cx.notify();
7616 })
7617 .ok();
7618 });
7619 }
7620
7621 #[ztracing::instrument(skip_all)]
7622 fn refresh_selected_text_highlights(
7623 &mut self,
7624 on_buffer_edit: bool,
7625 window: &mut Window,
7626 cx: &mut Context<Editor>,
7627 ) {
7628 let Some((query_text, query_range)) =
7629 self.prepare_highlight_query_from_selection(window, cx)
7630 else {
7631 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7632 self.quick_selection_highlight_task.take();
7633 self.debounced_selection_highlight_task.take();
7634 self.debounced_selection_highlight_complete = false;
7635 return;
7636 };
7637 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7638 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7639 let query_changed = self
7640 .quick_selection_highlight_task
7641 .as_ref()
7642 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7643 if query_changed {
7644 self.debounced_selection_highlight_complete = false;
7645 }
7646 if on_buffer_edit || query_changed {
7647 let multi_buffer_visible_start = self
7648 .scroll_manager
7649 .native_anchor(&display_snapshot, cx)
7650 .anchor
7651 .to_point(&multi_buffer_snapshot);
7652 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7653 multi_buffer_visible_start
7654 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7655 Bias::Left,
7656 );
7657 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7658 self.quick_selection_highlight_task = Some((
7659 query_range.clone(),
7660 self.update_selection_occurrence_highlights(
7661 query_text.clone(),
7662 query_range.clone(),
7663 multi_buffer_visible_range,
7664 false,
7665 window,
7666 cx,
7667 ),
7668 ));
7669 }
7670 if on_buffer_edit
7671 || self
7672 .debounced_selection_highlight_task
7673 .as_ref()
7674 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7675 {
7676 let multi_buffer_start = multi_buffer_snapshot
7677 .anchor_before(MultiBufferOffset(0))
7678 .to_point(&multi_buffer_snapshot);
7679 let multi_buffer_end = multi_buffer_snapshot
7680 .anchor_after(multi_buffer_snapshot.len())
7681 .to_point(&multi_buffer_snapshot);
7682 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7683 self.debounced_selection_highlight_task = Some((
7684 query_range.clone(),
7685 self.update_selection_occurrence_highlights(
7686 query_text,
7687 query_range,
7688 multi_buffer_full_range,
7689 true,
7690 window,
7691 cx,
7692 ),
7693 ));
7694 }
7695 }
7696
7697 pub fn refresh_edit_prediction(
7698 &mut self,
7699 debounce: bool,
7700 user_requested: bool,
7701 window: &mut Window,
7702 cx: &mut Context<Self>,
7703 ) -> Option<()> {
7704 if DisableAiSettings::get_global(cx).disable_ai {
7705 return None;
7706 }
7707
7708 let provider = self.edit_prediction_provider()?;
7709 let cursor = self.selections.newest_anchor().head();
7710 let (buffer, cursor_buffer_position) =
7711 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7712
7713 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7714 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7715 return None;
7716 }
7717
7718 self.update_visible_edit_prediction(window, cx);
7719
7720 if !user_requested
7721 && (!self.should_show_edit_predictions()
7722 || !self.is_focused(window)
7723 || buffer.read(cx).is_empty())
7724 {
7725 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7726 return None;
7727 }
7728
7729 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7730 Some(())
7731 }
7732
7733 fn show_edit_predictions_in_menu(&self) -> bool {
7734 match self.edit_prediction_settings {
7735 EditPredictionSettings::Disabled => false,
7736 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7737 }
7738 }
7739
7740 pub fn edit_predictions_enabled(&self) -> bool {
7741 match self.edit_prediction_settings {
7742 EditPredictionSettings::Disabled => false,
7743 EditPredictionSettings::Enabled { .. } => true,
7744 }
7745 }
7746
7747 fn edit_prediction_requires_modifier(&self) -> bool {
7748 match self.edit_prediction_settings {
7749 EditPredictionSettings::Disabled => false,
7750 EditPredictionSettings::Enabled {
7751 preview_requires_modifier,
7752 ..
7753 } => preview_requires_modifier,
7754 }
7755 }
7756
7757 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7758 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7759 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7760 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7761 } else {
7762 let selection = self.selections.newest_anchor();
7763 let cursor = selection.head();
7764
7765 if let Some((buffer, cursor_buffer_position)) =
7766 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7767 {
7768 self.edit_prediction_settings =
7769 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7770 }
7771 }
7772 }
7773
7774 fn edit_prediction_settings_at_position(
7775 &self,
7776 buffer: &Entity<Buffer>,
7777 buffer_position: language::Anchor,
7778 cx: &App,
7779 ) -> EditPredictionSettings {
7780 if !self.mode.is_full()
7781 || !self.show_edit_predictions_override.unwrap_or(true)
7782 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7783 {
7784 return EditPredictionSettings::Disabled;
7785 }
7786
7787 let buffer = buffer.read(cx);
7788
7789 let file = buffer.file();
7790
7791 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7792 return EditPredictionSettings::Disabled;
7793 };
7794
7795 let by_provider = matches!(
7796 self.menu_edit_predictions_policy,
7797 MenuEditPredictionsPolicy::ByProvider
7798 );
7799
7800 let show_in_menu = by_provider
7801 && self
7802 .edit_prediction_provider
7803 .as_ref()
7804 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7805
7806 let preview_requires_modifier =
7807 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7808
7809 EditPredictionSettings::Enabled {
7810 show_in_menu,
7811 preview_requires_modifier,
7812 }
7813 }
7814
7815 fn should_show_edit_predictions(&self) -> bool {
7816 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7817 }
7818
7819 pub fn edit_prediction_preview_is_active(&self) -> bool {
7820 matches!(
7821 self.edit_prediction_preview,
7822 EditPredictionPreview::Active { .. }
7823 )
7824 }
7825
7826 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7827 let cursor = self.selections.newest_anchor().head();
7828 if let Some((buffer, cursor_position)) =
7829 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7830 {
7831 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7832 } else {
7833 false
7834 }
7835 }
7836
7837 pub fn supports_minimap(&self, cx: &App) -> bool {
7838 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7839 }
7840
7841 fn edit_predictions_enabled_in_buffer(
7842 &self,
7843 buffer: &Entity<Buffer>,
7844 buffer_position: language::Anchor,
7845 cx: &App,
7846 ) -> bool {
7847 maybe!({
7848 if self.read_only(cx) {
7849 return Some(false);
7850 }
7851 let provider = self.edit_prediction_provider()?;
7852 if !provider.is_enabled(buffer, buffer_position, cx) {
7853 return Some(false);
7854 }
7855 let buffer = buffer.read(cx);
7856 let Some(file) = buffer.file() else {
7857 return Some(true);
7858 };
7859 let settings = all_language_settings(Some(file), cx);
7860 Some(settings.edit_predictions_enabled_for_file(file, cx))
7861 })
7862 .unwrap_or(false)
7863 }
7864
7865 pub fn show_edit_prediction(
7866 &mut self,
7867 _: &ShowEditPrediction,
7868 window: &mut Window,
7869 cx: &mut Context<Self>,
7870 ) {
7871 if !self.has_active_edit_prediction() {
7872 self.refresh_edit_prediction(false, true, window, cx);
7873 return;
7874 }
7875
7876 self.update_visible_edit_prediction(window, cx);
7877 }
7878
7879 pub fn display_cursor_names(
7880 &mut self,
7881 _: &DisplayCursorNames,
7882 window: &mut Window,
7883 cx: &mut Context<Self>,
7884 ) {
7885 self.show_cursor_names(window, cx);
7886 }
7887
7888 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7889 self.show_cursor_names = true;
7890 cx.notify();
7891 cx.spawn_in(window, async move |this, cx| {
7892 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7893 this.update(cx, |this, cx| {
7894 this.show_cursor_names = false;
7895 cx.notify()
7896 })
7897 .ok()
7898 })
7899 .detach();
7900 }
7901
7902 pub fn accept_partial_edit_prediction(
7903 &mut self,
7904 granularity: EditPredictionGranularity,
7905 window: &mut Window,
7906 cx: &mut Context<Self>,
7907 ) {
7908 if self.show_edit_predictions_in_menu() {
7909 self.hide_context_menu(window, cx);
7910 }
7911
7912 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7913 return;
7914 };
7915
7916 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7917 return;
7918 }
7919
7920 match &active_edit_prediction.completion {
7921 EditPrediction::MoveWithin { target, .. } => {
7922 let target = *target;
7923
7924 if matches!(granularity, EditPredictionGranularity::Full) {
7925 if let Some(position_map) = &self.last_position_map {
7926 let target_row = target.to_display_point(&position_map.snapshot).row();
7927 let is_visible = position_map.visible_row_range.contains(&target_row);
7928
7929 if is_visible || !self.edit_prediction_requires_modifier() {
7930 self.unfold_ranges(&[target..target], true, false, cx);
7931 self.change_selections(
7932 SelectionEffects::scroll(Autoscroll::newest()),
7933 window,
7934 cx,
7935 |selections| {
7936 selections.select_anchor_ranges([target..target]);
7937 },
7938 );
7939 self.clear_row_highlights::<EditPredictionPreview>();
7940 self.edit_prediction_preview
7941 .set_previous_scroll_position(None);
7942 } else {
7943 // Highlight and request scroll
7944 self.edit_prediction_preview
7945 .set_previous_scroll_position(Some(
7946 position_map.snapshot.scroll_anchor,
7947 ));
7948 self.highlight_rows::<EditPredictionPreview>(
7949 target..target,
7950 cx.theme().colors().editor_highlighted_line_background,
7951 RowHighlightOptions {
7952 autoscroll: true,
7953 ..Default::default()
7954 },
7955 cx,
7956 );
7957 self.request_autoscroll(Autoscroll::fit(), cx);
7958 }
7959 }
7960 } else {
7961 self.change_selections(
7962 SelectionEffects::scroll(Autoscroll::newest()),
7963 window,
7964 cx,
7965 |selections| {
7966 selections.select_anchor_ranges([target..target]);
7967 },
7968 );
7969 }
7970 }
7971 EditPrediction::MoveOutside { snapshot, target } => {
7972 if let Some(workspace) = self.workspace() {
7973 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7974 .detach_and_log_err(cx);
7975 }
7976 }
7977 EditPrediction::Edit {
7978 edits,
7979 cursor_position,
7980 ..
7981 } => {
7982 self.report_edit_prediction_event(
7983 active_edit_prediction.completion_id.clone(),
7984 true,
7985 cx,
7986 );
7987
7988 match granularity {
7989 EditPredictionGranularity::Full => {
7990 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7991
7992 // Compute fallback cursor position BEFORE applying the edit,
7993 // so the anchor tracks through the edit correctly
7994 let fallback_cursor_target = {
7995 let snapshot = self.buffer.read(cx).snapshot(cx);
7996 edits.last().unwrap().0.end.bias_right(&snapshot)
7997 };
7998
7999 self.buffer.update(cx, |buffer, cx| {
8000 buffer.edit(edits.iter().cloned(), None, cx)
8001 });
8002
8003 if let Some(provider) = self.edit_prediction_provider() {
8004 provider.accept(cx);
8005 }
8006
8007 // Resolve cursor position after the edit is applied
8008 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8009 // The anchor tracks through the edit, then we add the offset
8010 let snapshot = self.buffer.read(cx).snapshot(cx);
8011 let base_offset = anchor.to_offset(&snapshot).0;
8012 let target_offset =
8013 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8014 snapshot.anchor_after(target_offset)
8015 } else {
8016 fallback_cursor_target
8017 };
8018
8019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8020 s.select_anchor_ranges([cursor_target..cursor_target]);
8021 });
8022
8023 let selections = self.selections.disjoint_anchors_arc();
8024 if let Some(transaction_id_now) =
8025 self.buffer.read(cx).last_transaction_id(cx)
8026 {
8027 if transaction_id_prev != Some(transaction_id_now) {
8028 self.selection_history
8029 .insert_transaction(transaction_id_now, selections);
8030 }
8031 }
8032
8033 self.update_visible_edit_prediction(window, cx);
8034 if self.active_edit_prediction.is_none() {
8035 self.refresh_edit_prediction(true, true, window, cx);
8036 }
8037 cx.notify();
8038 }
8039 _ => {
8040 let snapshot = self.buffer.read(cx).snapshot(cx);
8041 let cursor_offset = self
8042 .selections
8043 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8044 .head();
8045
8046 let insertion = edits.iter().find_map(|(range, text)| {
8047 let range = range.to_offset(&snapshot);
8048 if range.is_empty() && range.start == cursor_offset {
8049 Some(text)
8050 } else {
8051 None
8052 }
8053 });
8054
8055 if let Some(text) = insertion {
8056 let text_to_insert = match granularity {
8057 EditPredictionGranularity::Word => {
8058 let mut partial = text
8059 .chars()
8060 .by_ref()
8061 .take_while(|c| c.is_alphabetic())
8062 .collect::<String>();
8063 if partial.is_empty() {
8064 partial = text
8065 .chars()
8066 .by_ref()
8067 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8068 .collect::<String>();
8069 }
8070 partial
8071 }
8072 EditPredictionGranularity::Line => {
8073 if let Some(line) = text.split_inclusive('\n').next() {
8074 line.to_string()
8075 } else {
8076 text.to_string()
8077 }
8078 }
8079 EditPredictionGranularity::Full => unreachable!(),
8080 };
8081
8082 cx.emit(EditorEvent::InputHandled {
8083 utf16_range_to_replace: None,
8084 text: text_to_insert.clone().into(),
8085 });
8086
8087 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
8088 self.refresh_edit_prediction(true, true, window, cx);
8089 cx.notify();
8090 } else {
8091 self.accept_partial_edit_prediction(
8092 EditPredictionGranularity::Full,
8093 window,
8094 cx,
8095 );
8096 }
8097 }
8098 }
8099 }
8100 }
8101
8102 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8103 }
8104
8105 pub fn accept_next_word_edit_prediction(
8106 &mut self,
8107 _: &AcceptNextWordEditPrediction,
8108 window: &mut Window,
8109 cx: &mut Context<Self>,
8110 ) {
8111 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8112 }
8113
8114 pub fn accept_next_line_edit_prediction(
8115 &mut self,
8116 _: &AcceptNextLineEditPrediction,
8117 window: &mut Window,
8118 cx: &mut Context<Self>,
8119 ) {
8120 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8121 }
8122
8123 pub fn accept_edit_prediction(
8124 &mut self,
8125 _: &AcceptEditPrediction,
8126 window: &mut Window,
8127 cx: &mut Context<Self>,
8128 ) {
8129 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8130 }
8131
8132 fn discard_edit_prediction(
8133 &mut self,
8134 reason: EditPredictionDiscardReason,
8135 cx: &mut Context<Self>,
8136 ) -> bool {
8137 if reason == EditPredictionDiscardReason::Rejected {
8138 let completion_id = self
8139 .active_edit_prediction
8140 .as_ref()
8141 .and_then(|active_completion| active_completion.completion_id.clone());
8142
8143 self.report_edit_prediction_event(completion_id, false, cx);
8144 }
8145
8146 if let Some(provider) = self.edit_prediction_provider() {
8147 provider.discard(reason, cx);
8148 }
8149
8150 self.take_active_edit_prediction(cx)
8151 }
8152
8153 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8154 let Some(provider) = self.edit_prediction_provider() else {
8155 return;
8156 };
8157
8158 let Some((_, buffer, _)) = self
8159 .buffer
8160 .read(cx)
8161 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8162 else {
8163 return;
8164 };
8165
8166 let extension = buffer
8167 .read(cx)
8168 .file()
8169 .and_then(|file| Some(file.path().extension()?.to_string()));
8170
8171 let event_type = match accepted {
8172 true => "Edit Prediction Accepted",
8173 false => "Edit Prediction Discarded",
8174 };
8175 telemetry::event!(
8176 event_type,
8177 provider = provider.name(),
8178 prediction_id = id,
8179 suggestion_accepted = accepted,
8180 file_extension = extension,
8181 );
8182 }
8183
8184 fn open_editor_at_anchor(
8185 snapshot: &language::BufferSnapshot,
8186 target: language::Anchor,
8187 workspace: &Entity<Workspace>,
8188 window: &mut Window,
8189 cx: &mut App,
8190 ) -> Task<Result<()>> {
8191 workspace.update(cx, |workspace, cx| {
8192 let path = snapshot.file().map(|file| file.full_path(cx));
8193 let Some(path) =
8194 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8195 else {
8196 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8197 };
8198 let target = text::ToPoint::to_point(&target, snapshot);
8199 let item = workspace.open_path(path, None, true, window, cx);
8200 window.spawn(cx, async move |cx| {
8201 let Some(editor) = item.await?.downcast::<Editor>() else {
8202 return Ok(());
8203 };
8204 editor
8205 .update_in(cx, |editor, window, cx| {
8206 editor.go_to_singleton_buffer_point(target, window, cx);
8207 })
8208 .ok();
8209 anyhow::Ok(())
8210 })
8211 })
8212 }
8213
8214 pub fn has_active_edit_prediction(&self) -> bool {
8215 self.active_edit_prediction.is_some()
8216 }
8217
8218 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8219 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8220 return false;
8221 };
8222
8223 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8224 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8225 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8226 true
8227 }
8228
8229 /// Returns true when we're displaying the edit prediction popover below the cursor
8230 /// like we are not previewing and the LSP autocomplete menu is visible
8231 /// or we are in `when_holding_modifier` mode.
8232 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8233 if self.edit_prediction_preview_is_active()
8234 || !self.show_edit_predictions_in_menu()
8235 || !self.edit_predictions_enabled()
8236 {
8237 return false;
8238 }
8239
8240 if self.has_visible_completions_menu() {
8241 return true;
8242 }
8243
8244 has_completion && self.edit_prediction_requires_modifier()
8245 }
8246
8247 fn handle_modifiers_changed(
8248 &mut self,
8249 modifiers: Modifiers,
8250 position_map: &PositionMap,
8251 window: &mut Window,
8252 cx: &mut Context<Self>,
8253 ) {
8254 // Ensure that the edit prediction preview is updated, even when not
8255 // enabled, if there's an active edit prediction preview.
8256 if self.show_edit_predictions_in_menu()
8257 || matches!(
8258 self.edit_prediction_preview,
8259 EditPredictionPreview::Active { .. }
8260 )
8261 {
8262 self.update_edit_prediction_preview(&modifiers, window, cx);
8263 }
8264
8265 self.update_selection_mode(&modifiers, position_map, window, cx);
8266
8267 let mouse_position = window.mouse_position();
8268 if !position_map.text_hitbox.is_hovered(window) {
8269 return;
8270 }
8271
8272 self.update_hovered_link(
8273 position_map.point_for_position(mouse_position),
8274 &position_map.snapshot,
8275 modifiers,
8276 window,
8277 cx,
8278 )
8279 }
8280
8281 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8282 match EditorSettings::get_global(cx).multi_cursor_modifier {
8283 MultiCursorModifier::Alt => modifiers.secondary(),
8284 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8285 }
8286 }
8287
8288 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8289 match EditorSettings::get_global(cx).multi_cursor_modifier {
8290 MultiCursorModifier::Alt => modifiers.alt,
8291 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8292 }
8293 }
8294
8295 fn columnar_selection_mode(
8296 modifiers: &Modifiers,
8297 cx: &mut Context<Self>,
8298 ) -> Option<ColumnarMode> {
8299 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8300 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8301 Some(ColumnarMode::FromMouse)
8302 } else if Self::is_alt_pressed(modifiers, cx) {
8303 Some(ColumnarMode::FromSelection)
8304 } else {
8305 None
8306 }
8307 } else {
8308 None
8309 }
8310 }
8311
8312 fn update_selection_mode(
8313 &mut self,
8314 modifiers: &Modifiers,
8315 position_map: &PositionMap,
8316 window: &mut Window,
8317 cx: &mut Context<Self>,
8318 ) {
8319 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8320 return;
8321 };
8322 if self.selections.pending_anchor().is_none() {
8323 return;
8324 }
8325
8326 let mouse_position = window.mouse_position();
8327 let point_for_position = position_map.point_for_position(mouse_position);
8328 let position = point_for_position.previous_valid;
8329
8330 self.select(
8331 SelectPhase::BeginColumnar {
8332 position,
8333 reset: false,
8334 mode,
8335 goal_column: point_for_position.exact_unclipped.column(),
8336 },
8337 window,
8338 cx,
8339 );
8340 }
8341
8342 fn update_edit_prediction_preview(
8343 &mut self,
8344 modifiers: &Modifiers,
8345 window: &mut Window,
8346 cx: &mut Context<Self>,
8347 ) {
8348 let mut modifiers_held = false;
8349
8350 // Check bindings for all granularities.
8351 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8352 let granularities = [
8353 EditPredictionGranularity::Full,
8354 EditPredictionGranularity::Line,
8355 EditPredictionGranularity::Word,
8356 ];
8357
8358 for granularity in granularities {
8359 if let Some(keystroke) = self
8360 .accept_edit_prediction_keybind(granularity, window, cx)
8361 .keystroke()
8362 {
8363 modifiers_held = modifiers_held
8364 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8365 }
8366 }
8367
8368 if modifiers_held {
8369 if matches!(
8370 self.edit_prediction_preview,
8371 EditPredictionPreview::Inactive { .. }
8372 ) {
8373 self.edit_prediction_preview = EditPredictionPreview::Active {
8374 previous_scroll_position: None,
8375 since: Instant::now(),
8376 };
8377
8378 self.update_visible_edit_prediction(window, cx);
8379 cx.notify();
8380 }
8381 } else if let EditPredictionPreview::Active {
8382 previous_scroll_position,
8383 since,
8384 } = self.edit_prediction_preview
8385 {
8386 if let (Some(previous_scroll_position), Some(position_map)) =
8387 (previous_scroll_position, self.last_position_map.as_ref())
8388 {
8389 self.set_scroll_position(
8390 previous_scroll_position
8391 .scroll_position(&position_map.snapshot.display_snapshot),
8392 window,
8393 cx,
8394 );
8395 }
8396
8397 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8398 released_too_fast: since.elapsed() < Duration::from_millis(200),
8399 };
8400 self.clear_row_highlights::<EditPredictionPreview>();
8401 self.update_visible_edit_prediction(window, cx);
8402 cx.notify();
8403 }
8404 }
8405
8406 fn update_visible_edit_prediction(
8407 &mut self,
8408 _window: &mut Window,
8409 cx: &mut Context<Self>,
8410 ) -> Option<()> {
8411 if DisableAiSettings::get_global(cx).disable_ai {
8412 return None;
8413 }
8414
8415 if self.ime_transaction.is_some() {
8416 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8417 return None;
8418 }
8419
8420 let selection = self.selections.newest_anchor();
8421 let cursor = selection.head();
8422 let multibuffer = self.buffer.read(cx).snapshot(cx);
8423 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8424 let excerpt_id = cursor.excerpt_id;
8425
8426 let show_in_menu = self.show_edit_predictions_in_menu();
8427 let completions_menu_has_precedence = !show_in_menu
8428 && (self.context_menu.borrow().is_some()
8429 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8430
8431 if completions_menu_has_precedence
8432 || !offset_selection.is_empty()
8433 || self
8434 .active_edit_prediction
8435 .as_ref()
8436 .is_some_and(|completion| {
8437 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8438 return false;
8439 };
8440 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8441 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8442 !invalidation_range.contains(&offset_selection.head())
8443 })
8444 {
8445 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8446 return None;
8447 }
8448
8449 self.take_active_edit_prediction(cx);
8450 let Some(provider) = self.edit_prediction_provider() else {
8451 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8452 return None;
8453 };
8454
8455 let (buffer, cursor_buffer_position) =
8456 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8457
8458 self.edit_prediction_settings =
8459 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8460
8461 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8462
8463 if self.edit_prediction_indent_conflict {
8464 let cursor_point = cursor.to_point(&multibuffer);
8465 let mut suggested_indent = None;
8466 multibuffer.suggested_indents_callback(
8467 cursor_point.row..cursor_point.row + 1,
8468 |_, indent| {
8469 suggested_indent = Some(indent);
8470 ControlFlow::Break(())
8471 },
8472 cx,
8473 );
8474
8475 if let Some(indent) = suggested_indent
8476 && indent.len == cursor_point.column
8477 {
8478 self.edit_prediction_indent_conflict = false;
8479 }
8480 }
8481
8482 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8483
8484 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8485 {
8486 edit_prediction_types::EditPrediction::Local {
8487 id,
8488 edits,
8489 cursor_position,
8490 edit_preview,
8491 } => (id, edits, cursor_position, edit_preview),
8492 edit_prediction_types::EditPrediction::Jump {
8493 id,
8494 snapshot,
8495 target,
8496 } => {
8497 if let Some(provider) = &self.edit_prediction_provider {
8498 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8499 }
8500 self.stale_edit_prediction_in_menu = None;
8501 self.active_edit_prediction = Some(EditPredictionState {
8502 inlay_ids: vec![],
8503 completion: EditPrediction::MoveOutside { snapshot, target },
8504 completion_id: id,
8505 invalidation_range: None,
8506 });
8507 cx.notify();
8508 return Some(());
8509 }
8510 };
8511
8512 let edits = edits
8513 .into_iter()
8514 .flat_map(|(range, new_text)| {
8515 Some((
8516 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8517 new_text,
8518 ))
8519 })
8520 .collect::<Vec<_>>();
8521 if edits.is_empty() {
8522 return None;
8523 }
8524
8525 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8526 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8527 Some((anchor, predicted.offset))
8528 });
8529
8530 let first_edit_start = edits.first().unwrap().0.start;
8531 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8532 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8533
8534 let last_edit_end = edits.last().unwrap().0.end;
8535 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8536 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8537
8538 let cursor_row = cursor.to_point(&multibuffer).row;
8539
8540 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8541
8542 let mut inlay_ids = Vec::new();
8543 let invalidation_row_range;
8544 let move_invalidation_row_range = if cursor_row < edit_start_row {
8545 Some(cursor_row..edit_end_row)
8546 } else if cursor_row > edit_end_row {
8547 Some(edit_start_row..cursor_row)
8548 } else {
8549 None
8550 };
8551 let supports_jump = self
8552 .edit_prediction_provider
8553 .as_ref()
8554 .map(|provider| provider.provider.supports_jump_to_edit())
8555 .unwrap_or(true);
8556
8557 let is_move = supports_jump
8558 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8559 let completion = if is_move {
8560 if let Some(provider) = &self.edit_prediction_provider {
8561 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8562 }
8563 invalidation_row_range =
8564 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8565 let target = first_edit_start;
8566 EditPrediction::MoveWithin { target, snapshot }
8567 } else {
8568 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8569 && !self.edit_predictions_hidden_for_vim_mode;
8570
8571 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8572 if provider.show_tab_accept_marker() {
8573 EditDisplayMode::TabAccept
8574 } else {
8575 EditDisplayMode::Inline
8576 }
8577 } else {
8578 EditDisplayMode::DiffPopover
8579 };
8580
8581 if show_completions_in_buffer {
8582 if let Some(provider) = &self.edit_prediction_provider {
8583 let suggestion_display_type = match display_mode {
8584 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8585 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8586 SuggestionDisplayType::GhostText
8587 }
8588 };
8589 provider.provider.did_show(suggestion_display_type, cx);
8590 }
8591 if edits
8592 .iter()
8593 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8594 {
8595 let mut inlays = Vec::new();
8596 for (range, new_text) in &edits {
8597 let inlay = Inlay::edit_prediction(
8598 post_inc(&mut self.next_inlay_id),
8599 range.start,
8600 new_text.as_ref(),
8601 );
8602 inlay_ids.push(inlay.id);
8603 inlays.push(inlay);
8604 }
8605
8606 self.splice_inlays(&[], inlays, cx);
8607 } else {
8608 let background_color = cx.theme().status().deleted_background;
8609 self.highlight_text(
8610 HighlightKey::EditPredictionHighlight,
8611 edits.iter().map(|(range, _)| range.clone()).collect(),
8612 HighlightStyle {
8613 background_color: Some(background_color),
8614 ..Default::default()
8615 },
8616 cx,
8617 );
8618 }
8619 }
8620
8621 invalidation_row_range = edit_start_row..edit_end_row;
8622
8623 EditPrediction::Edit {
8624 edits,
8625 cursor_position,
8626 edit_preview,
8627 display_mode,
8628 snapshot,
8629 }
8630 };
8631
8632 let invalidation_range = multibuffer
8633 .anchor_before(Point::new(invalidation_row_range.start, 0))
8634 ..multibuffer.anchor_after(Point::new(
8635 invalidation_row_range.end,
8636 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8637 ));
8638
8639 self.stale_edit_prediction_in_menu = None;
8640 self.active_edit_prediction = Some(EditPredictionState {
8641 inlay_ids,
8642 completion,
8643 completion_id,
8644 invalidation_range: Some(invalidation_range),
8645 });
8646
8647 cx.notify();
8648
8649 Some(())
8650 }
8651
8652 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8653 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8654 }
8655
8656 fn clear_tasks(&mut self) {
8657 self.tasks.clear()
8658 }
8659
8660 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8661 if self.tasks.insert(key, value).is_some() {
8662 // This case should hopefully be rare, but just in case...
8663 log::error!(
8664 "multiple different run targets found on a single line, only the last target will be rendered"
8665 )
8666 }
8667 }
8668
8669 /// Get all display points of breakpoints that will be rendered within editor
8670 ///
8671 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8672 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8673 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8674 fn active_breakpoints(
8675 &self,
8676 range: Range<DisplayRow>,
8677 window: &mut Window,
8678 cx: &mut Context<Self>,
8679 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8680 let mut breakpoint_display_points = HashMap::default();
8681
8682 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8683 return breakpoint_display_points;
8684 };
8685
8686 let snapshot = self.snapshot(window, cx);
8687
8688 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8689 let Some(project) = self.project() else {
8690 return breakpoint_display_points;
8691 };
8692
8693 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8694 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8695
8696 for (buffer_snapshot, range, excerpt_id) in
8697 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8698 {
8699 let Some(buffer) = project
8700 .read(cx)
8701 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8702 else {
8703 continue;
8704 };
8705 let breakpoints = breakpoint_store.read(cx).breakpoints(
8706 &buffer,
8707 Some(
8708 buffer_snapshot.anchor_before(range.start)
8709 ..buffer_snapshot.anchor_after(range.end),
8710 ),
8711 buffer_snapshot,
8712 cx,
8713 );
8714 for (breakpoint, state) in breakpoints {
8715 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8716 let position = multi_buffer_anchor
8717 .to_point(&multi_buffer_snapshot)
8718 .to_display_point(&snapshot);
8719
8720 breakpoint_display_points.insert(
8721 position.row(),
8722 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8723 );
8724 }
8725 }
8726
8727 breakpoint_display_points
8728 }
8729
8730 fn breakpoint_context_menu(
8731 &self,
8732 anchor: Anchor,
8733 window: &mut Window,
8734 cx: &mut Context<Self>,
8735 ) -> Entity<ui::ContextMenu> {
8736 let weak_editor = cx.weak_entity();
8737 let focus_handle = self.focus_handle(cx);
8738
8739 let row = self
8740 .buffer
8741 .read(cx)
8742 .snapshot(cx)
8743 .summary_for_anchor::<Point>(&anchor)
8744 .row;
8745
8746 let breakpoint = self
8747 .breakpoint_at_row(row, window, cx)
8748 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8749
8750 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8751 "Edit Log Breakpoint"
8752 } else {
8753 "Set Log Breakpoint"
8754 };
8755
8756 let condition_breakpoint_msg = if breakpoint
8757 .as_ref()
8758 .is_some_and(|bp| bp.1.condition.is_some())
8759 {
8760 "Edit Condition Breakpoint"
8761 } else {
8762 "Set Condition Breakpoint"
8763 };
8764
8765 let hit_condition_breakpoint_msg = if breakpoint
8766 .as_ref()
8767 .is_some_and(|bp| bp.1.hit_condition.is_some())
8768 {
8769 "Edit Hit Condition Breakpoint"
8770 } else {
8771 "Set Hit Condition Breakpoint"
8772 };
8773
8774 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8775 "Unset Breakpoint"
8776 } else {
8777 "Set Breakpoint"
8778 };
8779
8780 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8781
8782 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8783 BreakpointState::Enabled => Some("Disable"),
8784 BreakpointState::Disabled => Some("Enable"),
8785 });
8786
8787 let (anchor, breakpoint) =
8788 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8789
8790 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8791 menu.on_blur_subscription(Subscription::new(|| {}))
8792 .context(focus_handle)
8793 .when(run_to_cursor, |this| {
8794 let weak_editor = weak_editor.clone();
8795 this.entry("Run to cursor", None, move |window, cx| {
8796 weak_editor
8797 .update(cx, |editor, cx| {
8798 editor.change_selections(
8799 SelectionEffects::no_scroll(),
8800 window,
8801 cx,
8802 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8803 );
8804 })
8805 .ok();
8806
8807 window.dispatch_action(Box::new(RunToCursor), cx);
8808 })
8809 .separator()
8810 })
8811 .when_some(toggle_state_msg, |this, msg| {
8812 this.entry(msg, None, {
8813 let weak_editor = weak_editor.clone();
8814 let breakpoint = breakpoint.clone();
8815 move |_window, cx| {
8816 weak_editor
8817 .update(cx, |this, cx| {
8818 this.edit_breakpoint_at_anchor(
8819 anchor,
8820 breakpoint.as_ref().clone(),
8821 BreakpointEditAction::InvertState,
8822 cx,
8823 );
8824 })
8825 .log_err();
8826 }
8827 })
8828 })
8829 .entry(set_breakpoint_msg, None, {
8830 let weak_editor = weak_editor.clone();
8831 let breakpoint = breakpoint.clone();
8832 move |_window, cx| {
8833 weak_editor
8834 .update(cx, |this, cx| {
8835 this.edit_breakpoint_at_anchor(
8836 anchor,
8837 breakpoint.as_ref().clone(),
8838 BreakpointEditAction::Toggle,
8839 cx,
8840 );
8841 })
8842 .log_err();
8843 }
8844 })
8845 .entry(log_breakpoint_msg, None, {
8846 let breakpoint = breakpoint.clone();
8847 let weak_editor = weak_editor.clone();
8848 move |window, cx| {
8849 weak_editor
8850 .update(cx, |this, cx| {
8851 this.add_edit_breakpoint_block(
8852 anchor,
8853 breakpoint.as_ref(),
8854 BreakpointPromptEditAction::Log,
8855 window,
8856 cx,
8857 );
8858 })
8859 .log_err();
8860 }
8861 })
8862 .entry(condition_breakpoint_msg, None, {
8863 let breakpoint = breakpoint.clone();
8864 let weak_editor = weak_editor.clone();
8865 move |window, cx| {
8866 weak_editor
8867 .update(cx, |this, cx| {
8868 this.add_edit_breakpoint_block(
8869 anchor,
8870 breakpoint.as_ref(),
8871 BreakpointPromptEditAction::Condition,
8872 window,
8873 cx,
8874 );
8875 })
8876 .log_err();
8877 }
8878 })
8879 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8880 weak_editor
8881 .update(cx, |this, cx| {
8882 this.add_edit_breakpoint_block(
8883 anchor,
8884 breakpoint.as_ref(),
8885 BreakpointPromptEditAction::HitCondition,
8886 window,
8887 cx,
8888 );
8889 })
8890 .log_err();
8891 })
8892 })
8893 }
8894
8895 fn render_breakpoint(
8896 &self,
8897 position: Anchor,
8898 row: DisplayRow,
8899 breakpoint: &Breakpoint,
8900 state: Option<BreakpointSessionState>,
8901 cx: &mut Context<Self>,
8902 ) -> IconButton {
8903 let is_rejected = state.is_some_and(|s| !s.verified);
8904 // Is it a breakpoint that shows up when hovering over gutter?
8905 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8906 (false, false),
8907 |PhantomBreakpointIndicator {
8908 is_active,
8909 display_row,
8910 collides_with_existing_breakpoint,
8911 }| {
8912 (
8913 is_active && display_row == row,
8914 collides_with_existing_breakpoint,
8915 )
8916 },
8917 );
8918
8919 let (color, icon) = {
8920 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8921 (false, false) => ui::IconName::DebugBreakpoint,
8922 (true, false) => ui::IconName::DebugLogBreakpoint,
8923 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8924 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8925 };
8926
8927 let theme_colors = cx.theme().colors();
8928
8929 let color = if is_phantom {
8930 if collides_with_existing {
8931 Color::Custom(
8932 theme_colors
8933 .debugger_accent
8934 .blend(theme_colors.text.opacity(0.6)),
8935 )
8936 } else {
8937 Color::Hint
8938 }
8939 } else if is_rejected {
8940 Color::Disabled
8941 } else {
8942 Color::Debugger
8943 };
8944
8945 (color, icon)
8946 };
8947
8948 let breakpoint = Arc::from(breakpoint.clone());
8949
8950 let alt_as_text = gpui::Keystroke {
8951 modifiers: Modifiers::secondary_key(),
8952 ..Default::default()
8953 };
8954 let primary_action_text = if breakpoint.is_disabled() {
8955 "Enable breakpoint"
8956 } else if is_phantom && !collides_with_existing {
8957 "Set breakpoint"
8958 } else {
8959 "Unset breakpoint"
8960 };
8961 let focus_handle = self.focus_handle.clone();
8962
8963 let meta = if is_rejected {
8964 SharedString::from("No executable code is associated with this line.")
8965 } else if collides_with_existing && !breakpoint.is_disabled() {
8966 SharedString::from(format!(
8967 "{alt_as_text}-click to disable,\nright-click for more options."
8968 ))
8969 } else {
8970 SharedString::from("Right-click for more options.")
8971 };
8972 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8973 .icon_size(IconSize::XSmall)
8974 .size(ui::ButtonSize::None)
8975 .when(is_rejected, |this| {
8976 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8977 })
8978 .icon_color(color)
8979 .style(ButtonStyle::Transparent)
8980 .on_click(cx.listener({
8981 move |editor, event: &ClickEvent, window, cx| {
8982 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8983 BreakpointEditAction::InvertState
8984 } else {
8985 BreakpointEditAction::Toggle
8986 };
8987
8988 window.focus(&editor.focus_handle(cx), cx);
8989 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
8990 editor.edit_breakpoint_at_anchor(
8991 position,
8992 breakpoint.as_ref().clone(),
8993 edit_action,
8994 cx,
8995 );
8996 }
8997 }))
8998 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8999 editor.set_breakpoint_context_menu(
9000 row,
9001 Some(position),
9002 event.position(),
9003 window,
9004 cx,
9005 );
9006 }))
9007 .tooltip(move |_window, cx| {
9008 Tooltip::with_meta_in(
9009 primary_action_text,
9010 Some(&ToggleBreakpoint),
9011 meta.clone(),
9012 &focus_handle,
9013 cx,
9014 )
9015 })
9016 }
9017
9018 fn build_tasks_context(
9019 project: &Entity<Project>,
9020 buffer: &Entity<Buffer>,
9021 buffer_row: u32,
9022 tasks: &Arc<RunnableTasks>,
9023 cx: &mut Context<Self>,
9024 ) -> Task<Option<task::TaskContext>> {
9025 let position = Point::new(buffer_row, tasks.column);
9026 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9027 let location = Location {
9028 buffer: buffer.clone(),
9029 range: range_start..range_start,
9030 };
9031 // Fill in the environmental variables from the tree-sitter captures
9032 let mut captured_task_variables = TaskVariables::default();
9033 for (capture_name, value) in tasks.extra_variables.clone() {
9034 captured_task_variables.insert(
9035 task::VariableName::Custom(capture_name.into()),
9036 value.clone(),
9037 );
9038 }
9039 project.update(cx, |project, cx| {
9040 project.task_store().update(cx, |task_store, cx| {
9041 task_store.task_context_for_location(captured_task_variables, location, cx)
9042 })
9043 })
9044 }
9045
9046 pub fn spawn_nearest_task(
9047 &mut self,
9048 action: &SpawnNearestTask,
9049 window: &mut Window,
9050 cx: &mut Context<Self>,
9051 ) {
9052 let Some((workspace, _)) = self.workspace.clone() else {
9053 return;
9054 };
9055 let Some(project) = self.project.clone() else {
9056 return;
9057 };
9058
9059 // Try to find a closest, enclosing node using tree-sitter that has a task
9060 let Some((buffer, buffer_row, tasks)) = self
9061 .find_enclosing_node_task(cx)
9062 // Or find the task that's closest in row-distance.
9063 .or_else(|| self.find_closest_task(cx))
9064 else {
9065 return;
9066 };
9067
9068 let reveal_strategy = action.reveal;
9069 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9070 cx.spawn_in(window, async move |_, cx| {
9071 let context = task_context.await?;
9072 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9073
9074 let resolved = &mut resolved_task.resolved;
9075 resolved.reveal = reveal_strategy;
9076
9077 workspace
9078 .update_in(cx, |workspace, window, cx| {
9079 workspace.schedule_resolved_task(
9080 task_source_kind,
9081 resolved_task,
9082 false,
9083 window,
9084 cx,
9085 );
9086 })
9087 .ok()
9088 })
9089 .detach();
9090 }
9091
9092 fn find_closest_task(
9093 &mut self,
9094 cx: &mut Context<Self>,
9095 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9096 let cursor_row = self
9097 .selections
9098 .newest_adjusted(&self.display_snapshot(cx))
9099 .head()
9100 .row;
9101
9102 let ((buffer_id, row), tasks) = self
9103 .tasks
9104 .iter()
9105 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9106
9107 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9108 let tasks = Arc::new(tasks.to_owned());
9109 Some((buffer, *row, tasks))
9110 }
9111
9112 fn find_enclosing_node_task(
9113 &mut self,
9114 cx: &mut Context<Self>,
9115 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9116 let snapshot = self.buffer.read(cx).snapshot(cx);
9117 let offset = self
9118 .selections
9119 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9120 .head();
9121 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9122 let offset = excerpt.map_offset_to_buffer(offset);
9123 let buffer_id = excerpt.buffer().remote_id();
9124
9125 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9126 let mut cursor = layer.node().walk();
9127
9128 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9129 if cursor.node().end_byte() == offset.0 {
9130 cursor.goto_next_sibling();
9131 }
9132 }
9133
9134 // Ascend to the smallest ancestor that contains the range and has a task.
9135 loop {
9136 let node = cursor.node();
9137 let node_range = node.byte_range();
9138 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9139
9140 // Check if this node contains our offset
9141 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9142 // If it contains offset, check for task
9143 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9144 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9145 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9146 }
9147 }
9148
9149 if !cursor.goto_parent() {
9150 break;
9151 }
9152 }
9153 None
9154 }
9155
9156 fn render_run_indicator(
9157 &self,
9158 _style: &EditorStyle,
9159 is_active: bool,
9160 row: DisplayRow,
9161 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9162 cx: &mut Context<Self>,
9163 ) -> IconButton {
9164 let color = Color::Muted;
9165 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9166
9167 IconButton::new(
9168 ("run_indicator", row.0 as usize),
9169 ui::IconName::PlayOutlined,
9170 )
9171 .shape(ui::IconButtonShape::Square)
9172 .icon_size(IconSize::XSmall)
9173 .icon_color(color)
9174 .toggle_state(is_active)
9175 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9176 let quick_launch = match e {
9177 ClickEvent::Keyboard(_) => true,
9178 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9179 };
9180
9181 window.focus(&editor.focus_handle(cx), cx);
9182 editor.toggle_code_actions(
9183 &ToggleCodeActions {
9184 deployed_from: Some(CodeActionSource::RunMenu(row)),
9185 quick_launch,
9186 },
9187 window,
9188 cx,
9189 );
9190 }))
9191 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9192 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9193 }))
9194 }
9195
9196 pub fn context_menu_visible(&self) -> bool {
9197 !self.edit_prediction_preview_is_active()
9198 && self
9199 .context_menu
9200 .borrow()
9201 .as_ref()
9202 .is_some_and(|menu| menu.visible())
9203 }
9204
9205 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9206 self.context_menu
9207 .borrow()
9208 .as_ref()
9209 .map(|menu| menu.origin())
9210 }
9211
9212 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9213 self.context_menu_options = Some(options);
9214 }
9215
9216 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9217 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9218
9219 fn render_edit_prediction_popover(
9220 &mut self,
9221 text_bounds: &Bounds<Pixels>,
9222 content_origin: gpui::Point<Pixels>,
9223 right_margin: Pixels,
9224 editor_snapshot: &EditorSnapshot,
9225 visible_row_range: Range<DisplayRow>,
9226 scroll_top: ScrollOffset,
9227 scroll_bottom: ScrollOffset,
9228 line_layouts: &[LineWithInvisibles],
9229 line_height: Pixels,
9230 scroll_position: gpui::Point<ScrollOffset>,
9231 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9232 newest_selection_head: Option<DisplayPoint>,
9233 editor_width: Pixels,
9234 style: &EditorStyle,
9235 window: &mut Window,
9236 cx: &mut App,
9237 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9238 if self.mode().is_minimap() {
9239 return None;
9240 }
9241 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9242
9243 if self.edit_prediction_visible_in_cursor_popover(true) {
9244 return None;
9245 }
9246
9247 match &active_edit_prediction.completion {
9248 EditPrediction::MoveWithin { target, .. } => {
9249 let target_display_point = target.to_display_point(editor_snapshot);
9250
9251 if self.edit_prediction_requires_modifier() {
9252 if !self.edit_prediction_preview_is_active() {
9253 return None;
9254 }
9255
9256 self.render_edit_prediction_modifier_jump_popover(
9257 text_bounds,
9258 content_origin,
9259 visible_row_range,
9260 line_layouts,
9261 line_height,
9262 scroll_pixel_position,
9263 newest_selection_head,
9264 target_display_point,
9265 window,
9266 cx,
9267 )
9268 } else {
9269 self.render_edit_prediction_eager_jump_popover(
9270 text_bounds,
9271 content_origin,
9272 editor_snapshot,
9273 visible_row_range,
9274 scroll_top,
9275 scroll_bottom,
9276 line_height,
9277 scroll_pixel_position,
9278 target_display_point,
9279 editor_width,
9280 window,
9281 cx,
9282 )
9283 }
9284 }
9285 EditPrediction::Edit {
9286 display_mode: EditDisplayMode::Inline,
9287 ..
9288 } => None,
9289 EditPrediction::Edit {
9290 display_mode: EditDisplayMode::TabAccept,
9291 edits,
9292 ..
9293 } => {
9294 let range = &edits.first()?.0;
9295 let target_display_point = range.end.to_display_point(editor_snapshot);
9296
9297 self.render_edit_prediction_end_of_line_popover(
9298 "Accept",
9299 editor_snapshot,
9300 visible_row_range,
9301 target_display_point,
9302 line_height,
9303 scroll_pixel_position,
9304 content_origin,
9305 editor_width,
9306 window,
9307 cx,
9308 )
9309 }
9310 EditPrediction::Edit {
9311 edits,
9312 edit_preview,
9313 display_mode: EditDisplayMode::DiffPopover,
9314 snapshot,
9315 ..
9316 } => self.render_edit_prediction_diff_popover(
9317 text_bounds,
9318 content_origin,
9319 right_margin,
9320 editor_snapshot,
9321 visible_row_range,
9322 line_layouts,
9323 line_height,
9324 scroll_position,
9325 scroll_pixel_position,
9326 newest_selection_head,
9327 editor_width,
9328 style,
9329 edits,
9330 edit_preview,
9331 snapshot,
9332 window,
9333 cx,
9334 ),
9335 EditPrediction::MoveOutside { snapshot, .. } => {
9336 let mut element = self
9337 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9338 .into_any();
9339
9340 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9341 let origin_x = text_bounds.size.width - size.width - px(30.);
9342 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9343 element.prepaint_at(origin, window, cx);
9344
9345 Some((element, origin))
9346 }
9347 }
9348 }
9349
9350 fn render_edit_prediction_modifier_jump_popover(
9351 &mut self,
9352 text_bounds: &Bounds<Pixels>,
9353 content_origin: gpui::Point<Pixels>,
9354 visible_row_range: Range<DisplayRow>,
9355 line_layouts: &[LineWithInvisibles],
9356 line_height: Pixels,
9357 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9358 newest_selection_head: Option<DisplayPoint>,
9359 target_display_point: DisplayPoint,
9360 window: &mut Window,
9361 cx: &mut App,
9362 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9363 let scrolled_content_origin =
9364 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9365
9366 const SCROLL_PADDING_Y: Pixels = px(12.);
9367
9368 if target_display_point.row() < visible_row_range.start {
9369 return self.render_edit_prediction_scroll_popover(
9370 |_| SCROLL_PADDING_Y,
9371 IconName::ArrowUp,
9372 visible_row_range,
9373 line_layouts,
9374 newest_selection_head,
9375 scrolled_content_origin,
9376 window,
9377 cx,
9378 );
9379 } else if target_display_point.row() >= visible_row_range.end {
9380 return self.render_edit_prediction_scroll_popover(
9381 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9382 IconName::ArrowDown,
9383 visible_row_range,
9384 line_layouts,
9385 newest_selection_head,
9386 scrolled_content_origin,
9387 window,
9388 cx,
9389 );
9390 }
9391
9392 const POLE_WIDTH: Pixels = px(2.);
9393
9394 let line_layout =
9395 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9396 let target_column = target_display_point.column() as usize;
9397
9398 let target_x = line_layout.x_for_index(target_column);
9399 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9400 - scroll_pixel_position.y;
9401
9402 let flag_on_right = target_x < text_bounds.size.width / 2.;
9403
9404 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9405 border_color.l += 0.001;
9406
9407 let mut element = v_flex()
9408 .items_end()
9409 .when(flag_on_right, |el| el.items_start())
9410 .child(if flag_on_right {
9411 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9412 .rounded_bl(px(0.))
9413 .rounded_tl(px(0.))
9414 .border_l_2()
9415 .border_color(border_color)
9416 } else {
9417 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9418 .rounded_br(px(0.))
9419 .rounded_tr(px(0.))
9420 .border_r_2()
9421 .border_color(border_color)
9422 })
9423 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9424 .into_any();
9425
9426 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9427
9428 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9429 - point(
9430 if flag_on_right {
9431 POLE_WIDTH
9432 } else {
9433 size.width - POLE_WIDTH
9434 },
9435 size.height - line_height,
9436 );
9437
9438 origin.x = origin.x.max(content_origin.x);
9439
9440 element.prepaint_at(origin, window, cx);
9441
9442 Some((element, origin))
9443 }
9444
9445 fn render_edit_prediction_scroll_popover(
9446 &mut self,
9447 to_y: impl Fn(Size<Pixels>) -> Pixels,
9448 scroll_icon: IconName,
9449 visible_row_range: Range<DisplayRow>,
9450 line_layouts: &[LineWithInvisibles],
9451 newest_selection_head: Option<DisplayPoint>,
9452 scrolled_content_origin: gpui::Point<Pixels>,
9453 window: &mut Window,
9454 cx: &mut App,
9455 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9456 let mut element = self
9457 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9458 .into_any();
9459
9460 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9461
9462 let cursor = newest_selection_head?;
9463 let cursor_row_layout =
9464 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9465 let cursor_column = cursor.column() as usize;
9466
9467 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9468
9469 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9470
9471 element.prepaint_at(origin, window, cx);
9472 Some((element, origin))
9473 }
9474
9475 fn render_edit_prediction_eager_jump_popover(
9476 &mut self,
9477 text_bounds: &Bounds<Pixels>,
9478 content_origin: gpui::Point<Pixels>,
9479 editor_snapshot: &EditorSnapshot,
9480 visible_row_range: Range<DisplayRow>,
9481 scroll_top: ScrollOffset,
9482 scroll_bottom: ScrollOffset,
9483 line_height: Pixels,
9484 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9485 target_display_point: DisplayPoint,
9486 editor_width: Pixels,
9487 window: &mut Window,
9488 cx: &mut App,
9489 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9490 if target_display_point.row().as_f64() < scroll_top {
9491 let mut element = self
9492 .render_edit_prediction_line_popover(
9493 "Jump to Edit",
9494 Some(IconName::ArrowUp),
9495 window,
9496 cx,
9497 )
9498 .into_any();
9499
9500 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9501 let offset = point(
9502 (text_bounds.size.width - size.width) / 2.,
9503 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9504 );
9505
9506 let origin = text_bounds.origin + offset;
9507 element.prepaint_at(origin, window, cx);
9508 Some((element, origin))
9509 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9510 let mut element = self
9511 .render_edit_prediction_line_popover(
9512 "Jump to Edit",
9513 Some(IconName::ArrowDown),
9514 window,
9515 cx,
9516 )
9517 .into_any();
9518
9519 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9520 let offset = point(
9521 (text_bounds.size.width - size.width) / 2.,
9522 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9523 );
9524
9525 let origin = text_bounds.origin + offset;
9526 element.prepaint_at(origin, window, cx);
9527 Some((element, origin))
9528 } else {
9529 self.render_edit_prediction_end_of_line_popover(
9530 "Jump to Edit",
9531 editor_snapshot,
9532 visible_row_range,
9533 target_display_point,
9534 line_height,
9535 scroll_pixel_position,
9536 content_origin,
9537 editor_width,
9538 window,
9539 cx,
9540 )
9541 }
9542 }
9543
9544 fn render_edit_prediction_end_of_line_popover(
9545 self: &mut Editor,
9546 label: &'static str,
9547 editor_snapshot: &EditorSnapshot,
9548 visible_row_range: Range<DisplayRow>,
9549 target_display_point: DisplayPoint,
9550 line_height: Pixels,
9551 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9552 content_origin: gpui::Point<Pixels>,
9553 editor_width: Pixels,
9554 window: &mut Window,
9555 cx: &mut App,
9556 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9557 let target_line_end = DisplayPoint::new(
9558 target_display_point.row(),
9559 editor_snapshot.line_len(target_display_point.row()),
9560 );
9561
9562 let mut element = self
9563 .render_edit_prediction_line_popover(label, None, window, cx)
9564 .into_any();
9565
9566 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9567
9568 let line_origin =
9569 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9570
9571 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9572 let mut origin = start_point
9573 + line_origin
9574 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9575 origin.x = origin.x.max(content_origin.x);
9576
9577 let max_x = content_origin.x + editor_width - size.width;
9578
9579 if origin.x > max_x {
9580 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9581
9582 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9583 origin.y += offset;
9584 IconName::ArrowUp
9585 } else {
9586 origin.y -= offset;
9587 IconName::ArrowDown
9588 };
9589
9590 element = self
9591 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9592 .into_any();
9593
9594 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9595
9596 origin.x = content_origin.x + editor_width - size.width - px(2.);
9597 }
9598
9599 element.prepaint_at(origin, window, cx);
9600 Some((element, origin))
9601 }
9602
9603 fn render_edit_prediction_diff_popover(
9604 self: &Editor,
9605 text_bounds: &Bounds<Pixels>,
9606 content_origin: gpui::Point<Pixels>,
9607 right_margin: Pixels,
9608 editor_snapshot: &EditorSnapshot,
9609 visible_row_range: Range<DisplayRow>,
9610 line_layouts: &[LineWithInvisibles],
9611 line_height: Pixels,
9612 scroll_position: gpui::Point<ScrollOffset>,
9613 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9614 newest_selection_head: Option<DisplayPoint>,
9615 editor_width: Pixels,
9616 style: &EditorStyle,
9617 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9618 edit_preview: &Option<language::EditPreview>,
9619 snapshot: &language::BufferSnapshot,
9620 window: &mut Window,
9621 cx: &mut App,
9622 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9623 let edit_start = edits
9624 .first()
9625 .unwrap()
9626 .0
9627 .start
9628 .to_display_point(editor_snapshot);
9629 let edit_end = edits
9630 .last()
9631 .unwrap()
9632 .0
9633 .end
9634 .to_display_point(editor_snapshot);
9635
9636 let is_visible = visible_row_range.contains(&edit_start.row())
9637 || visible_row_range.contains(&edit_end.row());
9638 if !is_visible {
9639 return None;
9640 }
9641
9642 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9643 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9644 } else {
9645 // Fallback for providers without edit_preview
9646 crate::edit_prediction_fallback_text(edits, cx)
9647 };
9648
9649 let styled_text = highlighted_edits.to_styled_text(&style.text);
9650 let line_count = highlighted_edits.text.lines().count();
9651
9652 const BORDER_WIDTH: Pixels = px(1.);
9653
9654 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9655 let has_keybind = keybind.is_some();
9656
9657 let mut element = h_flex()
9658 .items_start()
9659 .child(
9660 h_flex()
9661 .bg(cx.theme().colors().editor_background)
9662 .border(BORDER_WIDTH)
9663 .shadow_xs()
9664 .border_color(cx.theme().colors().border)
9665 .rounded_l_lg()
9666 .when(line_count > 1, |el| el.rounded_br_lg())
9667 .pr_1()
9668 .child(styled_text),
9669 )
9670 .child(
9671 h_flex()
9672 .h(line_height + BORDER_WIDTH * 2.)
9673 .px_1p5()
9674 .gap_1()
9675 // Workaround: For some reason, there's a gap if we don't do this
9676 .ml(-BORDER_WIDTH)
9677 .shadow(vec![gpui::BoxShadow {
9678 color: gpui::black().opacity(0.05),
9679 offset: point(px(1.), px(1.)),
9680 blur_radius: px(2.),
9681 spread_radius: px(0.),
9682 }])
9683 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9684 .border(BORDER_WIDTH)
9685 .border_color(cx.theme().colors().border)
9686 .rounded_r_lg()
9687 .id("edit_prediction_diff_popover_keybind")
9688 .when(!has_keybind, |el| {
9689 let status_colors = cx.theme().status();
9690
9691 el.bg(status_colors.error_background)
9692 .border_color(status_colors.error.opacity(0.6))
9693 .child(Icon::new(IconName::Info).color(Color::Error))
9694 .cursor_default()
9695 .hoverable_tooltip(move |_window, cx| {
9696 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9697 })
9698 })
9699 .children(keybind),
9700 )
9701 .into_any();
9702
9703 let longest_row =
9704 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9705 let longest_line_width = if visible_row_range.contains(&longest_row) {
9706 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9707 } else {
9708 layout_line(
9709 longest_row,
9710 editor_snapshot,
9711 style,
9712 editor_width,
9713 |_| false,
9714 window,
9715 cx,
9716 )
9717 .width
9718 };
9719
9720 let viewport_bounds =
9721 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9722 right: -right_margin,
9723 ..Default::default()
9724 });
9725
9726 let x_after_longest = Pixels::from(
9727 ScrollPixelOffset::from(
9728 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9729 ) - scroll_pixel_position.x,
9730 );
9731
9732 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9733
9734 // Fully visible if it can be displayed within the window (allow overlapping other
9735 // panes). However, this is only allowed if the popover starts within text_bounds.
9736 let can_position_to_the_right = x_after_longest < text_bounds.right()
9737 && x_after_longest + element_bounds.width < viewport_bounds.right();
9738
9739 let mut origin = if can_position_to_the_right {
9740 point(
9741 x_after_longest,
9742 text_bounds.origin.y
9743 + Pixels::from(
9744 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9745 - scroll_pixel_position.y,
9746 ),
9747 )
9748 } else {
9749 let cursor_row = newest_selection_head.map(|head| head.row());
9750 let above_edit = edit_start
9751 .row()
9752 .0
9753 .checked_sub(line_count as u32)
9754 .map(DisplayRow);
9755 let below_edit = Some(edit_end.row() + 1);
9756 let above_cursor =
9757 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9758 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9759
9760 // Place the edit popover adjacent to the edit if there is a location
9761 // available that is onscreen and does not obscure the cursor. Otherwise,
9762 // place it adjacent to the cursor.
9763 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9764 .into_iter()
9765 .flatten()
9766 .find(|&start_row| {
9767 let end_row = start_row + line_count as u32;
9768 visible_row_range.contains(&start_row)
9769 && visible_row_range.contains(&end_row)
9770 && cursor_row
9771 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9772 })?;
9773
9774 content_origin
9775 + point(
9776 Pixels::from(-scroll_pixel_position.x),
9777 Pixels::from(
9778 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9779 ),
9780 )
9781 };
9782
9783 origin.x -= BORDER_WIDTH;
9784
9785 window.defer_draw(element, origin, 1);
9786
9787 // Do not return an element, since it will already be drawn due to defer_draw.
9788 None
9789 }
9790
9791 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9792 px(30.)
9793 }
9794
9795 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9796 if self.read_only(cx) {
9797 cx.theme().players().read_only()
9798 } else {
9799 self.style.as_ref().unwrap().local_player
9800 }
9801 }
9802
9803 fn render_edit_prediction_accept_keybind(
9804 &self,
9805 window: &mut Window,
9806 cx: &mut App,
9807 ) -> Option<AnyElement> {
9808 let accept_binding =
9809 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9810 let accept_keystroke = accept_binding.keystroke()?;
9811
9812 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9813
9814 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9815 Color::Accent
9816 } else {
9817 Color::Muted
9818 };
9819
9820 h_flex()
9821 .px_0p5()
9822 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9823 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9824 .text_size(TextSize::XSmall.rems(cx))
9825 .child(h_flex().children(ui::render_modifiers(
9826 accept_keystroke.modifiers(),
9827 PlatformStyle::platform(),
9828 Some(modifiers_color),
9829 Some(IconSize::XSmall.rems().into()),
9830 true,
9831 )))
9832 .when(is_platform_style_mac, |parent| {
9833 parent.child(accept_keystroke.key().to_string())
9834 })
9835 .when(!is_platform_style_mac, |parent| {
9836 parent.child(
9837 Key::new(
9838 util::capitalize(accept_keystroke.key()),
9839 Some(Color::Default),
9840 )
9841 .size(Some(IconSize::XSmall.rems().into())),
9842 )
9843 })
9844 .into_any()
9845 .into()
9846 }
9847
9848 fn render_edit_prediction_line_popover(
9849 &self,
9850 label: impl Into<SharedString>,
9851 icon: Option<IconName>,
9852 window: &mut Window,
9853 cx: &mut App,
9854 ) -> Stateful<Div> {
9855 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9856
9857 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9858 let has_keybind = keybind.is_some();
9859 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9860
9861 h_flex()
9862 .id("ep-line-popover")
9863 .py_0p5()
9864 .pl_1()
9865 .pr(padding_right)
9866 .gap_1()
9867 .rounded_md()
9868 .border_1()
9869 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9870 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9871 .shadow_xs()
9872 .when(!has_keybind, |el| {
9873 let status_colors = cx.theme().status();
9874
9875 el.bg(status_colors.error_background)
9876 .border_color(status_colors.error.opacity(0.6))
9877 .pl_2()
9878 .child(Icon::new(icons.error).color(Color::Error))
9879 .cursor_default()
9880 .hoverable_tooltip(move |_window, cx| {
9881 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9882 })
9883 })
9884 .children(keybind)
9885 .child(
9886 Label::new(label)
9887 .size(LabelSize::Small)
9888 .when(!has_keybind, |el| {
9889 el.color(cx.theme().status().error.into()).strikethrough()
9890 }),
9891 )
9892 .when(!has_keybind, |el| {
9893 el.child(
9894 h_flex().ml_1().child(
9895 Icon::new(IconName::Info)
9896 .size(IconSize::Small)
9897 .color(cx.theme().status().error.into()),
9898 ),
9899 )
9900 })
9901 .when_some(icon, |element, icon| {
9902 element.child(
9903 div()
9904 .mt(px(1.5))
9905 .child(Icon::new(icon).size(IconSize::Small)),
9906 )
9907 })
9908 }
9909
9910 fn render_edit_prediction_jump_outside_popover(
9911 &self,
9912 snapshot: &BufferSnapshot,
9913 window: &mut Window,
9914 cx: &mut App,
9915 ) -> Stateful<Div> {
9916 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9917 let has_keybind = keybind.is_some();
9918 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9919
9920 let file_name = snapshot
9921 .file()
9922 .map(|file| SharedString::new(file.file_name(cx)))
9923 .unwrap_or(SharedString::new_static("untitled"));
9924
9925 h_flex()
9926 .id("ep-jump-outside-popover")
9927 .py_1()
9928 .px_2()
9929 .gap_1()
9930 .rounded_md()
9931 .border_1()
9932 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9933 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9934 .shadow_xs()
9935 .when(!has_keybind, |el| {
9936 let status_colors = cx.theme().status();
9937
9938 el.bg(status_colors.error_background)
9939 .border_color(status_colors.error.opacity(0.6))
9940 .pl_2()
9941 .child(Icon::new(icons.error).color(Color::Error))
9942 .cursor_default()
9943 .hoverable_tooltip(move |_window, cx| {
9944 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9945 })
9946 })
9947 .children(keybind)
9948 .child(
9949 Label::new(file_name)
9950 .size(LabelSize::Small)
9951 .buffer_font(cx)
9952 .when(!has_keybind, |el| {
9953 el.color(cx.theme().status().error.into()).strikethrough()
9954 }),
9955 )
9956 .when(!has_keybind, |el| {
9957 el.child(
9958 h_flex().ml_1().child(
9959 Icon::new(IconName::Info)
9960 .size(IconSize::Small)
9961 .color(cx.theme().status().error.into()),
9962 ),
9963 )
9964 })
9965 .child(
9966 div()
9967 .mt(px(1.5))
9968 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9969 )
9970 }
9971
9972 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9973 let accent_color = cx.theme().colors().text_accent;
9974 let editor_bg_color = cx.theme().colors().editor_background;
9975 editor_bg_color.blend(accent_color.opacity(0.1))
9976 }
9977
9978 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9979 let accent_color = cx.theme().colors().text_accent;
9980 let editor_bg_color = cx.theme().colors().editor_background;
9981 editor_bg_color.blend(accent_color.opacity(0.6))
9982 }
9983 fn get_prediction_provider_icons(
9984 provider: &Option<RegisteredEditPredictionDelegate>,
9985 cx: &App,
9986 ) -> edit_prediction_types::EditPredictionIconSet {
9987 match provider {
9988 Some(provider) => provider.provider.icons(cx),
9989 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
9990 }
9991 }
9992
9993 fn render_edit_prediction_cursor_popover(
9994 &self,
9995 min_width: Pixels,
9996 max_width: Pixels,
9997 cursor_point: Point,
9998 style: &EditorStyle,
9999 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10000 _window: &Window,
10001 cx: &mut Context<Editor>,
10002 ) -> Option<AnyElement> {
10003 let provider = self.edit_prediction_provider.as_ref()?;
10004 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10005
10006 let is_refreshing = provider.provider.is_refreshing(cx);
10007
10008 fn pending_completion_container(icon: IconName) -> Div {
10009 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10010 }
10011
10012 let completion = match &self.active_edit_prediction {
10013 Some(prediction) => {
10014 if !self.has_visible_completions_menu() {
10015 const RADIUS: Pixels = px(6.);
10016 const BORDER_WIDTH: Pixels = px(1.);
10017
10018 return Some(
10019 h_flex()
10020 .elevation_2(cx)
10021 .border(BORDER_WIDTH)
10022 .border_color(cx.theme().colors().border)
10023 .when(accept_keystroke.is_none(), |el| {
10024 el.border_color(cx.theme().status().error)
10025 })
10026 .rounded(RADIUS)
10027 .rounded_tl(px(0.))
10028 .overflow_hidden()
10029 .child(div().px_1p5().child(match &prediction.completion {
10030 EditPrediction::MoveWithin { target, snapshot } => {
10031 use text::ToPoint as _;
10032 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10033 {
10034 Icon::new(icons.down)
10035 } else {
10036 Icon::new(icons.up)
10037 }
10038 }
10039 EditPrediction::MoveOutside { .. } => {
10040 // TODO [zeta2] custom icon for external jump?
10041 Icon::new(icons.base)
10042 }
10043 EditPrediction::Edit { .. } => Icon::new(icons.base),
10044 }))
10045 .child(
10046 h_flex()
10047 .gap_1()
10048 .py_1()
10049 .px_2()
10050 .rounded_r(RADIUS - BORDER_WIDTH)
10051 .border_l_1()
10052 .border_color(cx.theme().colors().border)
10053 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10054 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10055 el.child(
10056 Label::new("Hold")
10057 .size(LabelSize::Small)
10058 .when(accept_keystroke.is_none(), |el| {
10059 el.strikethrough()
10060 })
10061 .line_height_style(LineHeightStyle::UiLabel),
10062 )
10063 })
10064 .id("edit_prediction_cursor_popover_keybind")
10065 .when(accept_keystroke.is_none(), |el| {
10066 let status_colors = cx.theme().status();
10067
10068 el.bg(status_colors.error_background)
10069 .border_color(status_colors.error.opacity(0.6))
10070 .child(Icon::new(IconName::Info).color(Color::Error))
10071 .cursor_default()
10072 .hoverable_tooltip(move |_window, cx| {
10073 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10074 .into()
10075 })
10076 })
10077 .when_some(
10078 accept_keystroke.as_ref(),
10079 |el, accept_keystroke| {
10080 el.child(h_flex().children(ui::render_modifiers(
10081 accept_keystroke.modifiers(),
10082 PlatformStyle::platform(),
10083 Some(Color::Default),
10084 Some(IconSize::XSmall.rems().into()),
10085 false,
10086 )))
10087 },
10088 ),
10089 )
10090 .into_any(),
10091 );
10092 }
10093
10094 self.render_edit_prediction_cursor_popover_preview(
10095 prediction,
10096 cursor_point,
10097 style,
10098 cx,
10099 )?
10100 }
10101
10102 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10103 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10104 stale_completion,
10105 cursor_point,
10106 style,
10107 cx,
10108 )?,
10109
10110 None => pending_completion_container(icons.base)
10111 .child(Label::new("...").size(LabelSize::Small)),
10112 },
10113
10114 None => pending_completion_container(icons.base)
10115 .child(Label::new("...").size(LabelSize::Small)),
10116 };
10117
10118 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10119 completion
10120 .with_animation(
10121 "loading-completion",
10122 Animation::new(Duration::from_secs(2))
10123 .repeat()
10124 .with_easing(pulsating_between(0.4, 0.8)),
10125 |label, delta| label.opacity(delta),
10126 )
10127 .into_any_element()
10128 } else {
10129 completion.into_any_element()
10130 };
10131
10132 let has_completion = self.active_edit_prediction.is_some();
10133
10134 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10135 Some(
10136 h_flex()
10137 .min_w(min_width)
10138 .max_w(max_width)
10139 .flex_1()
10140 .elevation_2(cx)
10141 .border_color(cx.theme().colors().border)
10142 .child(
10143 div()
10144 .flex_1()
10145 .py_1()
10146 .px_2()
10147 .overflow_hidden()
10148 .child(completion),
10149 )
10150 .when_some(accept_keystroke, |el, accept_keystroke| {
10151 if !accept_keystroke.modifiers().modified() {
10152 return el;
10153 }
10154
10155 el.child(
10156 h_flex()
10157 .h_full()
10158 .border_l_1()
10159 .rounded_r_lg()
10160 .border_color(cx.theme().colors().border)
10161 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10162 .gap_1()
10163 .py_1()
10164 .px_2()
10165 .child(
10166 h_flex()
10167 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10168 .when(is_platform_style_mac, |parent| parent.gap_1())
10169 .child(h_flex().children(ui::render_modifiers(
10170 accept_keystroke.modifiers(),
10171 PlatformStyle::platform(),
10172 Some(if !has_completion {
10173 Color::Muted
10174 } else {
10175 Color::Default
10176 }),
10177 None,
10178 false,
10179 ))),
10180 )
10181 .child(Label::new("Preview").into_any_element())
10182 .opacity(if has_completion { 1.0 } else { 0.4 }),
10183 )
10184 })
10185 .into_any(),
10186 )
10187 }
10188
10189 fn render_edit_prediction_cursor_popover_preview(
10190 &self,
10191 completion: &EditPredictionState,
10192 cursor_point: Point,
10193 style: &EditorStyle,
10194 cx: &mut Context<Editor>,
10195 ) -> Option<Div> {
10196 use text::ToPoint as _;
10197
10198 fn render_relative_row_jump(
10199 prefix: impl Into<String>,
10200 current_row: u32,
10201 target_row: u32,
10202 ) -> Div {
10203 let (row_diff, arrow) = if target_row < current_row {
10204 (current_row - target_row, IconName::ArrowUp)
10205 } else {
10206 (target_row - current_row, IconName::ArrowDown)
10207 };
10208
10209 h_flex()
10210 .child(
10211 Label::new(format!("{}{}", prefix.into(), row_diff))
10212 .color(Color::Muted)
10213 .size(LabelSize::Small),
10214 )
10215 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10216 }
10217
10218 let supports_jump = self
10219 .edit_prediction_provider
10220 .as_ref()
10221 .map(|provider| provider.provider.supports_jump_to_edit())
10222 .unwrap_or(true);
10223
10224 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10225
10226 match &completion.completion {
10227 EditPrediction::MoveWithin {
10228 target, snapshot, ..
10229 } => {
10230 if !supports_jump {
10231 return None;
10232 }
10233
10234 Some(
10235 h_flex()
10236 .px_2()
10237 .gap_2()
10238 .flex_1()
10239 .child(
10240 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10241 Icon::new(icons.down)
10242 } else {
10243 Icon::new(icons.up)
10244 },
10245 )
10246 .child(Label::new("Jump to Edit")),
10247 )
10248 }
10249 EditPrediction::MoveOutside { snapshot, .. } => {
10250 let file_name = snapshot
10251 .file()
10252 .map(|file| file.file_name(cx))
10253 .unwrap_or("untitled");
10254 Some(
10255 h_flex()
10256 .px_2()
10257 .gap_2()
10258 .flex_1()
10259 .child(Icon::new(icons.base))
10260 .child(Label::new(format!("Jump to {file_name}"))),
10261 )
10262 }
10263 EditPrediction::Edit {
10264 edits,
10265 edit_preview,
10266 snapshot,
10267 ..
10268 } => {
10269 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10270
10271 let (highlighted_edits, has_more_lines) =
10272 if let Some(edit_preview) = edit_preview.as_ref() {
10273 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10274 .first_line_preview()
10275 } else {
10276 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10277 };
10278
10279 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10280 .with_default_highlights(&style.text, highlighted_edits.highlights);
10281
10282 let preview = h_flex()
10283 .gap_1()
10284 .min_w_16()
10285 .child(styled_text)
10286 .when(has_more_lines, |parent| parent.child("…"));
10287
10288 let left = if supports_jump && first_edit_row != cursor_point.row {
10289 render_relative_row_jump("", cursor_point.row, first_edit_row)
10290 .into_any_element()
10291 } else {
10292 Icon::new(icons.base).into_any_element()
10293 };
10294
10295 Some(
10296 h_flex()
10297 .h_full()
10298 .flex_1()
10299 .gap_2()
10300 .pr_1()
10301 .overflow_x_hidden()
10302 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10303 .child(left)
10304 .child(preview),
10305 )
10306 }
10307 }
10308 }
10309
10310 pub fn render_context_menu(
10311 &mut self,
10312 max_height_in_lines: u32,
10313 window: &mut Window,
10314 cx: &mut Context<Editor>,
10315 ) -> Option<AnyElement> {
10316 let menu = self.context_menu.borrow();
10317 let menu = menu.as_ref()?;
10318 if !menu.visible() {
10319 return None;
10320 };
10321 self.style
10322 .as_ref()
10323 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10324 }
10325
10326 fn render_context_menu_aside(
10327 &mut self,
10328 max_size: Size<Pixels>,
10329 window: &mut Window,
10330 cx: &mut Context<Editor>,
10331 ) -> Option<AnyElement> {
10332 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10333 if menu.visible() {
10334 menu.render_aside(max_size, window, cx)
10335 } else {
10336 None
10337 }
10338 })
10339 }
10340
10341 fn hide_context_menu(
10342 &mut self,
10343 window: &mut Window,
10344 cx: &mut Context<Self>,
10345 ) -> Option<CodeContextMenu> {
10346 cx.notify();
10347 self.completion_tasks.clear();
10348 let context_menu = self.context_menu.borrow_mut().take();
10349 self.stale_edit_prediction_in_menu.take();
10350 self.update_visible_edit_prediction(window, cx);
10351 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10352 && let Some(completion_provider) = &self.completion_provider
10353 {
10354 completion_provider.selection_changed(None, window, cx);
10355 }
10356 context_menu
10357 }
10358
10359 fn show_snippet_choices(
10360 &mut self,
10361 choices: &Vec<String>,
10362 selection: Range<Anchor>,
10363 cx: &mut Context<Self>,
10364 ) {
10365 let Some((_, buffer, _)) = self
10366 .buffer()
10367 .read(cx)
10368 .excerpt_containing(selection.start, cx)
10369 else {
10370 return;
10371 };
10372 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10373 else {
10374 return;
10375 };
10376 if buffer != end_buffer {
10377 log::error!("expected anchor range to have matching buffer IDs");
10378 return;
10379 }
10380
10381 let id = post_inc(&mut self.next_completion_id);
10382 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10383 let mut context_menu = self.context_menu.borrow_mut();
10384 let old_menu = context_menu.take();
10385 *context_menu = Some(CodeContextMenu::Completions(
10386 CompletionsMenu::new_snippet_choices(
10387 id,
10388 true,
10389 choices,
10390 selection,
10391 buffer,
10392 old_menu.map(|menu| menu.primary_scroll_handle()),
10393 snippet_sort_order,
10394 ),
10395 ));
10396 }
10397
10398 pub fn insert_snippet(
10399 &mut self,
10400 insertion_ranges: &[Range<MultiBufferOffset>],
10401 snippet: Snippet,
10402 window: &mut Window,
10403 cx: &mut Context<Self>,
10404 ) -> Result<()> {
10405 struct Tabstop<T> {
10406 is_end_tabstop: bool,
10407 ranges: Vec<Range<T>>,
10408 choices: Option<Vec<String>>,
10409 }
10410
10411 let tabstops = self.buffer.update(cx, |buffer, cx| {
10412 let snippet_text: Arc<str> = snippet.text.clone().into();
10413 let edits = insertion_ranges
10414 .iter()
10415 .cloned()
10416 .map(|range| (range, snippet_text.clone()));
10417 let autoindent_mode = AutoindentMode::Block {
10418 original_indent_columns: Vec::new(),
10419 };
10420 buffer.edit(edits, Some(autoindent_mode), cx);
10421
10422 let snapshot = &*buffer.read(cx);
10423 let snippet = &snippet;
10424 snippet
10425 .tabstops
10426 .iter()
10427 .map(|tabstop| {
10428 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10429 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10430 });
10431 let mut tabstop_ranges = tabstop
10432 .ranges
10433 .iter()
10434 .flat_map(|tabstop_range| {
10435 let mut delta = 0_isize;
10436 insertion_ranges.iter().map(move |insertion_range| {
10437 let insertion_start = insertion_range.start + delta;
10438 delta += snippet.text.len() as isize
10439 - (insertion_range.end - insertion_range.start) as isize;
10440
10441 let start =
10442 (insertion_start + tabstop_range.start).min(snapshot.len());
10443 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10444 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10445 })
10446 })
10447 .collect::<Vec<_>>();
10448 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10449
10450 Tabstop {
10451 is_end_tabstop,
10452 ranges: tabstop_ranges,
10453 choices: tabstop.choices.clone(),
10454 }
10455 })
10456 .collect::<Vec<_>>()
10457 });
10458 if let Some(tabstop) = tabstops.first() {
10459 self.change_selections(Default::default(), window, cx, |s| {
10460 // Reverse order so that the first range is the newest created selection.
10461 // Completions will use it and autoscroll will prioritize it.
10462 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10463 });
10464
10465 if let Some(choices) = &tabstop.choices
10466 && let Some(selection) = tabstop.ranges.first()
10467 {
10468 self.show_snippet_choices(choices, selection.clone(), cx)
10469 }
10470
10471 // If we're already at the last tabstop and it's at the end of the snippet,
10472 // we're done, we don't need to keep the state around.
10473 if !tabstop.is_end_tabstop {
10474 let choices = tabstops
10475 .iter()
10476 .map(|tabstop| tabstop.choices.clone())
10477 .collect();
10478
10479 let ranges = tabstops
10480 .into_iter()
10481 .map(|tabstop| tabstop.ranges)
10482 .collect::<Vec<_>>();
10483
10484 self.snippet_stack.push(SnippetState {
10485 active_index: 0,
10486 ranges,
10487 choices,
10488 });
10489 }
10490
10491 // Check whether the just-entered snippet ends with an auto-closable bracket.
10492 if self.autoclose_regions.is_empty() {
10493 let snapshot = self.buffer.read(cx).snapshot(cx);
10494 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10495 let selection_head = selection.head();
10496 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10497 continue;
10498 };
10499
10500 let mut bracket_pair = None;
10501 let max_lookup_length = scope
10502 .brackets()
10503 .map(|(pair, _)| {
10504 pair.start
10505 .as_str()
10506 .chars()
10507 .count()
10508 .max(pair.end.as_str().chars().count())
10509 })
10510 .max();
10511 if let Some(max_lookup_length) = max_lookup_length {
10512 let next_text = snapshot
10513 .chars_at(selection_head)
10514 .take(max_lookup_length)
10515 .collect::<String>();
10516 let prev_text = snapshot
10517 .reversed_chars_at(selection_head)
10518 .take(max_lookup_length)
10519 .collect::<String>();
10520
10521 for (pair, enabled) in scope.brackets() {
10522 if enabled
10523 && pair.close
10524 && prev_text.starts_with(pair.start.as_str())
10525 && next_text.starts_with(pair.end.as_str())
10526 {
10527 bracket_pair = Some(pair.clone());
10528 break;
10529 }
10530 }
10531 }
10532
10533 if let Some(pair) = bracket_pair {
10534 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10535 let autoclose_enabled =
10536 self.use_autoclose && snapshot_settings.use_autoclose;
10537 if autoclose_enabled {
10538 let start = snapshot.anchor_after(selection_head);
10539 let end = snapshot.anchor_after(selection_head);
10540 self.autoclose_regions.push(AutocloseRegion {
10541 selection_id: selection.id,
10542 range: start..end,
10543 pair,
10544 });
10545 }
10546 }
10547 }
10548 }
10549 }
10550 Ok(())
10551 }
10552
10553 pub fn move_to_next_snippet_tabstop(
10554 &mut self,
10555 window: &mut Window,
10556 cx: &mut Context<Self>,
10557 ) -> bool {
10558 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10559 }
10560
10561 pub fn move_to_prev_snippet_tabstop(
10562 &mut self,
10563 window: &mut Window,
10564 cx: &mut Context<Self>,
10565 ) -> bool {
10566 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10567 }
10568
10569 pub fn move_to_snippet_tabstop(
10570 &mut self,
10571 bias: Bias,
10572 window: &mut Window,
10573 cx: &mut Context<Self>,
10574 ) -> bool {
10575 if let Some(mut snippet) = self.snippet_stack.pop() {
10576 match bias {
10577 Bias::Left => {
10578 if snippet.active_index > 0 {
10579 snippet.active_index -= 1;
10580 } else {
10581 self.snippet_stack.push(snippet);
10582 return false;
10583 }
10584 }
10585 Bias::Right => {
10586 if snippet.active_index + 1 < snippet.ranges.len() {
10587 snippet.active_index += 1;
10588 } else {
10589 self.snippet_stack.push(snippet);
10590 return false;
10591 }
10592 }
10593 }
10594 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10595 self.change_selections(Default::default(), window, cx, |s| {
10596 // Reverse order so that the first range is the newest created selection.
10597 // Completions will use it and autoscroll will prioritize it.
10598 s.select_ranges(current_ranges.iter().rev().cloned())
10599 });
10600
10601 if let Some(choices) = &snippet.choices[snippet.active_index]
10602 && let Some(selection) = current_ranges.first()
10603 {
10604 self.show_snippet_choices(choices, selection.clone(), cx);
10605 }
10606
10607 // If snippet state is not at the last tabstop, push it back on the stack
10608 if snippet.active_index + 1 < snippet.ranges.len() {
10609 self.snippet_stack.push(snippet);
10610 }
10611 return true;
10612 }
10613 }
10614
10615 false
10616 }
10617
10618 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10619 self.transact(window, cx, |this, window, cx| {
10620 this.select_all(&SelectAll, window, cx);
10621 this.insert("", window, cx);
10622 });
10623 }
10624
10625 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10626 if self.read_only(cx) {
10627 return;
10628 }
10629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10630 self.transact(window, cx, |this, window, cx| {
10631 this.select_autoclose_pair(window, cx);
10632
10633 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10634
10635 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10636 if !this.linked_edit_ranges.is_empty() {
10637 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10638 let snapshot = this.buffer.read(cx).snapshot(cx);
10639
10640 for selection in selections.iter() {
10641 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10642 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10643 if selection_start.buffer_id != selection_end.buffer_id {
10644 continue;
10645 }
10646 if let Some(ranges) =
10647 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10648 {
10649 for (buffer, entries) in ranges {
10650 linked_ranges.entry(buffer).or_default().extend(entries);
10651 }
10652 }
10653 }
10654 }
10655
10656 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10657 for selection in &mut selections {
10658 if selection.is_empty() {
10659 let old_head = selection.head();
10660 let mut new_head =
10661 movement::left(&display_map, old_head.to_display_point(&display_map))
10662 .to_point(&display_map);
10663 if let Some((buffer, line_buffer_range)) = display_map
10664 .buffer_snapshot()
10665 .buffer_line_for_row(MultiBufferRow(old_head.row))
10666 {
10667 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10668 let indent_len = match indent_size.kind {
10669 IndentKind::Space => {
10670 buffer.settings_at(line_buffer_range.start, cx).tab_size
10671 }
10672 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10673 };
10674 if old_head.column <= indent_size.len && old_head.column > 0 {
10675 let indent_len = indent_len.get();
10676 new_head = cmp::min(
10677 new_head,
10678 MultiBufferPoint::new(
10679 old_head.row,
10680 ((old_head.column - 1) / indent_len) * indent_len,
10681 ),
10682 );
10683 }
10684 }
10685
10686 selection.set_head(new_head, SelectionGoal::None);
10687 }
10688 }
10689
10690 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10691 this.insert("", window, cx);
10692 let empty_str: Arc<str> = Arc::from("");
10693 for (buffer, edits) in linked_ranges {
10694 let snapshot = buffer.read(cx).snapshot();
10695 use text::ToPoint as TP;
10696
10697 let edits = edits
10698 .into_iter()
10699 .map(|range| {
10700 let end_point = TP::to_point(&range.end, &snapshot);
10701 let mut start_point = TP::to_point(&range.start, &snapshot);
10702
10703 if end_point == start_point {
10704 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10705 .saturating_sub(1);
10706 start_point =
10707 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10708 };
10709
10710 (start_point..end_point, empty_str.clone())
10711 })
10712 .sorted_by_key(|(range, _)| range.start)
10713 .collect::<Vec<_>>();
10714 buffer.update(cx, |this, cx| {
10715 this.edit(edits, None, cx);
10716 })
10717 }
10718 this.refresh_edit_prediction(true, false, window, cx);
10719 refresh_linked_ranges(this, window, cx);
10720 });
10721 }
10722
10723 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10724 if self.read_only(cx) {
10725 return;
10726 }
10727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10728 self.transact(window, cx, |this, window, cx| {
10729 this.change_selections(Default::default(), window, cx, |s| {
10730 s.move_with(|map, selection| {
10731 if selection.is_empty() {
10732 let cursor = movement::right(map, selection.head());
10733 selection.end = cursor;
10734 selection.reversed = true;
10735 selection.goal = SelectionGoal::None;
10736 }
10737 })
10738 });
10739 this.insert("", window, cx);
10740 this.refresh_edit_prediction(true, false, window, cx);
10741 });
10742 }
10743
10744 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10745 if self.mode.is_single_line() {
10746 cx.propagate();
10747 return;
10748 }
10749
10750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10751 if self.move_to_prev_snippet_tabstop(window, cx) {
10752 return;
10753 }
10754 self.outdent(&Outdent, window, cx);
10755 }
10756
10757 pub fn next_snippet_tabstop(
10758 &mut self,
10759 _: &NextSnippetTabstop,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) {
10763 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10764 cx.propagate();
10765 return;
10766 }
10767
10768 if self.move_to_next_snippet_tabstop(window, cx) {
10769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10770 return;
10771 }
10772 cx.propagate();
10773 }
10774
10775 pub fn previous_snippet_tabstop(
10776 &mut self,
10777 _: &PreviousSnippetTabstop,
10778 window: &mut Window,
10779 cx: &mut Context<Self>,
10780 ) {
10781 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10782 cx.propagate();
10783 return;
10784 }
10785
10786 if self.move_to_prev_snippet_tabstop(window, cx) {
10787 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10788 return;
10789 }
10790 cx.propagate();
10791 }
10792
10793 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10794 if self.mode.is_single_line() {
10795 cx.propagate();
10796 return;
10797 }
10798
10799 if self.move_to_next_snippet_tabstop(window, cx) {
10800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10801 return;
10802 }
10803 if self.read_only(cx) {
10804 return;
10805 }
10806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10807 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10808 let buffer = self.buffer.read(cx);
10809 let snapshot = buffer.snapshot(cx);
10810 let rows_iter = selections.iter().map(|s| s.head().row);
10811 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10812
10813 let has_some_cursor_in_whitespace = selections
10814 .iter()
10815 .filter(|selection| selection.is_empty())
10816 .any(|selection| {
10817 let cursor = selection.head();
10818 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10819 cursor.column < current_indent.len
10820 });
10821
10822 let mut edits = Vec::new();
10823 let mut prev_edited_row = 0;
10824 let mut row_delta = 0;
10825 for selection in &mut selections {
10826 if selection.start.row != prev_edited_row {
10827 row_delta = 0;
10828 }
10829 prev_edited_row = selection.end.row;
10830
10831 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10832 if selection.is_empty() {
10833 let cursor = selection.head();
10834 let settings = buffer.language_settings_at(cursor, cx);
10835 if settings.indent_list_on_tab {
10836 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10837 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10838 row_delta = Self::indent_selection(
10839 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10840 );
10841 continue;
10842 }
10843 }
10844 }
10845 }
10846
10847 // If the selection is non-empty, then increase the indentation of the selected lines.
10848 if !selection.is_empty() {
10849 row_delta =
10850 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10851 continue;
10852 }
10853
10854 let cursor = selection.head();
10855 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10856 if let Some(suggested_indent) =
10857 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10858 {
10859 // Don't do anything if already at suggested indent
10860 // and there is any other cursor which is not
10861 if has_some_cursor_in_whitespace
10862 && cursor.column == current_indent.len
10863 && current_indent.len == suggested_indent.len
10864 {
10865 continue;
10866 }
10867
10868 // Adjust line and move cursor to suggested indent
10869 // if cursor is not at suggested indent
10870 if cursor.column < suggested_indent.len
10871 && cursor.column <= current_indent.len
10872 && current_indent.len <= suggested_indent.len
10873 {
10874 selection.start = Point::new(cursor.row, suggested_indent.len);
10875 selection.end = selection.start;
10876 if row_delta == 0 {
10877 edits.extend(Buffer::edit_for_indent_size_adjustment(
10878 cursor.row,
10879 current_indent,
10880 suggested_indent,
10881 ));
10882 row_delta = suggested_indent.len - current_indent.len;
10883 }
10884 continue;
10885 }
10886
10887 // If current indent is more than suggested indent
10888 // only move cursor to current indent and skip indent
10889 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10890 selection.start = Point::new(cursor.row, current_indent.len);
10891 selection.end = selection.start;
10892 continue;
10893 }
10894 }
10895
10896 // Otherwise, insert a hard or soft tab.
10897 let settings = buffer.language_settings_at(cursor, cx);
10898 let tab_size = if settings.hard_tabs {
10899 IndentSize::tab()
10900 } else {
10901 let tab_size = settings.tab_size.get();
10902 let indent_remainder = snapshot
10903 .text_for_range(Point::new(cursor.row, 0)..cursor)
10904 .flat_map(str::chars)
10905 .fold(row_delta % tab_size, |counter: u32, c| {
10906 if c == '\t' {
10907 0
10908 } else {
10909 (counter + 1) % tab_size
10910 }
10911 });
10912
10913 let chars_to_next_tab_stop = tab_size - indent_remainder;
10914 IndentSize::spaces(chars_to_next_tab_stop)
10915 };
10916 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10917 selection.end = selection.start;
10918 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10919 row_delta += tab_size.len;
10920 }
10921
10922 self.transact(window, cx, |this, window, cx| {
10923 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10924 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10925 this.refresh_edit_prediction(true, false, window, cx);
10926 });
10927 }
10928
10929 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10930 if self.read_only(cx) {
10931 return;
10932 }
10933 if self.mode.is_single_line() {
10934 cx.propagate();
10935 return;
10936 }
10937
10938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10939 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10940 let mut prev_edited_row = 0;
10941 let mut row_delta = 0;
10942 let mut edits = Vec::new();
10943 let buffer = self.buffer.read(cx);
10944 let snapshot = buffer.snapshot(cx);
10945 for selection in &mut selections {
10946 if selection.start.row != prev_edited_row {
10947 row_delta = 0;
10948 }
10949 prev_edited_row = selection.end.row;
10950
10951 row_delta =
10952 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10953 }
10954
10955 self.transact(window, cx, |this, window, cx| {
10956 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10957 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10958 });
10959 }
10960
10961 fn indent_selection(
10962 buffer: &MultiBuffer,
10963 snapshot: &MultiBufferSnapshot,
10964 selection: &mut Selection<Point>,
10965 edits: &mut Vec<(Range<Point>, String)>,
10966 delta_for_start_row: u32,
10967 cx: &App,
10968 ) -> u32 {
10969 let settings = buffer.language_settings_at(selection.start, cx);
10970 let tab_size = settings.tab_size.get();
10971 let indent_kind = if settings.hard_tabs {
10972 IndentKind::Tab
10973 } else {
10974 IndentKind::Space
10975 };
10976 let mut start_row = selection.start.row;
10977 let mut end_row = selection.end.row + 1;
10978
10979 // If a selection ends at the beginning of a line, don't indent
10980 // that last line.
10981 if selection.end.column == 0 && selection.end.row > selection.start.row {
10982 end_row -= 1;
10983 }
10984
10985 // Avoid re-indenting a row that has already been indented by a
10986 // previous selection, but still update this selection's column
10987 // to reflect that indentation.
10988 if delta_for_start_row > 0 {
10989 start_row += 1;
10990 selection.start.column += delta_for_start_row;
10991 if selection.end.row == selection.start.row {
10992 selection.end.column += delta_for_start_row;
10993 }
10994 }
10995
10996 let mut delta_for_end_row = 0;
10997 let has_multiple_rows = start_row + 1 != end_row;
10998 for row in start_row..end_row {
10999 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11000 let indent_delta = match (current_indent.kind, indent_kind) {
11001 (IndentKind::Space, IndentKind::Space) => {
11002 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11003 IndentSize::spaces(columns_to_next_tab_stop)
11004 }
11005 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11006 (_, IndentKind::Tab) => IndentSize::tab(),
11007 };
11008
11009 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11010 0
11011 } else {
11012 selection.start.column
11013 };
11014 let row_start = Point::new(row, start);
11015 edits.push((
11016 row_start..row_start,
11017 indent_delta.chars().collect::<String>(),
11018 ));
11019
11020 // Update this selection's endpoints to reflect the indentation.
11021 if row == selection.start.row {
11022 selection.start.column += indent_delta.len;
11023 }
11024 if row == selection.end.row {
11025 selection.end.column += indent_delta.len;
11026 delta_for_end_row = indent_delta.len;
11027 }
11028 }
11029
11030 if selection.start.row == selection.end.row {
11031 delta_for_start_row + delta_for_end_row
11032 } else {
11033 delta_for_end_row
11034 }
11035 }
11036
11037 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11038 if self.read_only(cx) {
11039 return;
11040 }
11041 if self.mode.is_single_line() {
11042 cx.propagate();
11043 return;
11044 }
11045
11046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11048 let selections = self.selections.all::<Point>(&display_map);
11049 let mut deletion_ranges = Vec::new();
11050 let mut last_outdent = None;
11051 {
11052 let buffer = self.buffer.read(cx);
11053 let snapshot = buffer.snapshot(cx);
11054 for selection in &selections {
11055 let settings = buffer.language_settings_at(selection.start, cx);
11056 let tab_size = settings.tab_size.get();
11057 let mut rows = selection.spanned_rows(false, &display_map);
11058
11059 // Avoid re-outdenting a row that has already been outdented by a
11060 // previous selection.
11061 if let Some(last_row) = last_outdent
11062 && last_row == rows.start
11063 {
11064 rows.start = rows.start.next_row();
11065 }
11066 let has_multiple_rows = rows.len() > 1;
11067 for row in rows.iter_rows() {
11068 let indent_size = snapshot.indent_size_for_line(row);
11069 if indent_size.len > 0 {
11070 let deletion_len = match indent_size.kind {
11071 IndentKind::Space => {
11072 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11073 if columns_to_prev_tab_stop == 0 {
11074 tab_size
11075 } else {
11076 columns_to_prev_tab_stop
11077 }
11078 }
11079 IndentKind::Tab => 1,
11080 };
11081 let start = if has_multiple_rows
11082 || deletion_len > selection.start.column
11083 || indent_size.len < selection.start.column
11084 {
11085 0
11086 } else {
11087 selection.start.column - deletion_len
11088 };
11089 deletion_ranges.push(
11090 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11091 );
11092 last_outdent = Some(row);
11093 }
11094 }
11095 }
11096 }
11097
11098 self.transact(window, cx, |this, window, cx| {
11099 this.buffer.update(cx, |buffer, cx| {
11100 let empty_str: Arc<str> = Arc::default();
11101 buffer.edit(
11102 deletion_ranges
11103 .into_iter()
11104 .map(|range| (range, empty_str.clone())),
11105 None,
11106 cx,
11107 );
11108 });
11109 let selections = this
11110 .selections
11111 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11112 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11113 });
11114 }
11115
11116 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11117 if self.read_only(cx) {
11118 return;
11119 }
11120 if self.mode.is_single_line() {
11121 cx.propagate();
11122 return;
11123 }
11124
11125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11126 let selections = self
11127 .selections
11128 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11129 .into_iter()
11130 .map(|s| s.range());
11131
11132 self.transact(window, cx, |this, window, cx| {
11133 this.buffer.update(cx, |buffer, cx| {
11134 buffer.autoindent_ranges(selections, cx);
11135 });
11136 let selections = this
11137 .selections
11138 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11139 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11140 });
11141 }
11142
11143 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11145 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11146 let selections = self.selections.all::<Point>(&display_map);
11147
11148 let mut new_cursors = Vec::new();
11149 let mut edit_ranges = Vec::new();
11150 let mut selections = selections.iter().peekable();
11151 while let Some(selection) = selections.next() {
11152 let mut rows = selection.spanned_rows(false, &display_map);
11153
11154 // Accumulate contiguous regions of rows that we want to delete.
11155 while let Some(next_selection) = selections.peek() {
11156 let next_rows = next_selection.spanned_rows(false, &display_map);
11157 if next_rows.start <= rows.end {
11158 rows.end = next_rows.end;
11159 selections.next().unwrap();
11160 } else {
11161 break;
11162 }
11163 }
11164
11165 let buffer = display_map.buffer_snapshot();
11166 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11167 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11168 // If there's a line after the range, delete the \n from the end of the row range
11169 (
11170 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11171 rows.end,
11172 )
11173 } else {
11174 // If there isn't a line after the range, delete the \n from the line before the
11175 // start of the row range
11176 edit_start = edit_start.saturating_sub_usize(1);
11177 (buffer.len(), rows.start.previous_row())
11178 };
11179
11180 let text_layout_details = self.text_layout_details(window, cx);
11181 let x = display_map.x_for_display_point(
11182 selection.head().to_display_point(&display_map),
11183 &text_layout_details,
11184 );
11185 let row = Point::new(target_row.0, 0)
11186 .to_display_point(&display_map)
11187 .row();
11188 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11189
11190 new_cursors.push((
11191 selection.id,
11192 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11193 SelectionGoal::None,
11194 ));
11195 edit_ranges.push(edit_start..edit_end);
11196 }
11197
11198 self.transact(window, cx, |this, window, cx| {
11199 let buffer = this.buffer.update(cx, |buffer, cx| {
11200 let empty_str: Arc<str> = Arc::default();
11201 buffer.edit(
11202 edit_ranges
11203 .into_iter()
11204 .map(|range| (range, empty_str.clone())),
11205 None,
11206 cx,
11207 );
11208 buffer.snapshot(cx)
11209 });
11210 let new_selections = new_cursors
11211 .into_iter()
11212 .map(|(id, cursor, goal)| {
11213 let cursor = cursor.to_point(&buffer);
11214 Selection {
11215 id,
11216 start: cursor,
11217 end: cursor,
11218 reversed: false,
11219 goal,
11220 }
11221 })
11222 .collect();
11223
11224 this.change_selections(Default::default(), window, cx, |s| {
11225 s.select(new_selections);
11226 });
11227 });
11228 }
11229
11230 pub fn join_lines_impl(
11231 &mut self,
11232 insert_whitespace: bool,
11233 window: &mut Window,
11234 cx: &mut Context<Self>,
11235 ) {
11236 if self.read_only(cx) {
11237 return;
11238 }
11239 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11240 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11241 let start = MultiBufferRow(selection.start.row);
11242 // Treat single line selections as if they include the next line. Otherwise this action
11243 // would do nothing for single line selections individual cursors.
11244 let end = if selection.start.row == selection.end.row {
11245 MultiBufferRow(selection.start.row + 1)
11246 } else {
11247 MultiBufferRow(selection.end.row)
11248 };
11249
11250 if let Some(last_row_range) = row_ranges.last_mut()
11251 && start <= last_row_range.end
11252 {
11253 last_row_range.end = end;
11254 continue;
11255 }
11256 row_ranges.push(start..end);
11257 }
11258
11259 let snapshot = self.buffer.read(cx).snapshot(cx);
11260 let mut cursor_positions = Vec::new();
11261 for row_range in &row_ranges {
11262 let anchor = snapshot.anchor_before(Point::new(
11263 row_range.end.previous_row().0,
11264 snapshot.line_len(row_range.end.previous_row()),
11265 ));
11266 cursor_positions.push(anchor..anchor);
11267 }
11268
11269 self.transact(window, cx, |this, window, cx| {
11270 for row_range in row_ranges.into_iter().rev() {
11271 for row in row_range.iter_rows().rev() {
11272 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11273 let next_line_row = row.next_row();
11274 let indent = snapshot.indent_size_for_line(next_line_row);
11275 let start_of_next_line = Point::new(next_line_row.0, indent.len);
11276
11277 let replace =
11278 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11279 " "
11280 } else {
11281 ""
11282 };
11283
11284 this.buffer.update(cx, |buffer, cx| {
11285 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11286 });
11287 }
11288 }
11289
11290 this.change_selections(Default::default(), window, cx, |s| {
11291 s.select_anchor_ranges(cursor_positions)
11292 });
11293 });
11294 }
11295
11296 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11297 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11298 self.join_lines_impl(true, window, cx);
11299 }
11300
11301 pub fn sort_lines_case_sensitive(
11302 &mut self,
11303 _: &SortLinesCaseSensitive,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11308 }
11309
11310 pub fn sort_lines_by_length(
11311 &mut self,
11312 _: &SortLinesByLength,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 self.manipulate_immutable_lines(window, cx, |lines| {
11317 lines.sort_by_key(|&line| line.chars().count())
11318 })
11319 }
11320
11321 pub fn sort_lines_case_insensitive(
11322 &mut self,
11323 _: &SortLinesCaseInsensitive,
11324 window: &mut Window,
11325 cx: &mut Context<Self>,
11326 ) {
11327 self.manipulate_immutable_lines(window, cx, |lines| {
11328 lines.sort_by_key(|line| line.to_lowercase())
11329 })
11330 }
11331
11332 pub fn unique_lines_case_insensitive(
11333 &mut self,
11334 _: &UniqueLinesCaseInsensitive,
11335 window: &mut Window,
11336 cx: &mut Context<Self>,
11337 ) {
11338 self.manipulate_immutable_lines(window, cx, |lines| {
11339 let mut seen = HashSet::default();
11340 lines.retain(|line| seen.insert(line.to_lowercase()));
11341 })
11342 }
11343
11344 pub fn unique_lines_case_sensitive(
11345 &mut self,
11346 _: &UniqueLinesCaseSensitive,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_immutable_lines(window, cx, |lines| {
11351 let mut seen = HashSet::default();
11352 lines.retain(|line| seen.insert(*line));
11353 })
11354 }
11355
11356 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11357 let snapshot = self.buffer.read(cx).snapshot(cx);
11358 for selection in self.selections.disjoint_anchors_arc().iter() {
11359 if snapshot
11360 .language_at(selection.start)
11361 .and_then(|lang| lang.config().wrap_characters.as_ref())
11362 .is_some()
11363 {
11364 return true;
11365 }
11366 }
11367 false
11368 }
11369
11370 fn wrap_selections_in_tag(
11371 &mut self,
11372 _: &WrapSelectionsInTag,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11377
11378 let snapshot = self.buffer.read(cx).snapshot(cx);
11379
11380 let mut edits = Vec::new();
11381 let mut boundaries = Vec::new();
11382
11383 for selection in self
11384 .selections
11385 .all_adjusted(&self.display_snapshot(cx))
11386 .iter()
11387 {
11388 let Some(wrap_config) = snapshot
11389 .language_at(selection.start)
11390 .and_then(|lang| lang.config().wrap_characters.clone())
11391 else {
11392 continue;
11393 };
11394
11395 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11396 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11397
11398 let start_before = snapshot.anchor_before(selection.start);
11399 let end_after = snapshot.anchor_after(selection.end);
11400
11401 edits.push((start_before..start_before, open_tag));
11402 edits.push((end_after..end_after, close_tag));
11403
11404 boundaries.push((
11405 start_before,
11406 end_after,
11407 wrap_config.start_prefix.len(),
11408 wrap_config.end_suffix.len(),
11409 ));
11410 }
11411
11412 if edits.is_empty() {
11413 return;
11414 }
11415
11416 self.transact(window, cx, |this, window, cx| {
11417 let buffer = this.buffer.update(cx, |buffer, cx| {
11418 buffer.edit(edits, None, cx);
11419 buffer.snapshot(cx)
11420 });
11421
11422 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11423 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11424 boundaries.into_iter()
11425 {
11426 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11427 let close_offset = end_after
11428 .to_offset(&buffer)
11429 .saturating_sub_usize(end_suffix_len);
11430 new_selections.push(open_offset..open_offset);
11431 new_selections.push(close_offset..close_offset);
11432 }
11433
11434 this.change_selections(Default::default(), window, cx, |s| {
11435 s.select_ranges(new_selections);
11436 });
11437
11438 this.request_autoscroll(Autoscroll::fit(), cx);
11439 });
11440 }
11441
11442 pub fn toggle_read_only(
11443 &mut self,
11444 _: &workspace::ToggleReadOnlyFile,
11445 _: &mut Window,
11446 cx: &mut Context<Self>,
11447 ) {
11448 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11449 buffer.update(cx, |buffer, cx| {
11450 buffer.set_capability(
11451 match buffer.capability() {
11452 Capability::ReadWrite => Capability::Read,
11453 Capability::Read => Capability::ReadWrite,
11454 Capability::ReadOnly => Capability::ReadOnly,
11455 },
11456 cx,
11457 );
11458 })
11459 }
11460 }
11461
11462 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11463 let Some(project) = self.project.clone() else {
11464 return;
11465 };
11466 self.reload(project, window, cx)
11467 .detach_and_notify_err(window, cx);
11468 }
11469
11470 pub fn restore_file(
11471 &mut self,
11472 _: &::git::RestoreFile,
11473 window: &mut Window,
11474 cx: &mut Context<Self>,
11475 ) {
11476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11477 let mut buffer_ids = HashSet::default();
11478 let snapshot = self.buffer().read(cx).snapshot(cx);
11479 for selection in self
11480 .selections
11481 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11482 {
11483 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11484 }
11485
11486 let buffer = self.buffer().read(cx);
11487 let ranges = buffer_ids
11488 .into_iter()
11489 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11490 .collect::<Vec<_>>();
11491
11492 self.restore_hunks_in_ranges(ranges, window, cx);
11493 }
11494
11495 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11497 let selections = self
11498 .selections
11499 .all(&self.display_snapshot(cx))
11500 .into_iter()
11501 .map(|s| s.range())
11502 .collect();
11503 self.restore_hunks_in_ranges(selections, window, cx);
11504 }
11505
11506 pub fn restore_hunks_in_ranges(
11507 &mut self,
11508 ranges: Vec<Range<Point>>,
11509 window: &mut Window,
11510 cx: &mut Context<Editor>,
11511 ) {
11512 if self.delegate_stage_and_restore {
11513 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11514 if !hunks.is_empty() {
11515 cx.emit(EditorEvent::RestoreRequested { hunks });
11516 }
11517 return;
11518 }
11519 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11520 self.transact(window, cx, |editor, window, cx| {
11521 editor.restore_diff_hunks(hunks, cx);
11522 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11523 selections.refresh()
11524 });
11525 });
11526 }
11527
11528 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11529 let mut revert_changes = HashMap::default();
11530 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11531 for (buffer_id, hunks) in &chunk_by {
11532 let hunks = hunks.collect::<Vec<_>>();
11533 for hunk in &hunks {
11534 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11535 }
11536 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11537 }
11538 if !revert_changes.is_empty() {
11539 self.buffer().update(cx, |multi_buffer, cx| {
11540 for (buffer_id, changes) in revert_changes {
11541 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11542 buffer.update(cx, |buffer, cx| {
11543 buffer.edit(
11544 changes
11545 .into_iter()
11546 .map(|(range, text)| (range, text.to_string())),
11547 None,
11548 cx,
11549 );
11550 });
11551 }
11552 }
11553 });
11554 }
11555 }
11556
11557 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11558 if let Some(status) = self
11559 .addons
11560 .iter()
11561 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11562 {
11563 return Some(status);
11564 }
11565 self.project
11566 .as_ref()?
11567 .read(cx)
11568 .status_for_buffer_id(buffer_id, cx)
11569 }
11570
11571 pub fn open_active_item_in_terminal(
11572 &mut self,
11573 _: &OpenInTerminal,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 ) {
11577 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11578 let project_path = buffer.read(cx).project_path(cx)?;
11579 let project = self.project()?.read(cx);
11580 let entry = project.entry_for_path(&project_path, cx)?;
11581 let parent = match &entry.canonical_path {
11582 Some(canonical_path) => canonical_path.to_path_buf(),
11583 None => project.absolute_path(&project_path, cx)?,
11584 }
11585 .parent()?
11586 .to_path_buf();
11587 Some(parent)
11588 }) {
11589 window.dispatch_action(
11590 OpenTerminal {
11591 working_directory,
11592 local: false,
11593 }
11594 .boxed_clone(),
11595 cx,
11596 );
11597 }
11598 }
11599
11600 fn set_breakpoint_context_menu(
11601 &mut self,
11602 display_row: DisplayRow,
11603 position: Option<Anchor>,
11604 clicked_point: gpui::Point<Pixels>,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 let source = self
11609 .buffer
11610 .read(cx)
11611 .snapshot(cx)
11612 .anchor_before(Point::new(display_row.0, 0u32));
11613
11614 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11615
11616 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11617 self,
11618 source,
11619 clicked_point,
11620 context_menu,
11621 window,
11622 cx,
11623 );
11624 }
11625
11626 fn add_edit_breakpoint_block(
11627 &mut self,
11628 anchor: Anchor,
11629 breakpoint: &Breakpoint,
11630 edit_action: BreakpointPromptEditAction,
11631 window: &mut Window,
11632 cx: &mut Context<Self>,
11633 ) {
11634 let weak_editor = cx.weak_entity();
11635 let bp_prompt = cx.new(|cx| {
11636 BreakpointPromptEditor::new(
11637 weak_editor,
11638 anchor,
11639 breakpoint.clone(),
11640 edit_action,
11641 window,
11642 cx,
11643 )
11644 });
11645
11646 let height = bp_prompt.update(cx, |this, cx| {
11647 this.prompt
11648 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11649 });
11650 let cloned_prompt = bp_prompt.clone();
11651 let blocks = vec![BlockProperties {
11652 style: BlockStyle::Sticky,
11653 placement: BlockPlacement::Above(anchor),
11654 height: Some(height),
11655 render: Arc::new(move |cx| {
11656 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11657 cloned_prompt.clone().into_any_element()
11658 }),
11659 priority: 0,
11660 }];
11661
11662 let focus_handle = bp_prompt.focus_handle(cx);
11663 window.focus(&focus_handle, cx);
11664
11665 let block_ids = self.insert_blocks(blocks, None, cx);
11666 bp_prompt.update(cx, |prompt, _| {
11667 prompt.add_block_ids(block_ids);
11668 });
11669 }
11670
11671 pub(crate) fn breakpoint_at_row(
11672 &self,
11673 row: u32,
11674 window: &mut Window,
11675 cx: &mut Context<Self>,
11676 ) -> Option<(Anchor, Breakpoint)> {
11677 let snapshot = self.snapshot(window, cx);
11678 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11679
11680 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11681 }
11682
11683 pub(crate) fn breakpoint_at_anchor(
11684 &self,
11685 breakpoint_position: Anchor,
11686 snapshot: &EditorSnapshot,
11687 cx: &mut Context<Self>,
11688 ) -> Option<(Anchor, Breakpoint)> {
11689 let buffer = self
11690 .buffer
11691 .read(cx)
11692 .buffer_for_anchor(breakpoint_position, cx)?;
11693
11694 let enclosing_excerpt = breakpoint_position.excerpt_id;
11695 let buffer_snapshot = buffer.read(cx).snapshot();
11696
11697 let row = buffer_snapshot
11698 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11699 .row;
11700
11701 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11702 let anchor_end = snapshot
11703 .buffer_snapshot()
11704 .anchor_after(Point::new(row, line_len));
11705
11706 self.breakpoint_store
11707 .as_ref()?
11708 .read_with(cx, |breakpoint_store, cx| {
11709 breakpoint_store
11710 .breakpoints(
11711 &buffer,
11712 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11713 &buffer_snapshot,
11714 cx,
11715 )
11716 .next()
11717 .and_then(|(bp, _)| {
11718 let breakpoint_row = buffer_snapshot
11719 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11720 .row;
11721
11722 if breakpoint_row == row {
11723 snapshot
11724 .buffer_snapshot()
11725 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11726 .map(|position| (position, bp.bp.clone()))
11727 } else {
11728 None
11729 }
11730 })
11731 })
11732 }
11733
11734 pub fn edit_log_breakpoint(
11735 &mut self,
11736 _: &EditLogBreakpoint,
11737 window: &mut Window,
11738 cx: &mut Context<Self>,
11739 ) {
11740 if self.breakpoint_store.is_none() {
11741 return;
11742 }
11743
11744 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11745 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11746 message: None,
11747 state: BreakpointState::Enabled,
11748 condition: None,
11749 hit_condition: None,
11750 });
11751
11752 self.add_edit_breakpoint_block(
11753 anchor,
11754 &breakpoint,
11755 BreakpointPromptEditAction::Log,
11756 window,
11757 cx,
11758 );
11759 }
11760 }
11761
11762 fn breakpoints_at_cursors(
11763 &self,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11767 let snapshot = self.snapshot(window, cx);
11768 let cursors = self
11769 .selections
11770 .disjoint_anchors_arc()
11771 .iter()
11772 .map(|selection| {
11773 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11774
11775 let breakpoint_position = self
11776 .breakpoint_at_row(cursor_position.row, window, cx)
11777 .map(|bp| bp.0)
11778 .unwrap_or_else(|| {
11779 snapshot
11780 .display_snapshot
11781 .buffer_snapshot()
11782 .anchor_after(Point::new(cursor_position.row, 0))
11783 });
11784
11785 let breakpoint = self
11786 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11787 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11788
11789 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11790 })
11791 // 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.
11792 .collect::<HashMap<Anchor, _>>();
11793
11794 cursors.into_iter().collect()
11795 }
11796
11797 pub fn enable_breakpoint(
11798 &mut self,
11799 _: &crate::actions::EnableBreakpoint,
11800 window: &mut Window,
11801 cx: &mut Context<Self>,
11802 ) {
11803 if self.breakpoint_store.is_none() {
11804 return;
11805 }
11806
11807 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11808 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11809 continue;
11810 };
11811 self.edit_breakpoint_at_anchor(
11812 anchor,
11813 breakpoint,
11814 BreakpointEditAction::InvertState,
11815 cx,
11816 );
11817 }
11818 }
11819
11820 pub fn disable_breakpoint(
11821 &mut self,
11822 _: &crate::actions::DisableBreakpoint,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 if self.breakpoint_store.is_none() {
11827 return;
11828 }
11829
11830 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11831 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11832 continue;
11833 };
11834 self.edit_breakpoint_at_anchor(
11835 anchor,
11836 breakpoint,
11837 BreakpointEditAction::InvertState,
11838 cx,
11839 );
11840 }
11841 }
11842
11843 pub fn toggle_breakpoint(
11844 &mut self,
11845 _: &crate::actions::ToggleBreakpoint,
11846 window: &mut Window,
11847 cx: &mut Context<Self>,
11848 ) {
11849 if self.breakpoint_store.is_none() {
11850 return;
11851 }
11852
11853 let snapshot = self.snapshot(window, cx);
11854 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11855 if self.gutter_breakpoint_indicator.0.is_some() {
11856 let display_row = anchor
11857 .to_point(snapshot.buffer_snapshot())
11858 .to_display_point(&snapshot.display_snapshot)
11859 .row();
11860 self.update_breakpoint_collision_on_toggle(
11861 display_row,
11862 &BreakpointEditAction::Toggle,
11863 );
11864 }
11865
11866 if let Some(breakpoint) = breakpoint {
11867 self.edit_breakpoint_at_anchor(
11868 anchor,
11869 breakpoint,
11870 BreakpointEditAction::Toggle,
11871 cx,
11872 );
11873 } else {
11874 self.edit_breakpoint_at_anchor(
11875 anchor,
11876 Breakpoint::new_standard(),
11877 BreakpointEditAction::Toggle,
11878 cx,
11879 );
11880 }
11881 }
11882 }
11883
11884 fn update_breakpoint_collision_on_toggle(
11885 &mut self,
11886 display_row: DisplayRow,
11887 edit_action: &BreakpointEditAction,
11888 ) {
11889 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
11890 if breakpoint_indicator.display_row == display_row
11891 && matches!(edit_action, BreakpointEditAction::Toggle)
11892 {
11893 breakpoint_indicator.collides_with_existing_breakpoint =
11894 !breakpoint_indicator.collides_with_existing_breakpoint;
11895 }
11896 }
11897 }
11898
11899 pub fn edit_breakpoint_at_anchor(
11900 &mut self,
11901 breakpoint_position: Anchor,
11902 breakpoint: Breakpoint,
11903 edit_action: BreakpointEditAction,
11904 cx: &mut Context<Self>,
11905 ) {
11906 let Some(breakpoint_store) = &self.breakpoint_store else {
11907 return;
11908 };
11909
11910 let Some(buffer) = self
11911 .buffer
11912 .read(cx)
11913 .buffer_for_anchor(breakpoint_position, cx)
11914 else {
11915 return;
11916 };
11917
11918 breakpoint_store.update(cx, |breakpoint_store, cx| {
11919 breakpoint_store.toggle_breakpoint(
11920 buffer,
11921 BreakpointWithPosition {
11922 position: breakpoint_position.text_anchor,
11923 bp: breakpoint,
11924 },
11925 edit_action,
11926 cx,
11927 );
11928 });
11929
11930 cx.notify();
11931 }
11932
11933 #[cfg(any(test, feature = "test-support"))]
11934 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11935 self.breakpoint_store.clone()
11936 }
11937
11938 pub fn prepare_restore_change(
11939 &self,
11940 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11941 hunk: &MultiBufferDiffHunk,
11942 cx: &mut App,
11943 ) -> Option<()> {
11944 if hunk.is_created_file() {
11945 return None;
11946 }
11947 let buffer = self.buffer.read(cx);
11948 let diff = buffer.diff_for(hunk.buffer_id)?;
11949 let buffer = buffer.buffer(hunk.buffer_id)?;
11950 let buffer = buffer.read(cx);
11951 let original_text = diff
11952 .read(cx)
11953 .base_text(cx)
11954 .as_rope()
11955 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11956 let buffer_snapshot = buffer.snapshot();
11957 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11958 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11959 probe
11960 .0
11961 .start
11962 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11963 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11964 }) {
11965 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11966 Some(())
11967 } else {
11968 None
11969 }
11970 }
11971
11972 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11973 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11974 }
11975
11976 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11977 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11978 }
11979
11980 pub fn rotate_selections_forward(
11981 &mut self,
11982 _: &RotateSelectionsForward,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) {
11986 self.rotate_selections(window, cx, false)
11987 }
11988
11989 pub fn rotate_selections_backward(
11990 &mut self,
11991 _: &RotateSelectionsBackward,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.rotate_selections(window, cx, true)
11996 }
11997
11998 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12000 let display_snapshot = self.display_snapshot(cx);
12001 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12002
12003 if selections.len() < 2 {
12004 return;
12005 }
12006
12007 let (edits, new_selections) = {
12008 let buffer = self.buffer.read(cx).read(cx);
12009 let has_selections = selections.iter().any(|s| !s.is_empty());
12010 if has_selections {
12011 let mut selected_texts: Vec<String> = selections
12012 .iter()
12013 .map(|selection| {
12014 buffer
12015 .text_for_range(selection.start..selection.end)
12016 .collect()
12017 })
12018 .collect();
12019
12020 if reverse {
12021 selected_texts.rotate_left(1);
12022 } else {
12023 selected_texts.rotate_right(1);
12024 }
12025
12026 let mut offset_delta: i64 = 0;
12027 let mut new_selections = Vec::new();
12028 let edits: Vec<_> = selections
12029 .iter()
12030 .zip(selected_texts.iter())
12031 .map(|(selection, new_text)| {
12032 let old_len = (selection.end.0 - selection.start.0) as i64;
12033 let new_len = new_text.len() as i64;
12034 let adjusted_start =
12035 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12036 let adjusted_end =
12037 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12038
12039 new_selections.push(Selection {
12040 id: selection.id,
12041 start: adjusted_start,
12042 end: adjusted_end,
12043 reversed: selection.reversed,
12044 goal: selection.goal,
12045 });
12046
12047 offset_delta += new_len - old_len;
12048 (selection.start..selection.end, new_text.clone())
12049 })
12050 .collect();
12051 (edits, new_selections)
12052 } else {
12053 let mut all_rows: Vec<u32> = selections
12054 .iter()
12055 .map(|selection| buffer.offset_to_point(selection.start).row)
12056 .collect();
12057 all_rows.sort_unstable();
12058 all_rows.dedup();
12059
12060 if all_rows.len() < 2 {
12061 return;
12062 }
12063
12064 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12065 .iter()
12066 .map(|&row| {
12067 let start = Point::new(row, 0);
12068 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12069 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12070 })
12071 .collect();
12072
12073 let mut line_texts: Vec<String> = line_ranges
12074 .iter()
12075 .map(|range| buffer.text_for_range(range.clone()).collect())
12076 .collect();
12077
12078 if reverse {
12079 line_texts.rotate_left(1);
12080 } else {
12081 line_texts.rotate_right(1);
12082 }
12083
12084 let edits = line_ranges
12085 .iter()
12086 .zip(line_texts.iter())
12087 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12088 .collect();
12089
12090 let num_rows = all_rows.len();
12091 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12092 .iter()
12093 .enumerate()
12094 .map(|(i, &row)| (row, i))
12095 .collect();
12096
12097 // Compute new line start offsets after rotation (handles CRLF)
12098 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12099 let first_line_start = line_ranges[0].start.0;
12100 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12101 for text in line_texts.iter().take(num_rows - 1) {
12102 let prev_start = *new_line_starts.last().unwrap();
12103 new_line_starts.push(prev_start + text.len() + newline_len);
12104 }
12105
12106 let new_selections = selections
12107 .iter()
12108 .map(|selection| {
12109 let point = buffer.offset_to_point(selection.start);
12110 let old_index = row_to_index[&point.row];
12111 let new_index = if reverse {
12112 (old_index + num_rows - 1) % num_rows
12113 } else {
12114 (old_index + 1) % num_rows
12115 };
12116 let new_offset =
12117 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12118 Selection {
12119 id: selection.id,
12120 start: new_offset,
12121 end: new_offset,
12122 reversed: selection.reversed,
12123 goal: selection.goal,
12124 }
12125 })
12126 .collect();
12127
12128 (edits, new_selections)
12129 }
12130 };
12131
12132 self.transact(window, cx, |this, window, cx| {
12133 this.buffer.update(cx, |buffer, cx| {
12134 buffer.edit(edits, None, cx);
12135 });
12136 this.change_selections(Default::default(), window, cx, |s| {
12137 s.select(new_selections);
12138 });
12139 });
12140 }
12141
12142 fn manipulate_lines<M>(
12143 &mut self,
12144 window: &mut Window,
12145 cx: &mut Context<Self>,
12146 mut manipulate: M,
12147 ) where
12148 M: FnMut(&str) -> LineManipulationResult,
12149 {
12150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12151
12152 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12153 let buffer = self.buffer.read(cx).snapshot(cx);
12154
12155 let mut edits = Vec::new();
12156
12157 let selections = self.selections.all::<Point>(&display_map);
12158 let mut selections = selections.iter().peekable();
12159 let mut contiguous_row_selections = Vec::new();
12160 let mut new_selections = Vec::new();
12161 let mut added_lines = 0;
12162 let mut removed_lines = 0;
12163
12164 while let Some(selection) = selections.next() {
12165 let (start_row, end_row) = consume_contiguous_rows(
12166 &mut contiguous_row_selections,
12167 selection,
12168 &display_map,
12169 &mut selections,
12170 );
12171
12172 let start_point = Point::new(start_row.0, 0);
12173 let end_point = Point::new(
12174 end_row.previous_row().0,
12175 buffer.line_len(end_row.previous_row()),
12176 );
12177 let text = buffer
12178 .text_for_range(start_point..end_point)
12179 .collect::<String>();
12180
12181 let LineManipulationResult {
12182 new_text,
12183 line_count_before,
12184 line_count_after,
12185 } = manipulate(&text);
12186
12187 edits.push((start_point..end_point, new_text));
12188
12189 // Selections must change based on added and removed line count
12190 let start_row =
12191 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12192 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12193 new_selections.push(Selection {
12194 id: selection.id,
12195 start: start_row,
12196 end: end_row,
12197 goal: SelectionGoal::None,
12198 reversed: selection.reversed,
12199 });
12200
12201 if line_count_after > line_count_before {
12202 added_lines += line_count_after - line_count_before;
12203 } else if line_count_before > line_count_after {
12204 removed_lines += line_count_before - line_count_after;
12205 }
12206 }
12207
12208 self.transact(window, cx, |this, window, cx| {
12209 let buffer = this.buffer.update(cx, |buffer, cx| {
12210 buffer.edit(edits, None, cx);
12211 buffer.snapshot(cx)
12212 });
12213
12214 // Recalculate offsets on newly edited buffer
12215 let new_selections = new_selections
12216 .iter()
12217 .map(|s| {
12218 let start_point = Point::new(s.start.0, 0);
12219 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12220 Selection {
12221 id: s.id,
12222 start: buffer.point_to_offset(start_point),
12223 end: buffer.point_to_offset(end_point),
12224 goal: s.goal,
12225 reversed: s.reversed,
12226 }
12227 })
12228 .collect();
12229
12230 this.change_selections(Default::default(), window, cx, |s| {
12231 s.select(new_selections);
12232 });
12233
12234 this.request_autoscroll(Autoscroll::fit(), cx);
12235 });
12236 }
12237
12238 fn manipulate_immutable_lines<Fn>(
12239 &mut self,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 mut callback: Fn,
12243 ) where
12244 Fn: FnMut(&mut Vec<&str>),
12245 {
12246 self.manipulate_lines(window, cx, |text| {
12247 let mut lines: Vec<&str> = text.split('\n').collect();
12248 let line_count_before = lines.len();
12249
12250 callback(&mut lines);
12251
12252 LineManipulationResult {
12253 new_text: lines.join("\n"),
12254 line_count_before,
12255 line_count_after: lines.len(),
12256 }
12257 });
12258 }
12259
12260 fn manipulate_mutable_lines<Fn>(
12261 &mut self,
12262 window: &mut Window,
12263 cx: &mut Context<Self>,
12264 mut callback: Fn,
12265 ) where
12266 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12267 {
12268 self.manipulate_lines(window, cx, |text| {
12269 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12270 let line_count_before = lines.len();
12271
12272 callback(&mut lines);
12273
12274 LineManipulationResult {
12275 new_text: lines.join("\n"),
12276 line_count_before,
12277 line_count_after: lines.len(),
12278 }
12279 });
12280 }
12281
12282 pub fn convert_indentation_to_spaces(
12283 &mut self,
12284 _: &ConvertIndentationToSpaces,
12285 window: &mut Window,
12286 cx: &mut Context<Self>,
12287 ) {
12288 let settings = self.buffer.read(cx).language_settings(cx);
12289 let tab_size = settings.tab_size.get() as usize;
12290
12291 self.manipulate_mutable_lines(window, cx, |lines| {
12292 // Allocates a reasonably sized scratch buffer once for the whole loop
12293 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12294 // Avoids recomputing spaces that could be inserted many times
12295 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12296 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12297 .collect();
12298
12299 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12300 let mut chars = line.as_ref().chars();
12301 let mut col = 0;
12302 let mut changed = false;
12303
12304 for ch in chars.by_ref() {
12305 match ch {
12306 ' ' => {
12307 reindented_line.push(' ');
12308 col += 1;
12309 }
12310 '\t' => {
12311 // \t are converted to spaces depending on the current column
12312 let spaces_len = tab_size - (col % tab_size);
12313 reindented_line.extend(&space_cache[spaces_len - 1]);
12314 col += spaces_len;
12315 changed = true;
12316 }
12317 _ => {
12318 // If we dont append before break, the character is consumed
12319 reindented_line.push(ch);
12320 break;
12321 }
12322 }
12323 }
12324
12325 if !changed {
12326 reindented_line.clear();
12327 continue;
12328 }
12329 // Append the rest of the line and replace old reference with new one
12330 reindented_line.extend(chars);
12331 *line = Cow::Owned(reindented_line.clone());
12332 reindented_line.clear();
12333 }
12334 });
12335 }
12336
12337 pub fn convert_indentation_to_tabs(
12338 &mut self,
12339 _: &ConvertIndentationToTabs,
12340 window: &mut Window,
12341 cx: &mut Context<Self>,
12342 ) {
12343 let settings = self.buffer.read(cx).language_settings(cx);
12344 let tab_size = settings.tab_size.get() as usize;
12345
12346 self.manipulate_mutable_lines(window, cx, |lines| {
12347 // Allocates a reasonably sized buffer once for the whole loop
12348 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12349 // Avoids recomputing spaces that could be inserted many times
12350 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12351 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12352 .collect();
12353
12354 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12355 let mut chars = line.chars();
12356 let mut spaces_count = 0;
12357 let mut first_non_indent_char = None;
12358 let mut changed = false;
12359
12360 for ch in chars.by_ref() {
12361 match ch {
12362 ' ' => {
12363 // Keep track of spaces. Append \t when we reach tab_size
12364 spaces_count += 1;
12365 changed = true;
12366 if spaces_count == tab_size {
12367 reindented_line.push('\t');
12368 spaces_count = 0;
12369 }
12370 }
12371 '\t' => {
12372 reindented_line.push('\t');
12373 spaces_count = 0;
12374 }
12375 _ => {
12376 // Dont append it yet, we might have remaining spaces
12377 first_non_indent_char = Some(ch);
12378 break;
12379 }
12380 }
12381 }
12382
12383 if !changed {
12384 reindented_line.clear();
12385 continue;
12386 }
12387 // Remaining spaces that didn't make a full tab stop
12388 if spaces_count > 0 {
12389 reindented_line.extend(&space_cache[spaces_count - 1]);
12390 }
12391 // If we consume an extra character that was not indentation, add it back
12392 if let Some(extra_char) = first_non_indent_char {
12393 reindented_line.push(extra_char);
12394 }
12395 // Append the rest of the line and replace old reference with new one
12396 reindented_line.extend(chars);
12397 *line = Cow::Owned(reindented_line.clone());
12398 reindented_line.clear();
12399 }
12400 });
12401 }
12402
12403 pub fn convert_to_upper_case(
12404 &mut self,
12405 _: &ConvertToUpperCase,
12406 window: &mut Window,
12407 cx: &mut Context<Self>,
12408 ) {
12409 self.manipulate_text(window, cx, |text| text.to_uppercase())
12410 }
12411
12412 pub fn convert_to_lower_case(
12413 &mut self,
12414 _: &ConvertToLowerCase,
12415 window: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) {
12418 self.manipulate_text(window, cx, |text| text.to_lowercase())
12419 }
12420
12421 pub fn convert_to_title_case(
12422 &mut self,
12423 _: &ConvertToTitleCase,
12424 window: &mut Window,
12425 cx: &mut Context<Self>,
12426 ) {
12427 self.manipulate_text(window, cx, |text| {
12428 text.split('\n')
12429 .map(|line| line.to_case(Case::Title))
12430 .join("\n")
12431 })
12432 }
12433
12434 pub fn convert_to_snake_case(
12435 &mut self,
12436 _: &ConvertToSnakeCase,
12437 window: &mut Window,
12438 cx: &mut Context<Self>,
12439 ) {
12440 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12441 }
12442
12443 pub fn convert_to_kebab_case(
12444 &mut self,
12445 _: &ConvertToKebabCase,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12450 }
12451
12452 pub fn convert_to_upper_camel_case(
12453 &mut self,
12454 _: &ConvertToUpperCamelCase,
12455 window: &mut Window,
12456 cx: &mut Context<Self>,
12457 ) {
12458 self.manipulate_text(window, cx, |text| {
12459 text.split('\n')
12460 .map(|line| line.to_case(Case::UpperCamel))
12461 .join("\n")
12462 })
12463 }
12464
12465 pub fn convert_to_lower_camel_case(
12466 &mut self,
12467 _: &ConvertToLowerCamelCase,
12468 window: &mut Window,
12469 cx: &mut Context<Self>,
12470 ) {
12471 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12472 }
12473
12474 pub fn convert_to_opposite_case(
12475 &mut self,
12476 _: &ConvertToOppositeCase,
12477 window: &mut Window,
12478 cx: &mut Context<Self>,
12479 ) {
12480 self.manipulate_text(window, cx, |text| {
12481 text.chars()
12482 .fold(String::with_capacity(text.len()), |mut t, c| {
12483 if c.is_uppercase() {
12484 t.extend(c.to_lowercase());
12485 } else {
12486 t.extend(c.to_uppercase());
12487 }
12488 t
12489 })
12490 })
12491 }
12492
12493 pub fn convert_to_sentence_case(
12494 &mut self,
12495 _: &ConvertToSentenceCase,
12496 window: &mut Window,
12497 cx: &mut Context<Self>,
12498 ) {
12499 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12500 }
12501
12502 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12503 self.manipulate_text(window, cx, |text| {
12504 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12505 if has_upper_case_characters {
12506 text.to_lowercase()
12507 } else {
12508 text.to_uppercase()
12509 }
12510 })
12511 }
12512
12513 pub fn convert_to_rot13(
12514 &mut self,
12515 _: &ConvertToRot13,
12516 window: &mut Window,
12517 cx: &mut Context<Self>,
12518 ) {
12519 self.manipulate_text(window, cx, |text| {
12520 text.chars()
12521 .map(|c| match c {
12522 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12523 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12524 _ => c,
12525 })
12526 .collect()
12527 })
12528 }
12529
12530 pub fn convert_to_rot47(
12531 &mut self,
12532 _: &ConvertToRot47,
12533 window: &mut Window,
12534 cx: &mut Context<Self>,
12535 ) {
12536 self.manipulate_text(window, cx, |text| {
12537 text.chars()
12538 .map(|c| {
12539 let code_point = c as u32;
12540 if code_point >= 33 && code_point <= 126 {
12541 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12542 }
12543 c
12544 })
12545 .collect()
12546 })
12547 }
12548
12549 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12550 where
12551 Fn: FnMut(&str) -> String,
12552 {
12553 let buffer = self.buffer.read(cx).snapshot(cx);
12554
12555 let mut new_selections = Vec::new();
12556 let mut edits = Vec::new();
12557 let mut selection_adjustment = 0isize;
12558
12559 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12560 let selection_is_empty = selection.is_empty();
12561
12562 let (start, end) = if selection_is_empty {
12563 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12564 (word_range.start, word_range.end)
12565 } else {
12566 (
12567 buffer.point_to_offset(selection.start),
12568 buffer.point_to_offset(selection.end),
12569 )
12570 };
12571
12572 let text = buffer.text_for_range(start..end).collect::<String>();
12573 let old_length = text.len() as isize;
12574 let text = callback(&text);
12575
12576 new_selections.push(Selection {
12577 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12578 end: MultiBufferOffset(
12579 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12580 ),
12581 goal: SelectionGoal::None,
12582 id: selection.id,
12583 reversed: selection.reversed,
12584 });
12585
12586 selection_adjustment += old_length - text.len() as isize;
12587
12588 edits.push((start..end, text));
12589 }
12590
12591 self.transact(window, cx, |this, window, cx| {
12592 this.buffer.update(cx, |buffer, cx| {
12593 buffer.edit(edits, None, cx);
12594 });
12595
12596 this.change_selections(Default::default(), window, cx, |s| {
12597 s.select(new_selections);
12598 });
12599
12600 this.request_autoscroll(Autoscroll::fit(), cx);
12601 });
12602 }
12603
12604 pub fn move_selection_on_drop(
12605 &mut self,
12606 selection: &Selection<Anchor>,
12607 target: DisplayPoint,
12608 is_cut: bool,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12613 let buffer = display_map.buffer_snapshot();
12614 let mut edits = Vec::new();
12615 let insert_point = display_map
12616 .clip_point(target, Bias::Left)
12617 .to_point(&display_map);
12618 let text = buffer
12619 .text_for_range(selection.start..selection.end)
12620 .collect::<String>();
12621 if is_cut {
12622 edits.push(((selection.start..selection.end), String::new()));
12623 }
12624 let insert_anchor = buffer.anchor_before(insert_point);
12625 edits.push(((insert_anchor..insert_anchor), text));
12626 let last_edit_start = insert_anchor.bias_left(buffer);
12627 let last_edit_end = insert_anchor.bias_right(buffer);
12628 self.transact(window, cx, |this, window, cx| {
12629 this.buffer.update(cx, |buffer, cx| {
12630 buffer.edit(edits, None, cx);
12631 });
12632 this.change_selections(Default::default(), window, cx, |s| {
12633 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12634 });
12635 });
12636 }
12637
12638 pub fn clear_selection_drag_state(&mut self) {
12639 self.selection_drag_state = SelectionDragState::None;
12640 }
12641
12642 pub fn duplicate(
12643 &mut self,
12644 upwards: bool,
12645 whole_lines: bool,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) {
12649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12650
12651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12652 let buffer = display_map.buffer_snapshot();
12653 let selections = self.selections.all::<Point>(&display_map);
12654
12655 let mut edits = Vec::new();
12656 let mut selections_iter = selections.iter().peekable();
12657 while let Some(selection) = selections_iter.next() {
12658 let mut rows = selection.spanned_rows(false, &display_map);
12659 // duplicate line-wise
12660 if whole_lines || selection.start == selection.end {
12661 // Avoid duplicating the same lines twice.
12662 while let Some(next_selection) = selections_iter.peek() {
12663 let next_rows = next_selection.spanned_rows(false, &display_map);
12664 if next_rows.start < rows.end {
12665 rows.end = next_rows.end;
12666 selections_iter.next().unwrap();
12667 } else {
12668 break;
12669 }
12670 }
12671
12672 // Copy the text from the selected row region and splice it either at the start
12673 // or end of the region.
12674 let start = Point::new(rows.start.0, 0);
12675 let end = Point::new(
12676 rows.end.previous_row().0,
12677 buffer.line_len(rows.end.previous_row()),
12678 );
12679
12680 let mut text = buffer.text_for_range(start..end).collect::<String>();
12681
12682 let insert_location = if upwards {
12683 // When duplicating upward, we need to insert before the current line.
12684 // If we're on the last line and it doesn't end with a newline,
12685 // we need to add a newline before the duplicated content.
12686 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12687 && buffer.max_point().column > 0
12688 && !text.ends_with('\n');
12689
12690 if needs_leading_newline {
12691 text.insert(0, '\n');
12692 end
12693 } else {
12694 text.push('\n');
12695 Point::new(rows.start.0, 0)
12696 }
12697 } else {
12698 text.push('\n');
12699 start
12700 };
12701 edits.push((insert_location..insert_location, text));
12702 } else {
12703 // duplicate character-wise
12704 let start = selection.start;
12705 let end = selection.end;
12706 let text = buffer.text_for_range(start..end).collect::<String>();
12707 edits.push((selection.end..selection.end, text));
12708 }
12709 }
12710
12711 self.transact(window, cx, |this, window, cx| {
12712 this.buffer.update(cx, |buffer, cx| {
12713 buffer.edit(edits, None, cx);
12714 });
12715
12716 // When duplicating upward with whole lines, move the cursor to the duplicated line
12717 if upwards && whole_lines {
12718 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12719
12720 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12721 let mut new_ranges = Vec::new();
12722 let selections = s.all::<Point>(&display_map);
12723 let mut selections_iter = selections.iter().peekable();
12724
12725 while let Some(first_selection) = selections_iter.next() {
12726 // Group contiguous selections together to find the total row span
12727 let mut group_selections = vec![first_selection];
12728 let mut rows = first_selection.spanned_rows(false, &display_map);
12729
12730 while let Some(next_selection) = selections_iter.peek() {
12731 let next_rows = next_selection.spanned_rows(false, &display_map);
12732 if next_rows.start < rows.end {
12733 rows.end = next_rows.end;
12734 group_selections.push(selections_iter.next().unwrap());
12735 } else {
12736 break;
12737 }
12738 }
12739
12740 let row_count = rows.end.0 - rows.start.0;
12741
12742 // Move all selections in this group up by the total number of duplicated rows
12743 for selection in group_selections {
12744 let new_start = Point::new(
12745 selection.start.row.saturating_sub(row_count),
12746 selection.start.column,
12747 );
12748
12749 let new_end = Point::new(
12750 selection.end.row.saturating_sub(row_count),
12751 selection.end.column,
12752 );
12753
12754 new_ranges.push(new_start..new_end);
12755 }
12756 }
12757
12758 s.select_ranges(new_ranges);
12759 });
12760 }
12761
12762 this.request_autoscroll(Autoscroll::fit(), cx);
12763 });
12764 }
12765
12766 pub fn duplicate_line_up(
12767 &mut self,
12768 _: &DuplicateLineUp,
12769 window: &mut Window,
12770 cx: &mut Context<Self>,
12771 ) {
12772 self.duplicate(true, true, window, cx);
12773 }
12774
12775 pub fn duplicate_line_down(
12776 &mut self,
12777 _: &DuplicateLineDown,
12778 window: &mut Window,
12779 cx: &mut Context<Self>,
12780 ) {
12781 self.duplicate(false, true, window, cx);
12782 }
12783
12784 pub fn duplicate_selection(
12785 &mut self,
12786 _: &DuplicateSelection,
12787 window: &mut Window,
12788 cx: &mut Context<Self>,
12789 ) {
12790 self.duplicate(false, false, window, cx);
12791 }
12792
12793 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12795 if self.mode.is_single_line() {
12796 cx.propagate();
12797 return;
12798 }
12799
12800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12801 let buffer = self.buffer.read(cx).snapshot(cx);
12802
12803 let mut edits = Vec::new();
12804 let mut unfold_ranges = Vec::new();
12805 let mut refold_creases = Vec::new();
12806
12807 let selections = self.selections.all::<Point>(&display_map);
12808 let mut selections = selections.iter().peekable();
12809 let mut contiguous_row_selections = Vec::new();
12810 let mut new_selections = Vec::new();
12811
12812 while let Some(selection) = selections.next() {
12813 // Find all the selections that span a contiguous row range
12814 let (start_row, end_row) = consume_contiguous_rows(
12815 &mut contiguous_row_selections,
12816 selection,
12817 &display_map,
12818 &mut selections,
12819 );
12820
12821 // Move the text spanned by the row range to be before the line preceding the row range
12822 if start_row.0 > 0 {
12823 let range_to_move = Point::new(
12824 start_row.previous_row().0,
12825 buffer.line_len(start_row.previous_row()),
12826 )
12827 ..Point::new(
12828 end_row.previous_row().0,
12829 buffer.line_len(end_row.previous_row()),
12830 );
12831 let insertion_point = display_map
12832 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12833 .0;
12834
12835 // Don't move lines across excerpts
12836 if buffer
12837 .excerpt_containing(insertion_point..range_to_move.end)
12838 .is_some()
12839 {
12840 let text = buffer
12841 .text_for_range(range_to_move.clone())
12842 .flat_map(|s| s.chars())
12843 .skip(1)
12844 .chain(['\n'])
12845 .collect::<String>();
12846
12847 edits.push((
12848 buffer.anchor_after(range_to_move.start)
12849 ..buffer.anchor_before(range_to_move.end),
12850 String::new(),
12851 ));
12852 let insertion_anchor = buffer.anchor_after(insertion_point);
12853 edits.push((insertion_anchor..insertion_anchor, text));
12854
12855 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12856
12857 // Move selections up
12858 new_selections.extend(contiguous_row_selections.drain(..).map(
12859 |mut selection| {
12860 selection.start.row -= row_delta;
12861 selection.end.row -= row_delta;
12862 selection
12863 },
12864 ));
12865
12866 // Move folds up
12867 unfold_ranges.push(range_to_move.clone());
12868 for fold in display_map.folds_in_range(
12869 buffer.anchor_before(range_to_move.start)
12870 ..buffer.anchor_after(range_to_move.end),
12871 ) {
12872 let mut start = fold.range.start.to_point(&buffer);
12873 let mut end = fold.range.end.to_point(&buffer);
12874 start.row -= row_delta;
12875 end.row -= row_delta;
12876 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12877 }
12878 }
12879 }
12880
12881 // If we didn't move line(s), preserve the existing selections
12882 new_selections.append(&mut contiguous_row_selections);
12883 }
12884
12885 self.transact(window, cx, |this, window, cx| {
12886 this.unfold_ranges(&unfold_ranges, true, true, cx);
12887 this.buffer.update(cx, |buffer, cx| {
12888 for (range, text) in edits {
12889 buffer.edit([(range, text)], None, cx);
12890 }
12891 });
12892 this.fold_creases(refold_creases, true, window, cx);
12893 this.change_selections(Default::default(), window, cx, |s| {
12894 s.select(new_selections);
12895 })
12896 });
12897 }
12898
12899 pub fn move_line_down(
12900 &mut self,
12901 _: &MoveLineDown,
12902 window: &mut Window,
12903 cx: &mut Context<Self>,
12904 ) {
12905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12906 if self.mode.is_single_line() {
12907 cx.propagate();
12908 return;
12909 }
12910
12911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12912 let buffer = self.buffer.read(cx).snapshot(cx);
12913
12914 let mut edits = Vec::new();
12915 let mut unfold_ranges = Vec::new();
12916 let mut refold_creases = Vec::new();
12917
12918 let selections = self.selections.all::<Point>(&display_map);
12919 let mut selections = selections.iter().peekable();
12920 let mut contiguous_row_selections = Vec::new();
12921 let mut new_selections = Vec::new();
12922
12923 while let Some(selection) = selections.next() {
12924 // Find all the selections that span a contiguous row range
12925 let (start_row, end_row) = consume_contiguous_rows(
12926 &mut contiguous_row_selections,
12927 selection,
12928 &display_map,
12929 &mut selections,
12930 );
12931
12932 // Move the text spanned by the row range to be after the last line of the row range
12933 if end_row.0 <= buffer.max_point().row {
12934 let range_to_move =
12935 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12936 let insertion_point = display_map
12937 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12938 .0;
12939
12940 // Don't move lines across excerpt boundaries
12941 if buffer
12942 .excerpt_containing(range_to_move.start..insertion_point)
12943 .is_some()
12944 {
12945 let mut text = String::from("\n");
12946 text.extend(buffer.text_for_range(range_to_move.clone()));
12947 text.pop(); // Drop trailing newline
12948 edits.push((
12949 buffer.anchor_after(range_to_move.start)
12950 ..buffer.anchor_before(range_to_move.end),
12951 String::new(),
12952 ));
12953 let insertion_anchor = buffer.anchor_after(insertion_point);
12954 edits.push((insertion_anchor..insertion_anchor, text));
12955
12956 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12957
12958 // Move selections down
12959 new_selections.extend(contiguous_row_selections.drain(..).map(
12960 |mut selection| {
12961 selection.start.row += row_delta;
12962 selection.end.row += row_delta;
12963 selection
12964 },
12965 ));
12966
12967 // Move folds down
12968 unfold_ranges.push(range_to_move.clone());
12969 for fold in display_map.folds_in_range(
12970 buffer.anchor_before(range_to_move.start)
12971 ..buffer.anchor_after(range_to_move.end),
12972 ) {
12973 let mut start = fold.range.start.to_point(&buffer);
12974 let mut end = fold.range.end.to_point(&buffer);
12975 start.row += row_delta;
12976 end.row += row_delta;
12977 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12978 }
12979 }
12980 }
12981
12982 // If we didn't move line(s), preserve the existing selections
12983 new_selections.append(&mut contiguous_row_selections);
12984 }
12985
12986 self.transact(window, cx, |this, window, cx| {
12987 this.unfold_ranges(&unfold_ranges, true, true, cx);
12988 this.buffer.update(cx, |buffer, cx| {
12989 for (range, text) in edits {
12990 buffer.edit([(range, text)], None, cx);
12991 }
12992 });
12993 this.fold_creases(refold_creases, true, window, cx);
12994 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12995 });
12996 }
12997
12998 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13000 let text_layout_details = &self.text_layout_details(window, cx);
13001 self.transact(window, cx, |this, window, cx| {
13002 let edits = this.change_selections(Default::default(), window, cx, |s| {
13003 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13004 s.move_with(|display_map, selection| {
13005 if !selection.is_empty() {
13006 return;
13007 }
13008
13009 let mut head = selection.head();
13010 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13011 if head.column() == display_map.line_len(head.row()) {
13012 transpose_offset = display_map
13013 .buffer_snapshot()
13014 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13015 }
13016
13017 if transpose_offset == MultiBufferOffset(0) {
13018 return;
13019 }
13020
13021 *head.column_mut() += 1;
13022 head = display_map.clip_point(head, Bias::Right);
13023 let goal = SelectionGoal::HorizontalPosition(
13024 display_map
13025 .x_for_display_point(head, text_layout_details)
13026 .into(),
13027 );
13028 selection.collapse_to(head, goal);
13029
13030 let transpose_start = display_map
13031 .buffer_snapshot()
13032 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13033 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13034 let transpose_end = display_map
13035 .buffer_snapshot()
13036 .clip_offset(transpose_offset + 1usize, Bias::Right);
13037 if let Some(ch) = display_map
13038 .buffer_snapshot()
13039 .chars_at(transpose_start)
13040 .next()
13041 {
13042 edits.push((transpose_start..transpose_offset, String::new()));
13043 edits.push((transpose_end..transpose_end, ch.to_string()));
13044 }
13045 }
13046 });
13047 edits
13048 });
13049 this.buffer
13050 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13051 let selections = this
13052 .selections
13053 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13054 this.change_selections(Default::default(), window, cx, |s| {
13055 s.select(selections);
13056 });
13057 });
13058 }
13059
13060 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13061 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13062 if self.mode.is_single_line() {
13063 cx.propagate();
13064 return;
13065 }
13066
13067 self.rewrap_impl(RewrapOptions::default(), cx)
13068 }
13069
13070 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13071 let buffer = self.buffer.read(cx).snapshot(cx);
13072 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13073
13074 #[derive(Clone, Debug, PartialEq)]
13075 enum CommentFormat {
13076 /// single line comment, with prefix for line
13077 Line(String),
13078 /// single line within a block comment, with prefix for line
13079 BlockLine(String),
13080 /// a single line of a block comment that includes the initial delimiter
13081 BlockCommentWithStart(BlockCommentConfig),
13082 /// a single line of a block comment that includes the ending delimiter
13083 BlockCommentWithEnd(BlockCommentConfig),
13084 }
13085
13086 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13087 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13088 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13089 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13090 .peekable();
13091
13092 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13093 row
13094 } else {
13095 return Vec::new();
13096 };
13097
13098 let language_settings = buffer.language_settings_at(selection.head(), cx);
13099 let language_scope = buffer.language_scope_at(selection.head());
13100
13101 let indent_and_prefix_for_row =
13102 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13103 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13104 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13105 &language_scope
13106 {
13107 let indent_end = Point::new(row, indent.len);
13108 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13109 let line_text_after_indent = buffer
13110 .text_for_range(indent_end..line_end)
13111 .collect::<String>();
13112
13113 let is_within_comment_override = buffer
13114 .language_scope_at(indent_end)
13115 .is_some_and(|scope| scope.override_name() == Some("comment"));
13116 let comment_delimiters = if is_within_comment_override {
13117 // we are within a comment syntax node, but we don't
13118 // yet know what kind of comment: block, doc or line
13119 match (
13120 language_scope.documentation_comment(),
13121 language_scope.block_comment(),
13122 ) {
13123 (Some(config), _) | (_, Some(config))
13124 if buffer.contains_str_at(indent_end, &config.start) =>
13125 {
13126 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13127 }
13128 (Some(config), _) | (_, Some(config))
13129 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13130 {
13131 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13132 }
13133 (Some(config), _) | (_, Some(config))
13134 if buffer.contains_str_at(indent_end, &config.prefix) =>
13135 {
13136 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13137 }
13138 (_, _) => language_scope
13139 .line_comment_prefixes()
13140 .iter()
13141 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13142 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13143 }
13144 } else {
13145 // we not in an overridden comment node, but we may
13146 // be within a non-overridden line comment node
13147 language_scope
13148 .line_comment_prefixes()
13149 .iter()
13150 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13151 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13152 };
13153
13154 let rewrap_prefix = language_scope
13155 .rewrap_prefixes()
13156 .iter()
13157 .find_map(|prefix_regex| {
13158 prefix_regex.find(&line_text_after_indent).map(|mat| {
13159 if mat.start() == 0 {
13160 Some(mat.as_str().to_string())
13161 } else {
13162 None
13163 }
13164 })
13165 })
13166 .flatten();
13167 (comment_delimiters, rewrap_prefix)
13168 } else {
13169 (None, None)
13170 };
13171 (indent, comment_prefix, rewrap_prefix)
13172 };
13173
13174 let mut ranges = Vec::new();
13175 let from_empty_selection = selection.is_empty();
13176
13177 let mut current_range_start = first_row;
13178 let mut prev_row = first_row;
13179 let (
13180 mut current_range_indent,
13181 mut current_range_comment_delimiters,
13182 mut current_range_rewrap_prefix,
13183 ) = indent_and_prefix_for_row(first_row);
13184
13185 for row in non_blank_rows_iter.skip(1) {
13186 let has_paragraph_break = row > prev_row + 1;
13187
13188 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13189 indent_and_prefix_for_row(row);
13190
13191 let has_indent_change = row_indent != current_range_indent;
13192 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13193
13194 let has_boundary_change = has_comment_change
13195 || row_rewrap_prefix.is_some()
13196 || (has_indent_change && current_range_comment_delimiters.is_some());
13197
13198 if has_paragraph_break || has_boundary_change {
13199 ranges.push((
13200 language_settings.clone(),
13201 Point::new(current_range_start, 0)
13202 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13203 current_range_indent,
13204 current_range_comment_delimiters.clone(),
13205 current_range_rewrap_prefix.clone(),
13206 from_empty_selection,
13207 ));
13208 current_range_start = row;
13209 current_range_indent = row_indent;
13210 current_range_comment_delimiters = row_comment_delimiters;
13211 current_range_rewrap_prefix = row_rewrap_prefix;
13212 }
13213 prev_row = row;
13214 }
13215
13216 ranges.push((
13217 language_settings.clone(),
13218 Point::new(current_range_start, 0)
13219 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13220 current_range_indent,
13221 current_range_comment_delimiters,
13222 current_range_rewrap_prefix,
13223 from_empty_selection,
13224 ));
13225
13226 ranges
13227 });
13228
13229 let mut edits = Vec::new();
13230 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13231
13232 for (
13233 language_settings,
13234 wrap_range,
13235 mut indent_size,
13236 comment_prefix,
13237 rewrap_prefix,
13238 from_empty_selection,
13239 ) in wrap_ranges
13240 {
13241 let mut start_row = wrap_range.start.row;
13242 let mut end_row = wrap_range.end.row;
13243
13244 // Skip selections that overlap with a range that has already been rewrapped.
13245 let selection_range = start_row..end_row;
13246 if rewrapped_row_ranges
13247 .iter()
13248 .any(|range| range.overlaps(&selection_range))
13249 {
13250 continue;
13251 }
13252
13253 let tab_size = language_settings.tab_size;
13254
13255 let (line_prefix, inside_comment) = match &comment_prefix {
13256 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13257 (Some(prefix.as_str()), true)
13258 }
13259 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13260 (Some(prefix.as_ref()), true)
13261 }
13262 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13263 start: _,
13264 end: _,
13265 prefix,
13266 tab_size,
13267 })) => {
13268 indent_size.len += tab_size;
13269 (Some(prefix.as_ref()), true)
13270 }
13271 None => (None, false),
13272 };
13273 let indent_prefix = indent_size.chars().collect::<String>();
13274 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13275
13276 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13277 RewrapBehavior::InComments => inside_comment,
13278 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13279 RewrapBehavior::Anywhere => true,
13280 };
13281
13282 let should_rewrap = options.override_language_settings
13283 || allow_rewrap_based_on_language
13284 || self.hard_wrap.is_some();
13285 if !should_rewrap {
13286 continue;
13287 }
13288
13289 if from_empty_selection {
13290 'expand_upwards: while start_row > 0 {
13291 let prev_row = start_row - 1;
13292 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13293 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13294 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13295 {
13296 start_row = prev_row;
13297 } else {
13298 break 'expand_upwards;
13299 }
13300 }
13301
13302 'expand_downwards: while end_row < buffer.max_point().row {
13303 let next_row = end_row + 1;
13304 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13305 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13306 && !buffer.is_line_blank(MultiBufferRow(next_row))
13307 {
13308 end_row = next_row;
13309 } else {
13310 break 'expand_downwards;
13311 }
13312 }
13313 }
13314
13315 let start = Point::new(start_row, 0);
13316 let start_offset = ToOffset::to_offset(&start, &buffer);
13317 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13318 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13319 let mut first_line_delimiter = None;
13320 let mut last_line_delimiter = None;
13321 let Some(lines_without_prefixes) = selection_text
13322 .lines()
13323 .enumerate()
13324 .map(|(ix, line)| {
13325 let line_trimmed = line.trim_start();
13326 if rewrap_prefix.is_some() && ix > 0 {
13327 Ok(line_trimmed)
13328 } else if let Some(
13329 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13330 start,
13331 prefix,
13332 end,
13333 tab_size,
13334 })
13335 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13336 start,
13337 prefix,
13338 end,
13339 tab_size,
13340 }),
13341 ) = &comment_prefix
13342 {
13343 let line_trimmed = line_trimmed
13344 .strip_prefix(start.as_ref())
13345 .map(|s| {
13346 let mut indent_size = indent_size;
13347 indent_size.len -= tab_size;
13348 let indent_prefix: String = indent_size.chars().collect();
13349 first_line_delimiter = Some((indent_prefix, start));
13350 s.trim_start()
13351 })
13352 .unwrap_or(line_trimmed);
13353 let line_trimmed = line_trimmed
13354 .strip_suffix(end.as_ref())
13355 .map(|s| {
13356 last_line_delimiter = Some(end);
13357 s.trim_end()
13358 })
13359 .unwrap_or(line_trimmed);
13360 let line_trimmed = line_trimmed
13361 .strip_prefix(prefix.as_ref())
13362 .unwrap_or(line_trimmed);
13363 Ok(line_trimmed)
13364 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13365 line_trimmed.strip_prefix(prefix).with_context(|| {
13366 format!("line did not start with prefix {prefix:?}: {line:?}")
13367 })
13368 } else {
13369 line_trimmed
13370 .strip_prefix(&line_prefix.trim_start())
13371 .with_context(|| {
13372 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13373 })
13374 }
13375 })
13376 .collect::<Result<Vec<_>, _>>()
13377 .log_err()
13378 else {
13379 continue;
13380 };
13381
13382 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13383 buffer
13384 .language_settings_at(Point::new(start_row, 0), cx)
13385 .preferred_line_length as usize
13386 });
13387
13388 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13389 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13390 } else {
13391 line_prefix.clone()
13392 };
13393
13394 let wrapped_text = {
13395 let mut wrapped_text = wrap_with_prefix(
13396 line_prefix,
13397 subsequent_lines_prefix,
13398 lines_without_prefixes.join("\n"),
13399 wrap_column,
13400 tab_size,
13401 options.preserve_existing_whitespace,
13402 );
13403
13404 if let Some((indent, delimiter)) = first_line_delimiter {
13405 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13406 }
13407 if let Some(last_line) = last_line_delimiter {
13408 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13409 }
13410
13411 wrapped_text
13412 };
13413
13414 // TODO: should always use char-based diff while still supporting cursor behavior that
13415 // matches vim.
13416 let mut diff_options = DiffOptions::default();
13417 if options.override_language_settings {
13418 diff_options.max_word_diff_len = 0;
13419 diff_options.max_word_diff_line_count = 0;
13420 } else {
13421 diff_options.max_word_diff_len = usize::MAX;
13422 diff_options.max_word_diff_line_count = usize::MAX;
13423 }
13424
13425 for (old_range, new_text) in
13426 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13427 {
13428 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13429 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13430 edits.push((edit_start..edit_end, new_text));
13431 }
13432
13433 rewrapped_row_ranges.push(start_row..=end_row);
13434 }
13435
13436 self.buffer
13437 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13438 }
13439
13440 pub fn cut_common(
13441 &mut self,
13442 cut_no_selection_line: bool,
13443 window: &mut Window,
13444 cx: &mut Context<Self>,
13445 ) -> ClipboardItem {
13446 let mut text = String::new();
13447 let buffer = self.buffer.read(cx).snapshot(cx);
13448 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13449 let mut clipboard_selections = Vec::with_capacity(selections.len());
13450 {
13451 let max_point = buffer.max_point();
13452 let mut is_first = true;
13453 let mut prev_selection_was_entire_line = false;
13454 for selection in &mut selections {
13455 let is_entire_line =
13456 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13457 if is_entire_line {
13458 selection.start = Point::new(selection.start.row, 0);
13459 if !selection.is_empty() && selection.end.column == 0 {
13460 selection.end = cmp::min(max_point, selection.end);
13461 } else {
13462 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13463 }
13464 selection.goal = SelectionGoal::None;
13465 }
13466 if is_first {
13467 is_first = false;
13468 } else if !prev_selection_was_entire_line {
13469 text += "\n";
13470 }
13471 prev_selection_was_entire_line = is_entire_line;
13472 let mut len = 0;
13473 for chunk in buffer.text_for_range(selection.start..selection.end) {
13474 text.push_str(chunk);
13475 len += chunk.len();
13476 }
13477
13478 clipboard_selections.push(ClipboardSelection::for_buffer(
13479 len,
13480 is_entire_line,
13481 selection.range(),
13482 &buffer,
13483 self.project.as_ref(),
13484 cx,
13485 ));
13486 }
13487 }
13488
13489 self.transact(window, cx, |this, window, cx| {
13490 this.change_selections(Default::default(), window, cx, |s| {
13491 s.select(selections);
13492 });
13493 this.insert("", window, cx);
13494 });
13495 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13496 }
13497
13498 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13500 let item = self.cut_common(true, window, cx);
13501 cx.write_to_clipboard(item);
13502 }
13503
13504 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13506 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13507 s.move_with(|snapshot, sel| {
13508 if sel.is_empty() {
13509 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13510 }
13511 if sel.is_empty() {
13512 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13513 }
13514 });
13515 });
13516 let item = self.cut_common(false, window, cx);
13517 cx.set_global(KillRing(item))
13518 }
13519
13520 pub fn kill_ring_yank(
13521 &mut self,
13522 _: &KillRingYank,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13527 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13528 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13529 (kill_ring.text().to_string(), kill_ring.metadata_json())
13530 } else {
13531 return;
13532 }
13533 } else {
13534 return;
13535 };
13536 self.do_paste(&text, metadata, false, window, cx);
13537 }
13538
13539 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13540 self.do_copy(true, cx);
13541 }
13542
13543 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13544 self.do_copy(false, cx);
13545 }
13546
13547 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13548 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13549 let buffer = self.buffer.read(cx).read(cx);
13550 let mut text = String::new();
13551
13552 let mut clipboard_selections = Vec::with_capacity(selections.len());
13553 {
13554 let max_point = buffer.max_point();
13555 let mut is_first = true;
13556 let mut prev_selection_was_entire_line = false;
13557 for selection in &selections {
13558 let mut start = selection.start;
13559 let mut end = selection.end;
13560 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13561 let mut add_trailing_newline = false;
13562 if is_entire_line {
13563 start = Point::new(start.row, 0);
13564 let next_line_start = Point::new(end.row + 1, 0);
13565 if next_line_start <= max_point {
13566 end = next_line_start;
13567 } else {
13568 // We're on the last line without a trailing newline.
13569 // Copy to the end of the line and add a newline afterwards.
13570 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13571 add_trailing_newline = true;
13572 }
13573 }
13574
13575 let mut trimmed_selections = Vec::new();
13576 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13577 let row = MultiBufferRow(start.row);
13578 let first_indent = buffer.indent_size_for_line(row);
13579 if first_indent.len == 0 || start.column > first_indent.len {
13580 trimmed_selections.push(start..end);
13581 } else {
13582 trimmed_selections.push(
13583 Point::new(row.0, first_indent.len)
13584 ..Point::new(row.0, buffer.line_len(row)),
13585 );
13586 for row in start.row + 1..=end.row {
13587 let mut line_len = buffer.line_len(MultiBufferRow(row));
13588 if row == end.row {
13589 line_len = end.column;
13590 }
13591 if line_len == 0 {
13592 trimmed_selections
13593 .push(Point::new(row, 0)..Point::new(row, line_len));
13594 continue;
13595 }
13596 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13597 if row_indent_size.len >= first_indent.len {
13598 trimmed_selections.push(
13599 Point::new(row, first_indent.len)..Point::new(row, line_len),
13600 );
13601 } else {
13602 trimmed_selections.clear();
13603 trimmed_selections.push(start..end);
13604 break;
13605 }
13606 }
13607 }
13608 } else {
13609 trimmed_selections.push(start..end);
13610 }
13611
13612 let is_multiline_trim = trimmed_selections.len() > 1;
13613 for trimmed_range in trimmed_selections {
13614 if is_first {
13615 is_first = false;
13616 } else if is_multiline_trim || !prev_selection_was_entire_line {
13617 text += "\n";
13618 }
13619 prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13620 let mut len = 0;
13621 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13622 text.push_str(chunk);
13623 len += chunk.len();
13624 }
13625 if add_trailing_newline {
13626 text.push('\n');
13627 len += 1;
13628 }
13629 clipboard_selections.push(ClipboardSelection::for_buffer(
13630 len,
13631 is_entire_line,
13632 trimmed_range,
13633 &buffer,
13634 self.project.as_ref(),
13635 cx,
13636 ));
13637 }
13638 }
13639 }
13640
13641 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13642 text,
13643 clipboard_selections,
13644 ));
13645 }
13646
13647 pub fn do_paste(
13648 &mut self,
13649 text: &String,
13650 clipboard_selections: Option<Vec<ClipboardSelection>>,
13651 handle_entire_lines: bool,
13652 window: &mut Window,
13653 cx: &mut Context<Self>,
13654 ) {
13655 if self.read_only(cx) {
13656 return;
13657 }
13658
13659 let clipboard_text = Cow::Borrowed(text.as_str());
13660
13661 self.transact(window, cx, |this, window, cx| {
13662 let had_active_edit_prediction = this.has_active_edit_prediction();
13663 let display_map = this.display_snapshot(cx);
13664 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13665 let cursor_offset = this
13666 .selections
13667 .last::<MultiBufferOffset>(&display_map)
13668 .head();
13669
13670 if let Some(mut clipboard_selections) = clipboard_selections {
13671 let all_selections_were_entire_line =
13672 clipboard_selections.iter().all(|s| s.is_entire_line);
13673 let first_selection_indent_column =
13674 clipboard_selections.first().map(|s| s.first_line_indent);
13675 if clipboard_selections.len() != old_selections.len() {
13676 clipboard_selections.drain(..);
13677 }
13678 let mut auto_indent_on_paste = true;
13679
13680 this.buffer.update(cx, |buffer, cx| {
13681 let snapshot = buffer.read(cx);
13682 auto_indent_on_paste = snapshot
13683 .language_settings_at(cursor_offset, cx)
13684 .auto_indent_on_paste;
13685
13686 let mut start_offset = 0;
13687 let mut edits = Vec::new();
13688 let mut original_indent_columns = Vec::new();
13689 for (ix, selection) in old_selections.iter().enumerate() {
13690 let to_insert;
13691 let entire_line;
13692 let original_indent_column;
13693 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13694 let end_offset = start_offset + clipboard_selection.len;
13695 to_insert = &clipboard_text[start_offset..end_offset];
13696 entire_line = clipboard_selection.is_entire_line;
13697 start_offset = if entire_line {
13698 end_offset
13699 } else {
13700 end_offset + 1
13701 };
13702 original_indent_column = Some(clipboard_selection.first_line_indent);
13703 } else {
13704 to_insert = &*clipboard_text;
13705 entire_line = all_selections_were_entire_line;
13706 original_indent_column = first_selection_indent_column
13707 }
13708
13709 let (range, to_insert) =
13710 if selection.is_empty() && handle_entire_lines && entire_line {
13711 // If the corresponding selection was empty when this slice of the
13712 // clipboard text was written, then the entire line containing the
13713 // selection was copied. If this selection is also currently empty,
13714 // then paste the line before the current line of the buffer.
13715 let column = selection.start.to_point(&snapshot).column as usize;
13716 let line_start = selection.start - column;
13717 (line_start..line_start, Cow::Borrowed(to_insert))
13718 } else {
13719 let language = snapshot.language_at(selection.head());
13720 let range = selection.range();
13721 if let Some(language) = language
13722 && language.name() == "Markdown".into()
13723 {
13724 edit_for_markdown_paste(
13725 &snapshot,
13726 range,
13727 to_insert,
13728 url::Url::parse(to_insert).ok(),
13729 )
13730 } else {
13731 (range, Cow::Borrowed(to_insert))
13732 }
13733 };
13734
13735 edits.push((range, to_insert));
13736 original_indent_columns.push(original_indent_column);
13737 }
13738 drop(snapshot);
13739
13740 buffer.edit(
13741 edits,
13742 if auto_indent_on_paste {
13743 Some(AutoindentMode::Block {
13744 original_indent_columns,
13745 })
13746 } else {
13747 None
13748 },
13749 cx,
13750 );
13751 });
13752
13753 let selections = this
13754 .selections
13755 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13756 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13757 } else {
13758 let url = url::Url::parse(&clipboard_text).ok();
13759
13760 let auto_indent_mode = if !clipboard_text.is_empty() {
13761 Some(AutoindentMode::Block {
13762 original_indent_columns: Vec::new(),
13763 })
13764 } else {
13765 None
13766 };
13767
13768 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13769 let snapshot = buffer.snapshot(cx);
13770
13771 let anchors = old_selections
13772 .iter()
13773 .map(|s| {
13774 let anchor = snapshot.anchor_after(s.head());
13775 s.map(|_| anchor)
13776 })
13777 .collect::<Vec<_>>();
13778
13779 let mut edits = Vec::new();
13780
13781 for selection in old_selections.iter() {
13782 let language = snapshot.language_at(selection.head());
13783 let range = selection.range();
13784
13785 let (edit_range, edit_text) = if let Some(language) = language
13786 && language.name() == "Markdown".into()
13787 {
13788 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13789 } else {
13790 (range, clipboard_text.clone())
13791 };
13792
13793 edits.push((edit_range, edit_text));
13794 }
13795
13796 drop(snapshot);
13797 buffer.edit(edits, auto_indent_mode, cx);
13798
13799 anchors
13800 });
13801
13802 this.change_selections(Default::default(), window, cx, |s| {
13803 s.select_anchors(selection_anchors);
13804 });
13805 }
13806
13807 // 🤔 | .. | show_in_menu |
13808 // | .. | true true
13809 // | had_edit_prediction | false true
13810
13811 let trigger_in_words =
13812 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13813
13814 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13815 });
13816 }
13817
13818 pub fn diff_clipboard_with_selection(
13819 &mut self,
13820 _: &DiffClipboardWithSelection,
13821 window: &mut Window,
13822 cx: &mut Context<Self>,
13823 ) {
13824 let selections = self
13825 .selections
13826 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13827
13828 if selections.is_empty() {
13829 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13830 return;
13831 };
13832
13833 let clipboard_text = match cx.read_from_clipboard() {
13834 Some(item) => match item.entries().first() {
13835 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13836 _ => None,
13837 },
13838 None => None,
13839 };
13840
13841 let Some(clipboard_text) = clipboard_text else {
13842 log::warn!("Clipboard doesn't contain text.");
13843 return;
13844 };
13845
13846 window.dispatch_action(
13847 Box::new(DiffClipboardWithSelectionData {
13848 clipboard_text,
13849 editor: cx.entity(),
13850 }),
13851 cx,
13852 );
13853 }
13854
13855 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13857 if let Some(item) = cx.read_from_clipboard() {
13858 let entries = item.entries();
13859
13860 match entries.first() {
13861 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13862 // of all the pasted entries.
13863 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13864 .do_paste(
13865 clipboard_string.text(),
13866 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13867 true,
13868 window,
13869 cx,
13870 ),
13871 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13872 }
13873 }
13874 }
13875
13876 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13877 if self.read_only(cx) {
13878 return;
13879 }
13880
13881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13882
13883 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13884 if let Some((selections, _)) =
13885 self.selection_history.transaction(transaction_id).cloned()
13886 {
13887 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13888 s.select_anchors(selections.to_vec());
13889 });
13890 } else {
13891 log::error!(
13892 "No entry in selection_history found for undo. \
13893 This may correspond to a bug where undo does not update the selection. \
13894 If this is occurring, please add details to \
13895 https://github.com/zed-industries/zed/issues/22692"
13896 );
13897 }
13898 self.request_autoscroll(Autoscroll::fit(), cx);
13899 self.unmark_text(window, cx);
13900 self.refresh_edit_prediction(true, false, window, cx);
13901 cx.emit(EditorEvent::Edited { transaction_id });
13902 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13903 }
13904 }
13905
13906 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13907 if self.read_only(cx) {
13908 return;
13909 }
13910
13911 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13912
13913 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13914 if let Some((_, Some(selections))) =
13915 self.selection_history.transaction(transaction_id).cloned()
13916 {
13917 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13918 s.select_anchors(selections.to_vec());
13919 });
13920 } else {
13921 log::error!(
13922 "No entry in selection_history found for redo. \
13923 This may correspond to a bug where undo does not update the selection. \
13924 If this is occurring, please add details to \
13925 https://github.com/zed-industries/zed/issues/22692"
13926 );
13927 }
13928 self.request_autoscroll(Autoscroll::fit(), cx);
13929 self.unmark_text(window, cx);
13930 self.refresh_edit_prediction(true, false, window, cx);
13931 cx.emit(EditorEvent::Edited { transaction_id });
13932 }
13933 }
13934
13935 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13936 self.buffer
13937 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13938 }
13939
13940 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13941 self.buffer
13942 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13943 }
13944
13945 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_with(|map, selection| {
13949 let cursor = if selection.is_empty() {
13950 movement::left(map, selection.start)
13951 } else {
13952 selection.start
13953 };
13954 selection.collapse_to(cursor, SelectionGoal::None);
13955 });
13956 })
13957 }
13958
13959 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13961 self.change_selections(Default::default(), window, cx, |s| {
13962 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13963 })
13964 }
13965
13966 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.move_with(|map, selection| {
13970 let cursor = if selection.is_empty() {
13971 movement::right(map, selection.end)
13972 } else {
13973 selection.end
13974 };
13975 selection.collapse_to(cursor, SelectionGoal::None)
13976 });
13977 })
13978 }
13979
13980 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13982 self.change_selections(Default::default(), window, cx, |s| {
13983 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13984 });
13985 }
13986
13987 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13988 if self.take_rename(true, window, cx).is_some() {
13989 return;
13990 }
13991
13992 if self.mode.is_single_line() {
13993 cx.propagate();
13994 return;
13995 }
13996
13997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13998
13999 let text_layout_details = &self.text_layout_details(window, cx);
14000 let selection_count = self.selections.count();
14001 let first_selection = self.selections.first_anchor();
14002
14003 self.change_selections(Default::default(), window, cx, |s| {
14004 s.move_with(|map, selection| {
14005 if !selection.is_empty() {
14006 selection.goal = SelectionGoal::None;
14007 }
14008 let (cursor, goal) = movement::up(
14009 map,
14010 selection.start,
14011 selection.goal,
14012 false,
14013 text_layout_details,
14014 );
14015 selection.collapse_to(cursor, goal);
14016 });
14017 });
14018
14019 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14020 {
14021 cx.propagate();
14022 }
14023 }
14024
14025 pub fn move_up_by_lines(
14026 &mut self,
14027 action: &MoveUpByLines,
14028 window: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) {
14031 if self.take_rename(true, window, cx).is_some() {
14032 return;
14033 }
14034
14035 if self.mode.is_single_line() {
14036 cx.propagate();
14037 return;
14038 }
14039
14040 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14041
14042 let text_layout_details = &self.text_layout_details(window, cx);
14043
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.move_with(|map, selection| {
14046 if !selection.is_empty() {
14047 selection.goal = SelectionGoal::None;
14048 }
14049 let (cursor, goal) = movement::up_by_rows(
14050 map,
14051 selection.start,
14052 action.lines,
14053 selection.goal,
14054 false,
14055 text_layout_details,
14056 );
14057 selection.collapse_to(cursor, goal);
14058 });
14059 })
14060 }
14061
14062 pub fn move_down_by_lines(
14063 &mut self,
14064 action: &MoveDownByLines,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 if self.take_rename(true, window, cx).is_some() {
14069 return;
14070 }
14071
14072 if self.mode.is_single_line() {
14073 cx.propagate();
14074 return;
14075 }
14076
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078
14079 let text_layout_details = &self.text_layout_details(window, cx);
14080
14081 self.change_selections(Default::default(), window, cx, |s| {
14082 s.move_with(|map, selection| {
14083 if !selection.is_empty() {
14084 selection.goal = SelectionGoal::None;
14085 }
14086 let (cursor, goal) = movement::down_by_rows(
14087 map,
14088 selection.start,
14089 action.lines,
14090 selection.goal,
14091 false,
14092 text_layout_details,
14093 );
14094 selection.collapse_to(cursor, goal);
14095 });
14096 })
14097 }
14098
14099 pub fn select_down_by_lines(
14100 &mut self,
14101 action: &SelectDownByLines,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14106 let text_layout_details = &self.text_layout_details(window, cx);
14107 self.change_selections(Default::default(), window, cx, |s| {
14108 s.move_heads_with(|map, head, goal| {
14109 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14110 })
14111 })
14112 }
14113
14114 pub fn select_up_by_lines(
14115 &mut self,
14116 action: &SelectUpByLines,
14117 window: &mut Window,
14118 cx: &mut Context<Self>,
14119 ) {
14120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14121 let text_layout_details = &self.text_layout_details(window, cx);
14122 self.change_selections(Default::default(), window, cx, |s| {
14123 s.move_heads_with(|map, head, goal| {
14124 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14125 })
14126 })
14127 }
14128
14129 pub fn select_page_up(
14130 &mut self,
14131 _: &SelectPageUp,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 let Some(row_count) = self.visible_row_count() else {
14136 return;
14137 };
14138
14139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14140
14141 let text_layout_details = &self.text_layout_details(window, cx);
14142
14143 self.change_selections(Default::default(), window, cx, |s| {
14144 s.move_heads_with(|map, head, goal| {
14145 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14146 })
14147 })
14148 }
14149
14150 pub fn move_page_up(
14151 &mut self,
14152 action: &MovePageUp,
14153 window: &mut Window,
14154 cx: &mut Context<Self>,
14155 ) {
14156 if self.take_rename(true, window, cx).is_some() {
14157 return;
14158 }
14159
14160 if self
14161 .context_menu
14162 .borrow_mut()
14163 .as_mut()
14164 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14165 .unwrap_or(false)
14166 {
14167 return;
14168 }
14169
14170 if matches!(self.mode, EditorMode::SingleLine) {
14171 cx.propagate();
14172 return;
14173 }
14174
14175 let Some(row_count) = self.visible_row_count() else {
14176 return;
14177 };
14178
14179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14180
14181 let effects = if action.center_cursor {
14182 SelectionEffects::scroll(Autoscroll::center())
14183 } else {
14184 SelectionEffects::default()
14185 };
14186
14187 let text_layout_details = &self.text_layout_details(window, cx);
14188
14189 self.change_selections(effects, window, cx, |s| {
14190 s.move_with(|map, selection| {
14191 if !selection.is_empty() {
14192 selection.goal = SelectionGoal::None;
14193 }
14194 let (cursor, goal) = movement::up_by_rows(
14195 map,
14196 selection.end,
14197 row_count,
14198 selection.goal,
14199 false,
14200 text_layout_details,
14201 );
14202 selection.collapse_to(cursor, goal);
14203 });
14204 });
14205 }
14206
14207 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14209 let text_layout_details = &self.text_layout_details(window, cx);
14210 self.change_selections(Default::default(), window, cx, |s| {
14211 s.move_heads_with(|map, head, goal| {
14212 movement::up(map, head, goal, false, text_layout_details)
14213 })
14214 })
14215 }
14216
14217 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14218 self.take_rename(true, window, cx);
14219
14220 if self.mode.is_single_line() {
14221 cx.propagate();
14222 return;
14223 }
14224
14225 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14226
14227 let text_layout_details = &self.text_layout_details(window, cx);
14228 let selection_count = self.selections.count();
14229 let first_selection = self.selections.first_anchor();
14230
14231 self.change_selections(Default::default(), window, cx, |s| {
14232 s.move_with(|map, selection| {
14233 if !selection.is_empty() {
14234 selection.goal = SelectionGoal::None;
14235 }
14236 let (cursor, goal) = movement::down(
14237 map,
14238 selection.end,
14239 selection.goal,
14240 false,
14241 text_layout_details,
14242 );
14243 selection.collapse_to(cursor, goal);
14244 });
14245 });
14246
14247 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14248 {
14249 cx.propagate();
14250 }
14251 }
14252
14253 pub fn select_page_down(
14254 &mut self,
14255 _: &SelectPageDown,
14256 window: &mut Window,
14257 cx: &mut Context<Self>,
14258 ) {
14259 let Some(row_count) = self.visible_row_count() else {
14260 return;
14261 };
14262
14263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14264
14265 let text_layout_details = &self.text_layout_details(window, cx);
14266
14267 self.change_selections(Default::default(), window, cx, |s| {
14268 s.move_heads_with(|map, head, goal| {
14269 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14270 })
14271 })
14272 }
14273
14274 pub fn move_page_down(
14275 &mut self,
14276 action: &MovePageDown,
14277 window: &mut Window,
14278 cx: &mut Context<Self>,
14279 ) {
14280 if self.take_rename(true, window, cx).is_some() {
14281 return;
14282 }
14283
14284 if self
14285 .context_menu
14286 .borrow_mut()
14287 .as_mut()
14288 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14289 .unwrap_or(false)
14290 {
14291 return;
14292 }
14293
14294 if matches!(self.mode, EditorMode::SingleLine) {
14295 cx.propagate();
14296 return;
14297 }
14298
14299 let Some(row_count) = self.visible_row_count() else {
14300 return;
14301 };
14302
14303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14304
14305 let effects = if action.center_cursor {
14306 SelectionEffects::scroll(Autoscroll::center())
14307 } else {
14308 SelectionEffects::default()
14309 };
14310
14311 let text_layout_details = &self.text_layout_details(window, cx);
14312 self.change_selections(effects, window, cx, |s| {
14313 s.move_with(|map, selection| {
14314 if !selection.is_empty() {
14315 selection.goal = SelectionGoal::None;
14316 }
14317 let (cursor, goal) = movement::down_by_rows(
14318 map,
14319 selection.end,
14320 row_count,
14321 selection.goal,
14322 false,
14323 text_layout_details,
14324 );
14325 selection.collapse_to(cursor, goal);
14326 });
14327 });
14328 }
14329
14330 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14332 let text_layout_details = &self.text_layout_details(window, cx);
14333 self.change_selections(Default::default(), window, cx, |s| {
14334 s.move_heads_with(|map, head, goal| {
14335 movement::down(map, head, goal, false, text_layout_details)
14336 })
14337 });
14338 }
14339
14340 pub fn context_menu_first(
14341 &mut self,
14342 _: &ContextMenuFirst,
14343 window: &mut Window,
14344 cx: &mut Context<Self>,
14345 ) {
14346 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14347 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14348 }
14349 }
14350
14351 pub fn context_menu_prev(
14352 &mut self,
14353 _: &ContextMenuPrevious,
14354 window: &mut Window,
14355 cx: &mut Context<Self>,
14356 ) {
14357 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14358 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14359 }
14360 }
14361
14362 pub fn context_menu_next(
14363 &mut self,
14364 _: &ContextMenuNext,
14365 window: &mut Window,
14366 cx: &mut Context<Self>,
14367 ) {
14368 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14369 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14370 }
14371 }
14372
14373 pub fn context_menu_last(
14374 &mut self,
14375 _: &ContextMenuLast,
14376 window: &mut Window,
14377 cx: &mut Context<Self>,
14378 ) {
14379 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14380 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14381 }
14382 }
14383
14384 pub fn signature_help_prev(
14385 &mut self,
14386 _: &SignatureHelpPrevious,
14387 _: &mut Window,
14388 cx: &mut Context<Self>,
14389 ) {
14390 if let Some(popover) = self.signature_help_state.popover_mut() {
14391 if popover.current_signature == 0 {
14392 popover.current_signature = popover.signatures.len() - 1;
14393 } else {
14394 popover.current_signature -= 1;
14395 }
14396 cx.notify();
14397 }
14398 }
14399
14400 pub fn signature_help_next(
14401 &mut self,
14402 _: &SignatureHelpNext,
14403 _: &mut Window,
14404 cx: &mut Context<Self>,
14405 ) {
14406 if let Some(popover) = self.signature_help_state.popover_mut() {
14407 if popover.current_signature + 1 == popover.signatures.len() {
14408 popover.current_signature = 0;
14409 } else {
14410 popover.current_signature += 1;
14411 }
14412 cx.notify();
14413 }
14414 }
14415
14416 pub fn move_to_previous_word_start(
14417 &mut self,
14418 _: &MoveToPreviousWordStart,
14419 window: &mut Window,
14420 cx: &mut Context<Self>,
14421 ) {
14422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14423 self.change_selections(Default::default(), window, cx, |s| {
14424 s.move_cursors_with(|map, head, _| {
14425 (
14426 movement::previous_word_start(map, head),
14427 SelectionGoal::None,
14428 )
14429 });
14430 })
14431 }
14432
14433 pub fn move_to_previous_subword_start(
14434 &mut self,
14435 _: &MoveToPreviousSubwordStart,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) {
14439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14440 self.change_selections(Default::default(), window, cx, |s| {
14441 s.move_cursors_with(|map, head, _| {
14442 (
14443 movement::previous_subword_start(map, head),
14444 SelectionGoal::None,
14445 )
14446 });
14447 })
14448 }
14449
14450 pub fn select_to_previous_word_start(
14451 &mut self,
14452 _: &SelectToPreviousWordStart,
14453 window: &mut Window,
14454 cx: &mut Context<Self>,
14455 ) {
14456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14457 self.change_selections(Default::default(), window, cx, |s| {
14458 s.move_heads_with(|map, head, _| {
14459 (
14460 movement::previous_word_start(map, head),
14461 SelectionGoal::None,
14462 )
14463 });
14464 })
14465 }
14466
14467 pub fn select_to_previous_subword_start(
14468 &mut self,
14469 _: &SelectToPreviousSubwordStart,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) {
14473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14474 self.change_selections(Default::default(), window, cx, |s| {
14475 s.move_heads_with(|map, head, _| {
14476 (
14477 movement::previous_subword_start(map, head),
14478 SelectionGoal::None,
14479 )
14480 });
14481 })
14482 }
14483
14484 pub fn delete_to_previous_word_start(
14485 &mut self,
14486 action: &DeleteToPreviousWordStart,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) {
14490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14491 self.transact(window, cx, |this, window, cx| {
14492 this.select_autoclose_pair(window, cx);
14493 this.change_selections(Default::default(), window, cx, |s| {
14494 s.move_with(|map, selection| {
14495 if selection.is_empty() {
14496 let mut cursor = if action.ignore_newlines {
14497 movement::previous_word_start(map, selection.head())
14498 } else {
14499 movement::previous_word_start_or_newline(map, selection.head())
14500 };
14501 cursor = movement::adjust_greedy_deletion(
14502 map,
14503 selection.head(),
14504 cursor,
14505 action.ignore_brackets,
14506 );
14507 selection.set_head(cursor, SelectionGoal::None);
14508 }
14509 });
14510 });
14511 this.insert("", window, cx);
14512 });
14513 }
14514
14515 pub fn delete_to_previous_subword_start(
14516 &mut self,
14517 action: &DeleteToPreviousSubwordStart,
14518 window: &mut Window,
14519 cx: &mut Context<Self>,
14520 ) {
14521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14522 self.transact(window, cx, |this, window, cx| {
14523 this.select_autoclose_pair(window, cx);
14524 this.change_selections(Default::default(), window, cx, |s| {
14525 s.move_with(|map, selection| {
14526 if selection.is_empty() {
14527 let mut cursor = if action.ignore_newlines {
14528 movement::previous_subword_start(map, selection.head())
14529 } else {
14530 movement::previous_subword_start_or_newline(map, selection.head())
14531 };
14532 cursor = movement::adjust_greedy_deletion(
14533 map,
14534 selection.head(),
14535 cursor,
14536 action.ignore_brackets,
14537 );
14538 selection.set_head(cursor, SelectionGoal::None);
14539 }
14540 });
14541 });
14542 this.insert("", window, cx);
14543 });
14544 }
14545
14546 pub fn move_to_next_word_end(
14547 &mut self,
14548 _: &MoveToNextWordEnd,
14549 window: &mut Window,
14550 cx: &mut Context<Self>,
14551 ) {
14552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14553 self.change_selections(Default::default(), window, cx, |s| {
14554 s.move_cursors_with(|map, head, _| {
14555 (movement::next_word_end(map, head), SelectionGoal::None)
14556 });
14557 })
14558 }
14559
14560 pub fn move_to_next_subword_end(
14561 &mut self,
14562 _: &MoveToNextSubwordEnd,
14563 window: &mut Window,
14564 cx: &mut Context<Self>,
14565 ) {
14566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14567 self.change_selections(Default::default(), window, cx, |s| {
14568 s.move_cursors_with(|map, head, _| {
14569 (movement::next_subword_end(map, head), SelectionGoal::None)
14570 });
14571 })
14572 }
14573
14574 pub fn select_to_next_word_end(
14575 &mut self,
14576 _: &SelectToNextWordEnd,
14577 window: &mut Window,
14578 cx: &mut Context<Self>,
14579 ) {
14580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14581 self.change_selections(Default::default(), window, cx, |s| {
14582 s.move_heads_with(|map, head, _| {
14583 (movement::next_word_end(map, head), SelectionGoal::None)
14584 });
14585 })
14586 }
14587
14588 pub fn select_to_next_subword_end(
14589 &mut self,
14590 _: &SelectToNextSubwordEnd,
14591 window: &mut Window,
14592 cx: &mut Context<Self>,
14593 ) {
14594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14595 self.change_selections(Default::default(), window, cx, |s| {
14596 s.move_heads_with(|map, head, _| {
14597 (movement::next_subword_end(map, head), SelectionGoal::None)
14598 });
14599 })
14600 }
14601
14602 pub fn delete_to_next_word_end(
14603 &mut self,
14604 action: &DeleteToNextWordEnd,
14605 window: &mut Window,
14606 cx: &mut Context<Self>,
14607 ) {
14608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14609 self.transact(window, cx, |this, window, cx| {
14610 this.change_selections(Default::default(), window, cx, |s| {
14611 s.move_with(|map, selection| {
14612 if selection.is_empty() {
14613 let mut cursor = if action.ignore_newlines {
14614 movement::next_word_end(map, selection.head())
14615 } else {
14616 movement::next_word_end_or_newline(map, selection.head())
14617 };
14618 cursor = movement::adjust_greedy_deletion(
14619 map,
14620 selection.head(),
14621 cursor,
14622 action.ignore_brackets,
14623 );
14624 selection.set_head(cursor, SelectionGoal::None);
14625 }
14626 });
14627 });
14628 this.insert("", window, cx);
14629 });
14630 }
14631
14632 pub fn delete_to_next_subword_end(
14633 &mut self,
14634 action: &DeleteToNextSubwordEnd,
14635 window: &mut Window,
14636 cx: &mut Context<Self>,
14637 ) {
14638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14639 self.transact(window, cx, |this, window, cx| {
14640 this.change_selections(Default::default(), window, cx, |s| {
14641 s.move_with(|map, selection| {
14642 if selection.is_empty() {
14643 let mut cursor = if action.ignore_newlines {
14644 movement::next_subword_end(map, selection.head())
14645 } else {
14646 movement::next_subword_end_or_newline(map, selection.head())
14647 };
14648 cursor = movement::adjust_greedy_deletion(
14649 map,
14650 selection.head(),
14651 cursor,
14652 action.ignore_brackets,
14653 );
14654 selection.set_head(cursor, SelectionGoal::None);
14655 }
14656 });
14657 });
14658 this.insert("", window, cx);
14659 });
14660 }
14661
14662 pub fn move_to_beginning_of_line(
14663 &mut self,
14664 action: &MoveToBeginningOfLine,
14665 window: &mut Window,
14666 cx: &mut Context<Self>,
14667 ) {
14668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14669 self.change_selections(Default::default(), window, cx, |s| {
14670 s.move_cursors_with(|map, head, _| {
14671 (
14672 movement::indented_line_beginning(
14673 map,
14674 head,
14675 action.stop_at_soft_wraps,
14676 action.stop_at_indent,
14677 ),
14678 SelectionGoal::None,
14679 )
14680 });
14681 })
14682 }
14683
14684 pub fn select_to_beginning_of_line(
14685 &mut self,
14686 action: &SelectToBeginningOfLine,
14687 window: &mut Window,
14688 cx: &mut Context<Self>,
14689 ) {
14690 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14691 self.change_selections(Default::default(), window, cx, |s| {
14692 s.move_heads_with(|map, head, _| {
14693 (
14694 movement::indented_line_beginning(
14695 map,
14696 head,
14697 action.stop_at_soft_wraps,
14698 action.stop_at_indent,
14699 ),
14700 SelectionGoal::None,
14701 )
14702 });
14703 });
14704 }
14705
14706 pub fn delete_to_beginning_of_line(
14707 &mut self,
14708 action: &DeleteToBeginningOfLine,
14709 window: &mut Window,
14710 cx: &mut Context<Self>,
14711 ) {
14712 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14713 self.transact(window, cx, |this, window, cx| {
14714 this.change_selections(Default::default(), window, cx, |s| {
14715 s.move_with(|_, selection| {
14716 selection.reversed = true;
14717 });
14718 });
14719
14720 this.select_to_beginning_of_line(
14721 &SelectToBeginningOfLine {
14722 stop_at_soft_wraps: false,
14723 stop_at_indent: action.stop_at_indent,
14724 },
14725 window,
14726 cx,
14727 );
14728 this.backspace(&Backspace, window, cx);
14729 });
14730 }
14731
14732 pub fn move_to_end_of_line(
14733 &mut self,
14734 action: &MoveToEndOfLine,
14735 window: &mut Window,
14736 cx: &mut Context<Self>,
14737 ) {
14738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14739 self.change_selections(Default::default(), window, cx, |s| {
14740 s.move_cursors_with(|map, head, _| {
14741 (
14742 movement::line_end(map, head, action.stop_at_soft_wraps),
14743 SelectionGoal::None,
14744 )
14745 });
14746 })
14747 }
14748
14749 pub fn select_to_end_of_line(
14750 &mut self,
14751 action: &SelectToEndOfLine,
14752 window: &mut Window,
14753 cx: &mut Context<Self>,
14754 ) {
14755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14756 self.change_selections(Default::default(), window, cx, |s| {
14757 s.move_heads_with(|map, head, _| {
14758 (
14759 movement::line_end(map, head, action.stop_at_soft_wraps),
14760 SelectionGoal::None,
14761 )
14762 });
14763 })
14764 }
14765
14766 pub fn delete_to_end_of_line(
14767 &mut self,
14768 _: &DeleteToEndOfLine,
14769 window: &mut Window,
14770 cx: &mut Context<Self>,
14771 ) {
14772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14773 self.transact(window, cx, |this, window, cx| {
14774 this.select_to_end_of_line(
14775 &SelectToEndOfLine {
14776 stop_at_soft_wraps: false,
14777 },
14778 window,
14779 cx,
14780 );
14781 this.delete(&Delete, window, cx);
14782 });
14783 }
14784
14785 pub fn cut_to_end_of_line(
14786 &mut self,
14787 action: &CutToEndOfLine,
14788 window: &mut Window,
14789 cx: &mut Context<Self>,
14790 ) {
14791 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14792 self.transact(window, cx, |this, window, cx| {
14793 this.select_to_end_of_line(
14794 &SelectToEndOfLine {
14795 stop_at_soft_wraps: false,
14796 },
14797 window,
14798 cx,
14799 );
14800 if !action.stop_at_newlines {
14801 this.change_selections(Default::default(), window, cx, |s| {
14802 s.move_with(|_, sel| {
14803 if sel.is_empty() {
14804 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14805 }
14806 });
14807 });
14808 }
14809 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14810 let item = this.cut_common(false, window, cx);
14811 cx.write_to_clipboard(item);
14812 });
14813 }
14814
14815 pub fn move_to_start_of_paragraph(
14816 &mut self,
14817 _: &MoveToStartOfParagraph,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 if matches!(self.mode, EditorMode::SingleLine) {
14822 cx.propagate();
14823 return;
14824 }
14825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14826 self.change_selections(Default::default(), window, cx, |s| {
14827 s.move_with(|map, selection| {
14828 selection.collapse_to(
14829 movement::start_of_paragraph(map, selection.head(), 1),
14830 SelectionGoal::None,
14831 )
14832 });
14833 })
14834 }
14835
14836 pub fn move_to_end_of_paragraph(
14837 &mut self,
14838 _: &MoveToEndOfParagraph,
14839 window: &mut Window,
14840 cx: &mut Context<Self>,
14841 ) {
14842 if matches!(self.mode, EditorMode::SingleLine) {
14843 cx.propagate();
14844 return;
14845 }
14846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14847 self.change_selections(Default::default(), window, cx, |s| {
14848 s.move_with(|map, selection| {
14849 selection.collapse_to(
14850 movement::end_of_paragraph(map, selection.head(), 1),
14851 SelectionGoal::None,
14852 )
14853 });
14854 })
14855 }
14856
14857 pub fn select_to_start_of_paragraph(
14858 &mut self,
14859 _: &SelectToStartOfParagraph,
14860 window: &mut Window,
14861 cx: &mut Context<Self>,
14862 ) {
14863 if matches!(self.mode, EditorMode::SingleLine) {
14864 cx.propagate();
14865 return;
14866 }
14867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14868 self.change_selections(Default::default(), window, cx, |s| {
14869 s.move_heads_with(|map, head, _| {
14870 (
14871 movement::start_of_paragraph(map, head, 1),
14872 SelectionGoal::None,
14873 )
14874 });
14875 })
14876 }
14877
14878 pub fn select_to_end_of_paragraph(
14879 &mut self,
14880 _: &SelectToEndOfParagraph,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) {
14884 if matches!(self.mode, EditorMode::SingleLine) {
14885 cx.propagate();
14886 return;
14887 }
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14889 self.change_selections(Default::default(), window, cx, |s| {
14890 s.move_heads_with(|map, head, _| {
14891 (
14892 movement::end_of_paragraph(map, head, 1),
14893 SelectionGoal::None,
14894 )
14895 });
14896 })
14897 }
14898
14899 pub fn move_to_start_of_excerpt(
14900 &mut self,
14901 _: &MoveToStartOfExcerpt,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) {
14905 if matches!(self.mode, EditorMode::SingleLine) {
14906 cx.propagate();
14907 return;
14908 }
14909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14910 self.change_selections(Default::default(), window, cx, |s| {
14911 s.move_with(|map, selection| {
14912 selection.collapse_to(
14913 movement::start_of_excerpt(
14914 map,
14915 selection.head(),
14916 workspace::searchable::Direction::Prev,
14917 ),
14918 SelectionGoal::None,
14919 )
14920 });
14921 })
14922 }
14923
14924 pub fn move_to_start_of_next_excerpt(
14925 &mut self,
14926 _: &MoveToStartOfNextExcerpt,
14927 window: &mut Window,
14928 cx: &mut Context<Self>,
14929 ) {
14930 if matches!(self.mode, EditorMode::SingleLine) {
14931 cx.propagate();
14932 return;
14933 }
14934
14935 self.change_selections(Default::default(), window, cx, |s| {
14936 s.move_with(|map, selection| {
14937 selection.collapse_to(
14938 movement::start_of_excerpt(
14939 map,
14940 selection.head(),
14941 workspace::searchable::Direction::Next,
14942 ),
14943 SelectionGoal::None,
14944 )
14945 });
14946 })
14947 }
14948
14949 pub fn move_to_end_of_excerpt(
14950 &mut self,
14951 _: &MoveToEndOfExcerpt,
14952 window: &mut Window,
14953 cx: &mut Context<Self>,
14954 ) {
14955 if matches!(self.mode, EditorMode::SingleLine) {
14956 cx.propagate();
14957 return;
14958 }
14959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14960 self.change_selections(Default::default(), window, cx, |s| {
14961 s.move_with(|map, selection| {
14962 selection.collapse_to(
14963 movement::end_of_excerpt(
14964 map,
14965 selection.head(),
14966 workspace::searchable::Direction::Next,
14967 ),
14968 SelectionGoal::None,
14969 )
14970 });
14971 })
14972 }
14973
14974 pub fn move_to_end_of_previous_excerpt(
14975 &mut self,
14976 _: &MoveToEndOfPreviousExcerpt,
14977 window: &mut Window,
14978 cx: &mut Context<Self>,
14979 ) {
14980 if matches!(self.mode, EditorMode::SingleLine) {
14981 cx.propagate();
14982 return;
14983 }
14984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14985 self.change_selections(Default::default(), window, cx, |s| {
14986 s.move_with(|map, selection| {
14987 selection.collapse_to(
14988 movement::end_of_excerpt(
14989 map,
14990 selection.head(),
14991 workspace::searchable::Direction::Prev,
14992 ),
14993 SelectionGoal::None,
14994 )
14995 });
14996 })
14997 }
14998
14999 pub fn select_to_start_of_excerpt(
15000 &mut self,
15001 _: &SelectToStartOfExcerpt,
15002 window: &mut Window,
15003 cx: &mut Context<Self>,
15004 ) {
15005 if matches!(self.mode, EditorMode::SingleLine) {
15006 cx.propagate();
15007 return;
15008 }
15009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15010 self.change_selections(Default::default(), window, cx, |s| {
15011 s.move_heads_with(|map, head, _| {
15012 (
15013 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15014 SelectionGoal::None,
15015 )
15016 });
15017 })
15018 }
15019
15020 pub fn select_to_start_of_next_excerpt(
15021 &mut self,
15022 _: &SelectToStartOfNextExcerpt,
15023 window: &mut Window,
15024 cx: &mut Context<Self>,
15025 ) {
15026 if matches!(self.mode, EditorMode::SingleLine) {
15027 cx.propagate();
15028 return;
15029 }
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031 self.change_selections(Default::default(), window, cx, |s| {
15032 s.move_heads_with(|map, head, _| {
15033 (
15034 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15035 SelectionGoal::None,
15036 )
15037 });
15038 })
15039 }
15040
15041 pub fn select_to_end_of_excerpt(
15042 &mut self,
15043 _: &SelectToEndOfExcerpt,
15044 window: &mut Window,
15045 cx: &mut Context<Self>,
15046 ) {
15047 if matches!(self.mode, EditorMode::SingleLine) {
15048 cx.propagate();
15049 return;
15050 }
15051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15052 self.change_selections(Default::default(), window, cx, |s| {
15053 s.move_heads_with(|map, head, _| {
15054 (
15055 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15056 SelectionGoal::None,
15057 )
15058 });
15059 })
15060 }
15061
15062 pub fn select_to_end_of_previous_excerpt(
15063 &mut self,
15064 _: &SelectToEndOfPreviousExcerpt,
15065 window: &mut Window,
15066 cx: &mut Context<Self>,
15067 ) {
15068 if matches!(self.mode, EditorMode::SingleLine) {
15069 cx.propagate();
15070 return;
15071 }
15072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15073 self.change_selections(Default::default(), window, cx, |s| {
15074 s.move_heads_with(|map, head, _| {
15075 (
15076 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15077 SelectionGoal::None,
15078 )
15079 });
15080 })
15081 }
15082
15083 pub fn move_to_beginning(
15084 &mut self,
15085 _: &MoveToBeginning,
15086 window: &mut Window,
15087 cx: &mut Context<Self>,
15088 ) {
15089 if matches!(self.mode, EditorMode::SingleLine) {
15090 cx.propagate();
15091 return;
15092 }
15093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15094 self.change_selections(Default::default(), window, cx, |s| {
15095 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15096 });
15097 }
15098
15099 pub fn select_to_beginning(
15100 &mut self,
15101 _: &SelectToBeginning,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15106 selection.set_head(Point::zero(), SelectionGoal::None);
15107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15108 self.change_selections(Default::default(), window, cx, |s| {
15109 s.select(vec![selection]);
15110 });
15111 }
15112
15113 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15114 if matches!(self.mode, EditorMode::SingleLine) {
15115 cx.propagate();
15116 return;
15117 }
15118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15119 let cursor = self.buffer.read(cx).read(cx).len();
15120 self.change_selections(Default::default(), window, cx, |s| {
15121 s.select_ranges(vec![cursor..cursor])
15122 });
15123 }
15124
15125 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15126 self.nav_history = nav_history;
15127 }
15128
15129 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15130 self.nav_history.as_ref()
15131 }
15132
15133 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15134 self.push_to_nav_history(
15135 self.selections.newest_anchor().head(),
15136 None,
15137 false,
15138 true,
15139 cx,
15140 );
15141 }
15142
15143 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15144 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15145 let buffer = self.buffer.read(cx).read(cx);
15146 let cursor_position = cursor_anchor.to_point(&buffer);
15147 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15148 let scroll_top_row = scroll_anchor.top_row(&buffer);
15149 drop(buffer);
15150
15151 NavigationData {
15152 cursor_anchor,
15153 cursor_position,
15154 scroll_anchor,
15155 scroll_top_row,
15156 }
15157 }
15158
15159 fn navigation_entry(
15160 &self,
15161 cursor_anchor: Anchor,
15162 cx: &mut Context<Self>,
15163 ) -> Option<NavigationEntry> {
15164 let Some(history) = self.nav_history.clone() else {
15165 return None;
15166 };
15167 let data = self.navigation_data(cursor_anchor, cx);
15168 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15169 }
15170
15171 fn push_to_nav_history(
15172 &mut self,
15173 cursor_anchor: Anchor,
15174 new_position: Option<Point>,
15175 is_deactivate: bool,
15176 always: bool,
15177 cx: &mut Context<Self>,
15178 ) {
15179 let data = self.navigation_data(cursor_anchor, cx);
15180 if let Some(nav_history) = self.nav_history.as_mut() {
15181 if let Some(new_position) = new_position {
15182 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15183 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15184 return;
15185 }
15186 }
15187
15188 nav_history.push(Some(data), cx);
15189 cx.emit(EditorEvent::PushedToNavHistory {
15190 anchor: cursor_anchor,
15191 is_deactivate,
15192 })
15193 }
15194 }
15195
15196 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15198 let buffer = self.buffer.read(cx).snapshot(cx);
15199 let mut selection = self
15200 .selections
15201 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15202 selection.set_head(buffer.len(), SelectionGoal::None);
15203 self.change_selections(Default::default(), window, cx, |s| {
15204 s.select(vec![selection]);
15205 });
15206 }
15207
15208 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15210 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15211 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15212 });
15213 }
15214
15215 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15218 let mut selections = self.selections.all::<Point>(&display_map);
15219 let max_point = display_map.buffer_snapshot().max_point();
15220 for selection in &mut selections {
15221 let rows = selection.spanned_rows(true, &display_map);
15222 selection.start = Point::new(rows.start.0, 0);
15223 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15224 selection.reversed = false;
15225 }
15226 self.change_selections(Default::default(), window, cx, |s| {
15227 s.select(selections);
15228 });
15229 }
15230
15231 pub fn split_selection_into_lines(
15232 &mut self,
15233 action: &SplitSelectionIntoLines,
15234 window: &mut Window,
15235 cx: &mut Context<Self>,
15236 ) {
15237 let selections = self
15238 .selections
15239 .all::<Point>(&self.display_snapshot(cx))
15240 .into_iter()
15241 .map(|selection| selection.start..selection.end)
15242 .collect::<Vec<_>>();
15243 self.unfold_ranges(&selections, true, true, cx);
15244
15245 let mut new_selection_ranges = Vec::new();
15246 {
15247 let buffer = self.buffer.read(cx).read(cx);
15248 for selection in selections {
15249 for row in selection.start.row..selection.end.row {
15250 let line_start = Point::new(row, 0);
15251 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15252
15253 if action.keep_selections {
15254 // Keep the selection range for each line
15255 let selection_start = if row == selection.start.row {
15256 selection.start
15257 } else {
15258 line_start
15259 };
15260 new_selection_ranges.push(selection_start..line_end);
15261 } else {
15262 // Collapse to cursor at end of line
15263 new_selection_ranges.push(line_end..line_end);
15264 }
15265 }
15266
15267 let is_multiline_selection = selection.start.row != selection.end.row;
15268 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15269 // so this action feels more ergonomic when paired with other selection operations
15270 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15271 if !should_skip_last {
15272 if action.keep_selections {
15273 if is_multiline_selection {
15274 let line_start = Point::new(selection.end.row, 0);
15275 new_selection_ranges.push(line_start..selection.end);
15276 } else {
15277 new_selection_ranges.push(selection.start..selection.end);
15278 }
15279 } else {
15280 new_selection_ranges.push(selection.end..selection.end);
15281 }
15282 }
15283 }
15284 }
15285 self.change_selections(Default::default(), window, cx, |s| {
15286 s.select_ranges(new_selection_ranges);
15287 });
15288 }
15289
15290 pub fn add_selection_above(
15291 &mut self,
15292 action: &AddSelectionAbove,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 self.add_selection(true, action.skip_soft_wrap, window, cx);
15297 }
15298
15299 pub fn add_selection_below(
15300 &mut self,
15301 action: &AddSelectionBelow,
15302 window: &mut Window,
15303 cx: &mut Context<Self>,
15304 ) {
15305 self.add_selection(false, action.skip_soft_wrap, window, cx);
15306 }
15307
15308 fn add_selection(
15309 &mut self,
15310 above: bool,
15311 skip_soft_wrap: bool,
15312 window: &mut Window,
15313 cx: &mut Context<Self>,
15314 ) {
15315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15316
15317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15318 let all_selections = self.selections.all::<Point>(&display_map);
15319 let text_layout_details = self.text_layout_details(window, cx);
15320
15321 let (mut columnar_selections, new_selections_to_columnarize) = {
15322 if let Some(state) = self.add_selections_state.as_ref() {
15323 let columnar_selection_ids: HashSet<_> = state
15324 .groups
15325 .iter()
15326 .flat_map(|group| group.stack.iter())
15327 .copied()
15328 .collect();
15329
15330 all_selections
15331 .into_iter()
15332 .partition(|s| columnar_selection_ids.contains(&s.id))
15333 } else {
15334 (Vec::new(), all_selections)
15335 }
15336 };
15337
15338 let mut state = self
15339 .add_selections_state
15340 .take()
15341 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15342
15343 for selection in new_selections_to_columnarize {
15344 let range = selection.display_range(&display_map).sorted();
15345 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15346 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15347 let positions = start_x.min(end_x)..start_x.max(end_x);
15348 let mut stack = Vec::new();
15349 for row in range.start.row().0..=range.end.row().0 {
15350 if let Some(selection) = self.selections.build_columnar_selection(
15351 &display_map,
15352 DisplayRow(row),
15353 &positions,
15354 selection.reversed,
15355 &text_layout_details,
15356 ) {
15357 stack.push(selection.id);
15358 columnar_selections.push(selection);
15359 }
15360 }
15361 if !stack.is_empty() {
15362 if above {
15363 stack.reverse();
15364 }
15365 state.groups.push(AddSelectionsGroup { above, stack });
15366 }
15367 }
15368
15369 let mut final_selections = Vec::new();
15370 let end_row = if above {
15371 DisplayRow(0)
15372 } else {
15373 display_map.max_point().row()
15374 };
15375
15376 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15377 // positions to place new selections, so we need to keep track of the
15378 // column range of the oldest selection in each group, because
15379 // intermediate selections may have been clamped to shorter lines.
15380 // selections may have been clamped to shorter lines.
15381 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15382 let mut map = HashMap::default();
15383 for group in state.groups.iter() {
15384 if let Some(oldest_id) = group.stack.first() {
15385 if let Some(oldest_selection) =
15386 columnar_selections.iter().find(|s| s.id == *oldest_id)
15387 {
15388 let start_col = oldest_selection.start.column;
15389 let end_col = oldest_selection.end.column;
15390 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15391 for id in &group.stack {
15392 map.insert(*id, goal_columns.clone());
15393 }
15394 }
15395 }
15396 }
15397 map
15398 } else {
15399 HashMap::default()
15400 };
15401
15402 let mut last_added_item_per_group = HashMap::default();
15403 for group in state.groups.iter_mut() {
15404 if let Some(last_id) = group.stack.last() {
15405 last_added_item_per_group.insert(*last_id, group);
15406 }
15407 }
15408
15409 for selection in columnar_selections {
15410 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15411 if above == group.above {
15412 let range = selection.display_range(&display_map).sorted();
15413 debug_assert_eq!(range.start.row(), range.end.row());
15414 let row = range.start.row();
15415 let positions =
15416 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15417 Pixels::from(start)..Pixels::from(end)
15418 } else {
15419 let start_x =
15420 display_map.x_for_display_point(range.start, &text_layout_details);
15421 let end_x =
15422 display_map.x_for_display_point(range.end, &text_layout_details);
15423 start_x.min(end_x)..start_x.max(end_x)
15424 };
15425
15426 let maybe_new_selection = if skip_soft_wrap {
15427 let goal_columns = goal_columns_by_selection_id
15428 .remove(&selection.id)
15429 .unwrap_or_else(|| {
15430 let start_col = selection.start.column;
15431 let end_col = selection.end.column;
15432 start_col.min(end_col)..start_col.max(end_col)
15433 });
15434 self.selections.find_next_columnar_selection_by_buffer_row(
15435 &display_map,
15436 row,
15437 end_row,
15438 above,
15439 &goal_columns,
15440 selection.reversed,
15441 &text_layout_details,
15442 )
15443 } else {
15444 self.selections.find_next_columnar_selection_by_display_row(
15445 &display_map,
15446 row,
15447 end_row,
15448 above,
15449 &positions,
15450 selection.reversed,
15451 &text_layout_details,
15452 )
15453 };
15454
15455 if let Some(new_selection) = maybe_new_selection {
15456 group.stack.push(new_selection.id);
15457 if above {
15458 final_selections.push(new_selection);
15459 final_selections.push(selection);
15460 } else {
15461 final_selections.push(selection);
15462 final_selections.push(new_selection);
15463 }
15464 } else {
15465 final_selections.push(selection);
15466 }
15467 } else {
15468 group.stack.pop();
15469 }
15470 } else {
15471 final_selections.push(selection);
15472 }
15473 }
15474
15475 self.change_selections(Default::default(), window, cx, |s| {
15476 s.select(final_selections);
15477 });
15478
15479 let final_selection_ids: HashSet<_> = self
15480 .selections
15481 .all::<Point>(&display_map)
15482 .iter()
15483 .map(|s| s.id)
15484 .collect();
15485 state.groups.retain_mut(|group| {
15486 // selections might get merged above so we remove invalid items from stacks
15487 group.stack.retain(|id| final_selection_ids.contains(id));
15488
15489 // single selection in stack can be treated as initial state
15490 group.stack.len() > 1
15491 });
15492
15493 if !state.groups.is_empty() {
15494 self.add_selections_state = Some(state);
15495 }
15496 }
15497
15498 pub fn insert_snippet_at_selections(
15499 &mut self,
15500 action: &InsertSnippet,
15501 window: &mut Window,
15502 cx: &mut Context<Self>,
15503 ) {
15504 self.try_insert_snippet_at_selections(action, window, cx)
15505 .log_err();
15506 }
15507
15508 fn try_insert_snippet_at_selections(
15509 &mut self,
15510 action: &InsertSnippet,
15511 window: &mut Window,
15512 cx: &mut Context<Self>,
15513 ) -> Result<()> {
15514 let insertion_ranges = self
15515 .selections
15516 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15517 .into_iter()
15518 .map(|selection| selection.range())
15519 .collect_vec();
15520
15521 let snippet = if let Some(snippet_body) = &action.snippet {
15522 if action.language.is_none() && action.name.is_none() {
15523 Snippet::parse(snippet_body)?
15524 } else {
15525 bail!("`snippet` is mutually exclusive with `language` and `name`")
15526 }
15527 } else if let Some(name) = &action.name {
15528 let project = self.project().context("no project")?;
15529 let snippet_store = project.read(cx).snippets().read(cx);
15530 let snippet = snippet_store
15531 .snippets_for(action.language.clone(), cx)
15532 .into_iter()
15533 .find(|snippet| snippet.name == *name)
15534 .context("snippet not found")?;
15535 Snippet::parse(&snippet.body)?
15536 } else {
15537 // todo(andrew): open modal to select snippet
15538 bail!("`name` or `snippet` is required")
15539 };
15540
15541 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15542 }
15543
15544 fn select_match_ranges(
15545 &mut self,
15546 range: Range<MultiBufferOffset>,
15547 reversed: bool,
15548 replace_newest: bool,
15549 auto_scroll: Option<Autoscroll>,
15550 window: &mut Window,
15551 cx: &mut Context<Editor>,
15552 ) {
15553 self.unfold_ranges(
15554 std::slice::from_ref(&range),
15555 false,
15556 auto_scroll.is_some(),
15557 cx,
15558 );
15559 let effects = if let Some(scroll) = auto_scroll {
15560 SelectionEffects::scroll(scroll)
15561 } else {
15562 SelectionEffects::no_scroll()
15563 };
15564 self.change_selections(effects, window, cx, |s| {
15565 if replace_newest {
15566 s.delete(s.newest_anchor().id);
15567 }
15568 if reversed {
15569 s.insert_range(range.end..range.start);
15570 } else {
15571 s.insert_range(range);
15572 }
15573 });
15574 }
15575
15576 pub fn select_next_match_internal(
15577 &mut self,
15578 display_map: &DisplaySnapshot,
15579 replace_newest: bool,
15580 autoscroll: Option<Autoscroll>,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) -> Result<()> {
15584 let buffer = display_map.buffer_snapshot();
15585 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15586 if let Some(mut select_next_state) = self.select_next_state.take() {
15587 let query = &select_next_state.query;
15588 if !select_next_state.done {
15589 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15590 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15591 let mut next_selected_range = None;
15592
15593 let bytes_after_last_selection =
15594 buffer.bytes_in_range(last_selection.end..buffer.len());
15595 let bytes_before_first_selection =
15596 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15597 let query_matches = query
15598 .stream_find_iter(bytes_after_last_selection)
15599 .map(|result| (last_selection.end, result))
15600 .chain(
15601 query
15602 .stream_find_iter(bytes_before_first_selection)
15603 .map(|result| (MultiBufferOffset(0), result)),
15604 );
15605
15606 for (start_offset, query_match) in query_matches {
15607 let query_match = query_match.unwrap(); // can only fail due to I/O
15608 let offset_range =
15609 start_offset + query_match.start()..start_offset + query_match.end();
15610
15611 if !select_next_state.wordwise
15612 || (!buffer.is_inside_word(offset_range.start, None)
15613 && !buffer.is_inside_word(offset_range.end, None))
15614 {
15615 let idx = selections
15616 .partition_point(|selection| selection.end <= offset_range.start);
15617 let overlaps = selections
15618 .get(idx)
15619 .map_or(false, |selection| selection.start < offset_range.end);
15620
15621 if !overlaps {
15622 next_selected_range = Some(offset_range);
15623 break;
15624 }
15625 }
15626 }
15627
15628 if let Some(next_selected_range) = next_selected_range {
15629 self.select_match_ranges(
15630 next_selected_range,
15631 last_selection.reversed,
15632 replace_newest,
15633 autoscroll,
15634 window,
15635 cx,
15636 );
15637 } else {
15638 select_next_state.done = true;
15639 }
15640 }
15641
15642 self.select_next_state = Some(select_next_state);
15643 } else {
15644 let mut only_carets = true;
15645 let mut same_text_selected = true;
15646 let mut selected_text = None;
15647
15648 let mut selections_iter = selections.iter().peekable();
15649 while let Some(selection) = selections_iter.next() {
15650 if selection.start != selection.end {
15651 only_carets = false;
15652 }
15653
15654 if same_text_selected {
15655 if selected_text.is_none() {
15656 selected_text =
15657 Some(buffer.text_for_range(selection.range()).collect::<String>());
15658 }
15659
15660 if let Some(next_selection) = selections_iter.peek() {
15661 if next_selection.len() == selection.len() {
15662 let next_selected_text = buffer
15663 .text_for_range(next_selection.range())
15664 .collect::<String>();
15665 if Some(next_selected_text) != selected_text {
15666 same_text_selected = false;
15667 selected_text = None;
15668 }
15669 } else {
15670 same_text_selected = false;
15671 selected_text = None;
15672 }
15673 }
15674 }
15675 }
15676
15677 if only_carets {
15678 for selection in &mut selections {
15679 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15680 selection.start = word_range.start;
15681 selection.end = word_range.end;
15682 selection.goal = SelectionGoal::None;
15683 selection.reversed = false;
15684 self.select_match_ranges(
15685 selection.start..selection.end,
15686 selection.reversed,
15687 replace_newest,
15688 autoscroll,
15689 window,
15690 cx,
15691 );
15692 }
15693
15694 if selections.len() == 1 {
15695 let selection = selections
15696 .last()
15697 .expect("ensured that there's only one selection");
15698 let query = buffer
15699 .text_for_range(selection.start..selection.end)
15700 .collect::<String>();
15701 let is_empty = query.is_empty();
15702 let select_state = SelectNextState {
15703 query: self.build_query(&[query], cx)?,
15704 wordwise: true,
15705 done: is_empty,
15706 };
15707 self.select_next_state = Some(select_state);
15708 } else {
15709 self.select_next_state = None;
15710 }
15711 } else if let Some(selected_text) = selected_text {
15712 self.select_next_state = Some(SelectNextState {
15713 query: self.build_query(&[selected_text], cx)?,
15714 wordwise: false,
15715 done: false,
15716 });
15717 self.select_next_match_internal(
15718 display_map,
15719 replace_newest,
15720 autoscroll,
15721 window,
15722 cx,
15723 )?;
15724 }
15725 }
15726 Ok(())
15727 }
15728
15729 pub fn select_all_matches(
15730 &mut self,
15731 _action: &SelectAllMatches,
15732 window: &mut Window,
15733 cx: &mut Context<Self>,
15734 ) -> Result<()> {
15735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15736
15737 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15738
15739 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15740 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15741 else {
15742 return Ok(());
15743 };
15744
15745 let mut new_selections = Vec::new();
15746
15747 let reversed = self
15748 .selections
15749 .oldest::<MultiBufferOffset>(&display_map)
15750 .reversed;
15751 let buffer = display_map.buffer_snapshot();
15752 let query_matches = select_next_state
15753 .query
15754 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15755
15756 for query_match in query_matches.into_iter() {
15757 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15758 let offset_range = if reversed {
15759 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15760 } else {
15761 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15762 };
15763
15764 if !select_next_state.wordwise
15765 || (!buffer.is_inside_word(offset_range.start, None)
15766 && !buffer.is_inside_word(offset_range.end, None))
15767 {
15768 new_selections.push(offset_range.start..offset_range.end);
15769 }
15770 }
15771
15772 select_next_state.done = true;
15773
15774 if new_selections.is_empty() {
15775 log::error!("bug: new_selections is empty in select_all_matches");
15776 return Ok(());
15777 }
15778
15779 self.unfold_ranges(&new_selections, false, false, cx);
15780 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15781 selections.select_ranges(new_selections)
15782 });
15783
15784 Ok(())
15785 }
15786
15787 pub fn select_next(
15788 &mut self,
15789 action: &SelectNext,
15790 window: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) -> Result<()> {
15793 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15794 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15795 self.select_next_match_internal(
15796 &display_map,
15797 action.replace_newest,
15798 Some(Autoscroll::newest()),
15799 window,
15800 cx,
15801 )
15802 }
15803
15804 pub fn select_previous(
15805 &mut self,
15806 action: &SelectPrevious,
15807 window: &mut Window,
15808 cx: &mut Context<Self>,
15809 ) -> Result<()> {
15810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15812 let buffer = display_map.buffer_snapshot();
15813 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15814 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15815 let query = &select_prev_state.query;
15816 if !select_prev_state.done {
15817 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15818 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15819 let mut next_selected_range = None;
15820 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15821 let bytes_before_last_selection =
15822 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15823 let bytes_after_first_selection =
15824 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15825 let query_matches = query
15826 .stream_find_iter(bytes_before_last_selection)
15827 .map(|result| (last_selection.start, result))
15828 .chain(
15829 query
15830 .stream_find_iter(bytes_after_first_selection)
15831 .map(|result| (buffer.len(), result)),
15832 );
15833 for (end_offset, query_match) in query_matches {
15834 let query_match = query_match.unwrap(); // can only fail due to I/O
15835 let offset_range =
15836 end_offset - query_match.end()..end_offset - query_match.start();
15837
15838 if !select_prev_state.wordwise
15839 || (!buffer.is_inside_word(offset_range.start, None)
15840 && !buffer.is_inside_word(offset_range.end, None))
15841 {
15842 next_selected_range = Some(offset_range);
15843 break;
15844 }
15845 }
15846
15847 if let Some(next_selected_range) = next_selected_range {
15848 self.select_match_ranges(
15849 next_selected_range,
15850 last_selection.reversed,
15851 action.replace_newest,
15852 Some(Autoscroll::newest()),
15853 window,
15854 cx,
15855 );
15856 } else {
15857 select_prev_state.done = true;
15858 }
15859 }
15860
15861 self.select_prev_state = Some(select_prev_state);
15862 } else {
15863 let mut only_carets = true;
15864 let mut same_text_selected = true;
15865 let mut selected_text = None;
15866
15867 let mut selections_iter = selections.iter().peekable();
15868 while let Some(selection) = selections_iter.next() {
15869 if selection.start != selection.end {
15870 only_carets = false;
15871 }
15872
15873 if same_text_selected {
15874 if selected_text.is_none() {
15875 selected_text =
15876 Some(buffer.text_for_range(selection.range()).collect::<String>());
15877 }
15878
15879 if let Some(next_selection) = selections_iter.peek() {
15880 if next_selection.len() == selection.len() {
15881 let next_selected_text = buffer
15882 .text_for_range(next_selection.range())
15883 .collect::<String>();
15884 if Some(next_selected_text) != selected_text {
15885 same_text_selected = false;
15886 selected_text = None;
15887 }
15888 } else {
15889 same_text_selected = false;
15890 selected_text = None;
15891 }
15892 }
15893 }
15894 }
15895
15896 if only_carets {
15897 for selection in &mut selections {
15898 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15899 selection.start = word_range.start;
15900 selection.end = word_range.end;
15901 selection.goal = SelectionGoal::None;
15902 selection.reversed = false;
15903 self.select_match_ranges(
15904 selection.start..selection.end,
15905 selection.reversed,
15906 action.replace_newest,
15907 Some(Autoscroll::newest()),
15908 window,
15909 cx,
15910 );
15911 }
15912 if selections.len() == 1 {
15913 let selection = selections
15914 .last()
15915 .expect("ensured that there's only one selection");
15916 let query = buffer
15917 .text_for_range(selection.start..selection.end)
15918 .collect::<String>();
15919 let is_empty = query.is_empty();
15920 let select_state = SelectNextState {
15921 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15922 wordwise: true,
15923 done: is_empty,
15924 };
15925 self.select_prev_state = Some(select_state);
15926 } else {
15927 self.select_prev_state = None;
15928 }
15929 } else if let Some(selected_text) = selected_text {
15930 self.select_prev_state = Some(SelectNextState {
15931 query: self
15932 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15933 wordwise: false,
15934 done: false,
15935 });
15936 self.select_previous(action, window, cx)?;
15937 }
15938 }
15939 Ok(())
15940 }
15941
15942 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15943 /// setting the case sensitivity based on the global
15944 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15945 /// editor's settings.
15946 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15947 where
15948 I: IntoIterator<Item = P>,
15949 P: AsRef<[u8]>,
15950 {
15951 let case_sensitive = self
15952 .select_next_is_case_sensitive
15953 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15954
15955 let mut builder = AhoCorasickBuilder::new();
15956 builder.ascii_case_insensitive(!case_sensitive);
15957 builder.build(patterns)
15958 }
15959
15960 pub fn find_next_match(
15961 &mut self,
15962 _: &FindNextMatch,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) -> Result<()> {
15966 let selections = self.selections.disjoint_anchors_arc();
15967 match selections.first() {
15968 Some(first) if selections.len() >= 2 => {
15969 self.change_selections(Default::default(), window, cx, |s| {
15970 s.select_ranges([first.range()]);
15971 });
15972 }
15973 _ => self.select_next(
15974 &SelectNext {
15975 replace_newest: true,
15976 },
15977 window,
15978 cx,
15979 )?,
15980 }
15981 Ok(())
15982 }
15983
15984 pub fn find_previous_match(
15985 &mut self,
15986 _: &FindPreviousMatch,
15987 window: &mut Window,
15988 cx: &mut Context<Self>,
15989 ) -> Result<()> {
15990 let selections = self.selections.disjoint_anchors_arc();
15991 match selections.last() {
15992 Some(last) if selections.len() >= 2 => {
15993 self.change_selections(Default::default(), window, cx, |s| {
15994 s.select_ranges([last.range()]);
15995 });
15996 }
15997 _ => self.select_previous(
15998 &SelectPrevious {
15999 replace_newest: true,
16000 },
16001 window,
16002 cx,
16003 )?,
16004 }
16005 Ok(())
16006 }
16007
16008 pub fn toggle_comments(
16009 &mut self,
16010 action: &ToggleComments,
16011 window: &mut Window,
16012 cx: &mut Context<Self>,
16013 ) {
16014 if self.read_only(cx) {
16015 return;
16016 }
16017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16018 let text_layout_details = &self.text_layout_details(window, cx);
16019 self.transact(window, cx, |this, window, cx| {
16020 let mut selections = this
16021 .selections
16022 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16023 let mut edits = Vec::new();
16024 let mut selection_edit_ranges = Vec::new();
16025 let mut last_toggled_row = None;
16026 let snapshot = this.buffer.read(cx).read(cx);
16027 let empty_str: Arc<str> = Arc::default();
16028 let mut suffixes_inserted = Vec::new();
16029 let ignore_indent = action.ignore_indent;
16030
16031 fn comment_prefix_range(
16032 snapshot: &MultiBufferSnapshot,
16033 row: MultiBufferRow,
16034 comment_prefix: &str,
16035 comment_prefix_whitespace: &str,
16036 ignore_indent: bool,
16037 ) -> Range<Point> {
16038 let indent_size = if ignore_indent {
16039 0
16040 } else {
16041 snapshot.indent_size_for_line(row).len
16042 };
16043
16044 let start = Point::new(row.0, indent_size);
16045
16046 let mut line_bytes = snapshot
16047 .bytes_in_range(start..snapshot.max_point())
16048 .flatten()
16049 .copied();
16050
16051 // If this line currently begins with the line comment prefix, then record
16052 // the range containing the prefix.
16053 if line_bytes
16054 .by_ref()
16055 .take(comment_prefix.len())
16056 .eq(comment_prefix.bytes())
16057 {
16058 // Include any whitespace that matches the comment prefix.
16059 let matching_whitespace_len = line_bytes
16060 .zip(comment_prefix_whitespace.bytes())
16061 .take_while(|(a, b)| a == b)
16062 .count() as u32;
16063 let end = Point::new(
16064 start.row,
16065 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16066 );
16067 start..end
16068 } else {
16069 start..start
16070 }
16071 }
16072
16073 fn comment_suffix_range(
16074 snapshot: &MultiBufferSnapshot,
16075 row: MultiBufferRow,
16076 comment_suffix: &str,
16077 comment_suffix_has_leading_space: bool,
16078 ) -> Range<Point> {
16079 let end = Point::new(row.0, snapshot.line_len(row));
16080 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16081
16082 let mut line_end_bytes = snapshot
16083 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16084 .flatten()
16085 .copied();
16086
16087 let leading_space_len = if suffix_start_column > 0
16088 && line_end_bytes.next() == Some(b' ')
16089 && comment_suffix_has_leading_space
16090 {
16091 1
16092 } else {
16093 0
16094 };
16095
16096 // If this line currently begins with the line comment prefix, then record
16097 // the range containing the prefix.
16098 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16099 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16100 start..end
16101 } else {
16102 end..end
16103 }
16104 }
16105
16106 // TODO: Handle selections that cross excerpts
16107 for selection in &mut selections {
16108 let start_column = snapshot
16109 .indent_size_for_line(MultiBufferRow(selection.start.row))
16110 .len;
16111 let language = if let Some(language) =
16112 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16113 {
16114 language
16115 } else {
16116 continue;
16117 };
16118
16119 selection_edit_ranges.clear();
16120
16121 // If multiple selections contain a given row, avoid processing that
16122 // row more than once.
16123 let mut start_row = MultiBufferRow(selection.start.row);
16124 if last_toggled_row == Some(start_row) {
16125 start_row = start_row.next_row();
16126 }
16127 let end_row =
16128 if selection.end.row > selection.start.row && selection.end.column == 0 {
16129 MultiBufferRow(selection.end.row - 1)
16130 } else {
16131 MultiBufferRow(selection.end.row)
16132 };
16133 last_toggled_row = Some(end_row);
16134
16135 if start_row > end_row {
16136 continue;
16137 }
16138
16139 // If the language has line comments, toggle those.
16140 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16141
16142 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16143 if ignore_indent {
16144 full_comment_prefixes = full_comment_prefixes
16145 .into_iter()
16146 .map(|s| Arc::from(s.trim_end()))
16147 .collect();
16148 }
16149
16150 if !full_comment_prefixes.is_empty() {
16151 let first_prefix = full_comment_prefixes
16152 .first()
16153 .expect("prefixes is non-empty");
16154 let prefix_trimmed_lengths = full_comment_prefixes
16155 .iter()
16156 .map(|p| p.trim_end_matches(' ').len())
16157 .collect::<SmallVec<[usize; 4]>>();
16158
16159 let mut all_selection_lines_are_comments = true;
16160
16161 for row in start_row.0..=end_row.0 {
16162 let row = MultiBufferRow(row);
16163 if start_row < end_row && snapshot.is_line_blank(row) {
16164 continue;
16165 }
16166
16167 let prefix_range = full_comment_prefixes
16168 .iter()
16169 .zip(prefix_trimmed_lengths.iter().copied())
16170 .map(|(prefix, trimmed_prefix_len)| {
16171 comment_prefix_range(
16172 snapshot.deref(),
16173 row,
16174 &prefix[..trimmed_prefix_len],
16175 &prefix[trimmed_prefix_len..],
16176 ignore_indent,
16177 )
16178 })
16179 .max_by_key(|range| range.end.column - range.start.column)
16180 .expect("prefixes is non-empty");
16181
16182 if prefix_range.is_empty() {
16183 all_selection_lines_are_comments = false;
16184 }
16185
16186 selection_edit_ranges.push(prefix_range);
16187 }
16188
16189 if all_selection_lines_are_comments {
16190 edits.extend(
16191 selection_edit_ranges
16192 .iter()
16193 .cloned()
16194 .map(|range| (range, empty_str.clone())),
16195 );
16196 } else {
16197 let min_column = selection_edit_ranges
16198 .iter()
16199 .map(|range| range.start.column)
16200 .min()
16201 .unwrap_or(0);
16202 edits.extend(selection_edit_ranges.iter().map(|range| {
16203 let position = Point::new(range.start.row, min_column);
16204 (position..position, first_prefix.clone())
16205 }));
16206 }
16207 } else if let Some(BlockCommentConfig {
16208 start: full_comment_prefix,
16209 end: comment_suffix,
16210 ..
16211 }) = language.block_comment()
16212 {
16213 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16214 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16215 let prefix_range = comment_prefix_range(
16216 snapshot.deref(),
16217 start_row,
16218 comment_prefix,
16219 comment_prefix_whitespace,
16220 ignore_indent,
16221 );
16222 let suffix_range = comment_suffix_range(
16223 snapshot.deref(),
16224 end_row,
16225 comment_suffix.trim_start_matches(' '),
16226 comment_suffix.starts_with(' '),
16227 );
16228
16229 if prefix_range.is_empty() || suffix_range.is_empty() {
16230 edits.push((
16231 prefix_range.start..prefix_range.start,
16232 full_comment_prefix.clone(),
16233 ));
16234 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16235 suffixes_inserted.push((end_row, comment_suffix.len()));
16236 } else {
16237 edits.push((prefix_range, empty_str.clone()));
16238 edits.push((suffix_range, empty_str.clone()));
16239 }
16240 } else {
16241 continue;
16242 }
16243 }
16244
16245 drop(snapshot);
16246 this.buffer.update(cx, |buffer, cx| {
16247 buffer.edit(edits, None, cx);
16248 });
16249
16250 // Adjust selections so that they end before any comment suffixes that
16251 // were inserted.
16252 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16253 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16254 let snapshot = this.buffer.read(cx).read(cx);
16255 for selection in &mut selections {
16256 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16257 match row.cmp(&MultiBufferRow(selection.end.row)) {
16258 Ordering::Less => {
16259 suffixes_inserted.next();
16260 continue;
16261 }
16262 Ordering::Greater => break,
16263 Ordering::Equal => {
16264 if selection.end.column == snapshot.line_len(row) {
16265 if selection.is_empty() {
16266 selection.start.column -= suffix_len as u32;
16267 }
16268 selection.end.column -= suffix_len as u32;
16269 }
16270 break;
16271 }
16272 }
16273 }
16274 }
16275
16276 drop(snapshot);
16277 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16278
16279 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16280 let selections_on_single_row = selections.windows(2).all(|selections| {
16281 selections[0].start.row == selections[1].start.row
16282 && selections[0].end.row == selections[1].end.row
16283 && selections[0].start.row == selections[0].end.row
16284 });
16285 let selections_selecting = selections
16286 .iter()
16287 .any(|selection| selection.start != selection.end);
16288 let advance_downwards = action.advance_downwards
16289 && selections_on_single_row
16290 && !selections_selecting
16291 && !matches!(this.mode, EditorMode::SingleLine);
16292
16293 if advance_downwards {
16294 let snapshot = this.buffer.read(cx).snapshot(cx);
16295
16296 this.change_selections(Default::default(), window, cx, |s| {
16297 s.move_cursors_with(|display_snapshot, display_point, _| {
16298 let mut point = display_point.to_point(display_snapshot);
16299 point.row += 1;
16300 point = snapshot.clip_point(point, Bias::Left);
16301 let display_point = point.to_display_point(display_snapshot);
16302 let goal = SelectionGoal::HorizontalPosition(
16303 display_snapshot
16304 .x_for_display_point(display_point, text_layout_details)
16305 .into(),
16306 );
16307 (display_point, goal)
16308 })
16309 });
16310 }
16311 });
16312 }
16313
16314 pub fn select_enclosing_symbol(
16315 &mut self,
16316 _: &SelectEnclosingSymbol,
16317 window: &mut Window,
16318 cx: &mut Context<Self>,
16319 ) {
16320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16321
16322 let buffer = self.buffer.read(cx).snapshot(cx);
16323 let old_selections = self
16324 .selections
16325 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16326 .into_boxed_slice();
16327
16328 fn update_selection(
16329 selection: &Selection<MultiBufferOffset>,
16330 buffer_snap: &MultiBufferSnapshot,
16331 ) -> Option<Selection<MultiBufferOffset>> {
16332 let cursor = selection.head();
16333 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16334 for symbol in symbols.iter().rev() {
16335 let start = symbol.range.start.to_offset(buffer_snap);
16336 let end = symbol.range.end.to_offset(buffer_snap);
16337 let new_range = start..end;
16338 if start < selection.start || end > selection.end {
16339 return Some(Selection {
16340 id: selection.id,
16341 start: new_range.start,
16342 end: new_range.end,
16343 goal: SelectionGoal::None,
16344 reversed: selection.reversed,
16345 });
16346 }
16347 }
16348 None
16349 }
16350
16351 let mut selected_larger_symbol = false;
16352 let new_selections = old_selections
16353 .iter()
16354 .map(|selection| match update_selection(selection, &buffer) {
16355 Some(new_selection) => {
16356 if new_selection.range() != selection.range() {
16357 selected_larger_symbol = true;
16358 }
16359 new_selection
16360 }
16361 None => selection.clone(),
16362 })
16363 .collect::<Vec<_>>();
16364
16365 if selected_larger_symbol {
16366 self.change_selections(Default::default(), window, cx, |s| {
16367 s.select(new_selections);
16368 });
16369 }
16370 }
16371
16372 pub fn select_larger_syntax_node(
16373 &mut self,
16374 _: &SelectLargerSyntaxNode,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) {
16378 let Some(visible_row_count) = self.visible_row_count() else {
16379 return;
16380 };
16381 let old_selections: Box<[_]> = self
16382 .selections
16383 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16384 .into();
16385 if old_selections.is_empty() {
16386 return;
16387 }
16388
16389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16390
16391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16392 let buffer = self.buffer.read(cx).snapshot(cx);
16393
16394 let mut selected_larger_node = false;
16395 let mut new_selections = old_selections
16396 .iter()
16397 .map(|selection| {
16398 let old_range = selection.start..selection.end;
16399
16400 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16401 // manually select word at selection
16402 if ["string_content", "inline"].contains(&node.kind()) {
16403 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16404 // ignore if word is already selected
16405 if !word_range.is_empty() && old_range != word_range {
16406 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16407 // only select word if start and end point belongs to same word
16408 if word_range == last_word_range {
16409 selected_larger_node = true;
16410 return Selection {
16411 id: selection.id,
16412 start: word_range.start,
16413 end: word_range.end,
16414 goal: SelectionGoal::None,
16415 reversed: selection.reversed,
16416 };
16417 }
16418 }
16419 }
16420 }
16421
16422 let mut new_range = old_range.clone();
16423 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16424 new_range = range;
16425 if !node.is_named() {
16426 continue;
16427 }
16428 if !display_map.intersects_fold(new_range.start)
16429 && !display_map.intersects_fold(new_range.end)
16430 {
16431 break;
16432 }
16433 }
16434
16435 selected_larger_node |= new_range != old_range;
16436 Selection {
16437 id: selection.id,
16438 start: new_range.start,
16439 end: new_range.end,
16440 goal: SelectionGoal::None,
16441 reversed: selection.reversed,
16442 }
16443 })
16444 .collect::<Vec<_>>();
16445
16446 if !selected_larger_node {
16447 return; // don't put this call in the history
16448 }
16449
16450 // scroll based on transformation done to the last selection created by the user
16451 let (last_old, last_new) = old_selections
16452 .last()
16453 .zip(new_selections.last().cloned())
16454 .expect("old_selections isn't empty");
16455
16456 // revert selection
16457 let is_selection_reversed = {
16458 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16459 new_selections.last_mut().expect("checked above").reversed =
16460 should_newest_selection_be_reversed;
16461 should_newest_selection_be_reversed
16462 };
16463
16464 if selected_larger_node {
16465 self.select_syntax_node_history.disable_clearing = true;
16466 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16467 s.select(new_selections.clone());
16468 });
16469 self.select_syntax_node_history.disable_clearing = false;
16470 }
16471
16472 let start_row = last_new.start.to_display_point(&display_map).row().0;
16473 let end_row = last_new.end.to_display_point(&display_map).row().0;
16474 let selection_height = end_row - start_row + 1;
16475 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16476
16477 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16478 let scroll_behavior = if fits_on_the_screen {
16479 self.request_autoscroll(Autoscroll::fit(), cx);
16480 SelectSyntaxNodeScrollBehavior::FitSelection
16481 } else if is_selection_reversed {
16482 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16483 SelectSyntaxNodeScrollBehavior::CursorTop
16484 } else {
16485 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16486 SelectSyntaxNodeScrollBehavior::CursorBottom
16487 };
16488
16489 self.select_syntax_node_history.push((
16490 old_selections,
16491 scroll_behavior,
16492 is_selection_reversed,
16493 ));
16494 }
16495
16496 pub fn select_smaller_syntax_node(
16497 &mut self,
16498 _: &SelectSmallerSyntaxNode,
16499 window: &mut Window,
16500 cx: &mut Context<Self>,
16501 ) {
16502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16503
16504 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16505 self.select_syntax_node_history.pop()
16506 {
16507 if let Some(selection) = selections.last_mut() {
16508 selection.reversed = is_selection_reversed;
16509 }
16510
16511 self.select_syntax_node_history.disable_clearing = true;
16512 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16513 s.select(selections.to_vec());
16514 });
16515 self.select_syntax_node_history.disable_clearing = false;
16516
16517 match scroll_behavior {
16518 SelectSyntaxNodeScrollBehavior::CursorTop => {
16519 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16520 }
16521 SelectSyntaxNodeScrollBehavior::FitSelection => {
16522 self.request_autoscroll(Autoscroll::fit(), cx);
16523 }
16524 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16525 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16526 }
16527 }
16528 }
16529 }
16530
16531 pub fn unwrap_syntax_node(
16532 &mut self,
16533 _: &UnwrapSyntaxNode,
16534 window: &mut Window,
16535 cx: &mut Context<Self>,
16536 ) {
16537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16538
16539 let buffer = self.buffer.read(cx).snapshot(cx);
16540 let selections = self
16541 .selections
16542 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16543 .into_iter()
16544 // subtracting the offset requires sorting
16545 .sorted_by_key(|i| i.start);
16546
16547 let full_edits = selections
16548 .into_iter()
16549 .filter_map(|selection| {
16550 let child = if selection.is_empty()
16551 && let Some((_, ancestor_range)) =
16552 buffer.syntax_ancestor(selection.start..selection.end)
16553 {
16554 ancestor_range
16555 } else {
16556 selection.range()
16557 };
16558
16559 let mut parent = child.clone();
16560 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16561 parent = ancestor_range;
16562 if parent.start < child.start || parent.end > child.end {
16563 break;
16564 }
16565 }
16566
16567 if parent == child {
16568 return None;
16569 }
16570 let text = buffer.text_for_range(child).collect::<String>();
16571 Some((selection.id, parent, text))
16572 })
16573 .collect::<Vec<_>>();
16574 if full_edits.is_empty() {
16575 return;
16576 }
16577
16578 self.transact(window, cx, |this, window, cx| {
16579 this.buffer.update(cx, |buffer, cx| {
16580 buffer.edit(
16581 full_edits
16582 .iter()
16583 .map(|(_, p, t)| (p.clone(), t.clone()))
16584 .collect::<Vec<_>>(),
16585 None,
16586 cx,
16587 );
16588 });
16589 this.change_selections(Default::default(), window, cx, |s| {
16590 let mut offset = 0;
16591 let mut selections = vec![];
16592 for (id, parent, text) in full_edits {
16593 let start = parent.start - offset;
16594 offset += (parent.end - parent.start) - text.len();
16595 selections.push(Selection {
16596 id,
16597 start,
16598 end: start + text.len(),
16599 reversed: false,
16600 goal: Default::default(),
16601 });
16602 }
16603 s.select(selections);
16604 });
16605 });
16606 }
16607
16608 pub fn select_next_syntax_node(
16609 &mut self,
16610 _: &SelectNextSyntaxNode,
16611 window: &mut Window,
16612 cx: &mut Context<Self>,
16613 ) {
16614 let old_selections: Box<[_]> = self
16615 .selections
16616 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16617 .into();
16618 if old_selections.is_empty() {
16619 return;
16620 }
16621
16622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16623
16624 let buffer = self.buffer.read(cx).snapshot(cx);
16625 let mut selected_sibling = false;
16626
16627 let new_selections = old_selections
16628 .iter()
16629 .map(|selection| {
16630 let old_range = selection.start..selection.end;
16631
16632 let old_range =
16633 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16634 let excerpt = buffer.excerpt_containing(old_range.clone());
16635
16636 if let Some(mut excerpt) = excerpt
16637 && let Some(node) = excerpt
16638 .buffer()
16639 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16640 {
16641 let new_range = excerpt.map_range_from_buffer(
16642 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16643 );
16644 selected_sibling = true;
16645 Selection {
16646 id: selection.id,
16647 start: new_range.start,
16648 end: new_range.end,
16649 goal: SelectionGoal::None,
16650 reversed: selection.reversed,
16651 }
16652 } else {
16653 selection.clone()
16654 }
16655 })
16656 .collect::<Vec<_>>();
16657
16658 if selected_sibling {
16659 self.change_selections(
16660 SelectionEffects::scroll(Autoscroll::fit()),
16661 window,
16662 cx,
16663 |s| {
16664 s.select(new_selections);
16665 },
16666 );
16667 }
16668 }
16669
16670 pub fn select_prev_syntax_node(
16671 &mut self,
16672 _: &SelectPreviousSyntaxNode,
16673 window: &mut Window,
16674 cx: &mut Context<Self>,
16675 ) {
16676 let old_selections: Box<[_]> = self
16677 .selections
16678 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16679 .into();
16680 if old_selections.is_empty() {
16681 return;
16682 }
16683
16684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16685
16686 let buffer = self.buffer.read(cx).snapshot(cx);
16687 let mut selected_sibling = false;
16688
16689 let new_selections = old_selections
16690 .iter()
16691 .map(|selection| {
16692 let old_range = selection.start..selection.end;
16693 let old_range =
16694 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16695 let excerpt = buffer.excerpt_containing(old_range.clone());
16696
16697 if let Some(mut excerpt) = excerpt
16698 && let Some(node) = excerpt
16699 .buffer()
16700 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16701 {
16702 let new_range = excerpt.map_range_from_buffer(
16703 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16704 );
16705 selected_sibling = true;
16706 Selection {
16707 id: selection.id,
16708 start: new_range.start,
16709 end: new_range.end,
16710 goal: SelectionGoal::None,
16711 reversed: selection.reversed,
16712 }
16713 } else {
16714 selection.clone()
16715 }
16716 })
16717 .collect::<Vec<_>>();
16718
16719 if selected_sibling {
16720 self.change_selections(
16721 SelectionEffects::scroll(Autoscroll::fit()),
16722 window,
16723 cx,
16724 |s| {
16725 s.select(new_selections);
16726 },
16727 );
16728 }
16729 }
16730
16731 pub fn move_to_start_of_larger_syntax_node(
16732 &mut self,
16733 _: &MoveToStartOfLargerSyntaxNode,
16734 window: &mut Window,
16735 cx: &mut Context<Self>,
16736 ) {
16737 self.move_cursors_to_syntax_nodes(window, cx, false);
16738 }
16739
16740 pub fn move_to_end_of_larger_syntax_node(
16741 &mut self,
16742 _: &MoveToEndOfLargerSyntaxNode,
16743 window: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) {
16746 self.move_cursors_to_syntax_nodes(window, cx, true);
16747 }
16748
16749 fn find_syntax_node_boundary(
16750 &self,
16751 selection_pos: MultiBufferOffset,
16752 move_to_end: bool,
16753 display_map: &DisplaySnapshot,
16754 buffer: &MultiBufferSnapshot,
16755 ) -> MultiBufferOffset {
16756 let old_range = selection_pos..selection_pos;
16757 let mut new_pos = selection_pos;
16758 let mut search_range = old_range;
16759 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16760 search_range = range.clone();
16761 if !node.is_named()
16762 || display_map.intersects_fold(range.start)
16763 || display_map.intersects_fold(range.end)
16764 // If cursor is already at the end of the syntax node, continue searching
16765 || (move_to_end && range.end == selection_pos)
16766 // If cursor is already at the start of the syntax node, continue searching
16767 || (!move_to_end && range.start == selection_pos)
16768 {
16769 continue;
16770 }
16771
16772 // If we found a string_content node, find the largest parent that is still string_content
16773 // Enables us to skip to the end of strings without taking multiple steps inside the string
16774 let (_, final_range) = if node.kind() == "string_content" {
16775 let mut current_node = node;
16776 let mut current_range = range;
16777 while let Some((parent, parent_range)) =
16778 buffer.syntax_ancestor(current_range.clone())
16779 {
16780 if parent.kind() == "string_content" {
16781 current_node = parent;
16782 current_range = parent_range;
16783 } else {
16784 break;
16785 }
16786 }
16787
16788 (current_node, current_range)
16789 } else {
16790 (node, range)
16791 };
16792
16793 new_pos = if move_to_end {
16794 final_range.end
16795 } else {
16796 final_range.start
16797 };
16798
16799 break;
16800 }
16801
16802 new_pos
16803 }
16804
16805 fn move_cursors_to_syntax_nodes(
16806 &mut self,
16807 window: &mut Window,
16808 cx: &mut Context<Self>,
16809 move_to_end: bool,
16810 ) -> bool {
16811 let old_selections: Box<[_]> = self
16812 .selections
16813 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16814 .into();
16815 if old_selections.is_empty() {
16816 return false;
16817 }
16818
16819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16820
16821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16822 let buffer = self.buffer.read(cx).snapshot(cx);
16823
16824 let mut any_cursor_moved = false;
16825 let new_selections = old_selections
16826 .iter()
16827 .map(|selection| {
16828 if !selection.is_empty() {
16829 return selection.clone();
16830 }
16831
16832 let selection_pos = selection.head();
16833 let new_pos = self.find_syntax_node_boundary(
16834 selection_pos,
16835 move_to_end,
16836 &display_map,
16837 &buffer,
16838 );
16839
16840 any_cursor_moved |= new_pos != selection_pos;
16841
16842 Selection {
16843 id: selection.id,
16844 start: new_pos,
16845 end: new_pos,
16846 goal: SelectionGoal::None,
16847 reversed: false,
16848 }
16849 })
16850 .collect::<Vec<_>>();
16851
16852 self.change_selections(Default::default(), window, cx, |s| {
16853 s.select(new_selections);
16854 });
16855 self.request_autoscroll(Autoscroll::newest(), cx);
16856
16857 any_cursor_moved
16858 }
16859
16860 pub fn select_to_start_of_larger_syntax_node(
16861 &mut self,
16862 _: &SelectToStartOfLargerSyntaxNode,
16863 window: &mut Window,
16864 cx: &mut Context<Self>,
16865 ) {
16866 self.select_to_syntax_nodes(window, cx, false);
16867 }
16868
16869 pub fn select_to_end_of_larger_syntax_node(
16870 &mut self,
16871 _: &SelectToEndOfLargerSyntaxNode,
16872 window: &mut Window,
16873 cx: &mut Context<Self>,
16874 ) {
16875 self.select_to_syntax_nodes(window, cx, true);
16876 }
16877
16878 fn select_to_syntax_nodes(
16879 &mut self,
16880 window: &mut Window,
16881 cx: &mut Context<Self>,
16882 move_to_end: bool,
16883 ) {
16884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16885
16886 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16887 let buffer = self.buffer.read(cx).snapshot(cx);
16888 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
16889
16890 let new_selections = old_selections
16891 .iter()
16892 .map(|selection| {
16893 let new_pos = self.find_syntax_node_boundary(
16894 selection.head(),
16895 move_to_end,
16896 &display_map,
16897 &buffer,
16898 );
16899
16900 let mut new_selection = selection.clone();
16901 new_selection.set_head(new_pos, SelectionGoal::None);
16902 new_selection
16903 })
16904 .collect::<Vec<_>>();
16905
16906 self.change_selections(Default::default(), window, cx, |s| {
16907 s.select(new_selections);
16908 });
16909 }
16910
16911 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16912 if !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables {
16913 self.clear_tasks();
16914 return Task::ready(());
16915 }
16916 let project = self.project().map(Entity::downgrade);
16917 let task_sources = self.lsp_task_sources(cx);
16918 let multi_buffer = self.buffer.downgrade();
16919 cx.spawn_in(window, async move |editor, cx| {
16920 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16921 let Some(project) = project.and_then(|p| p.upgrade()) else {
16922 return;
16923 };
16924 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16925 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16926 }) else {
16927 return;
16928 };
16929
16930 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16931 if hide_runnables {
16932 return;
16933 }
16934 let new_rows =
16935 cx.background_spawn({
16936 let snapshot = display_snapshot.clone();
16937 async move {
16938 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16939 }
16940 })
16941 .await;
16942 let Ok(lsp_tasks) =
16943 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16944 else {
16945 return;
16946 };
16947 let lsp_tasks = lsp_tasks.await;
16948
16949 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16950 lsp_tasks
16951 .into_iter()
16952 .flat_map(|(kind, tasks)| {
16953 tasks.into_iter().filter_map(move |(location, task)| {
16954 Some((kind.clone(), location?, task))
16955 })
16956 })
16957 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16958 let buffer = location.target.buffer;
16959 let buffer_snapshot = buffer.read(cx).snapshot();
16960 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16961 |(excerpt_id, snapshot, _)| {
16962 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16963 display_snapshot
16964 .buffer_snapshot()
16965 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16966 } else {
16967 None
16968 }
16969 },
16970 );
16971 if let Some(offset) = offset {
16972 let task_buffer_range =
16973 location.target.range.to_point(&buffer_snapshot);
16974 let context_buffer_range =
16975 task_buffer_range.to_offset(&buffer_snapshot);
16976 let context_range = BufferOffset(context_buffer_range.start)
16977 ..BufferOffset(context_buffer_range.end);
16978
16979 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16980 .or_insert_with(|| RunnableTasks {
16981 templates: Vec::new(),
16982 offset,
16983 column: task_buffer_range.start.column,
16984 extra_variables: HashMap::default(),
16985 context_range,
16986 })
16987 .templates
16988 .push((kind, task.original_task().clone()));
16989 }
16990
16991 acc
16992 })
16993 }) else {
16994 return;
16995 };
16996
16997 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16998 buffer.language_settings(cx).tasks.prefer_lsp
16999 }) else {
17000 return;
17001 };
17002
17003 let rows = Self::runnable_rows(
17004 project,
17005 display_snapshot,
17006 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17007 new_rows,
17008 cx.clone(),
17009 )
17010 .await;
17011 editor
17012 .update(cx, |editor, _| {
17013 editor.clear_tasks();
17014 for (key, mut value) in rows {
17015 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17016 value.templates.extend(lsp_tasks.templates);
17017 }
17018
17019 editor.insert_tasks(key, value);
17020 }
17021 for (key, value) in lsp_tasks_by_rows {
17022 editor.insert_tasks(key, value);
17023 }
17024 })
17025 .ok();
17026 })
17027 }
17028 fn fetch_runnable_ranges(
17029 snapshot: &DisplaySnapshot,
17030 range: Range<Anchor>,
17031 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
17032 snapshot.buffer_snapshot().runnable_ranges(range).collect()
17033 }
17034
17035 fn runnable_rows(
17036 project: Entity<Project>,
17037 snapshot: DisplaySnapshot,
17038 prefer_lsp: bool,
17039 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17040 cx: AsyncWindowContext,
17041 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17042 cx.spawn(async move |cx| {
17043 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17044 for (run_range, mut runnable) in runnable_ranges {
17045 let Some(tasks) = cx
17046 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17047 .ok()
17048 else {
17049 continue;
17050 };
17051 let mut tasks = tasks.await;
17052
17053 if prefer_lsp {
17054 tasks.retain(|(task_kind, _)| {
17055 !matches!(task_kind, TaskSourceKind::Language { .. })
17056 });
17057 }
17058 if tasks.is_empty() {
17059 continue;
17060 }
17061
17062 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17063 let Some(row) = snapshot
17064 .buffer_snapshot()
17065 .buffer_line_for_row(MultiBufferRow(point.row))
17066 .map(|(_, range)| range.start.row)
17067 else {
17068 continue;
17069 };
17070
17071 let context_range =
17072 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17073 runnable_rows.push((
17074 (runnable.buffer_id, row),
17075 RunnableTasks {
17076 templates: tasks,
17077 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17078 context_range,
17079 column: point.column,
17080 extra_variables: runnable.extra_captures,
17081 },
17082 ));
17083 }
17084 runnable_rows
17085 })
17086 }
17087
17088 fn templates_with_tags(
17089 project: &Entity<Project>,
17090 runnable: &mut Runnable,
17091 cx: &mut App,
17092 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17093 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17094 let (worktree_id, file) = project
17095 .buffer_for_id(runnable.buffer, cx)
17096 .and_then(|buffer| buffer.read(cx).file())
17097 .map(|file| (file.worktree_id(cx), file.clone()))
17098 .unzip();
17099
17100 (
17101 project.task_store().read(cx).task_inventory().cloned(),
17102 worktree_id,
17103 file,
17104 )
17105 });
17106
17107 let tags = mem::take(&mut runnable.tags);
17108 let language = runnable.language.clone();
17109 cx.spawn(async move |cx| {
17110 let mut templates_with_tags = Vec::new();
17111 if let Some(inventory) = inventory {
17112 for RunnableTag(tag) in tags {
17113 let new_tasks = inventory.update(cx, |inventory, cx| {
17114 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17115 });
17116 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17117 move |(_, template)| {
17118 template.tags.iter().any(|source_tag| source_tag == &tag)
17119 },
17120 ));
17121 }
17122 }
17123 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17124
17125 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17126 // Strongest source wins; if we have worktree tag binding, prefer that to
17127 // global and language bindings;
17128 // if we have a global binding, prefer that to language binding.
17129 let first_mismatch = templates_with_tags
17130 .iter()
17131 .position(|(tag_source, _)| tag_source != leading_tag_source);
17132 if let Some(index) = first_mismatch {
17133 templates_with_tags.truncate(index);
17134 }
17135 }
17136
17137 templates_with_tags
17138 })
17139 }
17140
17141 pub fn move_to_enclosing_bracket(
17142 &mut self,
17143 _: &MoveToEnclosingBracket,
17144 window: &mut Window,
17145 cx: &mut Context<Self>,
17146 ) {
17147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17148 self.change_selections(Default::default(), window, cx, |s| {
17149 s.move_offsets_with(|snapshot, selection| {
17150 let Some(enclosing_bracket_ranges) =
17151 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17152 else {
17153 return;
17154 };
17155
17156 let mut best_length = usize::MAX;
17157 let mut best_inside = false;
17158 let mut best_in_bracket_range = false;
17159 let mut best_destination = None;
17160 for (open, close) in enclosing_bracket_ranges {
17161 let close = close.to_inclusive();
17162 let length = *close.end() - open.start;
17163 let inside = selection.start >= open.end && selection.end <= *close.start();
17164 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17165 || close.contains(&selection.head());
17166
17167 // If best is next to a bracket and current isn't, skip
17168 if !in_bracket_range && best_in_bracket_range {
17169 continue;
17170 }
17171
17172 // Prefer smaller lengths unless best is inside and current isn't
17173 if length > best_length && (best_inside || !inside) {
17174 continue;
17175 }
17176
17177 best_length = length;
17178 best_inside = inside;
17179 best_in_bracket_range = in_bracket_range;
17180 best_destination = Some(
17181 if close.contains(&selection.start) && close.contains(&selection.end) {
17182 if inside { open.end } else { open.start }
17183 } else if inside {
17184 *close.start()
17185 } else {
17186 *close.end()
17187 },
17188 );
17189 }
17190
17191 if let Some(destination) = best_destination {
17192 selection.collapse_to(destination, SelectionGoal::None);
17193 }
17194 })
17195 });
17196 }
17197
17198 pub fn undo_selection(
17199 &mut self,
17200 _: &UndoSelection,
17201 window: &mut Window,
17202 cx: &mut Context<Self>,
17203 ) {
17204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17205 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17206 self.selection_history.mode = SelectionHistoryMode::Undoing;
17207 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17208 this.end_selection(window, cx);
17209 this.change_selections(
17210 SelectionEffects::scroll(Autoscroll::newest()),
17211 window,
17212 cx,
17213 |s| s.select_anchors(entry.selections.to_vec()),
17214 );
17215 });
17216 self.selection_history.mode = SelectionHistoryMode::Normal;
17217
17218 self.select_next_state = entry.select_next_state;
17219 self.select_prev_state = entry.select_prev_state;
17220 self.add_selections_state = entry.add_selections_state;
17221 }
17222 }
17223
17224 pub fn redo_selection(
17225 &mut self,
17226 _: &RedoSelection,
17227 window: &mut Window,
17228 cx: &mut Context<Self>,
17229 ) {
17230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17231 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17232 self.selection_history.mode = SelectionHistoryMode::Redoing;
17233 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17234 this.end_selection(window, cx);
17235 this.change_selections(
17236 SelectionEffects::scroll(Autoscroll::newest()),
17237 window,
17238 cx,
17239 |s| s.select_anchors(entry.selections.to_vec()),
17240 );
17241 });
17242 self.selection_history.mode = SelectionHistoryMode::Normal;
17243
17244 self.select_next_state = entry.select_next_state;
17245 self.select_prev_state = entry.select_prev_state;
17246 self.add_selections_state = entry.add_selections_state;
17247 }
17248 }
17249
17250 pub fn expand_excerpts(
17251 &mut self,
17252 action: &ExpandExcerpts,
17253 _: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) {
17256 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17257 }
17258
17259 pub fn expand_excerpts_down(
17260 &mut self,
17261 action: &ExpandExcerptsDown,
17262 _: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) {
17265 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17266 }
17267
17268 pub fn expand_excerpts_up(
17269 &mut self,
17270 action: &ExpandExcerptsUp,
17271 _: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) {
17274 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17275 }
17276
17277 pub fn expand_excerpts_for_direction(
17278 &mut self,
17279 lines: u32,
17280 direction: ExpandExcerptDirection,
17281 cx: &mut Context<Self>,
17282 ) {
17283 let selections = self.selections.disjoint_anchors_arc();
17284
17285 let lines = if lines == 0 {
17286 EditorSettings::get_global(cx).expand_excerpt_lines
17287 } else {
17288 lines
17289 };
17290
17291 let snapshot = self.buffer.read(cx).snapshot(cx);
17292 let mut excerpt_ids = selections
17293 .iter()
17294 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17295 .collect::<Vec<_>>();
17296 excerpt_ids.sort();
17297 excerpt_ids.dedup();
17298
17299 if self.delegate_expand_excerpts {
17300 cx.emit(EditorEvent::ExpandExcerptsRequested {
17301 excerpt_ids,
17302 lines,
17303 direction,
17304 });
17305 return;
17306 }
17307
17308 self.buffer.update(cx, |buffer, cx| {
17309 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17310 })
17311 }
17312
17313 pub fn expand_excerpt(
17314 &mut self,
17315 excerpt: ExcerptId,
17316 direction: ExpandExcerptDirection,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17321
17322 if self.delegate_expand_excerpts {
17323 cx.emit(EditorEvent::ExpandExcerptsRequested {
17324 excerpt_ids: vec![excerpt],
17325 lines: lines_to_expand,
17326 direction,
17327 });
17328 return;
17329 }
17330
17331 let current_scroll_position = self.scroll_position(cx);
17332 let mut scroll = None;
17333
17334 if direction == ExpandExcerptDirection::Down {
17335 let multi_buffer = self.buffer.read(cx);
17336 let snapshot = multi_buffer.snapshot(cx);
17337 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17338 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17339 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17340 {
17341 let buffer_snapshot = buffer.read(cx).snapshot();
17342 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17343 let last_row = buffer_snapshot.max_point().row;
17344 let lines_below = last_row.saturating_sub(excerpt_end_row);
17345 if lines_below >= lines_to_expand {
17346 scroll = Some(
17347 current_scroll_position
17348 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17349 );
17350 }
17351 }
17352 }
17353 if direction == ExpandExcerptDirection::Up
17354 && self
17355 .buffer
17356 .read(cx)
17357 .snapshot(cx)
17358 .excerpt_before(excerpt)
17359 .is_none()
17360 {
17361 scroll = Some(current_scroll_position);
17362 }
17363
17364 self.buffer.update(cx, |buffer, cx| {
17365 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17366 });
17367
17368 if let Some(new_scroll_position) = scroll {
17369 self.set_scroll_position(new_scroll_position, window, cx);
17370 }
17371 }
17372
17373 pub fn go_to_singleton_buffer_point(
17374 &mut self,
17375 point: Point,
17376 window: &mut Window,
17377 cx: &mut Context<Self>,
17378 ) {
17379 self.go_to_singleton_buffer_range(point..point, window, cx);
17380 }
17381
17382 pub fn go_to_singleton_buffer_range(
17383 &mut self,
17384 range: Range<Point>,
17385 window: &mut Window,
17386 cx: &mut Context<Self>,
17387 ) {
17388 let multibuffer = self.buffer().read(cx);
17389 let Some(buffer) = multibuffer.as_singleton() else {
17390 return;
17391 };
17392 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17393 return;
17394 };
17395 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17396 return;
17397 };
17398 self.change_selections(
17399 SelectionEffects::default().nav_history(true),
17400 window,
17401 cx,
17402 |s| s.select_anchor_ranges([start..end]),
17403 );
17404 }
17405
17406 pub fn go_to_diagnostic(
17407 &mut self,
17408 action: &GoToDiagnostic,
17409 window: &mut Window,
17410 cx: &mut Context<Self>,
17411 ) {
17412 if !self.diagnostics_enabled() {
17413 return;
17414 }
17415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17416 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17417 }
17418
17419 pub fn go_to_prev_diagnostic(
17420 &mut self,
17421 action: &GoToPreviousDiagnostic,
17422 window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 if !self.diagnostics_enabled() {
17426 return;
17427 }
17428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17429 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17430 }
17431
17432 pub fn go_to_diagnostic_impl(
17433 &mut self,
17434 direction: Direction,
17435 severity: GoToDiagnosticSeverityFilter,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) {
17439 let buffer = self.buffer.read(cx).snapshot(cx);
17440 let selection = self
17441 .selections
17442 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17443
17444 let mut active_group_id = None;
17445 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17446 && active_group.active_range.start.to_offset(&buffer) == selection.start
17447 {
17448 active_group_id = Some(active_group.group_id);
17449 }
17450
17451 fn filtered<'a>(
17452 severity: GoToDiagnosticSeverityFilter,
17453 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17454 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17455 diagnostics
17456 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17457 .filter(|entry| entry.range.start != entry.range.end)
17458 .filter(|entry| !entry.diagnostic.is_unnecessary)
17459 }
17460
17461 let before = filtered(
17462 severity,
17463 buffer
17464 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17465 .filter(|entry| entry.range.start <= selection.start),
17466 );
17467 let after = filtered(
17468 severity,
17469 buffer
17470 .diagnostics_in_range(selection.start..buffer.len())
17471 .filter(|entry| entry.range.start >= selection.start),
17472 );
17473
17474 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17475 if direction == Direction::Prev {
17476 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17477 {
17478 for diagnostic in prev_diagnostics.into_iter().rev() {
17479 if diagnostic.range.start != selection.start
17480 || active_group_id
17481 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17482 {
17483 found = Some(diagnostic);
17484 break 'outer;
17485 }
17486 }
17487 }
17488 } else {
17489 for diagnostic in after.chain(before) {
17490 if diagnostic.range.start != selection.start
17491 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17492 {
17493 found = Some(diagnostic);
17494 break;
17495 }
17496 }
17497 }
17498 let Some(next_diagnostic) = found else {
17499 return;
17500 };
17501
17502 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17503 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17504 return;
17505 };
17506 let snapshot = self.snapshot(window, cx);
17507 if snapshot.intersects_fold(next_diagnostic.range.start) {
17508 self.unfold_ranges(
17509 std::slice::from_ref(&next_diagnostic.range),
17510 true,
17511 false,
17512 cx,
17513 );
17514 }
17515 self.change_selections(Default::default(), window, cx, |s| {
17516 s.select_ranges(vec![
17517 next_diagnostic.range.start..next_diagnostic.range.start,
17518 ])
17519 });
17520 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17521 self.refresh_edit_prediction(false, true, window, cx);
17522 }
17523
17524 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17526 let snapshot = self.snapshot(window, cx);
17527 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17528 self.go_to_hunk_before_or_after_position(
17529 &snapshot,
17530 selection.head(),
17531 Direction::Next,
17532 window,
17533 cx,
17534 );
17535 }
17536
17537 pub fn go_to_hunk_before_or_after_position(
17538 &mut self,
17539 snapshot: &EditorSnapshot,
17540 position: Point,
17541 direction: Direction,
17542 window: &mut Window,
17543 cx: &mut Context<Editor>,
17544 ) {
17545 let row = if direction == Direction::Next {
17546 self.hunk_after_position(snapshot, position)
17547 .map(|hunk| hunk.row_range.start)
17548 } else {
17549 self.hunk_before_position(snapshot, position)
17550 };
17551
17552 if let Some(row) = row {
17553 let destination = Point::new(row.0, 0);
17554 let autoscroll = Autoscroll::center();
17555
17556 self.unfold_ranges(&[destination..destination], false, false, cx);
17557 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17558 s.select_ranges([destination..destination]);
17559 });
17560 }
17561 }
17562
17563 fn hunk_after_position(
17564 &mut self,
17565 snapshot: &EditorSnapshot,
17566 position: Point,
17567 ) -> Option<MultiBufferDiffHunk> {
17568 snapshot
17569 .buffer_snapshot()
17570 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17571 .find(|hunk| hunk.row_range.start.0 > position.row)
17572 .or_else(|| {
17573 snapshot
17574 .buffer_snapshot()
17575 .diff_hunks_in_range(Point::zero()..position)
17576 .find(|hunk| hunk.row_range.end.0 < position.row)
17577 })
17578 }
17579
17580 fn go_to_prev_hunk(
17581 &mut self,
17582 _: &GoToPreviousHunk,
17583 window: &mut Window,
17584 cx: &mut Context<Self>,
17585 ) {
17586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17587 let snapshot = self.snapshot(window, cx);
17588 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17589 self.go_to_hunk_before_or_after_position(
17590 &snapshot,
17591 selection.head(),
17592 Direction::Prev,
17593 window,
17594 cx,
17595 );
17596 }
17597
17598 fn hunk_before_position(
17599 &mut self,
17600 snapshot: &EditorSnapshot,
17601 position: Point,
17602 ) -> Option<MultiBufferRow> {
17603 snapshot
17604 .buffer_snapshot()
17605 .diff_hunk_before(position)
17606 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17607 }
17608
17609 fn go_to_next_change(
17610 &mut self,
17611 _: &GoToNextChange,
17612 window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) {
17615 if let Some(selections) = self
17616 .change_list
17617 .next_change(1, Direction::Next)
17618 .map(|s| s.to_vec())
17619 {
17620 self.change_selections(Default::default(), window, cx, |s| {
17621 let map = s.display_snapshot();
17622 s.select_display_ranges(selections.iter().map(|a| {
17623 let point = a.to_display_point(&map);
17624 point..point
17625 }))
17626 })
17627 }
17628 }
17629
17630 fn go_to_previous_change(
17631 &mut self,
17632 _: &GoToPreviousChange,
17633 window: &mut Window,
17634 cx: &mut Context<Self>,
17635 ) {
17636 if let Some(selections) = self
17637 .change_list
17638 .next_change(1, Direction::Prev)
17639 .map(|s| s.to_vec())
17640 {
17641 self.change_selections(Default::default(), window, cx, |s| {
17642 let map = s.display_snapshot();
17643 s.select_display_ranges(selections.iter().map(|a| {
17644 let point = a.to_display_point(&map);
17645 point..point
17646 }))
17647 })
17648 }
17649 }
17650
17651 pub fn go_to_next_document_highlight(
17652 &mut self,
17653 _: &GoToNextDocumentHighlight,
17654 window: &mut Window,
17655 cx: &mut Context<Self>,
17656 ) {
17657 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17658 }
17659
17660 pub fn go_to_prev_document_highlight(
17661 &mut self,
17662 _: &GoToPreviousDocumentHighlight,
17663 window: &mut Window,
17664 cx: &mut Context<Self>,
17665 ) {
17666 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17667 }
17668
17669 pub fn go_to_document_highlight_before_or_after_position(
17670 &mut self,
17671 direction: Direction,
17672 window: &mut Window,
17673 cx: &mut Context<Editor>,
17674 ) {
17675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17676 let snapshot = self.snapshot(window, cx);
17677 let buffer = &snapshot.buffer_snapshot();
17678 let position = self
17679 .selections
17680 .newest::<Point>(&snapshot.display_snapshot)
17681 .head();
17682 let anchor_position = buffer.anchor_after(position);
17683
17684 // Get all document highlights (both read and write)
17685 let mut all_highlights = Vec::new();
17686
17687 if let Some((_, read_highlights)) = self
17688 .background_highlights
17689 .get(&HighlightKey::DocumentHighlightRead)
17690 {
17691 all_highlights.extend(read_highlights.iter());
17692 }
17693
17694 if let Some((_, write_highlights)) = self
17695 .background_highlights
17696 .get(&HighlightKey::DocumentHighlightWrite)
17697 {
17698 all_highlights.extend(write_highlights.iter());
17699 }
17700
17701 if all_highlights.is_empty() {
17702 return;
17703 }
17704
17705 // Sort highlights by position
17706 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17707
17708 let target_highlight = match direction {
17709 Direction::Next => {
17710 // Find the first highlight after the current position
17711 all_highlights
17712 .iter()
17713 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17714 }
17715 Direction::Prev => {
17716 // Find the last highlight before the current position
17717 all_highlights
17718 .iter()
17719 .rev()
17720 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17721 }
17722 };
17723
17724 if let Some(highlight) = target_highlight {
17725 let destination = highlight.start.to_point(buffer);
17726 let autoscroll = Autoscroll::center();
17727
17728 self.unfold_ranges(&[destination..destination], false, false, cx);
17729 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17730 s.select_ranges([destination..destination]);
17731 });
17732 }
17733 }
17734
17735 fn go_to_line<T: 'static>(
17736 &mut self,
17737 position: Anchor,
17738 highlight_color: Option<Hsla>,
17739 window: &mut Window,
17740 cx: &mut Context<Self>,
17741 ) {
17742 let snapshot = self.snapshot(window, cx).display_snapshot;
17743 let position = position.to_point(&snapshot.buffer_snapshot());
17744 let start = snapshot
17745 .buffer_snapshot()
17746 .clip_point(Point::new(position.row, 0), Bias::Left);
17747 let end = start + Point::new(1, 0);
17748 let start = snapshot.buffer_snapshot().anchor_before(start);
17749 let end = snapshot.buffer_snapshot().anchor_before(end);
17750
17751 self.highlight_rows::<T>(
17752 start..end,
17753 highlight_color
17754 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17755 Default::default(),
17756 cx,
17757 );
17758
17759 if self.buffer.read(cx).is_singleton() {
17760 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17761 }
17762 }
17763
17764 pub fn go_to_definition(
17765 &mut self,
17766 _: &GoToDefinition,
17767 window: &mut Window,
17768 cx: &mut Context<Self>,
17769 ) -> Task<Result<Navigated>> {
17770 let definition =
17771 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17772 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17773 cx.spawn_in(window, async move |editor, cx| {
17774 if definition.await? == Navigated::Yes {
17775 return Ok(Navigated::Yes);
17776 }
17777 match fallback_strategy {
17778 GoToDefinitionFallback::None => Ok(Navigated::No),
17779 GoToDefinitionFallback::FindAllReferences => {
17780 match editor.update_in(cx, |editor, window, cx| {
17781 editor.find_all_references(&FindAllReferences::default(), window, cx)
17782 })? {
17783 Some(references) => references.await,
17784 None => Ok(Navigated::No),
17785 }
17786 }
17787 }
17788 })
17789 }
17790
17791 pub fn go_to_declaration(
17792 &mut self,
17793 _: &GoToDeclaration,
17794 window: &mut Window,
17795 cx: &mut Context<Self>,
17796 ) -> Task<Result<Navigated>> {
17797 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17798 }
17799
17800 pub fn go_to_declaration_split(
17801 &mut self,
17802 _: &GoToDeclaration,
17803 window: &mut Window,
17804 cx: &mut Context<Self>,
17805 ) -> Task<Result<Navigated>> {
17806 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17807 }
17808
17809 pub fn go_to_implementation(
17810 &mut self,
17811 _: &GoToImplementation,
17812 window: &mut Window,
17813 cx: &mut Context<Self>,
17814 ) -> Task<Result<Navigated>> {
17815 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17816 }
17817
17818 pub fn go_to_implementation_split(
17819 &mut self,
17820 _: &GoToImplementationSplit,
17821 window: &mut Window,
17822 cx: &mut Context<Self>,
17823 ) -> Task<Result<Navigated>> {
17824 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17825 }
17826
17827 pub fn go_to_type_definition(
17828 &mut self,
17829 _: &GoToTypeDefinition,
17830 window: &mut Window,
17831 cx: &mut Context<Self>,
17832 ) -> Task<Result<Navigated>> {
17833 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17834 }
17835
17836 pub fn go_to_definition_split(
17837 &mut self,
17838 _: &GoToDefinitionSplit,
17839 window: &mut Window,
17840 cx: &mut Context<Self>,
17841 ) -> Task<Result<Navigated>> {
17842 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17843 }
17844
17845 pub fn go_to_type_definition_split(
17846 &mut self,
17847 _: &GoToTypeDefinitionSplit,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) -> Task<Result<Navigated>> {
17851 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17852 }
17853
17854 fn go_to_definition_of_kind(
17855 &mut self,
17856 kind: GotoDefinitionKind,
17857 split: bool,
17858 window: &mut Window,
17859 cx: &mut Context<Self>,
17860 ) -> Task<Result<Navigated>> {
17861 let Some(provider) = self.semantics_provider.clone() else {
17862 return Task::ready(Ok(Navigated::No));
17863 };
17864 let head = self
17865 .selections
17866 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17867 .head();
17868 let buffer = self.buffer.read(cx);
17869 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17870 return Task::ready(Ok(Navigated::No));
17871 };
17872 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17873 return Task::ready(Ok(Navigated::No));
17874 };
17875
17876 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
17877
17878 cx.spawn_in(window, async move |editor, cx| {
17879 let Some(definitions) = definitions.await? else {
17880 return Ok(Navigated::No);
17881 };
17882 let navigated = editor
17883 .update_in(cx, |editor, window, cx| {
17884 editor.navigate_to_hover_links(
17885 Some(kind),
17886 definitions
17887 .into_iter()
17888 .filter(|location| {
17889 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17890 })
17891 .map(HoverLink::Text)
17892 .collect::<Vec<_>>(),
17893 nav_entry,
17894 split,
17895 window,
17896 cx,
17897 )
17898 })?
17899 .await?;
17900 anyhow::Ok(navigated)
17901 })
17902 }
17903
17904 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17905 let selection = self.selections.newest_anchor();
17906 let head = selection.head();
17907 let tail = selection.tail();
17908
17909 let Some((buffer, start_position)) =
17910 self.buffer.read(cx).text_anchor_for_position(head, cx)
17911 else {
17912 return;
17913 };
17914
17915 let end_position = if head != tail {
17916 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17917 return;
17918 };
17919 Some(pos)
17920 } else {
17921 None
17922 };
17923
17924 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17925 let url = if let Some(end_pos) = end_position {
17926 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17927 } else {
17928 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17929 };
17930
17931 if let Some(url) = url {
17932 cx.update(|window, cx| {
17933 if parse_zed_link(&url, cx).is_some() {
17934 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17935 } else {
17936 cx.open_url(&url);
17937 }
17938 })?;
17939 }
17940
17941 anyhow::Ok(())
17942 });
17943
17944 url_finder.detach();
17945 }
17946
17947 pub fn open_selected_filename(
17948 &mut self,
17949 _: &OpenSelectedFilename,
17950 window: &mut Window,
17951 cx: &mut Context<Self>,
17952 ) {
17953 let Some(workspace) = self.workspace() else {
17954 return;
17955 };
17956
17957 let position = self.selections.newest_anchor().head();
17958
17959 let Some((buffer, buffer_position)) =
17960 self.buffer.read(cx).text_anchor_for_position(position, cx)
17961 else {
17962 return;
17963 };
17964
17965 let project = self.project.clone();
17966
17967 cx.spawn_in(window, async move |_, cx| {
17968 let result = find_file(&buffer, project, buffer_position, cx).await;
17969
17970 if let Some((_, path)) = result {
17971 workspace
17972 .update_in(cx, |workspace, window, cx| {
17973 workspace.open_resolved_path(path, window, cx)
17974 })?
17975 .await?;
17976 }
17977 anyhow::Ok(())
17978 })
17979 .detach();
17980 }
17981
17982 pub(crate) fn navigate_to_hover_links(
17983 &mut self,
17984 kind: Option<GotoDefinitionKind>,
17985 definitions: Vec<HoverLink>,
17986 origin: Option<NavigationEntry>,
17987 split: bool,
17988 window: &mut Window,
17989 cx: &mut Context<Editor>,
17990 ) -> Task<Result<Navigated>> {
17991 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17992 let mut first_url_or_file = None;
17993 let definitions: Vec<_> = definitions
17994 .into_iter()
17995 .filter_map(|def| match def {
17996 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17997 HoverLink::InlayHint(lsp_location, server_id) => {
17998 let computation =
17999 self.compute_target_location(lsp_location, server_id, window, cx);
18000 Some(cx.background_spawn(computation))
18001 }
18002 HoverLink::Url(url) => {
18003 first_url_or_file = Some(Either::Left(url));
18004 None
18005 }
18006 HoverLink::File(path) => {
18007 first_url_or_file = Some(Either::Right(path));
18008 None
18009 }
18010 })
18011 .collect();
18012
18013 let workspace = self.workspace();
18014
18015 cx.spawn_in(window, async move |editor, cx| {
18016 let locations: Vec<Location> = future::join_all(definitions)
18017 .await
18018 .into_iter()
18019 .filter_map(|location| location.transpose())
18020 .collect::<Result<_>>()
18021 .context("location tasks")?;
18022 let mut locations = cx.update(|_, cx| {
18023 locations
18024 .into_iter()
18025 .map(|location| {
18026 let buffer = location.buffer.read(cx);
18027 (location.buffer, location.range.to_point(buffer))
18028 })
18029 .into_group_map()
18030 })?;
18031 let mut num_locations = 0;
18032 for ranges in locations.values_mut() {
18033 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18034 ranges.dedup();
18035 num_locations += ranges.len();
18036 }
18037
18038 if num_locations > 1 {
18039 let tab_kind = match kind {
18040 Some(GotoDefinitionKind::Implementation) => "Implementations",
18041 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18042 Some(GotoDefinitionKind::Declaration) => "Declarations",
18043 Some(GotoDefinitionKind::Type) => "Types",
18044 };
18045 let title = editor
18046 .update_in(cx, |_, _, cx| {
18047 let target = locations
18048 .iter()
18049 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18050 .map(|(buffer, location)| {
18051 buffer
18052 .read(cx)
18053 .text_for_range(location.clone())
18054 .collect::<String>()
18055 })
18056 .filter(|text| !text.contains('\n'))
18057 .unique()
18058 .take(3)
18059 .join(", ");
18060 if target.is_empty() {
18061 tab_kind.to_owned()
18062 } else {
18063 format!("{tab_kind} for {target}")
18064 }
18065 })
18066 .context("buffer title")?;
18067
18068 let Some(workspace) = workspace else {
18069 return Ok(Navigated::No);
18070 };
18071
18072 let opened = workspace
18073 .update_in(cx, |workspace, window, cx| {
18074 let allow_preview = PreviewTabsSettings::get_global(cx)
18075 .enable_preview_multibuffer_from_code_navigation;
18076 if let Some((target_editor, target_pane)) =
18077 Self::open_locations_in_multibuffer(
18078 workspace,
18079 locations,
18080 title,
18081 split,
18082 allow_preview,
18083 MultibufferSelectionMode::First,
18084 window,
18085 cx,
18086 )
18087 {
18088 // We create our own nav history instead of using
18089 // `target_editor.nav_history` because `nav_history`
18090 // seems to be populated asynchronously when an item
18091 // is added to a pane
18092 let mut nav_history = target_pane
18093 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18094 target_editor.update(cx, |editor, cx| {
18095 let nav_data = editor
18096 .navigation_data(editor.selections.newest_anchor().head(), cx);
18097 let target =
18098 Some(nav_history.navigation_entry(Some(
18099 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18100 )));
18101 nav_history.push_tag(origin, target);
18102 })
18103 }
18104 })
18105 .is_ok();
18106
18107 anyhow::Ok(Navigated::from_bool(opened))
18108 } else if num_locations == 0 {
18109 // If there is one url or file, open it directly
18110 match first_url_or_file {
18111 Some(Either::Left(url)) => {
18112 cx.update(|window, cx| {
18113 if parse_zed_link(&url, cx).is_some() {
18114 window
18115 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18116 } else {
18117 cx.open_url(&url);
18118 }
18119 })?;
18120 Ok(Navigated::Yes)
18121 }
18122 Some(Either::Right(path)) => {
18123 // TODO(andrew): respect preview tab settings
18124 // `enable_keep_preview_on_code_navigation` and
18125 // `enable_preview_file_from_code_navigation`
18126 let Some(workspace) = workspace else {
18127 return Ok(Navigated::No);
18128 };
18129 workspace
18130 .update_in(cx, |workspace, window, cx| {
18131 workspace.open_resolved_path(path, window, cx)
18132 })?
18133 .await?;
18134 Ok(Navigated::Yes)
18135 }
18136 None => Ok(Navigated::No),
18137 }
18138 } else {
18139 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18140 let target_range = target_ranges.first().unwrap().clone();
18141
18142 editor.update_in(cx, |editor, window, cx| {
18143 let range = editor.range_for_match(&target_range);
18144 let range = collapse_multiline_range(range);
18145
18146 if !split
18147 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18148 {
18149 editor.go_to_singleton_buffer_range(range, window, cx);
18150
18151 let target =
18152 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18153 if let Some(mut nav_history) = editor.nav_history.clone() {
18154 nav_history.push_tag(origin, target);
18155 }
18156 } else {
18157 let Some(workspace) = workspace else {
18158 return Navigated::No;
18159 };
18160 let pane = workspace.read(cx).active_pane().clone();
18161 window.defer(cx, move |window, cx| {
18162 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18163 workspace.update(cx, |workspace, cx| {
18164 let pane = if split {
18165 workspace.adjacent_pane(window, cx)
18166 } else {
18167 workspace.active_pane().clone()
18168 };
18169
18170 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18171 let keep_old_preview = preview_tabs_settings
18172 .enable_keep_preview_on_code_navigation;
18173 let allow_new_preview = preview_tabs_settings
18174 .enable_preview_file_from_code_navigation;
18175
18176 let editor = workspace.open_project_item(
18177 pane.clone(),
18178 target_buffer.clone(),
18179 true,
18180 true,
18181 keep_old_preview,
18182 allow_new_preview,
18183 window,
18184 cx,
18185 );
18186 (editor, pane)
18187 });
18188 // We create our own nav history instead of using
18189 // `target_editor.nav_history` because `nav_history`
18190 // seems to be populated asynchronously when an item
18191 // is added to a pane
18192 let mut nav_history = target_pane
18193 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18194 target_editor.update(cx, |target_editor, cx| {
18195 // When selecting a definition in a different buffer, disable the nav history
18196 // to avoid creating a history entry at the previous cursor location.
18197 pane.update(cx, |pane, _| pane.disable_history());
18198 target_editor.go_to_singleton_buffer_range(range, window, cx);
18199
18200 let nav_data = target_editor.navigation_data(
18201 target_editor.selections.newest_anchor().head(),
18202 cx,
18203 );
18204 let target =
18205 Some(nav_history.navigation_entry(Some(
18206 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18207 )));
18208 nav_history.push_tag(origin, target);
18209 pane.update(cx, |pane, _| pane.enable_history());
18210 });
18211 });
18212 }
18213 Navigated::Yes
18214 })
18215 }
18216 })
18217 }
18218
18219 fn compute_target_location(
18220 &self,
18221 lsp_location: lsp::Location,
18222 server_id: LanguageServerId,
18223 window: &mut Window,
18224 cx: &mut Context<Self>,
18225 ) -> Task<anyhow::Result<Option<Location>>> {
18226 let Some(project) = self.project.clone() else {
18227 return Task::ready(Ok(None));
18228 };
18229
18230 cx.spawn_in(window, async move |editor, cx| {
18231 let location_task = editor.update(cx, |_, cx| {
18232 project.update(cx, |project, cx| {
18233 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18234 })
18235 })?;
18236 let location = Some({
18237 let target_buffer_handle = location_task.await.context("open local buffer")?;
18238 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18239 let target_start = target_buffer
18240 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18241 let target_end = target_buffer
18242 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18243 target_buffer.anchor_after(target_start)
18244 ..target_buffer.anchor_before(target_end)
18245 });
18246 Location {
18247 buffer: target_buffer_handle,
18248 range,
18249 }
18250 });
18251 Ok(location)
18252 })
18253 }
18254
18255 fn go_to_next_reference(
18256 &mut self,
18257 _: &GoToNextReference,
18258 window: &mut Window,
18259 cx: &mut Context<Self>,
18260 ) {
18261 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18262 if let Some(task) = task {
18263 task.detach();
18264 };
18265 }
18266
18267 fn go_to_prev_reference(
18268 &mut self,
18269 _: &GoToPreviousReference,
18270 window: &mut Window,
18271 cx: &mut Context<Self>,
18272 ) {
18273 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18274 if let Some(task) = task {
18275 task.detach();
18276 };
18277 }
18278
18279 pub fn go_to_reference_before_or_after_position(
18280 &mut self,
18281 direction: Direction,
18282 count: usize,
18283 window: &mut Window,
18284 cx: &mut Context<Self>,
18285 ) -> Option<Task<Result<()>>> {
18286 let selection = self.selections.newest_anchor();
18287 let head = selection.head();
18288
18289 let multi_buffer = self.buffer.read(cx);
18290
18291 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18292 let workspace = self.workspace()?;
18293 let project = workspace.read(cx).project().clone();
18294 let references =
18295 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18296 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18297 let Some(locations) = references.await? else {
18298 return Ok(());
18299 };
18300
18301 if locations.is_empty() {
18302 // totally normal - the cursor may be on something which is not
18303 // a symbol (e.g. a keyword)
18304 log::info!("no references found under cursor");
18305 return Ok(());
18306 }
18307
18308 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18309
18310 let (locations, current_location_index) =
18311 multi_buffer.update(cx, |multi_buffer, cx| {
18312 let mut locations = locations
18313 .into_iter()
18314 .filter_map(|loc| {
18315 let start = multi_buffer.buffer_anchor_to_anchor(
18316 &loc.buffer,
18317 loc.range.start,
18318 cx,
18319 )?;
18320 let end = multi_buffer.buffer_anchor_to_anchor(
18321 &loc.buffer,
18322 loc.range.end,
18323 cx,
18324 )?;
18325 Some(start..end)
18326 })
18327 .collect::<Vec<_>>();
18328
18329 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18330 // There is an O(n) implementation, but given this list will be
18331 // small (usually <100 items), the extra O(log(n)) factor isn't
18332 // worth the (surprisingly large amount of) extra complexity.
18333 locations
18334 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18335
18336 let head_offset = head.to_offset(&multi_buffer_snapshot);
18337
18338 let current_location_index = locations.iter().position(|loc| {
18339 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18340 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18341 });
18342
18343 (locations, current_location_index)
18344 });
18345
18346 let Some(current_location_index) = current_location_index else {
18347 // This indicates something has gone wrong, because we already
18348 // handle the "no references" case above
18349 log::error!(
18350 "failed to find current reference under cursor. Total references: {}",
18351 locations.len()
18352 );
18353 return Ok(());
18354 };
18355
18356 let destination_location_index = match direction {
18357 Direction::Next => (current_location_index + count) % locations.len(),
18358 Direction::Prev => {
18359 (current_location_index + locations.len() - count % locations.len())
18360 % locations.len()
18361 }
18362 };
18363
18364 // TODO(cameron): is this needed?
18365 // the thinking is to avoid "jumping to the current location" (avoid
18366 // polluting "jumplist" in vim terms)
18367 if current_location_index == destination_location_index {
18368 return Ok(());
18369 }
18370
18371 let Range { start, end } = locations[destination_location_index];
18372
18373 editor.update_in(cx, |editor, window, cx| {
18374 let effects = SelectionEffects::default();
18375
18376 editor.unfold_ranges(&[start..end], false, false, cx);
18377 editor.change_selections(effects, window, cx, |s| {
18378 s.select_ranges([start..start]);
18379 });
18380 })?;
18381
18382 Ok(())
18383 }))
18384 }
18385
18386 pub fn find_all_references(
18387 &mut self,
18388 action: &FindAllReferences,
18389 window: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) -> Option<Task<Result<Navigated>>> {
18392 let always_open_multibuffer = action.always_open_multibuffer;
18393 let selection = self.selections.newest_anchor();
18394 let multi_buffer = self.buffer.read(cx);
18395 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18396 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18397 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18398 let head = selection_offset.head();
18399
18400 let head_anchor = multi_buffer_snapshot.anchor_at(
18401 head,
18402 if head < selection_offset.tail() {
18403 Bias::Right
18404 } else {
18405 Bias::Left
18406 },
18407 );
18408
18409 match self
18410 .find_all_references_task_sources
18411 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18412 {
18413 Ok(_) => {
18414 log::info!(
18415 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18416 );
18417 return None;
18418 }
18419 Err(i) => {
18420 self.find_all_references_task_sources.insert(i, head_anchor);
18421 }
18422 }
18423
18424 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18425 let workspace = self.workspace()?;
18426 let project = workspace.read(cx).project().clone();
18427 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18428 Some(cx.spawn_in(window, async move |editor, cx| {
18429 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18430 if let Ok(i) = editor
18431 .find_all_references_task_sources
18432 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18433 {
18434 editor.find_all_references_task_sources.remove(i);
18435 }
18436 });
18437
18438 let Some(locations) = references.await? else {
18439 return anyhow::Ok(Navigated::No);
18440 };
18441 let mut locations = cx.update(|_, cx| {
18442 locations
18443 .into_iter()
18444 .map(|location| {
18445 let buffer = location.buffer.read(cx);
18446 (location.buffer, location.range.to_point(buffer))
18447 })
18448 // if special-casing the single-match case, remove ranges
18449 // that intersect current selection
18450 .filter(|(location_buffer, location)| {
18451 if always_open_multibuffer || &buffer != location_buffer {
18452 return true;
18453 }
18454
18455 !location.contains_inclusive(&selection_point.range())
18456 })
18457 .into_group_map()
18458 })?;
18459 if locations.is_empty() {
18460 return anyhow::Ok(Navigated::No);
18461 }
18462 for ranges in locations.values_mut() {
18463 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18464 ranges.dedup();
18465 }
18466 let mut num_locations = 0;
18467 for ranges in locations.values_mut() {
18468 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18469 ranges.dedup();
18470 num_locations += ranges.len();
18471 }
18472
18473 if num_locations == 1 && !always_open_multibuffer {
18474 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18475 let target_range = target_ranges.first().unwrap().clone();
18476
18477 return editor.update_in(cx, |editor, window, cx| {
18478 let range = target_range.to_point(target_buffer.read(cx));
18479 let range = editor.range_for_match(&range);
18480 let range = range.start..range.start;
18481
18482 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18483 editor.go_to_singleton_buffer_range(range, window, cx);
18484 } else {
18485 let pane = workspace.read(cx).active_pane().clone();
18486 window.defer(cx, move |window, cx| {
18487 let target_editor: Entity<Self> =
18488 workspace.update(cx, |workspace, cx| {
18489 let pane = workspace.active_pane().clone();
18490
18491 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18492 let keep_old_preview = preview_tabs_settings
18493 .enable_keep_preview_on_code_navigation;
18494 let allow_new_preview = preview_tabs_settings
18495 .enable_preview_file_from_code_navigation;
18496
18497 workspace.open_project_item(
18498 pane,
18499 target_buffer.clone(),
18500 true,
18501 true,
18502 keep_old_preview,
18503 allow_new_preview,
18504 window,
18505 cx,
18506 )
18507 });
18508 target_editor.update(cx, |target_editor, cx| {
18509 // When selecting a definition in a different buffer, disable the nav history
18510 // to avoid creating a history entry at the previous cursor location.
18511 pane.update(cx, |pane, _| pane.disable_history());
18512 target_editor.go_to_singleton_buffer_range(range, window, cx);
18513 pane.update(cx, |pane, _| pane.enable_history());
18514 });
18515 });
18516 }
18517 Navigated::No
18518 });
18519 }
18520
18521 workspace.update_in(cx, |workspace, window, cx| {
18522 let target = locations
18523 .iter()
18524 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18525 .map(|(buffer, location)| {
18526 buffer
18527 .read(cx)
18528 .text_for_range(location.clone())
18529 .collect::<String>()
18530 })
18531 .filter(|text| !text.contains('\n'))
18532 .unique()
18533 .take(3)
18534 .join(", ");
18535 let title = if target.is_empty() {
18536 "References".to_owned()
18537 } else {
18538 format!("References to {target}")
18539 };
18540 let allow_preview = PreviewTabsSettings::get_global(cx)
18541 .enable_preview_multibuffer_from_code_navigation;
18542 Self::open_locations_in_multibuffer(
18543 workspace,
18544 locations,
18545 title,
18546 false,
18547 allow_preview,
18548 MultibufferSelectionMode::First,
18549 window,
18550 cx,
18551 );
18552 Navigated::Yes
18553 })
18554 }))
18555 }
18556
18557 /// Opens a multibuffer with the given project locations in it.
18558 pub fn open_locations_in_multibuffer(
18559 workspace: &mut Workspace,
18560 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18561 title: String,
18562 split: bool,
18563 allow_preview: bool,
18564 multibuffer_selection_mode: MultibufferSelectionMode,
18565 window: &mut Window,
18566 cx: &mut Context<Workspace>,
18567 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18568 if locations.is_empty() {
18569 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18570 return None;
18571 }
18572
18573 let capability = workspace.project().read(cx).capability();
18574 let mut ranges = <Vec<Range<Anchor>>>::new();
18575
18576 // a key to find existing multibuffer editors with the same set of locations
18577 // to prevent us from opening more and more multibuffer tabs for searches and the like
18578 let mut key = (title.clone(), vec![]);
18579 let excerpt_buffer = cx.new(|cx| {
18580 let key = &mut key.1;
18581 let mut multibuffer = MultiBuffer::new(capability);
18582 for (buffer, mut ranges_for_buffer) in locations {
18583 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18584 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18585 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18586 PathKey::for_buffer(&buffer, cx),
18587 buffer.clone(),
18588 ranges_for_buffer,
18589 multibuffer_context_lines(cx),
18590 cx,
18591 );
18592 ranges.extend(new_ranges)
18593 }
18594
18595 multibuffer.with_title(title)
18596 });
18597 let existing = workspace.active_pane().update(cx, |pane, cx| {
18598 pane.items()
18599 .filter_map(|item| item.downcast::<Editor>())
18600 .find(|editor| {
18601 editor
18602 .read(cx)
18603 .lookup_key
18604 .as_ref()
18605 .and_then(|it| {
18606 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18607 })
18608 .is_some_and(|it| *it == key)
18609 })
18610 });
18611 let was_existing = existing.is_some();
18612 let editor = existing.unwrap_or_else(|| {
18613 cx.new(|cx| {
18614 let mut editor = Editor::for_multibuffer(
18615 excerpt_buffer,
18616 Some(workspace.project().clone()),
18617 window,
18618 cx,
18619 );
18620 editor.lookup_key = Some(Box::new(key));
18621 editor
18622 })
18623 });
18624 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18625 MultibufferSelectionMode::First => {
18626 if let Some(first_range) = ranges.first() {
18627 editor.change_selections(
18628 SelectionEffects::no_scroll(),
18629 window,
18630 cx,
18631 |selections| {
18632 selections.clear_disjoint();
18633 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18634 },
18635 );
18636 }
18637 editor.highlight_background(
18638 HighlightKey::Editor,
18639 &ranges,
18640 |_, theme| theme.colors().editor_highlighted_line_background,
18641 cx,
18642 );
18643 }
18644 MultibufferSelectionMode::All => {
18645 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18646 selections.clear_disjoint();
18647 selections.select_anchor_ranges(ranges);
18648 });
18649 }
18650 });
18651
18652 let item = Box::new(editor.clone());
18653
18654 let pane = if split {
18655 workspace.adjacent_pane(window, cx)
18656 } else {
18657 workspace.active_pane().clone()
18658 };
18659 let activate_pane = split;
18660
18661 let mut destination_index = None;
18662 pane.update(cx, |pane, cx| {
18663 if allow_preview && !was_existing {
18664 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18665 }
18666 if was_existing && !allow_preview {
18667 pane.unpreview_item_if_preview(item.item_id());
18668 }
18669 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18670 });
18671
18672 Some((editor, pane))
18673 }
18674
18675 pub fn rename(
18676 &mut self,
18677 _: &Rename,
18678 window: &mut Window,
18679 cx: &mut Context<Self>,
18680 ) -> Option<Task<Result<()>>> {
18681 use language::ToOffset as _;
18682
18683 let provider = self.semantics_provider.clone()?;
18684 let selection = self.selections.newest_anchor().clone();
18685 let (cursor_buffer, cursor_buffer_position) = self
18686 .buffer
18687 .read(cx)
18688 .text_anchor_for_position(selection.head(), cx)?;
18689 let (tail_buffer, cursor_buffer_position_end) = self
18690 .buffer
18691 .read(cx)
18692 .text_anchor_for_position(selection.tail(), cx)?;
18693 if tail_buffer != cursor_buffer {
18694 return None;
18695 }
18696
18697 let snapshot = cursor_buffer.read(cx).snapshot();
18698 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18699 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18700 let prepare_rename = provider
18701 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18702 .unwrap_or_else(|| Task::ready(Ok(None)));
18703 drop(snapshot);
18704
18705 Some(cx.spawn_in(window, async move |this, cx| {
18706 let rename_range = if let Some(range) = prepare_rename.await? {
18707 Some(range)
18708 } else {
18709 this.update(cx, |this, cx| {
18710 let buffer = this.buffer.read(cx).snapshot(cx);
18711 let mut buffer_highlights = this
18712 .document_highlights_for_position(selection.head(), &buffer)
18713 .filter(|highlight| {
18714 highlight.start.excerpt_id == selection.head().excerpt_id
18715 && highlight.end.excerpt_id == selection.head().excerpt_id
18716 });
18717 buffer_highlights
18718 .next()
18719 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18720 })?
18721 };
18722 if let Some(rename_range) = rename_range {
18723 this.update_in(cx, |this, window, cx| {
18724 let snapshot = cursor_buffer.read(cx).snapshot();
18725 let rename_buffer_range = rename_range.to_offset(&snapshot);
18726 let cursor_offset_in_rename_range =
18727 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18728 let cursor_offset_in_rename_range_end =
18729 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18730
18731 this.take_rename(false, window, cx);
18732 let buffer = this.buffer.read(cx).read(cx);
18733 let cursor_offset = selection.head().to_offset(&buffer);
18734 let rename_start =
18735 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18736 let rename_end = rename_start + rename_buffer_range.len();
18737 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18738 let mut old_highlight_id = None;
18739 let old_name: Arc<str> = buffer
18740 .chunks(rename_start..rename_end, true)
18741 .map(|chunk| {
18742 if old_highlight_id.is_none() {
18743 old_highlight_id = chunk.syntax_highlight_id;
18744 }
18745 chunk.text
18746 })
18747 .collect::<String>()
18748 .into();
18749
18750 drop(buffer);
18751
18752 // Position the selection in the rename editor so that it matches the current selection.
18753 this.show_local_selections = false;
18754 let rename_editor = cx.new(|cx| {
18755 let mut editor = Editor::single_line(window, cx);
18756 editor.buffer.update(cx, |buffer, cx| {
18757 buffer.edit(
18758 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18759 None,
18760 cx,
18761 )
18762 });
18763 let cursor_offset_in_rename_range =
18764 MultiBufferOffset(cursor_offset_in_rename_range);
18765 let cursor_offset_in_rename_range_end =
18766 MultiBufferOffset(cursor_offset_in_rename_range_end);
18767 let rename_selection_range = match cursor_offset_in_rename_range
18768 .cmp(&cursor_offset_in_rename_range_end)
18769 {
18770 Ordering::Equal => {
18771 editor.select_all(&SelectAll, window, cx);
18772 return editor;
18773 }
18774 Ordering::Less => {
18775 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18776 }
18777 Ordering::Greater => {
18778 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18779 }
18780 };
18781 if rename_selection_range.end.0 > old_name.len() {
18782 editor.select_all(&SelectAll, window, cx);
18783 } else {
18784 editor.change_selections(Default::default(), window, cx, |s| {
18785 s.select_ranges([rename_selection_range]);
18786 });
18787 }
18788 editor
18789 });
18790 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18791 if e == &EditorEvent::Focused {
18792 cx.emit(EditorEvent::FocusedIn)
18793 }
18794 })
18795 .detach();
18796
18797 let write_highlights =
18798 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18799 let read_highlights =
18800 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18801 let ranges = write_highlights
18802 .iter()
18803 .flat_map(|(_, ranges)| ranges.iter())
18804 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18805 .cloned()
18806 .collect();
18807
18808 this.highlight_text(
18809 HighlightKey::Rename,
18810 ranges,
18811 HighlightStyle {
18812 fade_out: Some(0.6),
18813 ..Default::default()
18814 },
18815 cx,
18816 );
18817 let rename_focus_handle = rename_editor.focus_handle(cx);
18818 window.focus(&rename_focus_handle, cx);
18819 let block_id = this.insert_blocks(
18820 [BlockProperties {
18821 style: BlockStyle::Flex,
18822 placement: BlockPlacement::Below(range.start),
18823 height: Some(1),
18824 render: Arc::new({
18825 let rename_editor = rename_editor.clone();
18826 move |cx: &mut BlockContext| {
18827 let mut text_style = cx.editor_style.text.clone();
18828 if let Some(highlight_style) = old_highlight_id
18829 .and_then(|h| h.style(&cx.editor_style.syntax))
18830 {
18831 text_style = text_style.highlight(highlight_style);
18832 }
18833 div()
18834 .block_mouse_except_scroll()
18835 .pl(cx.anchor_x)
18836 .child(EditorElement::new(
18837 &rename_editor,
18838 EditorStyle {
18839 background: cx.theme().system().transparent,
18840 local_player: cx.editor_style.local_player,
18841 text: text_style,
18842 scrollbar_width: cx.editor_style.scrollbar_width,
18843 syntax: cx.editor_style.syntax.clone(),
18844 status: cx.editor_style.status.clone(),
18845 inlay_hints_style: HighlightStyle {
18846 font_weight: Some(FontWeight::BOLD),
18847 ..make_inlay_hints_style(cx.app)
18848 },
18849 edit_prediction_styles: make_suggestion_styles(
18850 cx.app,
18851 ),
18852 ..EditorStyle::default()
18853 },
18854 ))
18855 .into_any_element()
18856 }
18857 }),
18858 priority: 0,
18859 }],
18860 Some(Autoscroll::fit()),
18861 cx,
18862 )[0];
18863 this.pending_rename = Some(RenameState {
18864 range,
18865 old_name,
18866 editor: rename_editor,
18867 block_id,
18868 });
18869 })?;
18870 }
18871
18872 Ok(())
18873 }))
18874 }
18875
18876 pub fn confirm_rename(
18877 &mut self,
18878 _: &ConfirmRename,
18879 window: &mut Window,
18880 cx: &mut Context<Self>,
18881 ) -> Option<Task<Result<()>>> {
18882 let rename = self.take_rename(false, window, cx)?;
18883 let workspace = self.workspace()?.downgrade();
18884 let (buffer, start) = self
18885 .buffer
18886 .read(cx)
18887 .text_anchor_for_position(rename.range.start, cx)?;
18888 let (end_buffer, _) = self
18889 .buffer
18890 .read(cx)
18891 .text_anchor_for_position(rename.range.end, cx)?;
18892 if buffer != end_buffer {
18893 return None;
18894 }
18895
18896 let old_name = rename.old_name;
18897 let new_name = rename.editor.read(cx).text(cx);
18898
18899 let rename = self.semantics_provider.as_ref()?.perform_rename(
18900 &buffer,
18901 start,
18902 new_name.clone(),
18903 cx,
18904 )?;
18905
18906 Some(cx.spawn_in(window, async move |editor, cx| {
18907 let project_transaction = rename.await?;
18908 Self::open_project_transaction(
18909 &editor,
18910 workspace,
18911 project_transaction,
18912 format!("Rename: {} → {}", old_name, new_name),
18913 cx,
18914 )
18915 .await?;
18916
18917 editor.update(cx, |editor, cx| {
18918 editor.refresh_document_highlights(cx);
18919 })?;
18920 Ok(())
18921 }))
18922 }
18923
18924 fn take_rename(
18925 &mut self,
18926 moving_cursor: bool,
18927 window: &mut Window,
18928 cx: &mut Context<Self>,
18929 ) -> Option<RenameState> {
18930 let rename = self.pending_rename.take()?;
18931 if rename.editor.focus_handle(cx).is_focused(window) {
18932 window.focus(&self.focus_handle, cx);
18933 }
18934
18935 self.remove_blocks(
18936 [rename.block_id].into_iter().collect(),
18937 Some(Autoscroll::fit()),
18938 cx,
18939 );
18940 self.clear_highlights(HighlightKey::Rename, cx);
18941 self.show_local_selections = true;
18942
18943 if moving_cursor {
18944 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18945 editor
18946 .selections
18947 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18948 .head()
18949 });
18950
18951 // Update the selection to match the position of the selection inside
18952 // the rename editor.
18953 let snapshot = self.buffer.read(cx).read(cx);
18954 let rename_range = rename.range.to_offset(&snapshot);
18955 let cursor_in_editor = snapshot
18956 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18957 .min(rename_range.end);
18958 drop(snapshot);
18959
18960 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18961 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18962 });
18963 } else {
18964 self.refresh_document_highlights(cx);
18965 }
18966
18967 Some(rename)
18968 }
18969
18970 pub fn pending_rename(&self) -> Option<&RenameState> {
18971 self.pending_rename.as_ref()
18972 }
18973
18974 fn format(
18975 &mut self,
18976 _: &Format,
18977 window: &mut Window,
18978 cx: &mut Context<Self>,
18979 ) -> Option<Task<Result<()>>> {
18980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18981
18982 let project = match &self.project {
18983 Some(project) => project.clone(),
18984 None => return None,
18985 };
18986
18987 Some(self.perform_format(
18988 project,
18989 FormatTrigger::Manual,
18990 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18991 window,
18992 cx,
18993 ))
18994 }
18995
18996 fn format_selections(
18997 &mut self,
18998 _: &FormatSelections,
18999 window: &mut Window,
19000 cx: &mut Context<Self>,
19001 ) -> Option<Task<Result<()>>> {
19002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19003
19004 let project = match &self.project {
19005 Some(project) => project.clone(),
19006 None => return None,
19007 };
19008
19009 let ranges = self
19010 .selections
19011 .all_adjusted(&self.display_snapshot(cx))
19012 .into_iter()
19013 .map(|selection| selection.range())
19014 .collect_vec();
19015
19016 Some(self.perform_format(
19017 project,
19018 FormatTrigger::Manual,
19019 FormatTarget::Ranges(ranges),
19020 window,
19021 cx,
19022 ))
19023 }
19024
19025 fn perform_format(
19026 &mut self,
19027 project: Entity<Project>,
19028 trigger: FormatTrigger,
19029 target: FormatTarget,
19030 window: &mut Window,
19031 cx: &mut Context<Self>,
19032 ) -> Task<Result<()>> {
19033 let buffer = self.buffer.clone();
19034 let (buffers, target) = match target {
19035 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19036 FormatTarget::Ranges(selection_ranges) => {
19037 let multi_buffer = buffer.read(cx);
19038 let snapshot = multi_buffer.read(cx);
19039 let mut buffers = HashSet::default();
19040 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19041 BTreeMap::new();
19042 for selection_range in selection_ranges {
19043 for (buffer, buffer_range, _) in
19044 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19045 {
19046 let buffer_id = buffer.remote_id();
19047 let start = buffer.anchor_before(buffer_range.start);
19048 let end = buffer.anchor_after(buffer_range.end);
19049 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19050 buffer_id_to_ranges
19051 .entry(buffer_id)
19052 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19053 .or_insert_with(|| vec![start..end]);
19054 }
19055 }
19056 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19057 }
19058 };
19059
19060 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19061 let selections_prev = transaction_id_prev
19062 .and_then(|transaction_id_prev| {
19063 // default to selections as they were after the last edit, if we have them,
19064 // instead of how they are now.
19065 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19066 // will take you back to where you made the last edit, instead of staying where you scrolled
19067 self.selection_history
19068 .transaction(transaction_id_prev)
19069 .map(|t| t.0.clone())
19070 })
19071 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19072
19073 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19074 let format = project.update(cx, |project, cx| {
19075 project.format(buffers, target, true, trigger, cx)
19076 });
19077
19078 cx.spawn_in(window, async move |editor, cx| {
19079 let transaction = futures::select_biased! {
19080 transaction = format.log_err().fuse() => transaction,
19081 () = timeout => {
19082 log::warn!("timed out waiting for formatting");
19083 None
19084 }
19085 };
19086
19087 buffer.update(cx, |buffer, cx| {
19088 if let Some(transaction) = transaction
19089 && !buffer.is_singleton()
19090 {
19091 buffer.push_transaction(&transaction.0, cx);
19092 }
19093 cx.notify();
19094 });
19095
19096 if let Some(transaction_id_now) =
19097 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19098 {
19099 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19100 if has_new_transaction {
19101 editor
19102 .update(cx, |editor, _| {
19103 editor
19104 .selection_history
19105 .insert_transaction(transaction_id_now, selections_prev);
19106 })
19107 .ok();
19108 }
19109 }
19110
19111 Ok(())
19112 })
19113 }
19114
19115 fn organize_imports(
19116 &mut self,
19117 _: &OrganizeImports,
19118 window: &mut Window,
19119 cx: &mut Context<Self>,
19120 ) -> Option<Task<Result<()>>> {
19121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19122 let project = match &self.project {
19123 Some(project) => project.clone(),
19124 None => return None,
19125 };
19126 Some(self.perform_code_action_kind(
19127 project,
19128 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19129 window,
19130 cx,
19131 ))
19132 }
19133
19134 fn perform_code_action_kind(
19135 &mut self,
19136 project: Entity<Project>,
19137 kind: CodeActionKind,
19138 window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) -> Task<Result<()>> {
19141 let buffer = self.buffer.clone();
19142 let buffers = buffer.read(cx).all_buffers();
19143 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19144 let apply_action = project.update(cx, |project, cx| {
19145 project.apply_code_action_kind(buffers, kind, true, cx)
19146 });
19147 cx.spawn_in(window, async move |_, cx| {
19148 let transaction = futures::select_biased! {
19149 () = timeout => {
19150 log::warn!("timed out waiting for executing code action");
19151 None
19152 }
19153 transaction = apply_action.log_err().fuse() => transaction,
19154 };
19155 buffer.update(cx, |buffer, cx| {
19156 // check if we need this
19157 if let Some(transaction) = transaction
19158 && !buffer.is_singleton()
19159 {
19160 buffer.push_transaction(&transaction.0, cx);
19161 }
19162 cx.notify();
19163 });
19164 Ok(())
19165 })
19166 }
19167
19168 pub fn restart_language_server(
19169 &mut self,
19170 _: &RestartLanguageServer,
19171 _: &mut Window,
19172 cx: &mut Context<Self>,
19173 ) {
19174 if let Some(project) = self.project.clone() {
19175 self.buffer.update(cx, |multi_buffer, cx| {
19176 project.update(cx, |project, cx| {
19177 project.restart_language_servers_for_buffers(
19178 multi_buffer.all_buffers().into_iter().collect(),
19179 HashSet::default(),
19180 cx,
19181 );
19182 });
19183 })
19184 }
19185 }
19186
19187 pub fn stop_language_server(
19188 &mut self,
19189 _: &StopLanguageServer,
19190 _: &mut Window,
19191 cx: &mut Context<Self>,
19192 ) {
19193 if let Some(project) = self.project.clone() {
19194 self.buffer.update(cx, |multi_buffer, cx| {
19195 project.update(cx, |project, cx| {
19196 project.stop_language_servers_for_buffers(
19197 multi_buffer.all_buffers().into_iter().collect(),
19198 HashSet::default(),
19199 cx,
19200 );
19201 });
19202 });
19203 }
19204 }
19205
19206 fn cancel_language_server_work(
19207 workspace: &mut Workspace,
19208 _: &actions::CancelLanguageServerWork,
19209 _: &mut Window,
19210 cx: &mut Context<Workspace>,
19211 ) {
19212 let project = workspace.project();
19213 let buffers = workspace
19214 .active_item(cx)
19215 .and_then(|item| item.act_as::<Editor>(cx))
19216 .map_or(HashSet::default(), |editor| {
19217 editor.read(cx).buffer.read(cx).all_buffers()
19218 });
19219 project.update(cx, |project, cx| {
19220 project.cancel_language_server_work_for_buffers(buffers, cx);
19221 });
19222 }
19223
19224 fn show_character_palette(
19225 &mut self,
19226 _: &ShowCharacterPalette,
19227 window: &mut Window,
19228 _: &mut Context<Self>,
19229 ) {
19230 window.show_character_palette();
19231 }
19232
19233 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19234 if !self.diagnostics_enabled() {
19235 return;
19236 }
19237
19238 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19239 let buffer = self.buffer.read(cx).snapshot(cx);
19240 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19241 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19242 let is_valid = buffer
19243 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19244 .any(|entry| {
19245 entry.diagnostic.is_primary
19246 && !entry.range.is_empty()
19247 && entry.range.start == primary_range_start
19248 && entry.diagnostic.message == active_diagnostics.active_message
19249 });
19250
19251 if !is_valid {
19252 self.dismiss_diagnostics(cx);
19253 }
19254 }
19255 }
19256
19257 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19258 match &self.active_diagnostics {
19259 ActiveDiagnostic::Group(group) => Some(group),
19260 _ => None,
19261 }
19262 }
19263
19264 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19265 if !self.diagnostics_enabled() {
19266 return;
19267 }
19268 self.dismiss_diagnostics(cx);
19269 self.active_diagnostics = ActiveDiagnostic::All;
19270 }
19271
19272 fn activate_diagnostics(
19273 &mut self,
19274 buffer_id: BufferId,
19275 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19276 window: &mut Window,
19277 cx: &mut Context<Self>,
19278 ) {
19279 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19280 return;
19281 }
19282 self.dismiss_diagnostics(cx);
19283 let snapshot = self.snapshot(window, cx);
19284 let buffer = self.buffer.read(cx).snapshot(cx);
19285 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19286 return;
19287 };
19288
19289 let diagnostic_group = buffer
19290 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19291 .collect::<Vec<_>>();
19292
19293 let language_registry = self
19294 .project()
19295 .map(|project| project.read(cx).languages().clone());
19296
19297 let blocks = renderer.render_group(
19298 diagnostic_group,
19299 buffer_id,
19300 snapshot,
19301 cx.weak_entity(),
19302 language_registry,
19303 cx,
19304 );
19305
19306 let blocks = self.display_map.update(cx, |display_map, cx| {
19307 display_map.insert_blocks(blocks, cx).into_iter().collect()
19308 });
19309 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19310 active_range: buffer.anchor_before(diagnostic.range.start)
19311 ..buffer.anchor_after(diagnostic.range.end),
19312 active_message: diagnostic.diagnostic.message.clone(),
19313 group_id: diagnostic.diagnostic.group_id,
19314 blocks,
19315 });
19316 cx.notify();
19317 }
19318
19319 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19320 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19321 return;
19322 };
19323
19324 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19325 if let ActiveDiagnostic::Group(group) = prev {
19326 self.display_map.update(cx, |display_map, cx| {
19327 display_map.remove_blocks(group.blocks, cx);
19328 });
19329 cx.notify();
19330 }
19331 }
19332
19333 /// Disable inline diagnostics rendering for this editor.
19334 pub fn disable_inline_diagnostics(&mut self) {
19335 self.inline_diagnostics_enabled = false;
19336 self.inline_diagnostics_update = Task::ready(());
19337 self.inline_diagnostics.clear();
19338 }
19339
19340 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19341 self.diagnostics_enabled = false;
19342 self.dismiss_diagnostics(cx);
19343 self.inline_diagnostics_update = Task::ready(());
19344 self.inline_diagnostics.clear();
19345 }
19346
19347 pub fn disable_word_completions(&mut self) {
19348 self.word_completions_enabled = false;
19349 }
19350
19351 pub fn diagnostics_enabled(&self) -> bool {
19352 self.diagnostics_enabled && self.mode.is_full()
19353 }
19354
19355 pub fn inline_diagnostics_enabled(&self) -> bool {
19356 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19357 }
19358
19359 pub fn show_inline_diagnostics(&self) -> bool {
19360 self.show_inline_diagnostics
19361 }
19362
19363 pub fn toggle_inline_diagnostics(
19364 &mut self,
19365 _: &ToggleInlineDiagnostics,
19366 window: &mut Window,
19367 cx: &mut Context<Editor>,
19368 ) {
19369 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19370 self.refresh_inline_diagnostics(false, window, cx);
19371 }
19372
19373 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19374 self.diagnostics_max_severity = severity;
19375 self.display_map.update(cx, |display_map, _| {
19376 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19377 });
19378 }
19379
19380 pub fn toggle_diagnostics(
19381 &mut self,
19382 _: &ToggleDiagnostics,
19383 window: &mut Window,
19384 cx: &mut Context<Editor>,
19385 ) {
19386 if !self.diagnostics_enabled() {
19387 return;
19388 }
19389
19390 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19391 EditorSettings::get_global(cx)
19392 .diagnostics_max_severity
19393 .filter(|severity| severity != &DiagnosticSeverity::Off)
19394 .unwrap_or(DiagnosticSeverity::Hint)
19395 } else {
19396 DiagnosticSeverity::Off
19397 };
19398 self.set_max_diagnostics_severity(new_severity, cx);
19399 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19400 self.active_diagnostics = ActiveDiagnostic::None;
19401 self.inline_diagnostics_update = Task::ready(());
19402 self.inline_diagnostics.clear();
19403 } else {
19404 self.refresh_inline_diagnostics(false, window, cx);
19405 }
19406
19407 cx.notify();
19408 }
19409
19410 pub fn toggle_minimap(
19411 &mut self,
19412 _: &ToggleMinimap,
19413 window: &mut Window,
19414 cx: &mut Context<Editor>,
19415 ) {
19416 if self.supports_minimap(cx) {
19417 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19418 }
19419 }
19420
19421 fn refresh_inline_diagnostics(
19422 &mut self,
19423 debounce: bool,
19424 window: &mut Window,
19425 cx: &mut Context<Self>,
19426 ) {
19427 let max_severity = ProjectSettings::get_global(cx)
19428 .diagnostics
19429 .inline
19430 .max_severity
19431 .unwrap_or(self.diagnostics_max_severity);
19432
19433 if !self.inline_diagnostics_enabled()
19434 || !self.diagnostics_enabled()
19435 || !self.show_inline_diagnostics
19436 || max_severity == DiagnosticSeverity::Off
19437 {
19438 self.inline_diagnostics_update = Task::ready(());
19439 self.inline_diagnostics.clear();
19440 return;
19441 }
19442
19443 let debounce_ms = ProjectSettings::get_global(cx)
19444 .diagnostics
19445 .inline
19446 .update_debounce_ms;
19447 let debounce = if debounce && debounce_ms > 0 {
19448 Some(Duration::from_millis(debounce_ms))
19449 } else {
19450 None
19451 };
19452 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19453 if let Some(debounce) = debounce {
19454 cx.background_executor().timer(debounce).await;
19455 }
19456 let Some(snapshot) = editor.upgrade().map(|editor| {
19457 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19458 }) else {
19459 return;
19460 };
19461
19462 let new_inline_diagnostics = cx
19463 .background_spawn(async move {
19464 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19465 for diagnostic_entry in
19466 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19467 {
19468 let message = diagnostic_entry
19469 .diagnostic
19470 .message
19471 .split_once('\n')
19472 .map(|(line, _)| line)
19473 .map(SharedString::new)
19474 .unwrap_or_else(|| {
19475 SharedString::new(&*diagnostic_entry.diagnostic.message)
19476 });
19477 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19478 let (Ok(i) | Err(i)) = inline_diagnostics
19479 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19480 inline_diagnostics.insert(
19481 i,
19482 (
19483 start_anchor,
19484 InlineDiagnostic {
19485 message,
19486 group_id: diagnostic_entry.diagnostic.group_id,
19487 start: diagnostic_entry.range.start.to_point(&snapshot),
19488 is_primary: diagnostic_entry.diagnostic.is_primary,
19489 severity: diagnostic_entry.diagnostic.severity,
19490 },
19491 ),
19492 );
19493 }
19494 inline_diagnostics
19495 })
19496 .await;
19497
19498 editor
19499 .update(cx, |editor, cx| {
19500 editor.inline_diagnostics = new_inline_diagnostics;
19501 cx.notify();
19502 })
19503 .ok();
19504 });
19505 }
19506
19507 fn pull_diagnostics(
19508 &mut self,
19509 buffer_id: BufferId,
19510 _window: &Window,
19511 cx: &mut Context<Self>,
19512 ) -> Option<()> {
19513 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19514 // skip any LSP updates for it.
19515
19516 if self.active_diagnostics == ActiveDiagnostic::All
19517 || !self.mode().is_full()
19518 || !self.diagnostics_enabled()
19519 {
19520 return None;
19521 }
19522 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19523 .diagnostics
19524 .lsp_pull_diagnostics;
19525 if !pull_diagnostics_settings.enabled {
19526 return None;
19527 }
19528 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19529 let project = self.project()?.downgrade();
19530 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19531
19532 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19533 cx.background_executor().timer(debounce).await;
19534 if let Ok(task) = project.update(cx, |project, cx| {
19535 project.lsp_store().update(cx, |lsp_store, cx| {
19536 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19537 })
19538 }) {
19539 task.await.log_err();
19540 }
19541 project
19542 .update(cx, |project, cx| {
19543 project.lsp_store().update(cx, |lsp_store, cx| {
19544 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19545 })
19546 })
19547 .log_err();
19548 });
19549
19550 Some(())
19551 }
19552
19553 pub fn set_selections_from_remote(
19554 &mut self,
19555 selections: Vec<Selection<Anchor>>,
19556 pending_selection: Option<Selection<Anchor>>,
19557 window: &mut Window,
19558 cx: &mut Context<Self>,
19559 ) {
19560 let old_cursor_position = self.selections.newest_anchor().head();
19561 self.selections
19562 .change_with(&self.display_snapshot(cx), |s| {
19563 s.select_anchors(selections);
19564 if let Some(pending_selection) = pending_selection {
19565 s.set_pending(pending_selection, SelectMode::Character);
19566 } else {
19567 s.clear_pending();
19568 }
19569 });
19570 self.selections_did_change(
19571 false,
19572 &old_cursor_position,
19573 SelectionEffects::default(),
19574 window,
19575 cx,
19576 );
19577 }
19578
19579 pub fn transact(
19580 &mut self,
19581 window: &mut Window,
19582 cx: &mut Context<Self>,
19583 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19584 ) -> Option<TransactionId> {
19585 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19586 this.start_transaction_at(Instant::now(), window, cx);
19587 update(this, window, cx);
19588 this.end_transaction_at(Instant::now(), cx)
19589 })
19590 }
19591
19592 pub fn start_transaction_at(
19593 &mut self,
19594 now: Instant,
19595 window: &mut Window,
19596 cx: &mut Context<Self>,
19597 ) -> Option<TransactionId> {
19598 self.end_selection(window, cx);
19599 if let Some(tx_id) = self
19600 .buffer
19601 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19602 {
19603 self.selection_history
19604 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19605 cx.emit(EditorEvent::TransactionBegun {
19606 transaction_id: tx_id,
19607 });
19608 Some(tx_id)
19609 } else {
19610 None
19611 }
19612 }
19613
19614 pub fn end_transaction_at(
19615 &mut self,
19616 now: Instant,
19617 cx: &mut Context<Self>,
19618 ) -> Option<TransactionId> {
19619 if let Some(transaction_id) = self
19620 .buffer
19621 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19622 {
19623 if let Some((_, end_selections)) =
19624 self.selection_history.transaction_mut(transaction_id)
19625 {
19626 *end_selections = Some(self.selections.disjoint_anchors_arc());
19627 } else {
19628 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19629 }
19630
19631 cx.emit(EditorEvent::Edited { transaction_id });
19632 Some(transaction_id)
19633 } else {
19634 None
19635 }
19636 }
19637
19638 pub fn modify_transaction_selection_history(
19639 &mut self,
19640 transaction_id: TransactionId,
19641 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19642 ) -> bool {
19643 self.selection_history
19644 .transaction_mut(transaction_id)
19645 .map(modify)
19646 .is_some()
19647 }
19648
19649 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19650 if self.selection_mark_mode {
19651 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19652 s.move_with(|_, sel| {
19653 sel.collapse_to(sel.head(), SelectionGoal::None);
19654 });
19655 })
19656 }
19657 self.selection_mark_mode = true;
19658 cx.notify();
19659 }
19660
19661 pub fn swap_selection_ends(
19662 &mut self,
19663 _: &actions::SwapSelectionEnds,
19664 window: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19668 s.move_with(|_, sel| {
19669 if sel.start != sel.end {
19670 sel.reversed = !sel.reversed
19671 }
19672 });
19673 });
19674 self.request_autoscroll(Autoscroll::newest(), cx);
19675 cx.notify();
19676 }
19677
19678 pub fn toggle_focus(
19679 workspace: &mut Workspace,
19680 _: &actions::ToggleFocus,
19681 window: &mut Window,
19682 cx: &mut Context<Workspace>,
19683 ) {
19684 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19685 return;
19686 };
19687 workspace.activate_item(&item, true, true, window, cx);
19688 }
19689
19690 pub fn toggle_fold(
19691 &mut self,
19692 _: &actions::ToggleFold,
19693 window: &mut Window,
19694 cx: &mut Context<Self>,
19695 ) {
19696 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19698 let selection = self.selections.newest::<Point>(&display_map);
19699
19700 let range = if selection.is_empty() {
19701 let point = selection.head().to_display_point(&display_map);
19702 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19703 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19704 .to_point(&display_map);
19705 start..end
19706 } else {
19707 selection.range()
19708 };
19709 if display_map.folds_in_range(range).next().is_some() {
19710 self.unfold_lines(&Default::default(), window, cx)
19711 } else {
19712 self.fold(&Default::default(), window, cx)
19713 }
19714 } else {
19715 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19716 let buffer_ids: HashSet<_> = self
19717 .selections
19718 .disjoint_anchor_ranges()
19719 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19720 .collect();
19721
19722 let should_unfold = buffer_ids
19723 .iter()
19724 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19725
19726 for buffer_id in buffer_ids {
19727 if should_unfold {
19728 self.unfold_buffer(buffer_id, cx);
19729 } else {
19730 self.fold_buffer(buffer_id, cx);
19731 }
19732 }
19733 }
19734 }
19735
19736 pub fn toggle_fold_recursive(
19737 &mut self,
19738 _: &actions::ToggleFoldRecursive,
19739 window: &mut Window,
19740 cx: &mut Context<Self>,
19741 ) {
19742 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19743
19744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19745 let range = if selection.is_empty() {
19746 let point = selection.head().to_display_point(&display_map);
19747 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19748 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19749 .to_point(&display_map);
19750 start..end
19751 } else {
19752 selection.range()
19753 };
19754 if display_map.folds_in_range(range).next().is_some() {
19755 self.unfold_recursive(&Default::default(), window, cx)
19756 } else {
19757 self.fold_recursive(&Default::default(), window, cx)
19758 }
19759 }
19760
19761 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19762 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19763 let mut to_fold = Vec::new();
19764 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19765 let selections = self.selections.all_adjusted(&display_map);
19766
19767 for selection in selections {
19768 let range = selection.range().sorted();
19769 let buffer_start_row = range.start.row;
19770
19771 if range.start.row != range.end.row {
19772 let mut found = false;
19773 let mut row = range.start.row;
19774 while row <= range.end.row {
19775 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19776 {
19777 found = true;
19778 row = crease.range().end.row + 1;
19779 to_fold.push(crease);
19780 } else {
19781 row += 1
19782 }
19783 }
19784 if found {
19785 continue;
19786 }
19787 }
19788
19789 for row in (0..=range.start.row).rev() {
19790 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19791 && crease.range().end.row >= buffer_start_row
19792 {
19793 to_fold.push(crease);
19794 if row <= range.start.row {
19795 break;
19796 }
19797 }
19798 }
19799 }
19800
19801 self.fold_creases(to_fold, true, window, cx);
19802 } else {
19803 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19804 let buffer_ids = self
19805 .selections
19806 .disjoint_anchor_ranges()
19807 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19808 .collect::<HashSet<_>>();
19809 for buffer_id in buffer_ids {
19810 self.fold_buffer(buffer_id, cx);
19811 }
19812 }
19813 }
19814
19815 pub fn toggle_fold_all(
19816 &mut self,
19817 _: &actions::ToggleFoldAll,
19818 window: &mut Window,
19819 cx: &mut Context<Self>,
19820 ) {
19821 let has_folds = if self.buffer.read(cx).is_singleton() {
19822 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19823 let has_folds = display_map
19824 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19825 .next()
19826 .is_some();
19827 has_folds
19828 } else {
19829 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19830 let has_folds = buffer_ids
19831 .iter()
19832 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19833 has_folds
19834 };
19835
19836 if has_folds {
19837 self.unfold_all(&actions::UnfoldAll, window, cx);
19838 } else {
19839 self.fold_all(&actions::FoldAll, window, cx);
19840 }
19841 }
19842
19843 fn fold_at_level(
19844 &mut self,
19845 fold_at: &FoldAtLevel,
19846 window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 if !self.buffer.read(cx).is_singleton() {
19850 return;
19851 }
19852
19853 let fold_at_level = fold_at.0;
19854 let snapshot = self.buffer.read(cx).snapshot(cx);
19855 let mut to_fold = Vec::new();
19856 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19857
19858 let row_ranges_to_keep: Vec<Range<u32>> = self
19859 .selections
19860 .all::<Point>(&self.display_snapshot(cx))
19861 .into_iter()
19862 .map(|sel| sel.start.row..sel.end.row)
19863 .collect();
19864
19865 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19866 while start_row < end_row {
19867 match self
19868 .snapshot(window, cx)
19869 .crease_for_buffer_row(MultiBufferRow(start_row))
19870 {
19871 Some(crease) => {
19872 let nested_start_row = crease.range().start.row + 1;
19873 let nested_end_row = crease.range().end.row;
19874
19875 if current_level < fold_at_level {
19876 stack.push((nested_start_row, nested_end_row, current_level + 1));
19877 } else if current_level == fold_at_level {
19878 // Fold iff there is no selection completely contained within the fold region
19879 if !row_ranges_to_keep.iter().any(|selection| {
19880 selection.end >= nested_start_row
19881 && selection.start <= nested_end_row
19882 }) {
19883 to_fold.push(crease);
19884 }
19885 }
19886
19887 start_row = nested_end_row + 1;
19888 }
19889 None => start_row += 1,
19890 }
19891 }
19892 }
19893
19894 self.fold_creases(to_fold, true, window, cx);
19895 }
19896
19897 pub fn fold_at_level_1(
19898 &mut self,
19899 _: &actions::FoldAtLevel1,
19900 window: &mut Window,
19901 cx: &mut Context<Self>,
19902 ) {
19903 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19904 }
19905
19906 pub fn fold_at_level_2(
19907 &mut self,
19908 _: &actions::FoldAtLevel2,
19909 window: &mut Window,
19910 cx: &mut Context<Self>,
19911 ) {
19912 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19913 }
19914
19915 pub fn fold_at_level_3(
19916 &mut self,
19917 _: &actions::FoldAtLevel3,
19918 window: &mut Window,
19919 cx: &mut Context<Self>,
19920 ) {
19921 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19922 }
19923
19924 pub fn fold_at_level_4(
19925 &mut self,
19926 _: &actions::FoldAtLevel4,
19927 window: &mut Window,
19928 cx: &mut Context<Self>,
19929 ) {
19930 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19931 }
19932
19933 pub fn fold_at_level_5(
19934 &mut self,
19935 _: &actions::FoldAtLevel5,
19936 window: &mut Window,
19937 cx: &mut Context<Self>,
19938 ) {
19939 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19940 }
19941
19942 pub fn fold_at_level_6(
19943 &mut self,
19944 _: &actions::FoldAtLevel6,
19945 window: &mut Window,
19946 cx: &mut Context<Self>,
19947 ) {
19948 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19949 }
19950
19951 pub fn fold_at_level_7(
19952 &mut self,
19953 _: &actions::FoldAtLevel7,
19954 window: &mut Window,
19955 cx: &mut Context<Self>,
19956 ) {
19957 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19958 }
19959
19960 pub fn fold_at_level_8(
19961 &mut self,
19962 _: &actions::FoldAtLevel8,
19963 window: &mut Window,
19964 cx: &mut Context<Self>,
19965 ) {
19966 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19967 }
19968
19969 pub fn fold_at_level_9(
19970 &mut self,
19971 _: &actions::FoldAtLevel9,
19972 window: &mut Window,
19973 cx: &mut Context<Self>,
19974 ) {
19975 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19976 }
19977
19978 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19979 if self.buffer.read(cx).is_singleton() {
19980 let mut fold_ranges = Vec::new();
19981 let snapshot = self.buffer.read(cx).snapshot(cx);
19982
19983 for row in 0..snapshot.max_row().0 {
19984 if let Some(foldable_range) = self
19985 .snapshot(window, cx)
19986 .crease_for_buffer_row(MultiBufferRow(row))
19987 {
19988 fold_ranges.push(foldable_range);
19989 }
19990 }
19991
19992 self.fold_creases(fold_ranges, true, window, cx);
19993 } else {
19994 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19995 editor
19996 .update_in(cx, |editor, _, cx| {
19997 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19998 editor.fold_buffer(buffer_id, cx);
19999 }
20000 })
20001 .ok();
20002 });
20003 }
20004 cx.emit(SearchEvent::ResultsCollapsedChanged(
20005 CollapseDirection::Collapsed,
20006 ));
20007 }
20008
20009 pub fn fold_function_bodies(
20010 &mut self,
20011 _: &actions::FoldFunctionBodies,
20012 window: &mut Window,
20013 cx: &mut Context<Self>,
20014 ) {
20015 let snapshot = self.buffer.read(cx).snapshot(cx);
20016
20017 let ranges = snapshot
20018 .text_object_ranges(
20019 MultiBufferOffset(0)..snapshot.len(),
20020 TreeSitterOptions::default(),
20021 )
20022 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20023 .collect::<Vec<_>>();
20024
20025 let creases = ranges
20026 .into_iter()
20027 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20028 .collect();
20029
20030 self.fold_creases(creases, true, window, cx);
20031 }
20032
20033 pub fn fold_recursive(
20034 &mut self,
20035 _: &actions::FoldRecursive,
20036 window: &mut Window,
20037 cx: &mut Context<Self>,
20038 ) {
20039 let mut to_fold = Vec::new();
20040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20041 let selections = self.selections.all_adjusted(&display_map);
20042
20043 for selection in selections {
20044 let range = selection.range().sorted();
20045 let buffer_start_row = range.start.row;
20046
20047 if range.start.row != range.end.row {
20048 let mut found = false;
20049 for row in range.start.row..=range.end.row {
20050 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20051 found = true;
20052 to_fold.push(crease);
20053 }
20054 }
20055 if found {
20056 continue;
20057 }
20058 }
20059
20060 for row in (0..=range.start.row).rev() {
20061 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20062 if crease.range().end.row >= buffer_start_row {
20063 to_fold.push(crease);
20064 } else {
20065 break;
20066 }
20067 }
20068 }
20069 }
20070
20071 self.fold_creases(to_fold, true, window, cx);
20072 }
20073
20074 pub fn fold_at(
20075 &mut self,
20076 buffer_row: MultiBufferRow,
20077 window: &mut Window,
20078 cx: &mut Context<Self>,
20079 ) {
20080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20081
20082 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20083 let autoscroll = self
20084 .selections
20085 .all::<Point>(&display_map)
20086 .iter()
20087 .any(|selection| crease.range().overlaps(&selection.range()));
20088
20089 self.fold_creases(vec![crease], autoscroll, window, cx);
20090 }
20091 }
20092
20093 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20094 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20095 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20096 let buffer = display_map.buffer_snapshot();
20097 let selections = self.selections.all::<Point>(&display_map);
20098 let ranges = selections
20099 .iter()
20100 .map(|s| {
20101 let range = s.display_range(&display_map).sorted();
20102 let mut start = range.start.to_point(&display_map);
20103 let mut end = range.end.to_point(&display_map);
20104 start.column = 0;
20105 end.column = buffer.line_len(MultiBufferRow(end.row));
20106 start..end
20107 })
20108 .collect::<Vec<_>>();
20109
20110 self.unfold_ranges(&ranges, true, true, cx);
20111 } else {
20112 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20113 let buffer_ids = self
20114 .selections
20115 .disjoint_anchor_ranges()
20116 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20117 .collect::<HashSet<_>>();
20118 for buffer_id in buffer_ids {
20119 self.unfold_buffer(buffer_id, cx);
20120 }
20121 }
20122 }
20123
20124 pub fn unfold_recursive(
20125 &mut self,
20126 _: &UnfoldRecursive,
20127 _window: &mut Window,
20128 cx: &mut Context<Self>,
20129 ) {
20130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20131 let selections = self.selections.all::<Point>(&display_map);
20132 let ranges = selections
20133 .iter()
20134 .map(|s| {
20135 let mut range = s.display_range(&display_map).sorted();
20136 *range.start.column_mut() = 0;
20137 *range.end.column_mut() = display_map.line_len(range.end.row());
20138 let start = range.start.to_point(&display_map);
20139 let end = range.end.to_point(&display_map);
20140 start..end
20141 })
20142 .collect::<Vec<_>>();
20143
20144 self.unfold_ranges(&ranges, true, true, cx);
20145 }
20146
20147 pub fn unfold_at(
20148 &mut self,
20149 buffer_row: MultiBufferRow,
20150 _window: &mut Window,
20151 cx: &mut Context<Self>,
20152 ) {
20153 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20154
20155 let intersection_range = Point::new(buffer_row.0, 0)
20156 ..Point::new(
20157 buffer_row.0,
20158 display_map.buffer_snapshot().line_len(buffer_row),
20159 );
20160
20161 let autoscroll = self
20162 .selections
20163 .all::<Point>(&display_map)
20164 .iter()
20165 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20166
20167 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20168 }
20169
20170 pub fn unfold_all(
20171 &mut self,
20172 _: &actions::UnfoldAll,
20173 _window: &mut Window,
20174 cx: &mut Context<Self>,
20175 ) {
20176 if self.buffer.read(cx).is_singleton() {
20177 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20178 self.unfold_ranges(
20179 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20180 true,
20181 true,
20182 cx,
20183 );
20184 } else {
20185 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20186 editor
20187 .update(cx, |editor, cx| {
20188 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20189 editor.unfold_buffer(buffer_id, cx);
20190 }
20191 })
20192 .ok();
20193 });
20194 }
20195 cx.emit(SearchEvent::ResultsCollapsedChanged(
20196 CollapseDirection::Expanded,
20197 ));
20198 }
20199
20200 pub fn fold_selected_ranges(
20201 &mut self,
20202 _: &FoldSelectedRanges,
20203 window: &mut Window,
20204 cx: &mut Context<Self>,
20205 ) {
20206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20207 let selections = self.selections.all_adjusted(&display_map);
20208 let ranges = selections
20209 .into_iter()
20210 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20211 .collect::<Vec<_>>();
20212 self.fold_creases(ranges, true, window, cx);
20213 }
20214
20215 pub fn fold_ranges<T: ToOffset + Clone>(
20216 &mut self,
20217 ranges: Vec<Range<T>>,
20218 auto_scroll: bool,
20219 window: &mut Window,
20220 cx: &mut Context<Self>,
20221 ) {
20222 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20223 let ranges = ranges
20224 .into_iter()
20225 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20226 .collect::<Vec<_>>();
20227 self.fold_creases(ranges, auto_scroll, window, cx);
20228 }
20229
20230 pub fn fold_creases<T: ToOffset + Clone>(
20231 &mut self,
20232 creases: Vec<Crease<T>>,
20233 auto_scroll: bool,
20234 _window: &mut Window,
20235 cx: &mut Context<Self>,
20236 ) {
20237 if creases.is_empty() {
20238 return;
20239 }
20240
20241 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20242
20243 if auto_scroll {
20244 self.request_autoscroll(Autoscroll::fit(), cx);
20245 }
20246
20247 cx.notify();
20248
20249 self.scrollbar_marker_state.dirty = true;
20250 self.folds_did_change(cx);
20251 }
20252
20253 /// Removes any folds whose ranges intersect any of the given ranges.
20254 pub fn unfold_ranges<T: ToOffset + Clone>(
20255 &mut self,
20256 ranges: &[Range<T>],
20257 inclusive: bool,
20258 auto_scroll: bool,
20259 cx: &mut Context<Self>,
20260 ) {
20261 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20262 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20263 });
20264 self.folds_did_change(cx);
20265 }
20266
20267 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20268 self.fold_buffers([buffer_id], cx);
20269 }
20270
20271 pub fn fold_buffers(
20272 &mut self,
20273 buffer_ids: impl IntoIterator<Item = BufferId>,
20274 cx: &mut Context<Self>,
20275 ) {
20276 if self.buffer().read(cx).is_singleton() {
20277 return;
20278 }
20279
20280 let ids_to_fold: Vec<BufferId> = buffer_ids
20281 .into_iter()
20282 .filter(|id| !self.is_buffer_folded(*id, cx))
20283 .collect();
20284
20285 if ids_to_fold.is_empty() {
20286 return;
20287 }
20288
20289 let mut all_folded_excerpt_ids = Vec::new();
20290 for buffer_id in &ids_to_fold {
20291 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20292 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
20293 }
20294
20295 self.display_map.update(cx, |display_map, cx| {
20296 display_map.fold_buffers(ids_to_fold.clone(), cx)
20297 });
20298
20299 let snapshot = self.display_snapshot(cx);
20300 self.selections.change_with(&snapshot, |selections| {
20301 for buffer_id in ids_to_fold {
20302 selections.remove_selections_from_buffer(buffer_id);
20303 }
20304 });
20305
20306 cx.emit(EditorEvent::BufferFoldToggled {
20307 ids: all_folded_excerpt_ids,
20308 folded: true,
20309 });
20310 cx.notify();
20311 }
20312
20313 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20314 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20315 return;
20316 }
20317 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20318 self.display_map.update(cx, |display_map, cx| {
20319 display_map.unfold_buffers([buffer_id], cx);
20320 });
20321 cx.emit(EditorEvent::BufferFoldToggled {
20322 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20323 folded: false,
20324 });
20325 cx.notify();
20326 }
20327
20328 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20329 self.display_map.read(cx).is_buffer_folded(buffer)
20330 }
20331
20332 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20333 self.display_map.read(cx).folded_buffers()
20334 }
20335
20336 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20337 self.display_map.update(cx, |display_map, cx| {
20338 display_map.disable_header_for_buffer(buffer_id, cx);
20339 });
20340 cx.notify();
20341 }
20342
20343 /// Removes any folds with the given ranges.
20344 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20345 &mut self,
20346 ranges: &[Range<T>],
20347 type_id: TypeId,
20348 auto_scroll: bool,
20349 cx: &mut Context<Self>,
20350 ) {
20351 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20352 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20353 });
20354 self.folds_did_change(cx);
20355 }
20356
20357 fn remove_folds_with<T: ToOffset + Clone>(
20358 &mut self,
20359 ranges: &[Range<T>],
20360 auto_scroll: bool,
20361 cx: &mut Context<Self>,
20362 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20363 ) {
20364 if ranges.is_empty() {
20365 return;
20366 }
20367
20368 let mut buffers_affected = HashSet::default();
20369 let multi_buffer = self.buffer().read(cx);
20370 for range in ranges {
20371 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20372 buffers_affected.insert(buffer.read(cx).remote_id());
20373 };
20374 }
20375
20376 self.display_map.update(cx, update);
20377
20378 if auto_scroll {
20379 self.request_autoscroll(Autoscroll::fit(), cx);
20380 }
20381
20382 cx.notify();
20383 self.scrollbar_marker_state.dirty = true;
20384 self.active_indent_guides_state.dirty = true;
20385 }
20386
20387 pub fn update_renderer_widths(
20388 &mut self,
20389 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20390 cx: &mut Context<Self>,
20391 ) -> bool {
20392 self.display_map
20393 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20394 }
20395
20396 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20397 self.display_map.read(cx).fold_placeholder.clone()
20398 }
20399
20400 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20401 self.buffer.update(cx, |buffer, cx| {
20402 buffer.set_all_diff_hunks_expanded(cx);
20403 });
20404 }
20405
20406 pub fn expand_all_diff_hunks(
20407 &mut self,
20408 _: &ExpandAllDiffHunks,
20409 _window: &mut Window,
20410 cx: &mut Context<Self>,
20411 ) {
20412 self.buffer.update(cx, |buffer, cx| {
20413 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20414 });
20415 }
20416
20417 pub fn collapse_all_diff_hunks(
20418 &mut self,
20419 _: &CollapseAllDiffHunks,
20420 _window: &mut Window,
20421 cx: &mut Context<Self>,
20422 ) {
20423 self.buffer.update(cx, |buffer, cx| {
20424 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20425 });
20426 }
20427
20428 pub fn toggle_selected_diff_hunks(
20429 &mut self,
20430 _: &ToggleSelectedDiffHunks,
20431 _window: &mut Window,
20432 cx: &mut Context<Self>,
20433 ) {
20434 let ranges: Vec<_> = self
20435 .selections
20436 .disjoint_anchors()
20437 .iter()
20438 .map(|s| s.range())
20439 .collect();
20440 self.toggle_diff_hunks_in_ranges(ranges, cx);
20441 }
20442
20443 pub fn diff_hunks_in_ranges<'a>(
20444 &'a self,
20445 ranges: &'a [Range<Anchor>],
20446 buffer: &'a MultiBufferSnapshot,
20447 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20448 ranges.iter().flat_map(move |range| {
20449 let end_excerpt_id = range.end.excerpt_id;
20450 let range = range.to_point(buffer);
20451 let mut peek_end = range.end;
20452 if range.end.row < buffer.max_row().0 {
20453 peek_end = Point::new(range.end.row + 1, 0);
20454 }
20455 buffer
20456 .diff_hunks_in_range(range.start..peek_end)
20457 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20458 })
20459 }
20460
20461 pub fn has_stageable_diff_hunks_in_ranges(
20462 &self,
20463 ranges: &[Range<Anchor>],
20464 snapshot: &MultiBufferSnapshot,
20465 ) -> bool {
20466 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20467 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20468 }
20469
20470 pub fn toggle_staged_selected_diff_hunks(
20471 &mut self,
20472 _: &::git::ToggleStaged,
20473 _: &mut Window,
20474 cx: &mut Context<Self>,
20475 ) {
20476 let snapshot = self.buffer.read(cx).snapshot(cx);
20477 let ranges: Vec<_> = self
20478 .selections
20479 .disjoint_anchors()
20480 .iter()
20481 .map(|s| s.range())
20482 .collect();
20483 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20484 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20485 }
20486
20487 pub fn set_render_diff_hunk_controls(
20488 &mut self,
20489 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20490 cx: &mut Context<Self>,
20491 ) {
20492 self.render_diff_hunk_controls = render_diff_hunk_controls;
20493 cx.notify();
20494 }
20495
20496 pub fn stage_and_next(
20497 &mut self,
20498 _: &::git::StageAndNext,
20499 window: &mut Window,
20500 cx: &mut Context<Self>,
20501 ) {
20502 self.do_stage_or_unstage_and_next(true, window, cx);
20503 }
20504
20505 pub fn unstage_and_next(
20506 &mut self,
20507 _: &::git::UnstageAndNext,
20508 window: &mut Window,
20509 cx: &mut Context<Self>,
20510 ) {
20511 self.do_stage_or_unstage_and_next(false, window, cx);
20512 }
20513
20514 pub fn stage_or_unstage_diff_hunks(
20515 &mut self,
20516 stage: bool,
20517 ranges: Vec<Range<Anchor>>,
20518 cx: &mut Context<Self>,
20519 ) {
20520 if self.delegate_stage_and_restore {
20521 let snapshot = self.buffer.read(cx).snapshot(cx);
20522 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20523 if !hunks.is_empty() {
20524 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20525 }
20526 return;
20527 }
20528 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20529 cx.spawn(async move |this, cx| {
20530 task.await?;
20531 this.update(cx, |this, cx| {
20532 let snapshot = this.buffer.read(cx).snapshot(cx);
20533 let chunk_by = this
20534 .diff_hunks_in_ranges(&ranges, &snapshot)
20535 .chunk_by(|hunk| hunk.buffer_id);
20536 for (buffer_id, hunks) in &chunk_by {
20537 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20538 }
20539 })
20540 })
20541 .detach_and_log_err(cx);
20542 }
20543
20544 fn save_buffers_for_ranges_if_needed(
20545 &mut self,
20546 ranges: &[Range<Anchor>],
20547 cx: &mut Context<Editor>,
20548 ) -> Task<Result<()>> {
20549 let multibuffer = self.buffer.read(cx);
20550 let snapshot = multibuffer.read(cx);
20551 let buffer_ids: HashSet<_> = ranges
20552 .iter()
20553 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20554 .collect();
20555 drop(snapshot);
20556
20557 let mut buffers = HashSet::default();
20558 for buffer_id in buffer_ids {
20559 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20560 let buffer = buffer_entity.read(cx);
20561 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20562 {
20563 buffers.insert(buffer_entity);
20564 }
20565 }
20566 }
20567
20568 if let Some(project) = &self.project {
20569 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20570 } else {
20571 Task::ready(Ok(()))
20572 }
20573 }
20574
20575 fn do_stage_or_unstage_and_next(
20576 &mut self,
20577 stage: bool,
20578 window: &mut Window,
20579 cx: &mut Context<Self>,
20580 ) {
20581 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20582
20583 if ranges.iter().any(|range| range.start != range.end) {
20584 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20585 return;
20586 }
20587
20588 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20589 let snapshot = self.snapshot(window, cx);
20590 let position = self
20591 .selections
20592 .newest::<Point>(&snapshot.display_snapshot)
20593 .head();
20594 let mut row = snapshot
20595 .buffer_snapshot()
20596 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20597 .find(|hunk| hunk.row_range.start.0 > position.row)
20598 .map(|hunk| hunk.row_range.start);
20599
20600 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20601 // Outside of the project diff editor, wrap around to the beginning.
20602 if !all_diff_hunks_expanded {
20603 row = row.or_else(|| {
20604 snapshot
20605 .buffer_snapshot()
20606 .diff_hunks_in_range(Point::zero()..position)
20607 .find(|hunk| hunk.row_range.end.0 < position.row)
20608 .map(|hunk| hunk.row_range.start)
20609 });
20610 }
20611
20612 if let Some(row) = row {
20613 let destination = Point::new(row.0, 0);
20614 let autoscroll = Autoscroll::center();
20615
20616 self.unfold_ranges(&[destination..destination], false, false, cx);
20617 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20618 s.select_ranges([destination..destination]);
20619 });
20620 }
20621 }
20622
20623 pub(crate) fn do_stage_or_unstage(
20624 &self,
20625 stage: bool,
20626 buffer_id: BufferId,
20627 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20628 cx: &mut App,
20629 ) -> Option<()> {
20630 let project = self.project()?;
20631 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20632 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20633 let buffer_snapshot = buffer.read(cx).snapshot();
20634 let file_exists = buffer_snapshot
20635 .file()
20636 .is_some_and(|file| file.disk_state().exists());
20637 diff.update(cx, |diff, cx| {
20638 diff.stage_or_unstage_hunks(
20639 stage,
20640 &hunks
20641 .map(|hunk| buffer_diff::DiffHunk {
20642 buffer_range: hunk.buffer_range,
20643 // We don't need to pass in word diffs here because they're only used for rendering and
20644 // this function changes internal state
20645 base_word_diffs: Vec::default(),
20646 buffer_word_diffs: Vec::default(),
20647 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20648 ..hunk.diff_base_byte_range.end.0,
20649 secondary_status: hunk.status.secondary,
20650 range: Point::zero()..Point::zero(), // unused
20651 })
20652 .collect::<Vec<_>>(),
20653 &buffer_snapshot,
20654 file_exists,
20655 cx,
20656 )
20657 });
20658 None
20659 }
20660
20661 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20662 let ranges: Vec<_> = self
20663 .selections
20664 .disjoint_anchors()
20665 .iter()
20666 .map(|s| s.range())
20667 .collect();
20668 self.buffer
20669 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20670 }
20671
20672 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20673 self.buffer.update(cx, |buffer, cx| {
20674 let ranges = vec![Anchor::min()..Anchor::max()];
20675 if !buffer.all_diff_hunks_expanded()
20676 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20677 {
20678 buffer.collapse_diff_hunks(ranges, cx);
20679 true
20680 } else {
20681 false
20682 }
20683 })
20684 }
20685
20686 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20687 if self.buffer.read(cx).all_diff_hunks_expanded() {
20688 return true;
20689 }
20690 let ranges = vec![Anchor::min()..Anchor::max()];
20691 self.buffer
20692 .read(cx)
20693 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20694 }
20695
20696 fn toggle_diff_hunks_in_ranges(
20697 &mut self,
20698 ranges: Vec<Range<Anchor>>,
20699 cx: &mut Context<Editor>,
20700 ) {
20701 self.buffer.update(cx, |buffer, cx| {
20702 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20703 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20704 })
20705 }
20706
20707 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20708 self.buffer.update(cx, |buffer, cx| {
20709 buffer.toggle_single_diff_hunk(range, cx);
20710 })
20711 }
20712
20713 pub(crate) fn apply_all_diff_hunks(
20714 &mut self,
20715 _: &ApplyAllDiffHunks,
20716 window: &mut Window,
20717 cx: &mut Context<Self>,
20718 ) {
20719 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20720
20721 let buffers = self.buffer.read(cx).all_buffers();
20722 for branch_buffer in buffers {
20723 branch_buffer.update(cx, |branch_buffer, cx| {
20724 branch_buffer.merge_into_base(Vec::new(), cx);
20725 });
20726 }
20727
20728 if let Some(project) = self.project.clone() {
20729 self.save(
20730 SaveOptions {
20731 format: true,
20732 autosave: false,
20733 },
20734 project,
20735 window,
20736 cx,
20737 )
20738 .detach_and_log_err(cx);
20739 }
20740 }
20741
20742 pub(crate) fn apply_selected_diff_hunks(
20743 &mut self,
20744 _: &ApplyDiffHunk,
20745 window: &mut Window,
20746 cx: &mut Context<Self>,
20747 ) {
20748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20749 let snapshot = self.snapshot(window, cx);
20750 let hunks = snapshot.hunks_for_ranges(
20751 self.selections
20752 .all(&snapshot.display_snapshot)
20753 .into_iter()
20754 .map(|selection| selection.range()),
20755 );
20756 let mut ranges_by_buffer = HashMap::default();
20757 self.transact(window, cx, |editor, _window, cx| {
20758 for hunk in hunks {
20759 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20760 ranges_by_buffer
20761 .entry(buffer.clone())
20762 .or_insert_with(Vec::new)
20763 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20764 }
20765 }
20766
20767 for (buffer, ranges) in ranges_by_buffer {
20768 buffer.update(cx, |buffer, cx| {
20769 buffer.merge_into_base(ranges, cx);
20770 });
20771 }
20772 });
20773
20774 if let Some(project) = self.project.clone() {
20775 self.save(
20776 SaveOptions {
20777 format: true,
20778 autosave: false,
20779 },
20780 project,
20781 window,
20782 cx,
20783 )
20784 .detach_and_log_err(cx);
20785 }
20786 }
20787
20788 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20789 if hovered != self.gutter_hovered {
20790 self.gutter_hovered = hovered;
20791 cx.notify();
20792 }
20793 }
20794
20795 pub fn insert_blocks(
20796 &mut self,
20797 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20798 autoscroll: Option<Autoscroll>,
20799 cx: &mut Context<Self>,
20800 ) -> Vec<CustomBlockId> {
20801 let blocks = self
20802 .display_map
20803 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20804 if let Some(autoscroll) = autoscroll {
20805 self.request_autoscroll(autoscroll, cx);
20806 }
20807 cx.notify();
20808 blocks
20809 }
20810
20811 pub fn resize_blocks(
20812 &mut self,
20813 heights: HashMap<CustomBlockId, u32>,
20814 autoscroll: Option<Autoscroll>,
20815 cx: &mut Context<Self>,
20816 ) {
20817 self.display_map
20818 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20819 if let Some(autoscroll) = autoscroll {
20820 self.request_autoscroll(autoscroll, cx);
20821 }
20822 cx.notify();
20823 }
20824
20825 pub fn replace_blocks(
20826 &mut self,
20827 renderers: HashMap<CustomBlockId, RenderBlock>,
20828 autoscroll: Option<Autoscroll>,
20829 cx: &mut Context<Self>,
20830 ) {
20831 self.display_map
20832 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20833 if let Some(autoscroll) = autoscroll {
20834 self.request_autoscroll(autoscroll, cx);
20835 }
20836 cx.notify();
20837 }
20838
20839 pub fn remove_blocks(
20840 &mut self,
20841 block_ids: HashSet<CustomBlockId>,
20842 autoscroll: Option<Autoscroll>,
20843 cx: &mut Context<Self>,
20844 ) {
20845 self.display_map.update(cx, |display_map, cx| {
20846 display_map.remove_blocks(block_ids, cx)
20847 });
20848 if let Some(autoscroll) = autoscroll {
20849 self.request_autoscroll(autoscroll, cx);
20850 }
20851 cx.notify();
20852 }
20853
20854 pub fn row_for_block(
20855 &self,
20856 block_id: CustomBlockId,
20857 cx: &mut Context<Self>,
20858 ) -> Option<DisplayRow> {
20859 self.display_map
20860 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20861 }
20862
20863 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20864 self.focused_block = Some(focused_block);
20865 }
20866
20867 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20868 self.focused_block.take()
20869 }
20870
20871 pub fn insert_creases(
20872 &mut self,
20873 creases: impl IntoIterator<Item = Crease<Anchor>>,
20874 cx: &mut Context<Self>,
20875 ) -> Vec<CreaseId> {
20876 self.display_map
20877 .update(cx, |map, cx| map.insert_creases(creases, cx))
20878 }
20879
20880 pub fn remove_creases(
20881 &mut self,
20882 ids: impl IntoIterator<Item = CreaseId>,
20883 cx: &mut Context<Self>,
20884 ) -> Vec<(CreaseId, Range<Anchor>)> {
20885 self.display_map
20886 .update(cx, |map, cx| map.remove_creases(ids, cx))
20887 }
20888
20889 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20890 self.display_map
20891 .update(cx, |map, cx| map.snapshot(cx))
20892 .longest_row()
20893 }
20894
20895 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20896 self.display_map
20897 .update(cx, |map, cx| map.snapshot(cx))
20898 .max_point()
20899 }
20900
20901 pub fn text(&self, cx: &App) -> String {
20902 self.buffer.read(cx).read(cx).text()
20903 }
20904
20905 pub fn is_empty(&self, cx: &App) -> bool {
20906 self.buffer.read(cx).read(cx).is_empty()
20907 }
20908
20909 pub fn text_option(&self, cx: &App) -> Option<String> {
20910 let text = self.text(cx);
20911 let text = text.trim();
20912
20913 if text.is_empty() {
20914 return None;
20915 }
20916
20917 Some(text.to_string())
20918 }
20919
20920 pub fn set_text(
20921 &mut self,
20922 text: impl Into<Arc<str>>,
20923 window: &mut Window,
20924 cx: &mut Context<Self>,
20925 ) {
20926 self.transact(window, cx, |this, _, cx| {
20927 this.buffer
20928 .read(cx)
20929 .as_singleton()
20930 .expect("you can only call set_text on editors for singleton buffers")
20931 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20932 });
20933 }
20934
20935 pub fn display_text(&self, cx: &mut App) -> String {
20936 self.display_map
20937 .update(cx, |map, cx| map.snapshot(cx))
20938 .text()
20939 }
20940
20941 fn create_minimap(
20942 &self,
20943 minimap_settings: MinimapSettings,
20944 window: &mut Window,
20945 cx: &mut Context<Self>,
20946 ) -> Option<Entity<Self>> {
20947 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20948 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20949 }
20950
20951 fn initialize_new_minimap(
20952 &self,
20953 minimap_settings: MinimapSettings,
20954 window: &mut Window,
20955 cx: &mut Context<Self>,
20956 ) -> Entity<Self> {
20957 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20958 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20959
20960 let mut minimap = Editor::new_internal(
20961 EditorMode::Minimap {
20962 parent: cx.weak_entity(),
20963 },
20964 self.buffer.clone(),
20965 None,
20966 Some(self.display_map.clone()),
20967 window,
20968 cx,
20969 );
20970 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20971 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20972 minimap.scroll_manager.clone_state(
20973 &self.scroll_manager,
20974 &my_snapshot,
20975 &minimap_snapshot,
20976 cx,
20977 );
20978 minimap.set_text_style_refinement(TextStyleRefinement {
20979 font_size: Some(MINIMAP_FONT_SIZE),
20980 font_weight: Some(MINIMAP_FONT_WEIGHT),
20981 font_family: Some(MINIMAP_FONT_FAMILY),
20982 ..Default::default()
20983 });
20984 minimap.update_minimap_configuration(minimap_settings, cx);
20985 cx.new(|_| minimap)
20986 }
20987
20988 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20989 let current_line_highlight = minimap_settings
20990 .current_line_highlight
20991 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20992 self.set_current_line_highlight(Some(current_line_highlight));
20993 }
20994
20995 pub fn minimap(&self) -> Option<&Entity<Self>> {
20996 self.minimap
20997 .as_ref()
20998 .filter(|_| self.minimap_visibility.visible())
20999 }
21000
21001 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21002 let mut wrap_guides = smallvec![];
21003
21004 if self.show_wrap_guides == Some(false) {
21005 return wrap_guides;
21006 }
21007
21008 let settings = self.buffer.read(cx).language_settings(cx);
21009 if settings.show_wrap_guides {
21010 match self.soft_wrap_mode(cx) {
21011 SoftWrap::Column(soft_wrap) => {
21012 wrap_guides.push((soft_wrap as usize, true));
21013 }
21014 SoftWrap::Bounded(soft_wrap) => {
21015 wrap_guides.push((soft_wrap as usize, true));
21016 }
21017 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21018 }
21019 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21020 }
21021
21022 wrap_guides
21023 }
21024
21025 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21026 let settings = self.buffer.read(cx).language_settings(cx);
21027 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21028 match mode {
21029 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21030 SoftWrap::None
21031 }
21032 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21033 language_settings::SoftWrap::PreferredLineLength => {
21034 SoftWrap::Column(settings.preferred_line_length)
21035 }
21036 language_settings::SoftWrap::Bounded => {
21037 SoftWrap::Bounded(settings.preferred_line_length)
21038 }
21039 }
21040 }
21041
21042 pub fn set_soft_wrap_mode(
21043 &mut self,
21044 mode: language_settings::SoftWrap,
21045 cx: &mut Context<Self>,
21046 ) {
21047 self.soft_wrap_mode_override = Some(mode);
21048 cx.notify();
21049 }
21050
21051 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21052 self.hard_wrap = hard_wrap;
21053 cx.notify();
21054 }
21055
21056 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21057 self.text_style_refinement = Some(style);
21058 }
21059
21060 /// called by the Element so we know what style we were most recently rendered with.
21061 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21062 // We intentionally do not inform the display map about the minimap style
21063 // so that wrapping is not recalculated and stays consistent for the editor
21064 // and its linked minimap.
21065 if !self.mode.is_minimap() {
21066 let font = style.text.font();
21067 let font_size = style.text.font_size.to_pixels(window.rem_size());
21068 let display_map = self
21069 .placeholder_display_map
21070 .as_ref()
21071 .filter(|_| self.is_empty(cx))
21072 .unwrap_or(&self.display_map);
21073
21074 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21075 }
21076 self.style = Some(style);
21077 }
21078
21079 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21080 if self.style.is_none() {
21081 self.style = Some(self.create_style(cx));
21082 }
21083 self.style.as_ref().unwrap()
21084 }
21085
21086 // Called by the element. This method is not designed to be called outside of the editor
21087 // element's layout code because it does not notify when rewrapping is computed synchronously.
21088 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21089 if self.is_empty(cx) {
21090 self.placeholder_display_map
21091 .as_ref()
21092 .map_or(false, |display_map| {
21093 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21094 })
21095 } else {
21096 self.display_map
21097 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21098 }
21099 }
21100
21101 pub fn set_soft_wrap(&mut self) {
21102 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21103 }
21104
21105 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21106 if self.soft_wrap_mode_override.is_some() {
21107 self.soft_wrap_mode_override.take();
21108 } else {
21109 let soft_wrap = match self.soft_wrap_mode(cx) {
21110 SoftWrap::GitDiff => return,
21111 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21112 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21113 language_settings::SoftWrap::None
21114 }
21115 };
21116 self.soft_wrap_mode_override = Some(soft_wrap);
21117 }
21118 cx.notify();
21119 }
21120
21121 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21122 let Some(workspace) = self.workspace() else {
21123 return;
21124 };
21125 let fs = workspace.read(cx).app_state().fs.clone();
21126 let current_show = TabBarSettings::get_global(cx).show;
21127 update_settings_file(fs, cx, move |setting, _| {
21128 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21129 });
21130 }
21131
21132 pub fn toggle_indent_guides(
21133 &mut self,
21134 _: &ToggleIndentGuides,
21135 _: &mut Window,
21136 cx: &mut Context<Self>,
21137 ) {
21138 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21139 self.buffer
21140 .read(cx)
21141 .language_settings(cx)
21142 .indent_guides
21143 .enabled
21144 });
21145 self.show_indent_guides = Some(!currently_enabled);
21146 cx.notify();
21147 }
21148
21149 fn should_show_indent_guides(&self) -> Option<bool> {
21150 self.show_indent_guides
21151 }
21152
21153 pub fn disable_indent_guides_for_buffer(
21154 &mut self,
21155 buffer_id: BufferId,
21156 cx: &mut Context<Self>,
21157 ) {
21158 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21159 cx.notify();
21160 }
21161
21162 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21163 self.buffers_with_disabled_indent_guides
21164 .contains(&buffer_id)
21165 }
21166
21167 pub fn toggle_line_numbers(
21168 &mut self,
21169 _: &ToggleLineNumbers,
21170 _: &mut Window,
21171 cx: &mut Context<Self>,
21172 ) {
21173 let mut editor_settings = EditorSettings::get_global(cx).clone();
21174 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21175 EditorSettings::override_global(editor_settings, cx);
21176 }
21177
21178 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21179 if let Some(show_line_numbers) = self.show_line_numbers {
21180 return show_line_numbers;
21181 }
21182 EditorSettings::get_global(cx).gutter.line_numbers
21183 }
21184
21185 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21186 match (
21187 self.use_relative_line_numbers,
21188 EditorSettings::get_global(cx).relative_line_numbers,
21189 ) {
21190 (None, setting) => setting,
21191 (Some(false), _) => RelativeLineNumbers::Disabled,
21192 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21193 (Some(true), _) => RelativeLineNumbers::Enabled,
21194 }
21195 }
21196
21197 pub fn toggle_relative_line_numbers(
21198 &mut self,
21199 _: &ToggleRelativeLineNumbers,
21200 _: &mut Window,
21201 cx: &mut Context<Self>,
21202 ) {
21203 let is_relative = self.relative_line_numbers(cx);
21204 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21205 }
21206
21207 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21208 self.use_relative_line_numbers = is_relative;
21209 cx.notify();
21210 }
21211
21212 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21213 self.show_gutter = show_gutter;
21214 cx.notify();
21215 }
21216
21217 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21218 self.show_scrollbars = ScrollbarAxes {
21219 horizontal: show,
21220 vertical: show,
21221 };
21222 cx.notify();
21223 }
21224
21225 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21226 self.show_scrollbars.vertical = show;
21227 cx.notify();
21228 }
21229
21230 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21231 self.show_scrollbars.horizontal = show;
21232 cx.notify();
21233 }
21234
21235 pub fn set_minimap_visibility(
21236 &mut self,
21237 minimap_visibility: MinimapVisibility,
21238 window: &mut Window,
21239 cx: &mut Context<Self>,
21240 ) {
21241 if self.minimap_visibility != minimap_visibility {
21242 if minimap_visibility.visible() && self.minimap.is_none() {
21243 let minimap_settings = EditorSettings::get_global(cx).minimap;
21244 self.minimap =
21245 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21246 }
21247 self.minimap_visibility = minimap_visibility;
21248 cx.notify();
21249 }
21250 }
21251
21252 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21253 self.set_show_scrollbars(false, cx);
21254 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21255 }
21256
21257 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21258 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21259 }
21260
21261 /// Normally the text in full mode and auto height editors is padded on the
21262 /// left side by roughly half a character width for improved hit testing.
21263 ///
21264 /// Use this method to disable this for cases where this is not wanted (e.g.
21265 /// if you want to align the editor text with some other text above or below)
21266 /// or if you want to add this padding to single-line editors.
21267 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21268 self.offset_content = offset_content;
21269 cx.notify();
21270 }
21271
21272 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21273 self.show_line_numbers = Some(show_line_numbers);
21274 cx.notify();
21275 }
21276
21277 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21278 self.disable_expand_excerpt_buttons = true;
21279 cx.notify();
21280 }
21281
21282 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21283 self.number_deleted_lines = number;
21284 cx.notify();
21285 }
21286
21287 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21288 self.delegate_expand_excerpts = delegate;
21289 }
21290
21291 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21292 self.delegate_stage_and_restore = delegate;
21293 }
21294
21295 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21296 self.delegate_open_excerpts = delegate;
21297 }
21298
21299 pub fn set_on_local_selections_changed(
21300 &mut self,
21301 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21302 ) {
21303 self.on_local_selections_changed = callback;
21304 }
21305
21306 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21307 self.suppress_selection_callback = suppress;
21308 }
21309
21310 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21311 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21312 cx.notify();
21313 }
21314
21315 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21316 self.show_code_actions = Some(show_code_actions);
21317 cx.notify();
21318 }
21319
21320 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21321 self.show_runnables = Some(show_runnables);
21322 cx.notify();
21323 }
21324
21325 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21326 self.show_breakpoints = Some(show_breakpoints);
21327 cx.notify();
21328 }
21329
21330 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21331 self.show_diff_review_button = show;
21332 cx.notify();
21333 }
21334
21335 pub fn show_diff_review_button(&self) -> bool {
21336 self.show_diff_review_button
21337 }
21338
21339 pub fn render_diff_review_button(
21340 &self,
21341 display_row: DisplayRow,
21342 width: Pixels,
21343 cx: &mut Context<Self>,
21344 ) -> impl IntoElement {
21345 let text_color = cx.theme().colors().text;
21346 let icon_color = cx.theme().colors().icon_accent;
21347
21348 h_flex()
21349 .id("diff_review_button")
21350 .cursor_pointer()
21351 .w(width - px(1.))
21352 .h(relative(0.9))
21353 .justify_center()
21354 .rounded_sm()
21355 .border_1()
21356 .border_color(text_color.opacity(0.1))
21357 .bg(text_color.opacity(0.15))
21358 .hover(|s| {
21359 s.bg(icon_color.opacity(0.4))
21360 .border_color(icon_color.opacity(0.5))
21361 })
21362 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21363 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21364 .on_mouse_down(
21365 gpui::MouseButton::Left,
21366 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21367 editor.start_diff_review_drag(display_row, window, cx);
21368 }),
21369 )
21370 }
21371
21372 pub fn start_diff_review_drag(
21373 &mut self,
21374 display_row: DisplayRow,
21375 window: &mut Window,
21376 cx: &mut Context<Self>,
21377 ) {
21378 let snapshot = self.snapshot(window, cx);
21379 let point = snapshot
21380 .display_snapshot
21381 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21382 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21383 self.diff_review_drag_state = Some(DiffReviewDragState {
21384 start_anchor: anchor,
21385 current_anchor: anchor,
21386 });
21387 cx.notify();
21388 }
21389
21390 pub fn update_diff_review_drag(
21391 &mut self,
21392 display_row: DisplayRow,
21393 window: &mut Window,
21394 cx: &mut Context<Self>,
21395 ) {
21396 if self.diff_review_drag_state.is_none() {
21397 return;
21398 }
21399 let snapshot = self.snapshot(window, cx);
21400 let point = snapshot
21401 .display_snapshot
21402 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21403 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21404 if let Some(drag_state) = &mut self.diff_review_drag_state {
21405 drag_state.current_anchor = anchor;
21406 cx.notify();
21407 }
21408 }
21409
21410 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21411 if let Some(drag_state) = self.diff_review_drag_state.take() {
21412 let snapshot = self.snapshot(window, cx);
21413 let range = drag_state.row_range(&snapshot.display_snapshot);
21414 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21415 }
21416 cx.notify();
21417 }
21418
21419 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21420 self.diff_review_drag_state = None;
21421 cx.notify();
21422 }
21423
21424 /// Calculates the appropriate block height for the diff review overlay.
21425 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21426 /// and 2 lines per comment when expanded.
21427 fn calculate_overlay_height(
21428 &self,
21429 hunk_key: &DiffHunkKey,
21430 comments_expanded: bool,
21431 snapshot: &MultiBufferSnapshot,
21432 ) -> u32 {
21433 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21434 let base_height: u32 = 2; // Input row with avatar and buttons
21435
21436 if comment_count == 0 {
21437 base_height
21438 } else if comments_expanded {
21439 // Header (1 line) + 2 lines per comment
21440 base_height + 1 + (comment_count as u32 * 2)
21441 } else {
21442 // Just header when collapsed
21443 base_height + 1
21444 }
21445 }
21446
21447 pub fn show_diff_review_overlay(
21448 &mut self,
21449 display_range: Range<DisplayRow>,
21450 window: &mut Window,
21451 cx: &mut Context<Self>,
21452 ) {
21453 let Range { start, end } = display_range.sorted();
21454
21455 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21456 let editor_snapshot = self.snapshot(window, cx);
21457
21458 // Convert display rows to multibuffer points
21459 let start_point = editor_snapshot
21460 .display_snapshot
21461 .display_point_to_point(start.as_display_point(), Bias::Left);
21462 let end_point = editor_snapshot
21463 .display_snapshot
21464 .display_point_to_point(end.as_display_point(), Bias::Left);
21465 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21466
21467 // Create anchor range for the selected lines (start of first line to end of last line)
21468 let line_end = Point::new(
21469 end_point.row,
21470 buffer_snapshot.line_len(end_multi_buffer_row),
21471 );
21472 let anchor_range =
21473 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21474
21475 // Compute the hunk key for this display row
21476 let file_path = buffer_snapshot
21477 .file_at(start_point)
21478 .map(|file: &Arc<dyn language::File>| file.path().clone())
21479 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21480 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21481 let new_hunk_key = DiffHunkKey {
21482 file_path,
21483 hunk_start_anchor,
21484 };
21485
21486 // Check if we already have an overlay for this hunk
21487 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21488 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21489 }) {
21490 // Just focus the existing overlay's prompt editor
21491 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21492 window.focus(&focus_handle, cx);
21493 return;
21494 }
21495
21496 // Dismiss overlays that have no comments for their hunks
21497 self.dismiss_overlays_without_comments(cx);
21498
21499 // Get the current user's avatar URI from the project's user_store
21500 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21501 let user_store = project.read(cx).user_store();
21502 user_store
21503 .read(cx)
21504 .current_user()
21505 .map(|user| user.avatar_uri.clone())
21506 });
21507
21508 // Create anchor at the end of the last row so the block appears immediately below it
21509 // Use multibuffer coordinates for anchor creation
21510 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21511 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21512
21513 // Use the hunk key we already computed
21514 let hunk_key = new_hunk_key;
21515
21516 // Create the prompt editor for the review input
21517 let prompt_editor = cx.new(|cx| {
21518 let mut editor = Editor::single_line(window, cx);
21519 editor.set_placeholder_text("Add a review comment...", window, cx);
21520 editor
21521 });
21522
21523 // Register the Newline action on the prompt editor to submit the review
21524 let parent_editor = cx.entity().downgrade();
21525 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21526 prompt_editor.register_action({
21527 let parent_editor = parent_editor.clone();
21528 move |_: &crate::actions::Newline, window, cx| {
21529 if let Some(editor) = parent_editor.upgrade() {
21530 editor.update(cx, |editor, cx| {
21531 editor.submit_diff_review_comment(window, cx);
21532 });
21533 }
21534 }
21535 })
21536 });
21537
21538 // Calculate initial height based on existing comments for this hunk
21539 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21540
21541 // Create the overlay block
21542 let prompt_editor_for_render = prompt_editor.clone();
21543 let hunk_key_for_render = hunk_key.clone();
21544 let editor_handle = cx.entity().downgrade();
21545 let block = BlockProperties {
21546 style: BlockStyle::Sticky,
21547 placement: BlockPlacement::Below(anchor),
21548 height: Some(initial_height),
21549 render: Arc::new(move |cx| {
21550 Self::render_diff_review_overlay(
21551 &prompt_editor_for_render,
21552 &hunk_key_for_render,
21553 &editor_handle,
21554 cx,
21555 )
21556 }),
21557 priority: 0,
21558 };
21559
21560 let block_ids = self.insert_blocks([block], None, cx);
21561 let Some(block_id) = block_ids.into_iter().next() else {
21562 log::error!("Failed to insert diff review overlay block");
21563 return;
21564 };
21565
21566 self.diff_review_overlays.push(DiffReviewOverlay {
21567 anchor_range,
21568 block_id,
21569 prompt_editor: prompt_editor.clone(),
21570 hunk_key,
21571 comments_expanded: true,
21572 inline_edit_editors: HashMap::default(),
21573 inline_edit_subscriptions: HashMap::default(),
21574 user_avatar_uri,
21575 _subscription: subscription,
21576 });
21577
21578 // Focus the prompt editor
21579 let focus_handle = prompt_editor.focus_handle(cx);
21580 window.focus(&focus_handle, cx);
21581
21582 cx.notify();
21583 }
21584
21585 /// Dismisses all diff review overlays.
21586 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21587 if self.diff_review_overlays.is_empty() {
21588 return;
21589 }
21590 let block_ids: HashSet<_> = self
21591 .diff_review_overlays
21592 .drain(..)
21593 .map(|overlay| overlay.block_id)
21594 .collect();
21595 self.remove_blocks(block_ids, None, cx);
21596 cx.notify();
21597 }
21598
21599 /// Dismisses overlays that have no comments stored for their hunks.
21600 /// Keeps overlays that have at least one comment.
21601 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21602 let snapshot = self.buffer.read(cx).snapshot(cx);
21603
21604 // First, compute which overlays have comments (to avoid borrow issues with retain)
21605 let overlays_with_comments: Vec<bool> = self
21606 .diff_review_overlays
21607 .iter()
21608 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21609 .collect();
21610
21611 // Now collect block IDs to remove and retain overlays
21612 let mut block_ids_to_remove = HashSet::default();
21613 let mut index = 0;
21614 self.diff_review_overlays.retain(|overlay| {
21615 let has_comments = overlays_with_comments[index];
21616 index += 1;
21617 if !has_comments {
21618 block_ids_to_remove.insert(overlay.block_id);
21619 }
21620 has_comments
21621 });
21622
21623 if !block_ids_to_remove.is_empty() {
21624 self.remove_blocks(block_ids_to_remove, None, cx);
21625 cx.notify();
21626 }
21627 }
21628
21629 /// Refreshes the diff review overlay block to update its height and render function.
21630 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21631 fn refresh_diff_review_overlay_height(
21632 &mut self,
21633 hunk_key: &DiffHunkKey,
21634 _window: &mut Window,
21635 cx: &mut Context<Self>,
21636 ) {
21637 // Extract all needed data from overlay first to avoid borrow conflicts
21638 let snapshot = self.buffer.read(cx).snapshot(cx);
21639 let (comments_expanded, block_id, prompt_editor) = {
21640 let Some(overlay) = self
21641 .diff_review_overlays
21642 .iter()
21643 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21644 else {
21645 return;
21646 };
21647
21648 (
21649 overlay.comments_expanded,
21650 overlay.block_id,
21651 overlay.prompt_editor.clone(),
21652 )
21653 };
21654
21655 // Calculate new height
21656 let snapshot = self.buffer.read(cx).snapshot(cx);
21657 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21658
21659 // Update the block height using resize_blocks (avoids flicker)
21660 let mut heights = HashMap::default();
21661 heights.insert(block_id, new_height);
21662 self.resize_blocks(heights, None, cx);
21663
21664 // Update the render function using replace_blocks (avoids flicker)
21665 let hunk_key_for_render = hunk_key.clone();
21666 let editor_handle = cx.entity().downgrade();
21667 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21668 Arc::new(move |cx| {
21669 Self::render_diff_review_overlay(
21670 &prompt_editor,
21671 &hunk_key_for_render,
21672 &editor_handle,
21673 cx,
21674 )
21675 });
21676
21677 let mut renderers = HashMap::default();
21678 renderers.insert(block_id, render);
21679 self.replace_blocks(renderers, None, cx);
21680 }
21681
21682 /// Action handler for SubmitDiffReviewComment.
21683 pub fn submit_diff_review_comment_action(
21684 &mut self,
21685 _: &SubmitDiffReviewComment,
21686 window: &mut Window,
21687 cx: &mut Context<Self>,
21688 ) {
21689 self.submit_diff_review_comment(window, cx);
21690 }
21691
21692 /// Stores the diff review comment locally.
21693 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21694 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21695 // Find the overlay that currently has focus
21696 let overlay_index = self
21697 .diff_review_overlays
21698 .iter()
21699 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21700 let Some(overlay_index) = overlay_index else {
21701 return;
21702 };
21703 let overlay = &self.diff_review_overlays[overlay_index];
21704
21705 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21706 if comment_text.is_empty() {
21707 return;
21708 }
21709
21710 let anchor_range = overlay.anchor_range.clone();
21711 let hunk_key = overlay.hunk_key.clone();
21712
21713 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21714
21715 // Clear the prompt editor but keep the overlay open
21716 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21717 overlay.prompt_editor.update(cx, |editor, cx| {
21718 editor.clear(window, cx);
21719 });
21720 }
21721
21722 // Refresh the overlay to update the block height for the new comment
21723 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21724
21725 cx.notify();
21726 }
21727
21728 /// Returns the prompt editor for the diff review overlay, if one is active.
21729 /// This is primarily used for testing.
21730 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21731 self.diff_review_overlays
21732 .first()
21733 .map(|overlay| &overlay.prompt_editor)
21734 }
21735
21736 /// Returns the line range for the first diff review overlay, if one is active.
21737 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21738 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21739 let overlay = self.diff_review_overlays.first()?;
21740 let snapshot = self.buffer.read(cx).snapshot(cx);
21741 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21742 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21743 let start_row = snapshot
21744 .point_to_buffer_point(start_point)
21745 .map(|(_, p, _)| p.row)
21746 .unwrap_or(start_point.row);
21747 let end_row = snapshot
21748 .point_to_buffer_point(end_point)
21749 .map(|(_, p, _)| p.row)
21750 .unwrap_or(end_point.row);
21751 Some((start_row, end_row))
21752 }
21753
21754 /// Sets whether the comments section is expanded in the diff review overlay.
21755 /// This is primarily used for testing.
21756 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21757 for overlay in &mut self.diff_review_overlays {
21758 overlay.comments_expanded = expanded;
21759 }
21760 cx.notify();
21761 }
21762
21763 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21764 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21765 a.file_path == b.file_path
21766 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21767 }
21768
21769 /// Returns comments for a specific hunk, ordered by creation time.
21770 pub fn comments_for_hunk<'a>(
21771 &'a self,
21772 key: &DiffHunkKey,
21773 snapshot: &MultiBufferSnapshot,
21774 ) -> &'a [StoredReviewComment] {
21775 let key_point = key.hunk_start_anchor.to_point(snapshot);
21776 self.stored_review_comments
21777 .iter()
21778 .find(|(k, _)| {
21779 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21780 })
21781 .map(|(_, comments)| comments.as_slice())
21782 .unwrap_or(&[])
21783 }
21784
21785 /// Returns the total count of stored review comments across all hunks.
21786 pub fn total_review_comment_count(&self) -> usize {
21787 self.stored_review_comments
21788 .iter()
21789 .map(|(_, v)| v.len())
21790 .sum()
21791 }
21792
21793 /// Returns the count of comments for a specific hunk.
21794 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21795 let key_point = key.hunk_start_anchor.to_point(snapshot);
21796 self.stored_review_comments
21797 .iter()
21798 .find(|(k, _)| {
21799 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21800 })
21801 .map(|(_, v)| v.len())
21802 .unwrap_or(0)
21803 }
21804
21805 /// Adds a new review comment to a specific hunk.
21806 pub fn add_review_comment(
21807 &mut self,
21808 hunk_key: DiffHunkKey,
21809 comment: String,
21810 anchor_range: Range<Anchor>,
21811 cx: &mut Context<Self>,
21812 ) -> usize {
21813 let id = self.next_review_comment_id;
21814 self.next_review_comment_id += 1;
21815
21816 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21817
21818 let snapshot = self.buffer.read(cx).snapshot(cx);
21819 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21820
21821 // Find existing entry for this hunk or add a new one
21822 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21823 k.file_path == hunk_key.file_path
21824 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21825 }) {
21826 comments.push(stored_comment);
21827 } else {
21828 self.stored_review_comments
21829 .push((hunk_key, vec![stored_comment]));
21830 }
21831
21832 cx.emit(EditorEvent::ReviewCommentsChanged {
21833 total_count: self.total_review_comment_count(),
21834 });
21835 cx.notify();
21836 id
21837 }
21838
21839 /// Removes a review comment by ID from any hunk.
21840 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21841 for (_, comments) in self.stored_review_comments.iter_mut() {
21842 if let Some(index) = comments.iter().position(|c| c.id == id) {
21843 comments.remove(index);
21844 cx.emit(EditorEvent::ReviewCommentsChanged {
21845 total_count: self.total_review_comment_count(),
21846 });
21847 cx.notify();
21848 return true;
21849 }
21850 }
21851 false
21852 }
21853
21854 /// Updates a review comment's text by ID.
21855 pub fn update_review_comment(
21856 &mut self,
21857 id: usize,
21858 new_comment: String,
21859 cx: &mut Context<Self>,
21860 ) -> bool {
21861 for (_, comments) in self.stored_review_comments.iter_mut() {
21862 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21863 comment.comment = new_comment;
21864 comment.is_editing = false;
21865 cx.emit(EditorEvent::ReviewCommentsChanged {
21866 total_count: self.total_review_comment_count(),
21867 });
21868 cx.notify();
21869 return true;
21870 }
21871 }
21872 false
21873 }
21874
21875 /// Sets a comment's editing state.
21876 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21877 for (_, comments) in self.stored_review_comments.iter_mut() {
21878 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21879 comment.is_editing = is_editing;
21880 cx.notify();
21881 return;
21882 }
21883 }
21884 }
21885
21886 /// Takes all stored comments from all hunks, clearing the storage.
21887 /// Returns a Vec of (hunk_key, comments) pairs.
21888 pub fn take_all_review_comments(
21889 &mut self,
21890 cx: &mut Context<Self>,
21891 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21892 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21893 self.dismiss_all_diff_review_overlays(cx);
21894 let comments = std::mem::take(&mut self.stored_review_comments);
21895 // Reset the ID counter since all comments have been taken
21896 self.next_review_comment_id = 0;
21897 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21898 cx.notify();
21899 comments
21900 }
21901
21902 /// Removes review comments whose anchors are no longer valid or whose
21903 /// associated diff hunks no longer exist.
21904 ///
21905 /// This should be called when the buffer changes to prevent orphaned comments
21906 /// from accumulating.
21907 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21908 let snapshot = self.buffer.read(cx).snapshot(cx);
21909 let original_count = self.total_review_comment_count();
21910
21911 // Remove comments with invalid hunk anchors
21912 self.stored_review_comments
21913 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21914
21915 // Also clean up individual comments with invalid anchor ranges
21916 for (_, comments) in &mut self.stored_review_comments {
21917 comments.retain(|comment| {
21918 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21919 });
21920 }
21921
21922 // Remove empty hunk entries
21923 self.stored_review_comments
21924 .retain(|(_, comments)| !comments.is_empty());
21925
21926 let new_count = self.total_review_comment_count();
21927 if new_count != original_count {
21928 cx.emit(EditorEvent::ReviewCommentsChanged {
21929 total_count: new_count,
21930 });
21931 cx.notify();
21932 }
21933 }
21934
21935 /// Toggles the expanded state of the comments section in the overlay.
21936 pub fn toggle_review_comments_expanded(
21937 &mut self,
21938 _: &ToggleReviewCommentsExpanded,
21939 window: &mut Window,
21940 cx: &mut Context<Self>,
21941 ) {
21942 // Find the overlay that currently has focus, or use the first one
21943 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21944 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21945 overlay.comments_expanded = !overlay.comments_expanded;
21946 Some(overlay.hunk_key.clone())
21947 } else {
21948 None
21949 }
21950 });
21951
21952 // If no focused overlay found, toggle the first one
21953 let hunk_key = overlay_info.or_else(|| {
21954 self.diff_review_overlays.first_mut().map(|overlay| {
21955 overlay.comments_expanded = !overlay.comments_expanded;
21956 overlay.hunk_key.clone()
21957 })
21958 });
21959
21960 if let Some(hunk_key) = hunk_key {
21961 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21962 cx.notify();
21963 }
21964 }
21965
21966 /// Handles the EditReviewComment action - sets a comment into editing mode.
21967 pub fn edit_review_comment(
21968 &mut self,
21969 action: &EditReviewComment,
21970 window: &mut Window,
21971 cx: &mut Context<Self>,
21972 ) {
21973 let comment_id = action.id;
21974
21975 // Set the comment to editing mode
21976 self.set_comment_editing(comment_id, true, cx);
21977
21978 // Find the overlay that contains this comment and create an inline editor if needed
21979 // First, find which hunk this comment belongs to
21980 let hunk_key = self
21981 .stored_review_comments
21982 .iter()
21983 .find_map(|(key, comments)| {
21984 if comments.iter().any(|c| c.id == comment_id) {
21985 Some(key.clone())
21986 } else {
21987 None
21988 }
21989 });
21990
21991 let snapshot = self.buffer.read(cx).snapshot(cx);
21992 if let Some(hunk_key) = hunk_key {
21993 if let Some(overlay) = self
21994 .diff_review_overlays
21995 .iter_mut()
21996 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21997 {
21998 if let std::collections::hash_map::Entry::Vacant(entry) =
21999 overlay.inline_edit_editors.entry(comment_id)
22000 {
22001 // Find the comment text
22002 let comment_text = self
22003 .stored_review_comments
22004 .iter()
22005 .flat_map(|(_, comments)| comments)
22006 .find(|c| c.id == comment_id)
22007 .map(|c| c.comment.clone())
22008 .unwrap_or_default();
22009
22010 // Create inline editor
22011 let parent_editor = cx.entity().downgrade();
22012 let inline_editor = cx.new(|cx| {
22013 let mut editor = Editor::single_line(window, cx);
22014 editor.set_text(&*comment_text, window, cx);
22015 // Select all text for easy replacement
22016 editor.select_all(&crate::actions::SelectAll, window, cx);
22017 editor
22018 });
22019
22020 // Register the Newline action to confirm the edit
22021 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22022 inline_editor.register_action({
22023 let parent_editor = parent_editor.clone();
22024 move |_: &crate::actions::Newline, window, cx| {
22025 if let Some(editor) = parent_editor.upgrade() {
22026 editor.update(cx, |editor, cx| {
22027 editor.confirm_edit_review_comment(comment_id, window, cx);
22028 });
22029 }
22030 }
22031 })
22032 });
22033
22034 // Store the subscription to keep the action handler alive
22035 overlay
22036 .inline_edit_subscriptions
22037 .insert(comment_id, subscription);
22038
22039 // Focus the inline editor
22040 let focus_handle = inline_editor.focus_handle(cx);
22041 window.focus(&focus_handle, cx);
22042
22043 entry.insert(inline_editor);
22044 }
22045 }
22046 }
22047
22048 cx.notify();
22049 }
22050
22051 /// Confirms an inline edit of a review comment.
22052 pub fn confirm_edit_review_comment(
22053 &mut self,
22054 comment_id: usize,
22055 _window: &mut Window,
22056 cx: &mut Context<Self>,
22057 ) {
22058 // Get the new text from the inline editor
22059 // Find the overlay containing this comment's inline editor
22060 let snapshot = self.buffer.read(cx).snapshot(cx);
22061 let hunk_key = self
22062 .stored_review_comments
22063 .iter()
22064 .find_map(|(key, comments)| {
22065 if comments.iter().any(|c| c.id == comment_id) {
22066 Some(key.clone())
22067 } else {
22068 None
22069 }
22070 });
22071
22072 let new_text = hunk_key
22073 .as_ref()
22074 .and_then(|hunk_key| {
22075 self.diff_review_overlays
22076 .iter()
22077 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22078 })
22079 .as_ref()
22080 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22081 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22082
22083 if let Some(new_text) = new_text {
22084 if !new_text.is_empty() {
22085 self.update_review_comment(comment_id, new_text, cx);
22086 }
22087 }
22088
22089 // Remove the inline editor and its subscription
22090 if let Some(hunk_key) = hunk_key {
22091 if let Some(overlay) = self
22092 .diff_review_overlays
22093 .iter_mut()
22094 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22095 {
22096 overlay.inline_edit_editors.remove(&comment_id);
22097 overlay.inline_edit_subscriptions.remove(&comment_id);
22098 }
22099 }
22100
22101 // Clear editing state
22102 self.set_comment_editing(comment_id, false, cx);
22103 }
22104
22105 /// Cancels an inline edit of a review comment.
22106 pub fn cancel_edit_review_comment(
22107 &mut self,
22108 comment_id: usize,
22109 _window: &mut Window,
22110 cx: &mut Context<Self>,
22111 ) {
22112 // Find which hunk this comment belongs to
22113 let hunk_key = self
22114 .stored_review_comments
22115 .iter()
22116 .find_map(|(key, comments)| {
22117 if comments.iter().any(|c| c.id == comment_id) {
22118 Some(key.clone())
22119 } else {
22120 None
22121 }
22122 });
22123
22124 // Remove the inline editor and its subscription
22125 if let Some(hunk_key) = hunk_key {
22126 let snapshot = self.buffer.read(cx).snapshot(cx);
22127 if let Some(overlay) = self
22128 .diff_review_overlays
22129 .iter_mut()
22130 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22131 {
22132 overlay.inline_edit_editors.remove(&comment_id);
22133 overlay.inline_edit_subscriptions.remove(&comment_id);
22134 }
22135 }
22136
22137 // Clear editing state
22138 self.set_comment_editing(comment_id, false, cx);
22139 }
22140
22141 /// Action handler for ConfirmEditReviewComment.
22142 pub fn confirm_edit_review_comment_action(
22143 &mut self,
22144 action: &ConfirmEditReviewComment,
22145 window: &mut Window,
22146 cx: &mut Context<Self>,
22147 ) {
22148 self.confirm_edit_review_comment(action.id, window, cx);
22149 }
22150
22151 /// Action handler for CancelEditReviewComment.
22152 pub fn cancel_edit_review_comment_action(
22153 &mut self,
22154 action: &CancelEditReviewComment,
22155 window: &mut Window,
22156 cx: &mut Context<Self>,
22157 ) {
22158 self.cancel_edit_review_comment(action.id, window, cx);
22159 }
22160
22161 /// Handles the DeleteReviewComment action - removes a comment.
22162 pub fn delete_review_comment(
22163 &mut self,
22164 action: &DeleteReviewComment,
22165 window: &mut Window,
22166 cx: &mut Context<Self>,
22167 ) {
22168 // Get the hunk key before removing the comment
22169 // Find the hunk key from the comment itself
22170 let comment_id = action.id;
22171 let hunk_key = self
22172 .stored_review_comments
22173 .iter()
22174 .find_map(|(key, comments)| {
22175 if comments.iter().any(|c| c.id == comment_id) {
22176 Some(key.clone())
22177 } else {
22178 None
22179 }
22180 });
22181
22182 // Also get it from the overlay for refresh purposes
22183 let overlay_hunk_key = self
22184 .diff_review_overlays
22185 .first()
22186 .map(|o| o.hunk_key.clone());
22187
22188 self.remove_review_comment(action.id, cx);
22189
22190 // Refresh the overlay height after removing a comment
22191 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22192 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22193 }
22194 }
22195
22196 fn render_diff_review_overlay(
22197 prompt_editor: &Entity<Editor>,
22198 hunk_key: &DiffHunkKey,
22199 editor_handle: &WeakEntity<Editor>,
22200 cx: &mut BlockContext,
22201 ) -> AnyElement {
22202 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22203 if ranges.is_empty() {
22204 return None;
22205 }
22206 let formatted: Vec<String> = ranges
22207 .iter()
22208 .map(|(start, end)| {
22209 let start_line = start + 1;
22210 let end_line = end + 1;
22211 if start_line == end_line {
22212 format!("Line {start_line}")
22213 } else {
22214 format!("Lines {start_line}-{end_line}")
22215 }
22216 })
22217 .collect();
22218 // Don't show label for single line in single excerpt
22219 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22220 return None;
22221 }
22222 Some(formatted.join(" ⋯ "))
22223 }
22224
22225 let theme = cx.theme();
22226 let colors = theme.colors();
22227
22228 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22229 editor_handle
22230 .upgrade()
22231 .map(|editor| {
22232 let editor = editor.read(cx);
22233 let snapshot = editor.buffer().read(cx).snapshot(cx);
22234 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22235 let (expanded, editors, avatar_uri, line_ranges) = editor
22236 .diff_review_overlays
22237 .iter()
22238 .find(|overlay| {
22239 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22240 })
22241 .map(|o| {
22242 let start_point = o.anchor_range.start.to_point(&snapshot);
22243 let end_point = o.anchor_range.end.to_point(&snapshot);
22244 // Get line ranges per excerpt to detect discontinuities
22245 let buffer_ranges =
22246 snapshot.range_to_buffer_ranges(start_point..end_point);
22247 let ranges: Vec<(u32, u32)> = buffer_ranges
22248 .iter()
22249 .map(|(buffer, range, _)| {
22250 let start = buffer.offset_to_point(range.start.0).row;
22251 let end = buffer.offset_to_point(range.end.0).row;
22252 (start, end)
22253 })
22254 .collect();
22255 (
22256 o.comments_expanded,
22257 o.inline_edit_editors.clone(),
22258 o.user_avatar_uri.clone(),
22259 if ranges.is_empty() {
22260 None
22261 } else {
22262 Some(ranges)
22263 },
22264 )
22265 })
22266 .unwrap_or((true, HashMap::default(), None, None));
22267 (comments, expanded, editors, avatar_uri, line_ranges)
22268 })
22269 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22270
22271 let comment_count = comments.len();
22272 let avatar_size = px(20.);
22273 let action_icon_size = IconSize::XSmall;
22274
22275 v_flex()
22276 .w_full()
22277 .bg(colors.editor_background)
22278 .border_b_1()
22279 .border_color(colors.border)
22280 .px_2()
22281 .pb_2()
22282 .gap_2()
22283 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22284 .when_some(line_ranges, |el, ranges| {
22285 let label = format_line_ranges(&ranges);
22286 if let Some(label) = label {
22287 el.child(
22288 h_flex()
22289 .w_full()
22290 .px_2()
22291 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22292 )
22293 } else {
22294 el
22295 }
22296 })
22297 // Top row: editable input with user's avatar
22298 .child(
22299 h_flex()
22300 .w_full()
22301 .items_center()
22302 .gap_2()
22303 .px_2()
22304 .py_1p5()
22305 .rounded_md()
22306 .bg(colors.surface_background)
22307 .child(
22308 div()
22309 .size(avatar_size)
22310 .flex_shrink_0()
22311 .rounded_full()
22312 .overflow_hidden()
22313 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22314 Avatar::new(avatar_uri.clone())
22315 .size(avatar_size)
22316 .into_any_element()
22317 } else {
22318 Icon::new(IconName::Person)
22319 .size(IconSize::Small)
22320 .color(ui::Color::Muted)
22321 .into_any_element()
22322 }),
22323 )
22324 .child(
22325 div()
22326 .flex_1()
22327 .border_1()
22328 .border_color(colors.border)
22329 .rounded_md()
22330 .bg(colors.editor_background)
22331 .px_2()
22332 .py_1()
22333 .child(prompt_editor.clone()),
22334 )
22335 .child(
22336 h_flex()
22337 .flex_shrink_0()
22338 .gap_1()
22339 .child(
22340 IconButton::new("diff-review-close", IconName::Close)
22341 .icon_color(ui::Color::Muted)
22342 .icon_size(action_icon_size)
22343 .tooltip(Tooltip::text("Close"))
22344 .on_click(|_, window, cx| {
22345 window
22346 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22347 }),
22348 )
22349 .child(
22350 IconButton::new("diff-review-add", IconName::Return)
22351 .icon_color(ui::Color::Muted)
22352 .icon_size(action_icon_size)
22353 .tooltip(Tooltip::text("Add comment"))
22354 .on_click(|_, window, cx| {
22355 window.dispatch_action(
22356 Box::new(crate::actions::SubmitDiffReviewComment),
22357 cx,
22358 );
22359 }),
22360 ),
22361 ),
22362 )
22363 // Expandable comments section (only shown when there are comments)
22364 .when(comment_count > 0, |el| {
22365 el.child(Self::render_comments_section(
22366 comments,
22367 comments_expanded,
22368 inline_editors,
22369 user_avatar_uri,
22370 avatar_size,
22371 action_icon_size,
22372 colors,
22373 ))
22374 })
22375 .into_any_element()
22376 }
22377
22378 fn render_comments_section(
22379 comments: Vec<StoredReviewComment>,
22380 expanded: bool,
22381 inline_editors: HashMap<usize, Entity<Editor>>,
22382 user_avatar_uri: Option<SharedUri>,
22383 avatar_size: Pixels,
22384 action_icon_size: IconSize,
22385 colors: &theme::ThemeColors,
22386 ) -> impl IntoElement {
22387 let comment_count = comments.len();
22388
22389 v_flex()
22390 .w_full()
22391 .gap_1()
22392 // Header with expand/collapse toggle
22393 .child(
22394 h_flex()
22395 .id("review-comments-header")
22396 .w_full()
22397 .items_center()
22398 .gap_1()
22399 .px_2()
22400 .py_1()
22401 .cursor_pointer()
22402 .rounded_md()
22403 .hover(|style| style.bg(colors.ghost_element_hover))
22404 .on_click(|_, window: &mut Window, cx| {
22405 window.dispatch_action(
22406 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22407 cx,
22408 );
22409 })
22410 .child(
22411 Icon::new(if expanded {
22412 IconName::ChevronDown
22413 } else {
22414 IconName::ChevronRight
22415 })
22416 .size(IconSize::Small)
22417 .color(ui::Color::Muted),
22418 )
22419 .child(
22420 Label::new(format!(
22421 "{} Comment{}",
22422 comment_count,
22423 if comment_count == 1 { "" } else { "s" }
22424 ))
22425 .size(LabelSize::Small)
22426 .color(Color::Muted),
22427 ),
22428 )
22429 // Comments list (when expanded)
22430 .when(expanded, |el| {
22431 el.children(comments.into_iter().map(|comment| {
22432 let inline_editor = inline_editors.get(&comment.id).cloned();
22433 Self::render_comment_row(
22434 comment,
22435 inline_editor,
22436 user_avatar_uri.clone(),
22437 avatar_size,
22438 action_icon_size,
22439 colors,
22440 )
22441 }))
22442 })
22443 }
22444
22445 fn render_comment_row(
22446 comment: StoredReviewComment,
22447 inline_editor: Option<Entity<Editor>>,
22448 user_avatar_uri: Option<SharedUri>,
22449 avatar_size: Pixels,
22450 action_icon_size: IconSize,
22451 colors: &theme::ThemeColors,
22452 ) -> impl IntoElement {
22453 let comment_id = comment.id;
22454 let is_editing = inline_editor.is_some();
22455
22456 h_flex()
22457 .w_full()
22458 .items_center()
22459 .gap_2()
22460 .px_2()
22461 .py_1p5()
22462 .rounded_md()
22463 .bg(colors.surface_background)
22464 .child(
22465 div()
22466 .size(avatar_size)
22467 .flex_shrink_0()
22468 .rounded_full()
22469 .overflow_hidden()
22470 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22471 Avatar::new(avatar_uri.clone())
22472 .size(avatar_size)
22473 .into_any_element()
22474 } else {
22475 Icon::new(IconName::Person)
22476 .size(IconSize::Small)
22477 .color(ui::Color::Muted)
22478 .into_any_element()
22479 }),
22480 )
22481 .child(if let Some(editor) = inline_editor {
22482 // Inline edit mode: show an editable text field
22483 div()
22484 .flex_1()
22485 .border_1()
22486 .border_color(colors.border)
22487 .rounded_md()
22488 .bg(colors.editor_background)
22489 .px_2()
22490 .py_1()
22491 .child(editor)
22492 .into_any_element()
22493 } else {
22494 // Display mode: show the comment text
22495 div()
22496 .flex_1()
22497 .text_sm()
22498 .text_color(colors.text)
22499 .child(comment.comment)
22500 .into_any_element()
22501 })
22502 .child(if is_editing {
22503 // Editing mode: show close and confirm buttons
22504 h_flex()
22505 .gap_1()
22506 .child(
22507 IconButton::new(
22508 format!("diff-review-cancel-edit-{comment_id}"),
22509 IconName::Close,
22510 )
22511 .icon_color(ui::Color::Muted)
22512 .icon_size(action_icon_size)
22513 .tooltip(Tooltip::text("Cancel"))
22514 .on_click(move |_, window, cx| {
22515 window.dispatch_action(
22516 Box::new(crate::actions::CancelEditReviewComment {
22517 id: comment_id,
22518 }),
22519 cx,
22520 );
22521 }),
22522 )
22523 .child(
22524 IconButton::new(
22525 format!("diff-review-confirm-edit-{comment_id}"),
22526 IconName::Return,
22527 )
22528 .icon_color(ui::Color::Muted)
22529 .icon_size(action_icon_size)
22530 .tooltip(Tooltip::text("Confirm"))
22531 .on_click(move |_, window, cx| {
22532 window.dispatch_action(
22533 Box::new(crate::actions::ConfirmEditReviewComment {
22534 id: comment_id,
22535 }),
22536 cx,
22537 );
22538 }),
22539 )
22540 .into_any_element()
22541 } else {
22542 // Display mode: no action buttons for now (edit/delete not yet implemented)
22543 gpui::Empty.into_any_element()
22544 })
22545 }
22546
22547 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22548 if self.display_map.read(cx).masked != masked {
22549 self.display_map.update(cx, |map, _| map.masked = masked);
22550 }
22551 cx.notify()
22552 }
22553
22554 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22555 self.show_wrap_guides = Some(show_wrap_guides);
22556 cx.notify();
22557 }
22558
22559 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22560 self.show_indent_guides = Some(show_indent_guides);
22561 cx.notify();
22562 }
22563
22564 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22565 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22566 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22567 && let Some(dir) = file.abs_path(cx).parent()
22568 {
22569 return Some(dir.to_owned());
22570 }
22571 }
22572
22573 None
22574 }
22575
22576 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22577 self.active_excerpt(cx)?
22578 .1
22579 .read(cx)
22580 .file()
22581 .and_then(|f| f.as_local())
22582 }
22583
22584 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22585 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22586 let buffer = buffer.read(cx);
22587 if let Some(project_path) = buffer.project_path(cx) {
22588 let project = self.project()?.read(cx);
22589 project.absolute_path(&project_path, cx)
22590 } else {
22591 buffer
22592 .file()
22593 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22594 }
22595 })
22596 }
22597
22598 pub fn reveal_in_finder(
22599 &mut self,
22600 _: &RevealInFileManager,
22601 _window: &mut Window,
22602 cx: &mut Context<Self>,
22603 ) {
22604 if let Some(path) = self.target_file_abs_path(cx) {
22605 if let Some(project) = self.project() {
22606 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22607 } else {
22608 cx.reveal_path(&path);
22609 }
22610 }
22611 }
22612
22613 pub fn copy_path(
22614 &mut self,
22615 _: &zed_actions::workspace::CopyPath,
22616 _window: &mut Window,
22617 cx: &mut Context<Self>,
22618 ) {
22619 if let Some(path) = self.target_file_abs_path(cx)
22620 && let Some(path) = path.to_str()
22621 {
22622 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22623 } else {
22624 cx.propagate();
22625 }
22626 }
22627
22628 pub fn copy_relative_path(
22629 &mut self,
22630 _: &zed_actions::workspace::CopyRelativePath,
22631 _window: &mut Window,
22632 cx: &mut Context<Self>,
22633 ) {
22634 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22635 let project = self.project()?.read(cx);
22636 let path = buffer.read(cx).file()?.path();
22637 let path = path.display(project.path_style(cx));
22638 Some(path)
22639 }) {
22640 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22641 } else {
22642 cx.propagate();
22643 }
22644 }
22645
22646 /// Returns the project path for the editor's buffer, if any buffer is
22647 /// opened in the editor.
22648 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22649 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22650 buffer.read(cx).project_path(cx)
22651 } else {
22652 None
22653 }
22654 }
22655
22656 // Returns true if the editor handled a go-to-line request
22657 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22658 maybe!({
22659 let breakpoint_store = self.breakpoint_store.as_ref()?;
22660
22661 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22662 else {
22663 self.clear_row_highlights::<ActiveDebugLine>();
22664 return None;
22665 };
22666
22667 let position = active_stack_frame.position;
22668 let buffer_id = position.buffer_id?;
22669 let snapshot = self
22670 .project
22671 .as_ref()?
22672 .read(cx)
22673 .buffer_for_id(buffer_id, cx)?
22674 .read(cx)
22675 .snapshot();
22676
22677 let mut handled = false;
22678 for (id, ExcerptRange { context, .. }) in
22679 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22680 {
22681 if context.start.cmp(&position, &snapshot).is_ge()
22682 || context.end.cmp(&position, &snapshot).is_lt()
22683 {
22684 continue;
22685 }
22686 let snapshot = self.buffer.read(cx).snapshot(cx);
22687 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22688
22689 handled = true;
22690 self.clear_row_highlights::<ActiveDebugLine>();
22691
22692 self.go_to_line::<ActiveDebugLine>(
22693 multibuffer_anchor,
22694 Some(cx.theme().colors().editor_debugger_active_line_background),
22695 window,
22696 cx,
22697 );
22698
22699 cx.notify();
22700 }
22701
22702 handled.then_some(())
22703 })
22704 .is_some()
22705 }
22706
22707 pub fn copy_file_name_without_extension(
22708 &mut self,
22709 _: &CopyFileNameWithoutExtension,
22710 _: &mut Window,
22711 cx: &mut Context<Self>,
22712 ) {
22713 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22714 let file = buffer.read(cx).file()?;
22715 file.path().file_stem()
22716 }) {
22717 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22718 }
22719 }
22720
22721 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22722 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22723 let file = buffer.read(cx).file()?;
22724 Some(file.file_name(cx))
22725 }) {
22726 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22727 }
22728 }
22729
22730 pub fn toggle_git_blame(
22731 &mut self,
22732 _: &::git::Blame,
22733 window: &mut Window,
22734 cx: &mut Context<Self>,
22735 ) {
22736 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22737
22738 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22739 self.start_git_blame(true, window, cx);
22740 }
22741
22742 cx.notify();
22743 }
22744
22745 pub fn toggle_git_blame_inline(
22746 &mut self,
22747 _: &ToggleGitBlameInline,
22748 window: &mut Window,
22749 cx: &mut Context<Self>,
22750 ) {
22751 self.toggle_git_blame_inline_internal(true, window, cx);
22752 cx.notify();
22753 }
22754
22755 pub fn open_git_blame_commit(
22756 &mut self,
22757 _: &OpenGitBlameCommit,
22758 window: &mut Window,
22759 cx: &mut Context<Self>,
22760 ) {
22761 self.open_git_blame_commit_internal(window, cx);
22762 }
22763
22764 fn open_git_blame_commit_internal(
22765 &mut self,
22766 window: &mut Window,
22767 cx: &mut Context<Self>,
22768 ) -> Option<()> {
22769 let blame = self.blame.as_ref()?;
22770 let snapshot = self.snapshot(window, cx);
22771 let cursor = self
22772 .selections
22773 .newest::<Point>(&snapshot.display_snapshot)
22774 .head();
22775 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22776 let (_, blame_entry) = blame
22777 .update(cx, |blame, cx| {
22778 blame
22779 .blame_for_rows(
22780 &[RowInfo {
22781 buffer_id: Some(buffer.remote_id()),
22782 buffer_row: Some(point.row),
22783 ..Default::default()
22784 }],
22785 cx,
22786 )
22787 .next()
22788 })
22789 .flatten()?;
22790 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22791 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22792 let workspace = self.workspace()?.downgrade();
22793 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22794 None
22795 }
22796
22797 pub fn git_blame_inline_enabled(&self) -> bool {
22798 self.git_blame_inline_enabled
22799 }
22800
22801 pub fn toggle_selection_menu(
22802 &mut self,
22803 _: &ToggleSelectionMenu,
22804 _: &mut Window,
22805 cx: &mut Context<Self>,
22806 ) {
22807 self.show_selection_menu = self
22808 .show_selection_menu
22809 .map(|show_selections_menu| !show_selections_menu)
22810 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22811
22812 cx.notify();
22813 }
22814
22815 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22816 self.show_selection_menu
22817 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22818 }
22819
22820 fn start_git_blame(
22821 &mut self,
22822 user_triggered: bool,
22823 window: &mut Window,
22824 cx: &mut Context<Self>,
22825 ) {
22826 if let Some(project) = self.project() {
22827 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22828 && buffer.read(cx).file().is_none()
22829 {
22830 return;
22831 }
22832
22833 let focused = self.focus_handle(cx).contains_focused(window, cx);
22834
22835 let project = project.clone();
22836 let blame = cx
22837 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22838 self.blame_subscription =
22839 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22840 self.blame = Some(blame);
22841 }
22842 }
22843
22844 fn toggle_git_blame_inline_internal(
22845 &mut self,
22846 user_triggered: bool,
22847 window: &mut Window,
22848 cx: &mut Context<Self>,
22849 ) {
22850 if self.git_blame_inline_enabled {
22851 self.git_blame_inline_enabled = false;
22852 self.show_git_blame_inline = false;
22853 self.show_git_blame_inline_delay_task.take();
22854 } else {
22855 self.git_blame_inline_enabled = true;
22856 self.start_git_blame_inline(user_triggered, window, cx);
22857 }
22858
22859 cx.notify();
22860 }
22861
22862 fn start_git_blame_inline(
22863 &mut self,
22864 user_triggered: bool,
22865 window: &mut Window,
22866 cx: &mut Context<Self>,
22867 ) {
22868 self.start_git_blame(user_triggered, window, cx);
22869
22870 if ProjectSettings::get_global(cx)
22871 .git
22872 .inline_blame_delay()
22873 .is_some()
22874 {
22875 self.start_inline_blame_timer(window, cx);
22876 } else {
22877 self.show_git_blame_inline = true
22878 }
22879 }
22880
22881 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22882 self.blame.as_ref()
22883 }
22884
22885 pub fn show_git_blame_gutter(&self) -> bool {
22886 self.show_git_blame_gutter
22887 }
22888
22889 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22890 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22891 }
22892
22893 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22894 self.show_git_blame_inline
22895 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22896 && !self.newest_selection_head_on_empty_line(cx)
22897 && self.has_blame_entries(cx)
22898 }
22899
22900 fn has_blame_entries(&self, cx: &App) -> bool {
22901 self.blame()
22902 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22903 }
22904
22905 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22906 let cursor_anchor = self.selections.newest_anchor().head();
22907
22908 let snapshot = self.buffer.read(cx).snapshot(cx);
22909 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22910
22911 snapshot.line_len(buffer_row) == 0
22912 }
22913
22914 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22915 let buffer_and_selection = maybe!({
22916 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22917 let selection_range = selection.range();
22918
22919 let multi_buffer = self.buffer().read(cx);
22920 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22921 let buffer_ranges = multi_buffer_snapshot
22922 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22923
22924 let (buffer, range, _) = if selection.reversed {
22925 buffer_ranges.first()
22926 } else {
22927 buffer_ranges.last()
22928 }?;
22929
22930 let buffer_range = range.to_point(buffer);
22931
22932 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22933 return Some((
22934 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22935 buffer_range.start.row..buffer_range.end.row,
22936 ));
22937 };
22938
22939 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22940 let start =
22941 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22942 let end =
22943 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22944
22945 Some((
22946 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22947 start.row..end.row,
22948 ))
22949 });
22950
22951 let Some((buffer, selection)) = buffer_and_selection else {
22952 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22953 };
22954
22955 let Some(project) = self.project() else {
22956 return Task::ready(Err(anyhow!("editor does not have project")));
22957 };
22958
22959 project.update(cx, |project, cx| {
22960 project.get_permalink_to_line(&buffer, selection, cx)
22961 })
22962 }
22963
22964 pub fn copy_permalink_to_line(
22965 &mut self,
22966 _: &CopyPermalinkToLine,
22967 window: &mut Window,
22968 cx: &mut Context<Self>,
22969 ) {
22970 let permalink_task = self.get_permalink_to_line(cx);
22971 let workspace = self.workspace();
22972
22973 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22974 Ok(permalink) => {
22975 cx.update(|_, cx| {
22976 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22977 })
22978 .ok();
22979 }
22980 Err(err) => {
22981 let message = format!("Failed to copy permalink: {err}");
22982
22983 anyhow::Result::<()>::Err(err).log_err();
22984
22985 if let Some(workspace) = workspace {
22986 workspace
22987 .update_in(cx, |workspace, _, cx| {
22988 struct CopyPermalinkToLine;
22989
22990 workspace.show_toast(
22991 Toast::new(
22992 NotificationId::unique::<CopyPermalinkToLine>(),
22993 message,
22994 ),
22995 cx,
22996 )
22997 })
22998 .ok();
22999 }
23000 }
23001 })
23002 .detach();
23003 }
23004
23005 pub fn copy_file_location(
23006 &mut self,
23007 _: &CopyFileLocation,
23008 _: &mut Window,
23009 cx: &mut Context<Self>,
23010 ) {
23011 let selection = self
23012 .selections
23013 .newest::<Point>(&self.display_snapshot(cx))
23014 .start
23015 .row
23016 + 1;
23017 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23018 let project = self.project()?.read(cx);
23019 let file = buffer.read(cx).file()?;
23020 let path = file.path().display(project.path_style(cx));
23021
23022 Some(format!("{path}:{selection}"))
23023 }) {
23024 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23025 }
23026 }
23027
23028 pub fn open_permalink_to_line(
23029 &mut self,
23030 _: &OpenPermalinkToLine,
23031 window: &mut Window,
23032 cx: &mut Context<Self>,
23033 ) {
23034 let permalink_task = self.get_permalink_to_line(cx);
23035 let workspace = self.workspace();
23036
23037 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23038 Ok(permalink) => {
23039 cx.update(|_, cx| {
23040 cx.open_url(permalink.as_ref());
23041 })
23042 .ok();
23043 }
23044 Err(err) => {
23045 let message = format!("Failed to open permalink: {err}");
23046
23047 anyhow::Result::<()>::Err(err).log_err();
23048
23049 if let Some(workspace) = workspace {
23050 workspace.update(cx, |workspace, cx| {
23051 struct OpenPermalinkToLine;
23052
23053 workspace.show_toast(
23054 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23055 cx,
23056 )
23057 });
23058 }
23059 }
23060 })
23061 .detach();
23062 }
23063
23064 pub fn insert_uuid_v4(
23065 &mut self,
23066 _: &InsertUuidV4,
23067 window: &mut Window,
23068 cx: &mut Context<Self>,
23069 ) {
23070 self.insert_uuid(UuidVersion::V4, window, cx);
23071 }
23072
23073 pub fn insert_uuid_v7(
23074 &mut self,
23075 _: &InsertUuidV7,
23076 window: &mut Window,
23077 cx: &mut Context<Self>,
23078 ) {
23079 self.insert_uuid(UuidVersion::V7, window, cx);
23080 }
23081
23082 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23084 self.transact(window, cx, |this, window, cx| {
23085 let edits = this
23086 .selections
23087 .all::<Point>(&this.display_snapshot(cx))
23088 .into_iter()
23089 .map(|selection| {
23090 let uuid = match version {
23091 UuidVersion::V4 => uuid::Uuid::new_v4(),
23092 UuidVersion::V7 => uuid::Uuid::now_v7(),
23093 };
23094
23095 (selection.range(), uuid.to_string())
23096 });
23097 this.edit(edits, cx);
23098 this.refresh_edit_prediction(true, false, window, cx);
23099 });
23100 }
23101
23102 pub fn open_selections_in_multibuffer(
23103 &mut self,
23104 _: &OpenSelectionsInMultibuffer,
23105 window: &mut Window,
23106 cx: &mut Context<Self>,
23107 ) {
23108 let multibuffer = self.buffer.read(cx);
23109
23110 let Some(buffer) = multibuffer.as_singleton() else {
23111 return;
23112 };
23113
23114 let Some(workspace) = self.workspace() else {
23115 return;
23116 };
23117
23118 let title = multibuffer.title(cx).to_string();
23119
23120 let locations = self
23121 .selections
23122 .all_anchors(&self.display_snapshot(cx))
23123 .iter()
23124 .map(|selection| {
23125 (
23126 buffer.clone(),
23127 (selection.start.text_anchor..selection.end.text_anchor)
23128 .to_point(buffer.read(cx)),
23129 )
23130 })
23131 .into_group_map();
23132
23133 cx.spawn_in(window, async move |_, cx| {
23134 workspace.update_in(cx, |workspace, window, cx| {
23135 Self::open_locations_in_multibuffer(
23136 workspace,
23137 locations,
23138 format!("Selections for '{title}'"),
23139 false,
23140 false,
23141 MultibufferSelectionMode::All,
23142 window,
23143 cx,
23144 );
23145 })
23146 })
23147 .detach();
23148 }
23149
23150 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23151 /// last highlight added will be used.
23152 ///
23153 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23154 pub fn highlight_rows<T: 'static>(
23155 &mut self,
23156 range: Range<Anchor>,
23157 color: Hsla,
23158 options: RowHighlightOptions,
23159 cx: &mut Context<Self>,
23160 ) {
23161 let snapshot = self.buffer().read(cx).snapshot(cx);
23162 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23163 let ix = row_highlights.binary_search_by(|highlight| {
23164 Ordering::Equal
23165 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23166 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23167 });
23168
23169 if let Err(mut ix) = ix {
23170 let index = post_inc(&mut self.highlight_order);
23171
23172 // If this range intersects with the preceding highlight, then merge it with
23173 // the preceding highlight. Otherwise insert a new highlight.
23174 let mut merged = false;
23175 if ix > 0 {
23176 let prev_highlight = &mut row_highlights[ix - 1];
23177 if prev_highlight
23178 .range
23179 .end
23180 .cmp(&range.start, &snapshot)
23181 .is_ge()
23182 {
23183 ix -= 1;
23184 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23185 prev_highlight.range.end = range.end;
23186 }
23187 merged = true;
23188 prev_highlight.index = index;
23189 prev_highlight.color = color;
23190 prev_highlight.options = options;
23191 }
23192 }
23193
23194 if !merged {
23195 row_highlights.insert(
23196 ix,
23197 RowHighlight {
23198 range,
23199 index,
23200 color,
23201 options,
23202 type_id: TypeId::of::<T>(),
23203 },
23204 );
23205 }
23206
23207 // If any of the following highlights intersect with this one, merge them.
23208 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23209 let highlight = &row_highlights[ix];
23210 if next_highlight
23211 .range
23212 .start
23213 .cmp(&highlight.range.end, &snapshot)
23214 .is_le()
23215 {
23216 if next_highlight
23217 .range
23218 .end
23219 .cmp(&highlight.range.end, &snapshot)
23220 .is_gt()
23221 {
23222 row_highlights[ix].range.end = next_highlight.range.end;
23223 }
23224 row_highlights.remove(ix + 1);
23225 } else {
23226 break;
23227 }
23228 }
23229 }
23230 }
23231
23232 /// Remove any highlighted row ranges of the given type that intersect the
23233 /// given ranges.
23234 pub fn remove_highlighted_rows<T: 'static>(
23235 &mut self,
23236 ranges_to_remove: Vec<Range<Anchor>>,
23237 cx: &mut Context<Self>,
23238 ) {
23239 let snapshot = self.buffer().read(cx).snapshot(cx);
23240 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23241 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23242 row_highlights.retain(|highlight| {
23243 while let Some(range_to_remove) = ranges_to_remove.peek() {
23244 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23245 Ordering::Less | Ordering::Equal => {
23246 ranges_to_remove.next();
23247 }
23248 Ordering::Greater => {
23249 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23250 Ordering::Less | Ordering::Equal => {
23251 return false;
23252 }
23253 Ordering::Greater => break,
23254 }
23255 }
23256 }
23257 }
23258
23259 true
23260 })
23261 }
23262
23263 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23264 pub fn clear_row_highlights<T: 'static>(&mut self) {
23265 self.highlighted_rows.remove(&TypeId::of::<T>());
23266 }
23267
23268 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23269 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23270 self.highlighted_rows
23271 .get(&TypeId::of::<T>())
23272 .map_or(&[] as &[_], |vec| vec.as_slice())
23273 .iter()
23274 .map(|highlight| (highlight.range.clone(), highlight.color))
23275 }
23276
23277 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23278 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23279 /// Allows to ignore certain kinds of highlights.
23280 pub fn highlighted_display_rows(
23281 &self,
23282 window: &mut Window,
23283 cx: &mut App,
23284 ) -> BTreeMap<DisplayRow, LineHighlight> {
23285 let snapshot = self.snapshot(window, cx);
23286 let mut used_highlight_orders = HashMap::default();
23287 self.highlighted_rows
23288 .iter()
23289 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23290 .fold(
23291 BTreeMap::<DisplayRow, LineHighlight>::new(),
23292 |mut unique_rows, highlight| {
23293 let start = highlight.range.start.to_display_point(&snapshot);
23294 let end = highlight.range.end.to_display_point(&snapshot);
23295 let start_row = start.row().0;
23296 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23297 {
23298 end.row().0.saturating_sub(1)
23299 } else {
23300 end.row().0
23301 };
23302 for row in start_row..=end_row {
23303 let used_index =
23304 used_highlight_orders.entry(row).or_insert(highlight.index);
23305 if highlight.index >= *used_index {
23306 *used_index = highlight.index;
23307 unique_rows.insert(
23308 DisplayRow(row),
23309 LineHighlight {
23310 include_gutter: highlight.options.include_gutter,
23311 border: None,
23312 background: highlight.color.into(),
23313 type_id: Some(highlight.type_id),
23314 },
23315 );
23316 }
23317 }
23318 unique_rows
23319 },
23320 )
23321 }
23322
23323 pub fn highlighted_display_row_for_autoscroll(
23324 &self,
23325 snapshot: &DisplaySnapshot,
23326 ) -> Option<DisplayRow> {
23327 self.highlighted_rows
23328 .values()
23329 .flat_map(|highlighted_rows| highlighted_rows.iter())
23330 .filter_map(|highlight| {
23331 if highlight.options.autoscroll {
23332 Some(highlight.range.start.to_display_point(snapshot).row())
23333 } else {
23334 None
23335 }
23336 })
23337 .min()
23338 }
23339
23340 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23341 self.highlight_background(
23342 HighlightKey::SearchWithinRange,
23343 ranges,
23344 |_, colors| colors.colors().editor_document_highlight_read_background,
23345 cx,
23346 )
23347 }
23348
23349 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23350 self.breadcrumb_header = Some(new_header);
23351 }
23352
23353 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23354 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23355 }
23356
23357 pub fn highlight_background(
23358 &mut self,
23359 key: HighlightKey,
23360 ranges: &[Range<Anchor>],
23361 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23362 cx: &mut Context<Self>,
23363 ) {
23364 self.background_highlights
23365 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23366 self.scrollbar_marker_state.dirty = true;
23367 cx.notify();
23368 }
23369
23370 pub fn highlight_background_key(
23371 &mut self,
23372 key: HighlightKey,
23373 ranges: &[Range<Anchor>],
23374 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23375 cx: &mut Context<Self>,
23376 ) {
23377 self.background_highlights
23378 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23379 self.scrollbar_marker_state.dirty = true;
23380 cx.notify();
23381 }
23382
23383 pub fn clear_background_highlights(
23384 &mut self,
23385 key: HighlightKey,
23386 cx: &mut Context<Self>,
23387 ) -> Option<BackgroundHighlight> {
23388 let text_highlights = self.background_highlights.remove(&key)?;
23389 if !text_highlights.1.is_empty() {
23390 self.scrollbar_marker_state.dirty = true;
23391 cx.notify();
23392 }
23393 Some(text_highlights)
23394 }
23395
23396 pub fn highlight_gutter<T: 'static>(
23397 &mut self,
23398 ranges: impl Into<Vec<Range<Anchor>>>,
23399 color_fetcher: fn(&App) -> Hsla,
23400 cx: &mut Context<Self>,
23401 ) {
23402 self.gutter_highlights
23403 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23404 cx.notify();
23405 }
23406
23407 pub fn clear_gutter_highlights<T: 'static>(
23408 &mut self,
23409 cx: &mut Context<Self>,
23410 ) -> Option<GutterHighlight> {
23411 cx.notify();
23412 self.gutter_highlights.remove(&TypeId::of::<T>())
23413 }
23414
23415 pub fn insert_gutter_highlight<T: 'static>(
23416 &mut self,
23417 range: Range<Anchor>,
23418 color_fetcher: fn(&App) -> Hsla,
23419 cx: &mut Context<Self>,
23420 ) {
23421 let snapshot = self.buffer().read(cx).snapshot(cx);
23422 let mut highlights = self
23423 .gutter_highlights
23424 .remove(&TypeId::of::<T>())
23425 .map(|(_, highlights)| highlights)
23426 .unwrap_or_default();
23427 let ix = highlights.binary_search_by(|highlight| {
23428 Ordering::Equal
23429 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23430 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23431 });
23432 if let Err(ix) = ix {
23433 highlights.insert(ix, range);
23434 }
23435 self.gutter_highlights
23436 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23437 }
23438
23439 pub fn remove_gutter_highlights<T: 'static>(
23440 &mut self,
23441 ranges_to_remove: Vec<Range<Anchor>>,
23442 cx: &mut Context<Self>,
23443 ) {
23444 let snapshot = self.buffer().read(cx).snapshot(cx);
23445 let Some((color_fetcher, mut gutter_highlights)) =
23446 self.gutter_highlights.remove(&TypeId::of::<T>())
23447 else {
23448 return;
23449 };
23450 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23451 gutter_highlights.retain(|highlight| {
23452 while let Some(range_to_remove) = ranges_to_remove.peek() {
23453 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23454 Ordering::Less | Ordering::Equal => {
23455 ranges_to_remove.next();
23456 }
23457 Ordering::Greater => {
23458 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23459 Ordering::Less | Ordering::Equal => {
23460 return false;
23461 }
23462 Ordering::Greater => break,
23463 }
23464 }
23465 }
23466 }
23467
23468 true
23469 });
23470 self.gutter_highlights
23471 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23472 }
23473
23474 #[cfg(feature = "test-support")]
23475 pub fn all_text_highlights(
23476 &self,
23477 window: &mut Window,
23478 cx: &mut Context<Self>,
23479 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23480 let snapshot = self.snapshot(window, cx);
23481 self.display_map.update(cx, |display_map, _| {
23482 display_map
23483 .all_text_highlights()
23484 .map(|(_, highlight)| {
23485 let (style, ranges) = highlight.as_ref();
23486 (
23487 *style,
23488 ranges
23489 .iter()
23490 .map(|range| range.clone().to_display_points(&snapshot))
23491 .collect(),
23492 )
23493 })
23494 .collect()
23495 })
23496 }
23497
23498 #[cfg(feature = "test-support")]
23499 pub fn all_text_background_highlights(
23500 &self,
23501 window: &mut Window,
23502 cx: &mut Context<Self>,
23503 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23504 let snapshot = self.snapshot(window, cx);
23505 let buffer = &snapshot.buffer_snapshot();
23506 let start = buffer.anchor_before(MultiBufferOffset(0));
23507 let end = buffer.anchor_after(buffer.len());
23508 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23509 }
23510
23511 #[cfg(any(test, feature = "test-support"))]
23512 pub fn sorted_background_highlights_in_range(
23513 &self,
23514 search_range: Range<Anchor>,
23515 display_snapshot: &DisplaySnapshot,
23516 theme: &Theme,
23517 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23518 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23519 res.sort_by(|a, b| {
23520 a.0.start
23521 .cmp(&b.0.start)
23522 .then_with(|| a.0.end.cmp(&b.0.end))
23523 .then_with(|| a.1.cmp(&b.1))
23524 });
23525 res
23526 }
23527
23528 #[cfg(feature = "test-support")]
23529 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23530 let snapshot = self.buffer().read(cx).snapshot(cx);
23531
23532 let highlights = self
23533 .background_highlights
23534 .get(&HighlightKey::BufferSearchHighlights);
23535
23536 if let Some((_color, ranges)) = highlights {
23537 ranges
23538 .iter()
23539 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23540 .collect_vec()
23541 } else {
23542 vec![]
23543 }
23544 }
23545
23546 fn document_highlights_for_position<'a>(
23547 &'a self,
23548 position: Anchor,
23549 buffer: &'a MultiBufferSnapshot,
23550 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23551 let read_highlights = self
23552 .background_highlights
23553 .get(&HighlightKey::DocumentHighlightRead)
23554 .map(|h| &h.1);
23555 let write_highlights = self
23556 .background_highlights
23557 .get(&HighlightKey::DocumentHighlightWrite)
23558 .map(|h| &h.1);
23559 let left_position = position.bias_left(buffer);
23560 let right_position = position.bias_right(buffer);
23561 read_highlights
23562 .into_iter()
23563 .chain(write_highlights)
23564 .flat_map(move |ranges| {
23565 let start_ix = match ranges.binary_search_by(|probe| {
23566 let cmp = probe.end.cmp(&left_position, buffer);
23567 if cmp.is_ge() {
23568 Ordering::Greater
23569 } else {
23570 Ordering::Less
23571 }
23572 }) {
23573 Ok(i) | Err(i) => i,
23574 };
23575
23576 ranges[start_ix..]
23577 .iter()
23578 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23579 })
23580 }
23581
23582 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23583 self.background_highlights
23584 .get(&key)
23585 .is_some_and(|(_, highlights)| !highlights.is_empty())
23586 }
23587
23588 /// Returns all background highlights for a given range.
23589 ///
23590 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23591 pub fn background_highlights_in_range(
23592 &self,
23593 search_range: Range<Anchor>,
23594 display_snapshot: &DisplaySnapshot,
23595 theme: &Theme,
23596 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23597 let mut results = Vec::new();
23598 for (color_fetcher, ranges) in self.background_highlights.values() {
23599 let start_ix = match ranges.binary_search_by(|probe| {
23600 let cmp = probe
23601 .end
23602 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23603 if cmp.is_gt() {
23604 Ordering::Greater
23605 } else {
23606 Ordering::Less
23607 }
23608 }) {
23609 Ok(i) | Err(i) => i,
23610 };
23611 for (index, range) in ranges[start_ix..].iter().enumerate() {
23612 if range
23613 .start
23614 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23615 .is_ge()
23616 {
23617 break;
23618 }
23619
23620 let color = color_fetcher(&(start_ix + index), theme);
23621 let start = range.start.to_display_point(display_snapshot);
23622 let end = range.end.to_display_point(display_snapshot);
23623 results.push((start..end, color))
23624 }
23625 }
23626 results
23627 }
23628
23629 pub fn gutter_highlights_in_range(
23630 &self,
23631 search_range: Range<Anchor>,
23632 display_snapshot: &DisplaySnapshot,
23633 cx: &App,
23634 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23635 let mut results = Vec::new();
23636 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23637 let color = color_fetcher(cx);
23638 let start_ix = match ranges.binary_search_by(|probe| {
23639 let cmp = probe
23640 .end
23641 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23642 if cmp.is_gt() {
23643 Ordering::Greater
23644 } else {
23645 Ordering::Less
23646 }
23647 }) {
23648 Ok(i) | Err(i) => i,
23649 };
23650 for range in &ranges[start_ix..] {
23651 if range
23652 .start
23653 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23654 .is_ge()
23655 {
23656 break;
23657 }
23658
23659 let start = range.start.to_display_point(display_snapshot);
23660 let end = range.end.to_display_point(display_snapshot);
23661 results.push((start..end, color))
23662 }
23663 }
23664 results
23665 }
23666
23667 /// Get the text ranges corresponding to the redaction query
23668 pub fn redacted_ranges(
23669 &self,
23670 search_range: Range<Anchor>,
23671 display_snapshot: &DisplaySnapshot,
23672 cx: &App,
23673 ) -> Vec<Range<DisplayPoint>> {
23674 display_snapshot
23675 .buffer_snapshot()
23676 .redacted_ranges(search_range, |file| {
23677 if let Some(file) = file {
23678 file.is_private()
23679 && EditorSettings::get(
23680 Some(SettingsLocation {
23681 worktree_id: file.worktree_id(cx),
23682 path: file.path().as_ref(),
23683 }),
23684 cx,
23685 )
23686 .redact_private_values
23687 } else {
23688 false
23689 }
23690 })
23691 .map(|range| {
23692 range.start.to_display_point(display_snapshot)
23693 ..range.end.to_display_point(display_snapshot)
23694 })
23695 .collect()
23696 }
23697
23698 pub fn highlight_text_key(
23699 &mut self,
23700 key: HighlightKey,
23701 ranges: Vec<Range<Anchor>>,
23702 style: HighlightStyle,
23703 merge: bool,
23704 cx: &mut Context<Self>,
23705 ) {
23706 self.display_map.update(cx, |map, cx| {
23707 map.highlight_text(key, ranges, style, merge, cx);
23708 });
23709 cx.notify();
23710 }
23711
23712 pub fn highlight_text(
23713 &mut self,
23714 key: HighlightKey,
23715 ranges: Vec<Range<Anchor>>,
23716 style: HighlightStyle,
23717 cx: &mut Context<Self>,
23718 ) {
23719 self.display_map.update(cx, |map, cx| {
23720 map.highlight_text(key, ranges, style, false, cx)
23721 });
23722 cx.notify();
23723 }
23724
23725 pub fn text_highlights<'a>(
23726 &'a self,
23727 key: HighlightKey,
23728 cx: &'a App,
23729 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23730 self.display_map.read(cx).text_highlights(key)
23731 }
23732
23733 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23734 let cleared = self
23735 .display_map
23736 .update(cx, |map, _| map.clear_highlights(key));
23737 if cleared {
23738 cx.notify();
23739 }
23740 }
23741
23742 pub fn clear_highlights_with(
23743 &mut self,
23744 f: impl FnMut(&HighlightKey) -> bool,
23745 cx: &mut Context<Self>,
23746 ) {
23747 let cleared = self
23748 .display_map
23749 .update(cx, |map, _| map.clear_highlights_with(f));
23750 if cleared {
23751 cx.notify();
23752 }
23753 }
23754
23755 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23756 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23757 && self.focus_handle.is_focused(window)
23758 }
23759
23760 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23761 self.show_cursor_when_unfocused = is_enabled;
23762 cx.notify();
23763 }
23764
23765 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23766 cx.notify();
23767 }
23768
23769 fn on_debug_session_event(
23770 &mut self,
23771 _session: Entity<Session>,
23772 event: &SessionEvent,
23773 cx: &mut Context<Self>,
23774 ) {
23775 if let SessionEvent::InvalidateInlineValue = event {
23776 self.refresh_inline_values(cx);
23777 }
23778 }
23779
23780 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23781 let Some(project) = self.project.clone() else {
23782 return;
23783 };
23784
23785 if !self.inline_value_cache.enabled {
23786 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23787 self.splice_inlays(&inlays, Vec::new(), cx);
23788 return;
23789 }
23790
23791 let current_execution_position = self
23792 .highlighted_rows
23793 .get(&TypeId::of::<ActiveDebugLine>())
23794 .and_then(|lines| lines.last().map(|line| line.range.end));
23795
23796 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23797 let inline_values = editor
23798 .update(cx, |editor, cx| {
23799 let Some(current_execution_position) = current_execution_position else {
23800 return Some(Task::ready(Ok(Vec::new())));
23801 };
23802
23803 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23804 let snapshot = buffer.snapshot(cx);
23805
23806 let excerpt = snapshot.excerpt_containing(
23807 current_execution_position..current_execution_position,
23808 )?;
23809
23810 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23811 })?;
23812
23813 let range =
23814 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23815
23816 project.inline_values(buffer, range, cx)
23817 })
23818 .ok()
23819 .flatten()?
23820 .await
23821 .context("refreshing debugger inlays")
23822 .log_err()?;
23823
23824 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23825
23826 for (buffer_id, inline_value) in inline_values
23827 .into_iter()
23828 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23829 {
23830 buffer_inline_values
23831 .entry(buffer_id)
23832 .or_default()
23833 .push(inline_value);
23834 }
23835
23836 editor
23837 .update(cx, |editor, cx| {
23838 let snapshot = editor.buffer.read(cx).snapshot(cx);
23839 let mut new_inlays = Vec::default();
23840
23841 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23842 let buffer_id = buffer_snapshot.remote_id();
23843 buffer_inline_values
23844 .get(&buffer_id)
23845 .into_iter()
23846 .flatten()
23847 .for_each(|hint| {
23848 let inlay = Inlay::debugger(
23849 post_inc(&mut editor.next_inlay_id),
23850 Anchor::in_buffer(excerpt_id, hint.position),
23851 hint.text(),
23852 );
23853 if !inlay.text().chars().contains(&'\n') {
23854 new_inlays.push(inlay);
23855 }
23856 });
23857 }
23858
23859 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23860 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23861
23862 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23863 })
23864 .ok()?;
23865 Some(())
23866 });
23867 }
23868
23869 fn on_buffer_event(
23870 &mut self,
23871 multibuffer: &Entity<MultiBuffer>,
23872 event: &multi_buffer::Event,
23873 window: &mut Window,
23874 cx: &mut Context<Self>,
23875 ) {
23876 match event {
23877 multi_buffer::Event::Edited { edited_buffer } => {
23878 self.scrollbar_marker_state.dirty = true;
23879 self.active_indent_guides_state.dirty = true;
23880 self.refresh_active_diagnostics(cx);
23881 self.refresh_code_actions(window, cx);
23882 self.refresh_single_line_folds(window, cx);
23883 let snapshot = self.snapshot(window, cx);
23884 self.refresh_matching_bracket_highlights(&snapshot, cx);
23885 self.refresh_outline_symbols(cx);
23886 self.refresh_sticky_headers(&snapshot, cx);
23887 if self.has_active_edit_prediction() {
23888 self.update_visible_edit_prediction(window, cx);
23889 }
23890
23891 // Clean up orphaned review comments after edits
23892 self.cleanup_orphaned_review_comments(cx);
23893
23894 if let Some(buffer) = edited_buffer {
23895 if buffer.read(cx).file().is_none() {
23896 cx.emit(EditorEvent::TitleChanged);
23897 }
23898
23899 if self.project.is_some() {
23900 let buffer_id = buffer.read(cx).remote_id();
23901 self.register_buffer(buffer_id, cx);
23902 self.update_lsp_data(Some(buffer_id), window, cx);
23903 self.refresh_inlay_hints(
23904 InlayHintRefreshReason::BufferEdited(buffer_id),
23905 cx,
23906 );
23907 }
23908 }
23909
23910 cx.emit(EditorEvent::BufferEdited);
23911 cx.emit(SearchEvent::MatchesInvalidated);
23912
23913 let Some(project) = &self.project else { return };
23914 let (telemetry, is_via_ssh) = {
23915 let project = project.read(cx);
23916 let telemetry = project.client().telemetry().clone();
23917 let is_via_ssh = project.is_via_remote_server();
23918 (telemetry, is_via_ssh)
23919 };
23920 telemetry.log_edit_event("editor", is_via_ssh);
23921 }
23922 multi_buffer::Event::ExcerptsAdded {
23923 buffer,
23924 predecessor,
23925 excerpts,
23926 } => {
23927 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23928 let buffer_id = buffer.read(cx).remote_id();
23929 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23930 && let Some(project) = &self.project
23931 {
23932 update_uncommitted_diff_for_buffer(
23933 cx.entity(),
23934 project,
23935 [buffer.clone()],
23936 self.buffer.clone(),
23937 cx,
23938 )
23939 .detach();
23940 }
23941 self.semantic_token_state
23942 .invalidate_buffer(&buffer.read(cx).remote_id());
23943 self.update_lsp_data(Some(buffer_id), window, cx);
23944 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23945 self.colorize_brackets(false, cx);
23946 self.refresh_selected_text_highlights(true, window, cx);
23947 cx.emit(EditorEvent::ExcerptsAdded {
23948 buffer: buffer.clone(),
23949 predecessor: *predecessor,
23950 excerpts: excerpts.clone(),
23951 });
23952 }
23953 multi_buffer::Event::ExcerptsRemoved {
23954 ids,
23955 removed_buffer_ids,
23956 } => {
23957 if let Some(inlay_hints) = &mut self.inlay_hints {
23958 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23959 }
23960 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23961 for buffer_id in removed_buffer_ids {
23962 self.registered_buffers.remove(buffer_id);
23963 self.tasks
23964 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
23965 self.semantic_token_state.invalidate_buffer(buffer_id);
23966 self.display_map.update(cx, |display_map, cx| {
23967 display_map.invalidate_semantic_highlights(*buffer_id);
23968 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
23969 });
23970 }
23971 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23972 cx.emit(EditorEvent::ExcerptsRemoved {
23973 ids: ids.clone(),
23974 removed_buffer_ids: removed_buffer_ids.clone(),
23975 });
23976 }
23977 multi_buffer::Event::ExcerptsEdited {
23978 excerpt_ids,
23979 buffer_ids,
23980 } => {
23981 self.display_map.update(cx, |map, cx| {
23982 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23983 });
23984 cx.emit(EditorEvent::ExcerptsEdited {
23985 ids: excerpt_ids.clone(),
23986 });
23987 }
23988 multi_buffer::Event::ExcerptsExpanded { ids } => {
23989 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23990 self.refresh_document_highlights(cx);
23991 let snapshot = multibuffer.read(cx).snapshot(cx);
23992 for id in ids {
23993 self.fetched_tree_sitter_chunks.remove(id);
23994 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
23995 self.semantic_token_state
23996 .invalidate_buffer(&buffer.remote_id());
23997 }
23998 }
23999 self.colorize_brackets(false, cx);
24000 self.update_lsp_data(None, window, cx);
24001 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24002 }
24003 multi_buffer::Event::Reparsed(buffer_id) => {
24004 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24005 self.refresh_selected_text_highlights(true, window, cx);
24006 self.colorize_brackets(true, cx);
24007 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24008
24009 cx.emit(EditorEvent::Reparsed(*buffer_id));
24010 }
24011 multi_buffer::Event::DiffHunksToggled => {
24012 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24013 }
24014 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24015 if !is_fresh_language {
24016 self.registered_buffers.remove(&buffer_id);
24017 }
24018 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24019 cx.emit(EditorEvent::Reparsed(*buffer_id));
24020 cx.notify();
24021 }
24022 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24023 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24024 multi_buffer::Event::FileHandleChanged
24025 | multi_buffer::Event::Reloaded
24026 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24027 multi_buffer::Event::DiagnosticsUpdated => {
24028 self.update_diagnostics_state(window, cx);
24029 }
24030 _ => {}
24031 };
24032 }
24033
24034 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24035 if !self.diagnostics_enabled() {
24036 return;
24037 }
24038 self.refresh_active_diagnostics(cx);
24039 self.refresh_inline_diagnostics(true, window, cx);
24040 self.scrollbar_marker_state.dirty = true;
24041 cx.notify();
24042 }
24043
24044 pub fn start_temporary_diff_override(&mut self) {
24045 self.load_diff_task.take();
24046 self.temporary_diff_override = true;
24047 }
24048
24049 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24050 self.temporary_diff_override = false;
24051 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24052 self.buffer.update(cx, |buffer, cx| {
24053 buffer.set_all_diff_hunks_collapsed(cx);
24054 });
24055
24056 if let Some(project) = self.project.clone() {
24057 self.load_diff_task = Some(
24058 update_uncommitted_diff_for_buffer(
24059 cx.entity(),
24060 &project,
24061 self.buffer.read(cx).all_buffers(),
24062 self.buffer.clone(),
24063 cx,
24064 )
24065 .shared(),
24066 );
24067 }
24068 }
24069
24070 fn on_display_map_changed(
24071 &mut self,
24072 _: Entity<DisplayMap>,
24073 _: &mut Window,
24074 cx: &mut Context<Self>,
24075 ) {
24076 cx.notify();
24077 }
24078
24079 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24080 if !self.mode.is_full() {
24081 return None;
24082 }
24083
24084 let theme_settings = theme::ThemeSettings::get_global(cx);
24085 let theme = cx.theme();
24086 let accent_colors = theme.accents().clone();
24087
24088 let accent_overrides = theme_settings
24089 .theme_overrides
24090 .get(theme.name.as_ref())
24091 .map(|theme_style| &theme_style.accents)
24092 .into_iter()
24093 .flatten()
24094 .chain(
24095 theme_settings
24096 .experimental_theme_overrides
24097 .as_ref()
24098 .map(|overrides| &overrides.accents)
24099 .into_iter()
24100 .flatten(),
24101 )
24102 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24103 .collect();
24104
24105 Some(AccentData {
24106 colors: accent_colors,
24107 overrides: accent_overrides,
24108 })
24109 }
24110
24111 fn fetch_applicable_language_settings(
24112 &self,
24113 cx: &App,
24114 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24115 if !self.mode.is_full() {
24116 return HashMap::default();
24117 }
24118
24119 self.buffer().read(cx).all_buffers().into_iter().fold(
24120 HashMap::default(),
24121 |mut acc, buffer| {
24122 let buffer = buffer.read(cx);
24123 let language = buffer.language().map(|language| language.name());
24124 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24125 let file = buffer.file();
24126 v.insert(language_settings(language, file, cx).into_owned());
24127 }
24128 acc
24129 },
24130 )
24131 }
24132
24133 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24134 let new_language_settings = self.fetch_applicable_language_settings(cx);
24135 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24136 self.applicable_language_settings = new_language_settings;
24137
24138 let new_accents = self.fetch_accent_data(cx);
24139 let accents_changed = new_accents != self.accent_data;
24140 self.accent_data = new_accents;
24141
24142 if self.diagnostics_enabled() {
24143 let new_severity = EditorSettings::get_global(cx)
24144 .diagnostics_max_severity
24145 .unwrap_or(DiagnosticSeverity::Hint);
24146 self.set_max_diagnostics_severity(new_severity, cx);
24147 }
24148 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24149 self.update_edit_prediction_settings(cx);
24150 self.refresh_edit_prediction(true, false, window, cx);
24151 self.refresh_inline_values(cx);
24152
24153 let old_cursor_shape = self.cursor_shape;
24154 let old_show_breadcrumbs = self.show_breadcrumbs;
24155
24156 {
24157 let editor_settings = EditorSettings::get_global(cx);
24158 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24159 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24160 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24161 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24162 }
24163
24164 if old_cursor_shape != self.cursor_shape {
24165 cx.emit(EditorEvent::CursorShapeChanged);
24166 }
24167
24168 if old_show_breadcrumbs != self.show_breadcrumbs {
24169 cx.emit(EditorEvent::BreadcrumbsChanged);
24170 }
24171
24172 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24173 let project_settings = ProjectSettings::get_global(cx);
24174 (
24175 project_settings.session.restore_unsaved_buffers,
24176 project_settings.diagnostics.inline.enabled,
24177 project_settings.git.inline_blame.enabled,
24178 )
24179 };
24180 self.buffer_serialization = self
24181 .should_serialize_buffer()
24182 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24183
24184 if self.mode.is_full() {
24185 if self.show_inline_diagnostics != show_inline_diagnostics {
24186 self.show_inline_diagnostics = show_inline_diagnostics;
24187 self.refresh_inline_diagnostics(false, window, cx);
24188 }
24189
24190 if self.git_blame_inline_enabled != inline_blame_enabled {
24191 self.toggle_git_blame_inline_internal(false, window, cx);
24192 }
24193
24194 let minimap_settings = EditorSettings::get_global(cx).minimap;
24195 if self.minimap_visibility != MinimapVisibility::Disabled {
24196 if self.minimap_visibility.settings_visibility()
24197 != minimap_settings.minimap_enabled()
24198 {
24199 self.set_minimap_visibility(
24200 MinimapVisibility::for_mode(self.mode(), cx),
24201 window,
24202 cx,
24203 );
24204 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24205 minimap_entity.update(cx, |minimap_editor, cx| {
24206 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24207 })
24208 }
24209 }
24210
24211 if language_settings_changed || accents_changed {
24212 self.colorize_brackets(true, cx);
24213 }
24214
24215 if language_settings_changed {
24216 self.clear_disabled_lsp_folding_ranges(window, cx);
24217 }
24218
24219 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24220 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24221 }) {
24222 if !inlay_splice.is_empty() {
24223 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24224 }
24225 self.refresh_colors_for_visible_range(None, window, cx);
24226 }
24227
24228 self.refresh_inlay_hints(
24229 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24230 self.selections.newest_anchor().head(),
24231 &self.buffer.read(cx).snapshot(cx),
24232 cx,
24233 )),
24234 cx,
24235 );
24236
24237 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24238 .global_lsp_settings
24239 .semantic_token_rules
24240 .clone();
24241 let semantic_token_rules_changed = self
24242 .semantic_token_state
24243 .update_rules(new_semantic_token_rules);
24244 if language_settings_changed || semantic_token_rules_changed {
24245 self.refresh_semantic_token_highlights(cx);
24246 }
24247 }
24248
24249 cx.notify();
24250 }
24251
24252 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24253 if !self.mode.is_full() {
24254 return;
24255 }
24256
24257 let new_accents = self.fetch_accent_data(cx);
24258 if new_accents != self.accent_data {
24259 self.accent_data = new_accents;
24260 self.colorize_brackets(true, cx);
24261 }
24262
24263 self.refresh_semantic_token_highlights(cx);
24264 }
24265
24266 pub fn set_searchable(&mut self, searchable: bool) {
24267 self.searchable = searchable;
24268 }
24269
24270 pub fn searchable(&self) -> bool {
24271 self.searchable
24272 }
24273
24274 pub fn open_excerpts_in_split(
24275 &mut self,
24276 _: &OpenExcerptsSplit,
24277 window: &mut Window,
24278 cx: &mut Context<Self>,
24279 ) {
24280 self.open_excerpts_common(None, true, window, cx)
24281 }
24282
24283 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24284 self.open_excerpts_common(None, false, window, cx)
24285 }
24286
24287 pub(crate) fn open_excerpts_common(
24288 &mut self,
24289 jump_data: Option<JumpData>,
24290 split: bool,
24291 window: &mut Window,
24292 cx: &mut Context<Self>,
24293 ) {
24294 if self.buffer.read(cx).is_singleton() {
24295 cx.propagate();
24296 return;
24297 }
24298
24299 let mut new_selections_by_buffer = HashMap::default();
24300 match &jump_data {
24301 Some(JumpData::MultiBufferPoint {
24302 excerpt_id,
24303 position,
24304 anchor,
24305 line_offset_from_top,
24306 }) => {
24307 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24308 if let Some(buffer) = multi_buffer_snapshot
24309 .buffer_id_for_excerpt(*excerpt_id)
24310 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24311 {
24312 let buffer_snapshot = buffer.read(cx).snapshot();
24313 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24314 language::ToPoint::to_point(anchor, &buffer_snapshot)
24315 } else {
24316 buffer_snapshot.clip_point(*position, Bias::Left)
24317 };
24318 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24319 new_selections_by_buffer.insert(
24320 buffer,
24321 (
24322 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24323 Some(*line_offset_from_top),
24324 ),
24325 );
24326 }
24327 }
24328 Some(JumpData::MultiBufferRow {
24329 row,
24330 line_offset_from_top,
24331 }) => {
24332 let point = MultiBufferPoint::new(row.0, 0);
24333 if let Some((buffer, buffer_point, _)) =
24334 self.buffer.read(cx).point_to_buffer_point(point, cx)
24335 {
24336 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24337 new_selections_by_buffer
24338 .entry(buffer)
24339 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24340 .0
24341 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24342 }
24343 }
24344 None => {
24345 let selections = self
24346 .selections
24347 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24348 let multi_buffer = self.buffer.read(cx);
24349 for selection in selections {
24350 for (snapshot, range, _, anchor) in multi_buffer
24351 .snapshot(cx)
24352 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24353 {
24354 if let Some(anchor) = anchor {
24355 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24356 else {
24357 continue;
24358 };
24359 let offset = text::ToOffset::to_offset(
24360 &anchor.text_anchor,
24361 &buffer_handle.read(cx).snapshot(),
24362 );
24363 let range = BufferOffset(offset)..BufferOffset(offset);
24364 new_selections_by_buffer
24365 .entry(buffer_handle)
24366 .or_insert((Vec::new(), None))
24367 .0
24368 .push(range)
24369 } else {
24370 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24371 else {
24372 continue;
24373 };
24374 new_selections_by_buffer
24375 .entry(buffer_handle)
24376 .or_insert((Vec::new(), None))
24377 .0
24378 .push(range)
24379 }
24380 }
24381 }
24382 }
24383 }
24384
24385 if self.delegate_open_excerpts {
24386 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24387 .into_iter()
24388 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24389 .collect();
24390 if !selections_by_buffer.is_empty() {
24391 cx.emit(EditorEvent::OpenExcerptsRequested {
24392 selections_by_buffer,
24393 split,
24394 });
24395 }
24396 return;
24397 }
24398
24399 let Some(workspace) = self.workspace() else {
24400 cx.propagate();
24401 return;
24402 };
24403
24404 new_selections_by_buffer
24405 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24406
24407 if new_selections_by_buffer.is_empty() {
24408 return;
24409 }
24410
24411 Self::open_buffers_in_workspace(
24412 workspace.downgrade(),
24413 new_selections_by_buffer,
24414 split,
24415 window,
24416 cx,
24417 );
24418 }
24419
24420 pub(crate) fn open_buffers_in_workspace(
24421 workspace: WeakEntity<Workspace>,
24422 new_selections_by_buffer: HashMap<
24423 Entity<language::Buffer>,
24424 (Vec<Range<BufferOffset>>, Option<u32>),
24425 >,
24426 split: bool,
24427 window: &mut Window,
24428 cx: &mut App,
24429 ) {
24430 // We defer the pane interaction because we ourselves are a workspace item
24431 // and activating a new item causes the pane to call a method on us reentrantly,
24432 // which panics if we're on the stack.
24433 window.defer(cx, move |window, cx| {
24434 workspace
24435 .update(cx, |workspace, cx| {
24436 let pane = if split {
24437 workspace.adjacent_pane(window, cx)
24438 } else {
24439 workspace.active_pane().clone()
24440 };
24441
24442 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24443 let buffer_read = buffer.read(cx);
24444 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24445 (true, project::File::from_dyn(Some(file)).is_some())
24446 } else {
24447 (false, false)
24448 };
24449
24450 // If project file is none workspace.open_project_item will fail to open the excerpt
24451 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24452 // so we check if there's a tab match in that case first
24453 let editor = (!has_file || !is_project_file)
24454 .then(|| {
24455 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24456 // so `workspace.open_project_item` will never find them, always opening a new editor.
24457 // Instead, we try to activate the existing editor in the pane first.
24458 let (editor, pane_item_index, pane_item_id) =
24459 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24460 let editor = item.downcast::<Editor>()?;
24461 let singleton_buffer =
24462 editor.read(cx).buffer().read(cx).as_singleton()?;
24463 if singleton_buffer == buffer {
24464 Some((editor, i, item.item_id()))
24465 } else {
24466 None
24467 }
24468 })?;
24469 pane.update(cx, |pane, cx| {
24470 pane.activate_item(pane_item_index, true, true, window, cx);
24471 if !PreviewTabsSettings::get_global(cx)
24472 .enable_preview_from_multibuffer
24473 {
24474 pane.unpreview_item_if_preview(pane_item_id);
24475 }
24476 });
24477 Some(editor)
24478 })
24479 .flatten()
24480 .unwrap_or_else(|| {
24481 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24482 .enable_keep_preview_on_code_navigation;
24483 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24484 .enable_preview_from_multibuffer;
24485 workspace.open_project_item::<Self>(
24486 pane.clone(),
24487 buffer,
24488 true,
24489 true,
24490 keep_old_preview,
24491 allow_new_preview,
24492 window,
24493 cx,
24494 )
24495 });
24496
24497 editor.update(cx, |editor, cx| {
24498 if has_file && !is_project_file {
24499 editor.set_read_only(true);
24500 }
24501 let autoscroll = match scroll_offset {
24502 Some(scroll_offset) => {
24503 Autoscroll::top_relative(scroll_offset as usize)
24504 }
24505 None => Autoscroll::newest(),
24506 };
24507 let nav_history = editor.nav_history.take();
24508 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24509 let Some((&excerpt_id, _, buffer_snapshot)) =
24510 multibuffer_snapshot.as_singleton()
24511 else {
24512 return;
24513 };
24514 editor.change_selections(
24515 SelectionEffects::scroll(autoscroll),
24516 window,
24517 cx,
24518 |s| {
24519 s.select_ranges(ranges.into_iter().map(|range| {
24520 let range = buffer_snapshot.anchor_before(range.start)
24521 ..buffer_snapshot.anchor_after(range.end);
24522 multibuffer_snapshot
24523 .anchor_range_in_excerpt(excerpt_id, range)
24524 .unwrap()
24525 }));
24526 },
24527 );
24528 editor.nav_history = nav_history;
24529 });
24530 }
24531 })
24532 .ok();
24533 });
24534 }
24535
24536 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24537 let snapshot = self.buffer.read(cx).read(cx);
24538 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24539 Some(
24540 ranges
24541 .iter()
24542 .map(move |range| {
24543 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24544 })
24545 .collect(),
24546 )
24547 }
24548
24549 fn selection_replacement_ranges(
24550 &self,
24551 range: Range<MultiBufferOffsetUtf16>,
24552 cx: &mut App,
24553 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24554 let selections = self
24555 .selections
24556 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24557 let newest_selection = selections
24558 .iter()
24559 .max_by_key(|selection| selection.id)
24560 .unwrap();
24561 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24562 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24563 let snapshot = self.buffer.read(cx).read(cx);
24564 selections
24565 .into_iter()
24566 .map(|mut selection| {
24567 selection.start.0.0 =
24568 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24569 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24570 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24571 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24572 })
24573 .collect()
24574 }
24575
24576 fn report_editor_event(
24577 &self,
24578 reported_event: ReportEditorEvent,
24579 file_extension: Option<String>,
24580 cx: &App,
24581 ) {
24582 if cfg!(any(test, feature = "test-support")) {
24583 return;
24584 }
24585
24586 let Some(project) = &self.project else { return };
24587
24588 // If None, we are in a file without an extension
24589 let file = self
24590 .buffer
24591 .read(cx)
24592 .as_singleton()
24593 .and_then(|b| b.read(cx).file());
24594 let file_extension = file_extension.or(file
24595 .as_ref()
24596 .and_then(|file| Path::new(file.file_name(cx)).extension())
24597 .and_then(|e| e.to_str())
24598 .map(|a| a.to_string()));
24599
24600 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24601 .map(|vim_mode| vim_mode.0)
24602 .unwrap_or(false);
24603
24604 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24605 let copilot_enabled = edit_predictions_provider
24606 == language::language_settings::EditPredictionProvider::Copilot;
24607 let copilot_enabled_for_language = self
24608 .buffer
24609 .read(cx)
24610 .language_settings(cx)
24611 .show_edit_predictions;
24612
24613 let project = project.read(cx);
24614 let event_type = reported_event.event_type();
24615
24616 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24617 telemetry::event!(
24618 event_type,
24619 type = if auto_saved {"autosave"} else {"manual"},
24620 file_extension,
24621 vim_mode,
24622 copilot_enabled,
24623 copilot_enabled_for_language,
24624 edit_predictions_provider,
24625 is_via_ssh = project.is_via_remote_server(),
24626 );
24627 } else {
24628 telemetry::event!(
24629 event_type,
24630 file_extension,
24631 vim_mode,
24632 copilot_enabled,
24633 copilot_enabled_for_language,
24634 edit_predictions_provider,
24635 is_via_ssh = project.is_via_remote_server(),
24636 );
24637 };
24638 }
24639
24640 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24641 /// with each line being an array of {text, highlight} objects.
24642 fn copy_highlight_json(
24643 &mut self,
24644 _: &CopyHighlightJson,
24645 window: &mut Window,
24646 cx: &mut Context<Self>,
24647 ) {
24648 #[derive(Serialize)]
24649 struct Chunk<'a> {
24650 text: String,
24651 highlight: Option<&'a str>,
24652 }
24653
24654 let snapshot = self.buffer.read(cx).snapshot(cx);
24655 let range = self
24656 .selected_text_range(false, window, cx)
24657 .and_then(|selection| {
24658 if selection.range.is_empty() {
24659 None
24660 } else {
24661 Some(
24662 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24663 selection.range.start,
24664 )))
24665 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24666 selection.range.end,
24667 ))),
24668 )
24669 }
24670 })
24671 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24672
24673 let chunks = snapshot.chunks(range, true);
24674 let mut lines = Vec::new();
24675 let mut line: VecDeque<Chunk> = VecDeque::new();
24676
24677 let Some(style) = self.style.as_ref() else {
24678 return;
24679 };
24680
24681 for chunk in chunks {
24682 let highlight = chunk
24683 .syntax_highlight_id
24684 .and_then(|id| id.name(&style.syntax));
24685 let mut chunk_lines = chunk.text.split('\n').peekable();
24686 while let Some(text) = chunk_lines.next() {
24687 let mut merged_with_last_token = false;
24688 if let Some(last_token) = line.back_mut()
24689 && last_token.highlight == highlight
24690 {
24691 last_token.text.push_str(text);
24692 merged_with_last_token = true;
24693 }
24694
24695 if !merged_with_last_token {
24696 line.push_back(Chunk {
24697 text: text.into(),
24698 highlight,
24699 });
24700 }
24701
24702 if chunk_lines.peek().is_some() {
24703 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24704 line.pop_front();
24705 }
24706 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24707 line.pop_back();
24708 }
24709
24710 lines.push(mem::take(&mut line));
24711 }
24712 }
24713 }
24714
24715 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24716 return;
24717 };
24718 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24719 }
24720
24721 pub fn open_context_menu(
24722 &mut self,
24723 _: &OpenContextMenu,
24724 window: &mut Window,
24725 cx: &mut Context<Self>,
24726 ) {
24727 self.request_autoscroll(Autoscroll::newest(), cx);
24728 let position = self
24729 .selections
24730 .newest_display(&self.display_snapshot(cx))
24731 .start;
24732 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24733 }
24734
24735 pub fn replay_insert_event(
24736 &mut self,
24737 text: &str,
24738 relative_utf16_range: Option<Range<isize>>,
24739 window: &mut Window,
24740 cx: &mut Context<Self>,
24741 ) {
24742 if !self.input_enabled {
24743 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24744 return;
24745 }
24746 if let Some(relative_utf16_range) = relative_utf16_range {
24747 let selections = self
24748 .selections
24749 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24750 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24751 let new_ranges = selections.into_iter().map(|range| {
24752 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24753 range
24754 .head()
24755 .0
24756 .0
24757 .saturating_add_signed(relative_utf16_range.start),
24758 ));
24759 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24760 range
24761 .head()
24762 .0
24763 .0
24764 .saturating_add_signed(relative_utf16_range.end),
24765 ));
24766 start..end
24767 });
24768 s.select_ranges(new_ranges);
24769 });
24770 }
24771
24772 self.handle_input(text, window, cx);
24773 }
24774
24775 pub fn is_focused(&self, window: &Window) -> bool {
24776 self.focus_handle.is_focused(window)
24777 }
24778
24779 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24780 cx.emit(EditorEvent::Focused);
24781
24782 if let Some(descendant) = self
24783 .last_focused_descendant
24784 .take()
24785 .and_then(|descendant| descendant.upgrade())
24786 {
24787 window.focus(&descendant, cx);
24788 } else {
24789 if let Some(blame) = self.blame.as_ref() {
24790 blame.update(cx, GitBlame::focus)
24791 }
24792
24793 self.blink_manager.update(cx, BlinkManager::enable);
24794 self.show_cursor_names(window, cx);
24795 self.buffer.update(cx, |buffer, cx| {
24796 buffer.finalize_last_transaction(cx);
24797 if self.leader_id.is_none() {
24798 buffer.set_active_selections(
24799 &self.selections.disjoint_anchors_arc(),
24800 self.selections.line_mode(),
24801 self.cursor_shape,
24802 cx,
24803 );
24804 }
24805 });
24806
24807 if let Some(position_map) = self.last_position_map.clone() {
24808 EditorElement::mouse_moved(
24809 self,
24810 &MouseMoveEvent {
24811 position: window.mouse_position(),
24812 pressed_button: None,
24813 modifiers: window.modifiers(),
24814 },
24815 &position_map,
24816 None,
24817 window,
24818 cx,
24819 );
24820 }
24821 }
24822 }
24823
24824 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24825 cx.emit(EditorEvent::FocusedIn)
24826 }
24827
24828 fn handle_focus_out(
24829 &mut self,
24830 event: FocusOutEvent,
24831 _window: &mut Window,
24832 cx: &mut Context<Self>,
24833 ) {
24834 if event.blurred != self.focus_handle {
24835 self.last_focused_descendant = Some(event.blurred);
24836 }
24837 self.selection_drag_state = SelectionDragState::None;
24838 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24839 }
24840
24841 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24842 self.blink_manager.update(cx, BlinkManager::disable);
24843 self.buffer
24844 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24845
24846 if let Some(blame) = self.blame.as_ref() {
24847 blame.update(cx, GitBlame::blur)
24848 }
24849 if !self.hover_state.focused(window, cx) {
24850 hide_hover(self, cx);
24851 }
24852 if !self
24853 .context_menu
24854 .borrow()
24855 .as_ref()
24856 .is_some_and(|context_menu| context_menu.focused(window, cx))
24857 {
24858 self.hide_context_menu(window, cx);
24859 }
24860 self.take_active_edit_prediction(cx);
24861 cx.emit(EditorEvent::Blurred);
24862 cx.notify();
24863 }
24864
24865 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24866 let mut pending: String = window
24867 .pending_input_keystrokes()
24868 .into_iter()
24869 .flatten()
24870 .filter_map(|keystroke| keystroke.key_char.clone())
24871 .collect();
24872
24873 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24874 pending = "".to_string();
24875 }
24876
24877 let existing_pending = self
24878 .text_highlights(HighlightKey::PendingInput, cx)
24879 .map(|(_, ranges)| ranges.to_vec());
24880 if existing_pending.is_none() && pending.is_empty() {
24881 return;
24882 }
24883 let transaction =
24884 self.transact(window, cx, |this, window, cx| {
24885 let selections = this
24886 .selections
24887 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24888 let edits = selections
24889 .iter()
24890 .map(|selection| (selection.end..selection.end, pending.clone()));
24891 this.edit(edits, cx);
24892 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24893 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24894 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24895 }));
24896 });
24897 if let Some(existing_ranges) = existing_pending {
24898 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24899 this.edit(edits, cx);
24900 }
24901 });
24902
24903 let snapshot = self.snapshot(window, cx);
24904 let ranges = self
24905 .selections
24906 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24907 .into_iter()
24908 .map(|selection| {
24909 snapshot.buffer_snapshot().anchor_after(selection.end)
24910 ..snapshot
24911 .buffer_snapshot()
24912 .anchor_before(selection.end + pending.len())
24913 })
24914 .collect();
24915
24916 if pending.is_empty() {
24917 self.clear_highlights(HighlightKey::PendingInput, cx);
24918 } else {
24919 self.highlight_text(
24920 HighlightKey::PendingInput,
24921 ranges,
24922 HighlightStyle {
24923 underline: Some(UnderlineStyle {
24924 thickness: px(1.),
24925 color: None,
24926 wavy: false,
24927 }),
24928 ..Default::default()
24929 },
24930 cx,
24931 );
24932 }
24933
24934 self.ime_transaction = self.ime_transaction.or(transaction);
24935 if let Some(transaction) = self.ime_transaction {
24936 self.buffer.update(cx, |buffer, cx| {
24937 buffer.group_until_transaction(transaction, cx);
24938 });
24939 }
24940
24941 if self
24942 .text_highlights(HighlightKey::PendingInput, cx)
24943 .is_none()
24944 {
24945 self.ime_transaction.take();
24946 }
24947 }
24948
24949 pub fn register_action_renderer(
24950 &mut self,
24951 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24952 ) -> Subscription {
24953 let id = self.next_editor_action_id.post_inc();
24954 self.editor_actions
24955 .borrow_mut()
24956 .insert(id, Box::new(listener));
24957
24958 let editor_actions = self.editor_actions.clone();
24959 Subscription::new(move || {
24960 editor_actions.borrow_mut().remove(&id);
24961 })
24962 }
24963
24964 pub fn register_action<A: Action>(
24965 &mut self,
24966 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24967 ) -> Subscription {
24968 let id = self.next_editor_action_id.post_inc();
24969 let listener = Arc::new(listener);
24970 self.editor_actions.borrow_mut().insert(
24971 id,
24972 Box::new(move |_, window, _| {
24973 let listener = listener.clone();
24974 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24975 let action = action.downcast_ref().unwrap();
24976 if phase == DispatchPhase::Bubble {
24977 listener(action, window, cx)
24978 }
24979 })
24980 }),
24981 );
24982
24983 let editor_actions = self.editor_actions.clone();
24984 Subscription::new(move || {
24985 editor_actions.borrow_mut().remove(&id);
24986 })
24987 }
24988
24989 pub fn file_header_size(&self) -> u32 {
24990 FILE_HEADER_HEIGHT
24991 }
24992
24993 pub fn restore(
24994 &mut self,
24995 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24996 window: &mut Window,
24997 cx: &mut Context<Self>,
24998 ) {
24999 self.buffer().update(cx, |multi_buffer, cx| {
25000 for (buffer_id, changes) in revert_changes {
25001 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25002 buffer.update(cx, |buffer, cx| {
25003 buffer.edit(
25004 changes
25005 .into_iter()
25006 .map(|(range, text)| (range, text.to_string())),
25007 None,
25008 cx,
25009 );
25010 });
25011 }
25012 }
25013 });
25014 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25015 selections.refresh()
25016 });
25017 }
25018
25019 pub fn to_pixel_point(
25020 &mut self,
25021 source: Anchor,
25022 editor_snapshot: &EditorSnapshot,
25023 window: &mut Window,
25024 cx: &mut App,
25025 ) -> Option<gpui::Point<Pixels>> {
25026 let source_point = source.to_display_point(editor_snapshot);
25027 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25028 }
25029
25030 pub fn display_to_pixel_point(
25031 &mut self,
25032 source: DisplayPoint,
25033 editor_snapshot: &EditorSnapshot,
25034 window: &mut Window,
25035 cx: &mut App,
25036 ) -> Option<gpui::Point<Pixels>> {
25037 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25038 let text_layout_details = self.text_layout_details(window, cx);
25039 let scroll_top = text_layout_details
25040 .scroll_anchor
25041 .scroll_position(editor_snapshot)
25042 .y;
25043
25044 if source.row().as_f64() < scroll_top.floor() {
25045 return None;
25046 }
25047 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25048 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25049 Some(gpui::Point::new(source_x, source_y))
25050 }
25051
25052 pub fn has_visible_completions_menu(&self) -> bool {
25053 !self.edit_prediction_preview_is_active()
25054 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25055 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25056 })
25057 }
25058
25059 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25060 if self.mode.is_minimap() {
25061 return;
25062 }
25063 self.addons
25064 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25065 }
25066
25067 pub fn unregister_addon<T: Addon>(&mut self) {
25068 self.addons.remove(&std::any::TypeId::of::<T>());
25069 }
25070
25071 pub fn addon<T: Addon>(&self) -> Option<&T> {
25072 let type_id = std::any::TypeId::of::<T>();
25073 self.addons
25074 .get(&type_id)
25075 .and_then(|item| item.to_any().downcast_ref::<T>())
25076 }
25077
25078 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25079 let type_id = std::any::TypeId::of::<T>();
25080 self.addons
25081 .get_mut(&type_id)
25082 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25083 }
25084
25085 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25086 let text_layout_details = self.text_layout_details(window, cx);
25087 let style = &text_layout_details.editor_style;
25088 let font_id = window.text_system().resolve_font(&style.text.font());
25089 let font_size = style.text.font_size.to_pixels(window.rem_size());
25090 let line_height = style.text.line_height_in_pixels(window.rem_size());
25091 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25092 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25093
25094 CharacterDimensions {
25095 em_width,
25096 em_advance,
25097 line_height,
25098 }
25099 }
25100
25101 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25102 self.load_diff_task.clone()
25103 }
25104
25105 fn read_metadata_from_db(
25106 &mut self,
25107 item_id: u64,
25108 workspace_id: WorkspaceId,
25109 window: &mut Window,
25110 cx: &mut Context<Editor>,
25111 ) {
25112 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25113 && !self.mode.is_minimap()
25114 && WorkspaceSettings::get(None, cx).restore_on_startup
25115 != RestoreOnStartupBehavior::EmptyTab
25116 {
25117 let buffer_snapshot = OnceCell::new();
25118
25119 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25120 && !folds.is_empty()
25121 {
25122 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25123 let snapshot_len = snapshot.len().0;
25124
25125 // Helper: search for fingerprint in buffer, return offset if found
25126 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25127 // Ensure we start at a character boundary (defensive)
25128 let search_start = snapshot
25129 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25130 .0;
25131 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25132
25133 let mut byte_offset = search_start;
25134 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25135 if byte_offset > search_end {
25136 break;
25137 }
25138 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25139 return Some(byte_offset);
25140 }
25141 byte_offset += ch.len_utf8();
25142 }
25143 None
25144 };
25145
25146 // Track search position to handle duplicate fingerprints correctly.
25147 // Folds are stored in document order, so we advance after each match.
25148 let mut search_start = 0usize;
25149
25150 let valid_folds: Vec<_> = folds
25151 .into_iter()
25152 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25153 // Skip folds without fingerprints (old data before migration)
25154 let sfp = start_fp?;
25155 let efp = end_fp?;
25156 let efp_len = efp.len();
25157
25158 // Fast path: check if fingerprints match at stored offsets
25159 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25160 let start_matches = stored_start < snapshot_len
25161 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25162 let efp_check_pos = stored_end.saturating_sub(efp_len);
25163 let end_matches = efp_check_pos >= stored_start
25164 && stored_end <= snapshot_len
25165 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25166
25167 let (new_start, new_end) = if start_matches && end_matches {
25168 // Offsets unchanged, use stored values
25169 (stored_start, stored_end)
25170 } else if sfp == efp {
25171 // Short fold: identical fingerprints can only match once per search
25172 // Use stored fold length to compute new_end
25173 let new_start = find_fingerprint(&sfp, search_start)?;
25174 let fold_len = stored_end - stored_start;
25175 let new_end = new_start + fold_len;
25176 (new_start, new_end)
25177 } else {
25178 // Slow path: search for fingerprints in buffer
25179 let new_start = find_fingerprint(&sfp, search_start)?;
25180 // Search for end_fp after start, then add efp_len to get actual fold end
25181 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25182 let new_end = efp_pos + efp_len;
25183 (new_start, new_end)
25184 };
25185
25186 // Advance search position for next fold
25187 search_start = new_end;
25188
25189 // Validate fold makes sense (end must be after start)
25190 if new_end <= new_start {
25191 return None;
25192 }
25193
25194 Some(
25195 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25196 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25197 )
25198 })
25199 .collect();
25200
25201 if !valid_folds.is_empty() {
25202 self.fold_ranges(valid_folds, false, window, cx);
25203
25204 // Migrate folds to current entity_id before workspace cleanup runs.
25205 // Entity IDs change between sessions, but workspace cleanup deletes
25206 // old editor rows (cascading to folds) based on current entity IDs.
25207 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
25208 if new_editor_id != item_id {
25209 cx.spawn(async move |_, _| {
25210 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
25211 .await
25212 .log_err();
25213 })
25214 .detach();
25215 }
25216 }
25217 }
25218
25219 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25220 && !selections.is_empty()
25221 {
25222 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25223 // skip adding the initial selection to selection history
25224 self.selection_history.mode = SelectionHistoryMode::Skipping;
25225 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25226 s.select_ranges(selections.into_iter().map(|(start, end)| {
25227 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25228 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25229 }));
25230 });
25231 self.selection_history.mode = SelectionHistoryMode::Normal;
25232 };
25233 }
25234
25235 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25236 }
25237
25238 fn update_lsp_data(
25239 &mut self,
25240 for_buffer: Option<BufferId>,
25241 window: &mut Window,
25242 cx: &mut Context<'_, Self>,
25243 ) {
25244 if !self.enable_lsp_data {
25245 return;
25246 }
25247
25248 if let Some(buffer_id) = for_buffer {
25249 self.pull_diagnostics(buffer_id, window, cx);
25250 self.update_semantic_tokens(Some(buffer_id), None, cx);
25251 } else {
25252 self.refresh_semantic_token_highlights(cx);
25253 }
25254 self.refresh_colors_for_visible_range(for_buffer, window, cx);
25255 self.refresh_folding_ranges(for_buffer, window, cx);
25256 }
25257
25258 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25259 if !self.mode().is_full() {
25260 return;
25261 }
25262 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25263 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25264 }
25265 }
25266
25267 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25268 if !self.mode().is_full() {
25269 return;
25270 }
25271
25272 if !self.registered_buffers.contains_key(&buffer_id)
25273 && let Some(project) = self.project.as_ref()
25274 {
25275 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25276 project.update(cx, |project, cx| {
25277 self.registered_buffers.insert(
25278 buffer_id,
25279 project.register_buffer_with_language_servers(&buffer, cx),
25280 );
25281 });
25282 } else {
25283 self.registered_buffers.remove(&buffer_id);
25284 }
25285 }
25286 }
25287
25288 fn create_style(&self, cx: &App) -> EditorStyle {
25289 let settings = ThemeSettings::get_global(cx);
25290
25291 let mut text_style = match self.mode {
25292 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25293 color: cx.theme().colors().editor_foreground,
25294 font_family: settings.ui_font.family.clone(),
25295 font_features: settings.ui_font.features.clone(),
25296 font_fallbacks: settings.ui_font.fallbacks.clone(),
25297 font_size: rems(0.875).into(),
25298 font_weight: settings.ui_font.weight,
25299 line_height: relative(settings.buffer_line_height.value()),
25300 ..Default::default()
25301 },
25302 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25303 color: cx.theme().colors().editor_foreground,
25304 font_family: settings.buffer_font.family.clone(),
25305 font_features: settings.buffer_font.features.clone(),
25306 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25307 font_size: settings.buffer_font_size(cx).into(),
25308 font_weight: settings.buffer_font.weight,
25309 line_height: relative(settings.buffer_line_height.value()),
25310 ..Default::default()
25311 },
25312 };
25313 if let Some(text_style_refinement) = &self.text_style_refinement {
25314 text_style.refine(text_style_refinement)
25315 }
25316
25317 let background = match self.mode {
25318 EditorMode::SingleLine => cx.theme().system().transparent,
25319 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25320 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25321 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25322 };
25323
25324 EditorStyle {
25325 background,
25326 border: cx.theme().colors().border,
25327 local_player: cx.theme().players().local(),
25328 text: text_style,
25329 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25330 syntax: cx.theme().syntax().clone(),
25331 status: cx.theme().status().clone(),
25332 inlay_hints_style: make_inlay_hints_style(cx),
25333 edit_prediction_styles: make_suggestion_styles(cx),
25334 unnecessary_code_fade: settings.unnecessary_code_fade,
25335 show_underlines: self.diagnostics_enabled(),
25336 }
25337 }
25338 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25339 let multibuffer = self.buffer().read(cx);
25340 let is_singleton = multibuffer.is_singleton();
25341 let (buffer_id, symbols) = self.outline_symbols.as_ref()?;
25342 let buffer = multibuffer.buffer(*buffer_id)?;
25343
25344 let buffer = buffer.read(cx);
25345 let settings = ThemeSettings::get_global(cx);
25346 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25347 let mut breadcrumbs = if is_singleton {
25348 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25349 buffer
25350 .snapshot()
25351 .resolve_file_path(
25352 self.project
25353 .as_ref()
25354 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25355 .unwrap_or_default(),
25356 cx,
25357 )
25358 .unwrap_or_else(|| {
25359 if multibuffer.is_singleton() {
25360 multibuffer.title(cx).to_string()
25361 } else {
25362 "untitled".to_string()
25363 }
25364 })
25365 });
25366 vec![BreadcrumbText {
25367 text,
25368 highlights: None,
25369 font: Some(settings.buffer_font.clone()),
25370 }]
25371 } else {
25372 vec![]
25373 };
25374
25375 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25376 text: symbol.text.clone(),
25377 highlights: Some(symbol.highlight_ranges.clone()),
25378 font: Some(settings.buffer_font.clone()),
25379 }));
25380 Some(breadcrumbs)
25381 }
25382
25383 fn disable_lsp_data(&mut self) {
25384 self.enable_lsp_data = false;
25385 }
25386
25387 fn disable_runnables(&mut self) {
25388 self.enable_runnables = false;
25389 }
25390}
25391
25392fn edit_for_markdown_paste<'a>(
25393 buffer: &MultiBufferSnapshot,
25394 range: Range<MultiBufferOffset>,
25395 to_insert: &'a str,
25396 url: Option<url::Url>,
25397) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25398 if url.is_none() {
25399 return (range, Cow::Borrowed(to_insert));
25400 };
25401
25402 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25403
25404 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25405 Cow::Borrowed(to_insert)
25406 } else {
25407 Cow::Owned(format!("[{old_text}]({to_insert})"))
25408 };
25409 (range, new_text)
25410}
25411
25412fn process_completion_for_edit(
25413 completion: &Completion,
25414 intent: CompletionIntent,
25415 buffer: &Entity<Buffer>,
25416 cursor_position: &text::Anchor,
25417 cx: &mut Context<Editor>,
25418) -> CompletionEdit {
25419 let buffer = buffer.read(cx);
25420 let buffer_snapshot = buffer.snapshot();
25421 let (snippet, new_text) = if completion.is_snippet() {
25422 let mut snippet_source = completion.new_text.clone();
25423 // Workaround for typescript language server issues so that methods don't expand within
25424 // strings and functions with type expressions. The previous point is used because the query
25425 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25426 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25427 let previous_point = if previous_point.column > 0 {
25428 cursor_position.to_previous_offset(&buffer_snapshot)
25429 } else {
25430 cursor_position.to_offset(&buffer_snapshot)
25431 };
25432 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25433 && scope.prefers_label_for_snippet_in_completion()
25434 && let Some(label) = completion.label()
25435 && matches!(
25436 completion.kind(),
25437 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25438 )
25439 {
25440 snippet_source = label;
25441 }
25442 match Snippet::parse(&snippet_source).log_err() {
25443 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25444 None => (None, completion.new_text.clone()),
25445 }
25446 } else {
25447 (None, completion.new_text.clone())
25448 };
25449
25450 let mut range_to_replace = {
25451 let replace_range = &completion.replace_range;
25452 if let CompletionSource::Lsp {
25453 insert_range: Some(insert_range),
25454 ..
25455 } = &completion.source
25456 {
25457 debug_assert_eq!(
25458 insert_range.start, replace_range.start,
25459 "insert_range and replace_range should start at the same position"
25460 );
25461 debug_assert!(
25462 insert_range
25463 .start
25464 .cmp(cursor_position, &buffer_snapshot)
25465 .is_le(),
25466 "insert_range should start before or at cursor position"
25467 );
25468 debug_assert!(
25469 replace_range
25470 .start
25471 .cmp(cursor_position, &buffer_snapshot)
25472 .is_le(),
25473 "replace_range should start before or at cursor position"
25474 );
25475
25476 let should_replace = match intent {
25477 CompletionIntent::CompleteWithInsert => false,
25478 CompletionIntent::CompleteWithReplace => true,
25479 CompletionIntent::Complete | CompletionIntent::Compose => {
25480 let insert_mode =
25481 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25482 .completions
25483 .lsp_insert_mode;
25484 match insert_mode {
25485 LspInsertMode::Insert => false,
25486 LspInsertMode::Replace => true,
25487 LspInsertMode::ReplaceSubsequence => {
25488 let mut text_to_replace = buffer.chars_for_range(
25489 buffer.anchor_before(replace_range.start)
25490 ..buffer.anchor_after(replace_range.end),
25491 );
25492 let mut current_needle = text_to_replace.next();
25493 for haystack_ch in completion.label.text.chars() {
25494 if let Some(needle_ch) = current_needle
25495 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25496 {
25497 current_needle = text_to_replace.next();
25498 }
25499 }
25500 current_needle.is_none()
25501 }
25502 LspInsertMode::ReplaceSuffix => {
25503 if replace_range
25504 .end
25505 .cmp(cursor_position, &buffer_snapshot)
25506 .is_gt()
25507 {
25508 let range_after_cursor = *cursor_position..replace_range.end;
25509 let text_after_cursor = buffer
25510 .text_for_range(
25511 buffer.anchor_before(range_after_cursor.start)
25512 ..buffer.anchor_after(range_after_cursor.end),
25513 )
25514 .collect::<String>()
25515 .to_ascii_lowercase();
25516 completion
25517 .label
25518 .text
25519 .to_ascii_lowercase()
25520 .ends_with(&text_after_cursor)
25521 } else {
25522 true
25523 }
25524 }
25525 }
25526 }
25527 };
25528
25529 if should_replace {
25530 replace_range.clone()
25531 } else {
25532 insert_range.clone()
25533 }
25534 } else {
25535 replace_range.clone()
25536 }
25537 };
25538
25539 if range_to_replace
25540 .end
25541 .cmp(cursor_position, &buffer_snapshot)
25542 .is_lt()
25543 {
25544 range_to_replace.end = *cursor_position;
25545 }
25546
25547 let replace_range = range_to_replace.to_offset(buffer);
25548 CompletionEdit {
25549 new_text,
25550 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25551 snippet,
25552 }
25553}
25554
25555struct CompletionEdit {
25556 new_text: String,
25557 replace_range: Range<BufferOffset>,
25558 snippet: Option<Snippet>,
25559}
25560
25561fn comment_delimiter_for_newline(
25562 start_point: &Point,
25563 buffer: &MultiBufferSnapshot,
25564 language: &LanguageScope,
25565) -> Option<Arc<str>> {
25566 let delimiters = language.line_comment_prefixes();
25567 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25568 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25569
25570 let num_of_whitespaces = snapshot
25571 .chars_for_range(range.clone())
25572 .take_while(|c| c.is_whitespace())
25573 .count();
25574 let comment_candidate = snapshot
25575 .chars_for_range(range.clone())
25576 .skip(num_of_whitespaces)
25577 .take(max_len_of_delimiter)
25578 .collect::<String>();
25579 let (delimiter, trimmed_len) = delimiters
25580 .iter()
25581 .filter_map(|delimiter| {
25582 let prefix = delimiter.trim_end();
25583 if comment_candidate.starts_with(prefix) {
25584 Some((delimiter, prefix.len()))
25585 } else {
25586 None
25587 }
25588 })
25589 .max_by_key(|(_, len)| *len)?;
25590
25591 if let Some(BlockCommentConfig {
25592 start: block_start, ..
25593 }) = language.block_comment()
25594 {
25595 let block_start_trimmed = block_start.trim_end();
25596 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25597 let line_content = snapshot
25598 .chars_for_range(range)
25599 .skip(num_of_whitespaces)
25600 .take(block_start_trimmed.len())
25601 .collect::<String>();
25602
25603 if line_content.starts_with(block_start_trimmed) {
25604 return None;
25605 }
25606 }
25607 }
25608
25609 let cursor_is_placed_after_comment_marker =
25610 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25611 if cursor_is_placed_after_comment_marker {
25612 Some(delimiter.clone())
25613 } else {
25614 None
25615 }
25616}
25617
25618fn documentation_delimiter_for_newline(
25619 start_point: &Point,
25620 buffer: &MultiBufferSnapshot,
25621 language: &LanguageScope,
25622 newline_config: &mut NewlineConfig,
25623) -> Option<Arc<str>> {
25624 let BlockCommentConfig {
25625 start: start_tag,
25626 end: end_tag,
25627 prefix: delimiter,
25628 tab_size: len,
25629 } = language.documentation_comment()?;
25630 let is_within_block_comment = buffer
25631 .language_scope_at(*start_point)
25632 .is_some_and(|scope| scope.override_name() == Some("comment"));
25633 if !is_within_block_comment {
25634 return None;
25635 }
25636
25637 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25638
25639 let num_of_whitespaces = snapshot
25640 .chars_for_range(range.clone())
25641 .take_while(|c| c.is_whitespace())
25642 .count();
25643
25644 // 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.
25645 let column = start_point.column;
25646 let cursor_is_after_start_tag = {
25647 let start_tag_len = start_tag.len();
25648 let start_tag_line = snapshot
25649 .chars_for_range(range.clone())
25650 .skip(num_of_whitespaces)
25651 .take(start_tag_len)
25652 .collect::<String>();
25653 if start_tag_line.starts_with(start_tag.as_ref()) {
25654 num_of_whitespaces + start_tag_len <= column as usize
25655 } else {
25656 false
25657 }
25658 };
25659
25660 let cursor_is_after_delimiter = {
25661 let delimiter_trim = delimiter.trim_end();
25662 let delimiter_line = snapshot
25663 .chars_for_range(range.clone())
25664 .skip(num_of_whitespaces)
25665 .take(delimiter_trim.len())
25666 .collect::<String>();
25667 if delimiter_line.starts_with(delimiter_trim) {
25668 num_of_whitespaces + delimiter_trim.len() <= column as usize
25669 } else {
25670 false
25671 }
25672 };
25673
25674 let mut needs_extra_line = false;
25675 let mut extra_line_additional_indent = IndentSize::spaces(0);
25676
25677 let cursor_is_before_end_tag_if_exists = {
25678 let mut char_position = 0u32;
25679 let mut end_tag_offset = None;
25680
25681 'outer: for chunk in snapshot.text_for_range(range) {
25682 if let Some(byte_pos) = chunk.find(&**end_tag) {
25683 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25684 end_tag_offset = Some(char_position + chars_before_match);
25685 break 'outer;
25686 }
25687 char_position += chunk.chars().count() as u32;
25688 }
25689
25690 if let Some(end_tag_offset) = end_tag_offset {
25691 let cursor_is_before_end_tag = column <= end_tag_offset;
25692 if cursor_is_after_start_tag {
25693 if cursor_is_before_end_tag {
25694 needs_extra_line = true;
25695 }
25696 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25697 if cursor_is_at_start_of_end_tag {
25698 extra_line_additional_indent.len = *len;
25699 }
25700 }
25701 cursor_is_before_end_tag
25702 } else {
25703 true
25704 }
25705 };
25706
25707 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25708 && cursor_is_before_end_tag_if_exists
25709 {
25710 let additional_indent = if cursor_is_after_start_tag {
25711 IndentSize::spaces(*len)
25712 } else {
25713 IndentSize::spaces(0)
25714 };
25715
25716 *newline_config = NewlineConfig::Newline {
25717 additional_indent,
25718 extra_line_additional_indent: if needs_extra_line {
25719 Some(extra_line_additional_indent)
25720 } else {
25721 None
25722 },
25723 prevent_auto_indent: true,
25724 };
25725 Some(delimiter.clone())
25726 } else {
25727 None
25728 }
25729}
25730
25731const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25732
25733fn list_delimiter_for_newline(
25734 start_point: &Point,
25735 buffer: &MultiBufferSnapshot,
25736 language: &LanguageScope,
25737 newline_config: &mut NewlineConfig,
25738) -> Option<Arc<str>> {
25739 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25740
25741 let num_of_whitespaces = snapshot
25742 .chars_for_range(range.clone())
25743 .take_while(|c| c.is_whitespace())
25744 .count();
25745
25746 let task_list_entries: Vec<_> = language
25747 .task_list()
25748 .into_iter()
25749 .flat_map(|config| {
25750 config
25751 .prefixes
25752 .iter()
25753 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25754 })
25755 .collect();
25756 let unordered_list_entries: Vec<_> = language
25757 .unordered_list()
25758 .iter()
25759 .map(|marker| (marker.as_ref(), marker.as_ref()))
25760 .collect();
25761
25762 let all_entries: Vec<_> = task_list_entries
25763 .into_iter()
25764 .chain(unordered_list_entries)
25765 .collect();
25766
25767 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25768 let candidate: String = snapshot
25769 .chars_for_range(range.clone())
25770 .skip(num_of_whitespaces)
25771 .take(max_prefix_len)
25772 .collect();
25773
25774 if let Some((prefix, continuation)) = all_entries
25775 .iter()
25776 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25777 .max_by_key(|(prefix, _)| prefix.len())
25778 {
25779 let end_of_prefix = num_of_whitespaces + prefix.len();
25780 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25781 let has_content_after_marker = snapshot
25782 .chars_for_range(range)
25783 .skip(end_of_prefix)
25784 .any(|c| !c.is_whitespace());
25785
25786 if has_content_after_marker && cursor_is_after_prefix {
25787 return Some((*continuation).into());
25788 }
25789
25790 if start_point.column as usize == end_of_prefix {
25791 if num_of_whitespaces == 0 {
25792 *newline_config = NewlineConfig::ClearCurrentLine;
25793 } else {
25794 *newline_config = NewlineConfig::UnindentCurrentLine {
25795 continuation: (*continuation).into(),
25796 };
25797 }
25798 }
25799
25800 return None;
25801 }
25802 }
25803
25804 let candidate: String = snapshot
25805 .chars_for_range(range.clone())
25806 .skip(num_of_whitespaces)
25807 .take(ORDERED_LIST_MAX_MARKER_LEN)
25808 .collect();
25809
25810 for ordered_config in language.ordered_list() {
25811 let regex = match Regex::new(&ordered_config.pattern) {
25812 Ok(r) => r,
25813 Err(_) => continue,
25814 };
25815
25816 if let Some(captures) = regex.captures(&candidate) {
25817 let full_match = captures.get(0)?;
25818 let marker_len = full_match.len();
25819 let end_of_prefix = num_of_whitespaces + marker_len;
25820 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25821
25822 let has_content_after_marker = snapshot
25823 .chars_for_range(range)
25824 .skip(end_of_prefix)
25825 .any(|c| !c.is_whitespace());
25826
25827 if has_content_after_marker && cursor_is_after_prefix {
25828 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25829 let continuation = ordered_config
25830 .format
25831 .replace("{1}", &(number + 1).to_string());
25832 return Some(continuation.into());
25833 }
25834
25835 if start_point.column as usize == end_of_prefix {
25836 let continuation = ordered_config.format.replace("{1}", "1");
25837 if num_of_whitespaces == 0 {
25838 *newline_config = NewlineConfig::ClearCurrentLine;
25839 } else {
25840 *newline_config = NewlineConfig::UnindentCurrentLine {
25841 continuation: continuation.into(),
25842 };
25843 }
25844 }
25845
25846 return None;
25847 }
25848 }
25849
25850 None
25851}
25852
25853fn is_list_prefix_row(
25854 row: MultiBufferRow,
25855 buffer: &MultiBufferSnapshot,
25856 language: &LanguageScope,
25857) -> bool {
25858 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25859 return false;
25860 };
25861
25862 let num_of_whitespaces = snapshot
25863 .chars_for_range(range.clone())
25864 .take_while(|c| c.is_whitespace())
25865 .count();
25866
25867 let task_list_prefixes: Vec<_> = language
25868 .task_list()
25869 .into_iter()
25870 .flat_map(|config| {
25871 config
25872 .prefixes
25873 .iter()
25874 .map(|p| p.as_ref())
25875 .collect::<Vec<_>>()
25876 })
25877 .collect();
25878 let unordered_list_markers: Vec<_> = language
25879 .unordered_list()
25880 .iter()
25881 .map(|marker| marker.as_ref())
25882 .collect();
25883 let all_prefixes: Vec<_> = task_list_prefixes
25884 .into_iter()
25885 .chain(unordered_list_markers)
25886 .collect();
25887 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25888 let candidate: String = snapshot
25889 .chars_for_range(range.clone())
25890 .skip(num_of_whitespaces)
25891 .take(max_prefix_len)
25892 .collect();
25893 if all_prefixes
25894 .iter()
25895 .any(|prefix| candidate.starts_with(*prefix))
25896 {
25897 return true;
25898 }
25899 }
25900
25901 let ordered_list_candidate: String = snapshot
25902 .chars_for_range(range)
25903 .skip(num_of_whitespaces)
25904 .take(ORDERED_LIST_MAX_MARKER_LEN)
25905 .collect();
25906 for ordered_config in language.ordered_list() {
25907 let regex = match Regex::new(&ordered_config.pattern) {
25908 Ok(r) => r,
25909 Err(_) => continue,
25910 };
25911 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25912 return captures.get(0).is_some();
25913 }
25914 }
25915
25916 false
25917}
25918
25919#[derive(Debug)]
25920enum NewlineConfig {
25921 /// Insert newline with optional additional indent and optional extra blank line
25922 Newline {
25923 additional_indent: IndentSize,
25924 extra_line_additional_indent: Option<IndentSize>,
25925 prevent_auto_indent: bool,
25926 },
25927 /// Clear the current line
25928 ClearCurrentLine,
25929 /// Unindent the current line and add continuation
25930 UnindentCurrentLine { continuation: Arc<str> },
25931}
25932
25933impl NewlineConfig {
25934 fn has_extra_line(&self) -> bool {
25935 matches!(
25936 self,
25937 Self::Newline {
25938 extra_line_additional_indent: Some(_),
25939 ..
25940 }
25941 )
25942 }
25943
25944 fn insert_extra_newline_brackets(
25945 buffer: &MultiBufferSnapshot,
25946 range: Range<MultiBufferOffset>,
25947 language: &language::LanguageScope,
25948 ) -> bool {
25949 let leading_whitespace_len = buffer
25950 .reversed_chars_at(range.start)
25951 .take_while(|c| c.is_whitespace() && *c != '\n')
25952 .map(|c| c.len_utf8())
25953 .sum::<usize>();
25954 let trailing_whitespace_len = buffer
25955 .chars_at(range.end)
25956 .take_while(|c| c.is_whitespace() && *c != '\n')
25957 .map(|c| c.len_utf8())
25958 .sum::<usize>();
25959 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25960
25961 language.brackets().any(|(pair, enabled)| {
25962 let pair_start = pair.start.trim_end();
25963 let pair_end = pair.end.trim_start();
25964
25965 enabled
25966 && pair.newline
25967 && buffer.contains_str_at(range.end, pair_end)
25968 && buffer.contains_str_at(
25969 range.start.saturating_sub_usize(pair_start.len()),
25970 pair_start,
25971 )
25972 })
25973 }
25974
25975 fn insert_extra_newline_tree_sitter(
25976 buffer: &MultiBufferSnapshot,
25977 range: Range<MultiBufferOffset>,
25978 ) -> bool {
25979 let (buffer, range) = match buffer
25980 .range_to_buffer_ranges(range.start..=range.end)
25981 .as_slice()
25982 {
25983 [(buffer, range, _)] => (*buffer, range.clone()),
25984 _ => return false,
25985 };
25986 let pair = {
25987 let mut result: Option<BracketMatch<usize>> = None;
25988
25989 for pair in buffer
25990 .all_bracket_ranges(range.start.0..range.end.0)
25991 .filter(move |pair| {
25992 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25993 })
25994 {
25995 let len = pair.close_range.end - pair.open_range.start;
25996
25997 if let Some(existing) = &result {
25998 let existing_len = existing.close_range.end - existing.open_range.start;
25999 if len > existing_len {
26000 continue;
26001 }
26002 }
26003
26004 result = Some(pair);
26005 }
26006
26007 result
26008 };
26009 let Some(pair) = pair else {
26010 return false;
26011 };
26012 pair.newline_only
26013 && buffer
26014 .chars_for_range(pair.open_range.end..range.start.0)
26015 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26016 .all(|c| c.is_whitespace() && c != '\n')
26017 }
26018}
26019
26020fn update_uncommitted_diff_for_buffer(
26021 editor: Entity<Editor>,
26022 project: &Entity<Project>,
26023 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26024 buffer: Entity<MultiBuffer>,
26025 cx: &mut App,
26026) -> Task<()> {
26027 let mut tasks = Vec::new();
26028 project.update(cx, |project, cx| {
26029 for buffer in buffers {
26030 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26031 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26032 }
26033 }
26034 });
26035 cx.spawn(async move |cx| {
26036 let diffs = future::join_all(tasks).await;
26037 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26038 return;
26039 }
26040
26041 buffer.update(cx, |buffer, cx| {
26042 for diff in diffs.into_iter().flatten() {
26043 buffer.add_diff(diff, cx);
26044 }
26045 });
26046 })
26047}
26048
26049fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26050 let tab_size = tab_size.get() as usize;
26051 let mut width = offset;
26052
26053 for ch in text.chars() {
26054 width += if ch == '\t' {
26055 tab_size - (width % tab_size)
26056 } else {
26057 1
26058 };
26059 }
26060
26061 width - offset
26062}
26063
26064#[cfg(test)]
26065mod tests {
26066 use super::*;
26067
26068 #[test]
26069 fn test_string_size_with_expanded_tabs() {
26070 let nz = |val| NonZeroU32::new(val).unwrap();
26071 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26072 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26073 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26074 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26075 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26076 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26077 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26078 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26079 }
26080}
26081
26082/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26083struct WordBreakingTokenizer<'a> {
26084 input: &'a str,
26085}
26086
26087impl<'a> WordBreakingTokenizer<'a> {
26088 fn new(input: &'a str) -> Self {
26089 Self { input }
26090 }
26091}
26092
26093fn is_char_ideographic(ch: char) -> bool {
26094 use unicode_script::Script::*;
26095 use unicode_script::UnicodeScript;
26096 matches!(ch.script(), Han | Tangut | Yi)
26097}
26098
26099fn is_grapheme_ideographic(text: &str) -> bool {
26100 text.chars().any(is_char_ideographic)
26101}
26102
26103fn is_grapheme_whitespace(text: &str) -> bool {
26104 text.chars().any(|x| x.is_whitespace())
26105}
26106
26107fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26108 text.chars()
26109 .next()
26110 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26111}
26112
26113#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26114enum WordBreakToken<'a> {
26115 Word { token: &'a str, grapheme_len: usize },
26116 InlineWhitespace { token: &'a str, grapheme_len: usize },
26117 Newline,
26118}
26119
26120impl<'a> Iterator for WordBreakingTokenizer<'a> {
26121 /// Yields a span, the count of graphemes in the token, and whether it was
26122 /// whitespace. Note that it also breaks at word boundaries.
26123 type Item = WordBreakToken<'a>;
26124
26125 fn next(&mut self) -> Option<Self::Item> {
26126 use unicode_segmentation::UnicodeSegmentation;
26127 if self.input.is_empty() {
26128 return None;
26129 }
26130
26131 let mut iter = self.input.graphemes(true).peekable();
26132 let mut offset = 0;
26133 let mut grapheme_len = 0;
26134 if let Some(first_grapheme) = iter.next() {
26135 let is_newline = first_grapheme == "\n";
26136 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26137 offset += first_grapheme.len();
26138 grapheme_len += 1;
26139 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26140 if let Some(grapheme) = iter.peek().copied()
26141 && should_stay_with_preceding_ideograph(grapheme)
26142 {
26143 offset += grapheme.len();
26144 grapheme_len += 1;
26145 }
26146 } else {
26147 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26148 let mut next_word_bound = words.peek().copied();
26149 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26150 next_word_bound = words.next();
26151 }
26152 while let Some(grapheme) = iter.peek().copied() {
26153 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26154 break;
26155 };
26156 if is_grapheme_whitespace(grapheme) != is_whitespace
26157 || (grapheme == "\n") != is_newline
26158 {
26159 break;
26160 };
26161 offset += grapheme.len();
26162 grapheme_len += 1;
26163 iter.next();
26164 }
26165 }
26166 let token = &self.input[..offset];
26167 self.input = &self.input[offset..];
26168 if token == "\n" {
26169 Some(WordBreakToken::Newline)
26170 } else if is_whitespace {
26171 Some(WordBreakToken::InlineWhitespace {
26172 token,
26173 grapheme_len,
26174 })
26175 } else {
26176 Some(WordBreakToken::Word {
26177 token,
26178 grapheme_len,
26179 })
26180 }
26181 } else {
26182 None
26183 }
26184 }
26185}
26186
26187#[test]
26188fn test_word_breaking_tokenizer() {
26189 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26190 ("", &[]),
26191 (" ", &[whitespace(" ", 2)]),
26192 ("Ʒ", &[word("Ʒ", 1)]),
26193 ("Ǽ", &[word("Ǽ", 1)]),
26194 ("⋑", &[word("⋑", 1)]),
26195 ("⋑⋑", &[word("⋑⋑", 2)]),
26196 (
26197 "原理,进而",
26198 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26199 ),
26200 (
26201 "hello world",
26202 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26203 ),
26204 (
26205 "hello, world",
26206 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26207 ),
26208 (
26209 " hello world",
26210 &[
26211 whitespace(" ", 2),
26212 word("hello", 5),
26213 whitespace(" ", 1),
26214 word("world", 5),
26215 ],
26216 ),
26217 (
26218 "这是什么 \n 钢笔",
26219 &[
26220 word("这", 1),
26221 word("是", 1),
26222 word("什", 1),
26223 word("么", 1),
26224 whitespace(" ", 1),
26225 newline(),
26226 whitespace(" ", 1),
26227 word("钢", 1),
26228 word("笔", 1),
26229 ],
26230 ),
26231 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26232 ];
26233
26234 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26235 WordBreakToken::Word {
26236 token,
26237 grapheme_len,
26238 }
26239 }
26240
26241 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26242 WordBreakToken::InlineWhitespace {
26243 token,
26244 grapheme_len,
26245 }
26246 }
26247
26248 fn newline() -> WordBreakToken<'static> {
26249 WordBreakToken::Newline
26250 }
26251
26252 for (input, result) in tests {
26253 assert_eq!(
26254 WordBreakingTokenizer::new(input)
26255 .collect::<Vec<_>>()
26256 .as_slice(),
26257 *result,
26258 );
26259 }
26260}
26261
26262fn wrap_with_prefix(
26263 first_line_prefix: String,
26264 subsequent_lines_prefix: String,
26265 unwrapped_text: String,
26266 wrap_column: usize,
26267 tab_size: NonZeroU32,
26268 preserve_existing_whitespace: bool,
26269) -> String {
26270 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26271 let subsequent_lines_prefix_len =
26272 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26273 let mut wrapped_text = String::new();
26274 let mut current_line = first_line_prefix;
26275 let mut is_first_line = true;
26276
26277 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26278 let mut current_line_len = first_line_prefix_len;
26279 let mut in_whitespace = false;
26280 for token in tokenizer {
26281 let have_preceding_whitespace = in_whitespace;
26282 match token {
26283 WordBreakToken::Word {
26284 token,
26285 grapheme_len,
26286 } => {
26287 in_whitespace = false;
26288 let current_prefix_len = if is_first_line {
26289 first_line_prefix_len
26290 } else {
26291 subsequent_lines_prefix_len
26292 };
26293 if current_line_len + grapheme_len > wrap_column
26294 && current_line_len != current_prefix_len
26295 {
26296 wrapped_text.push_str(current_line.trim_end());
26297 wrapped_text.push('\n');
26298 is_first_line = false;
26299 current_line = subsequent_lines_prefix.clone();
26300 current_line_len = subsequent_lines_prefix_len;
26301 }
26302 current_line.push_str(token);
26303 current_line_len += grapheme_len;
26304 }
26305 WordBreakToken::InlineWhitespace {
26306 mut token,
26307 mut grapheme_len,
26308 } => {
26309 in_whitespace = true;
26310 if have_preceding_whitespace && !preserve_existing_whitespace {
26311 continue;
26312 }
26313 if !preserve_existing_whitespace {
26314 // Keep a single whitespace grapheme as-is
26315 if let Some(first) =
26316 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26317 {
26318 token = first;
26319 } else {
26320 token = " ";
26321 }
26322 grapheme_len = 1;
26323 }
26324 let current_prefix_len = if is_first_line {
26325 first_line_prefix_len
26326 } else {
26327 subsequent_lines_prefix_len
26328 };
26329 if current_line_len + grapheme_len > wrap_column {
26330 wrapped_text.push_str(current_line.trim_end());
26331 wrapped_text.push('\n');
26332 is_first_line = false;
26333 current_line = subsequent_lines_prefix.clone();
26334 current_line_len = subsequent_lines_prefix_len;
26335 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26336 current_line.push_str(token);
26337 current_line_len += grapheme_len;
26338 }
26339 }
26340 WordBreakToken::Newline => {
26341 in_whitespace = true;
26342 let current_prefix_len = if is_first_line {
26343 first_line_prefix_len
26344 } else {
26345 subsequent_lines_prefix_len
26346 };
26347 if preserve_existing_whitespace {
26348 wrapped_text.push_str(current_line.trim_end());
26349 wrapped_text.push('\n');
26350 is_first_line = false;
26351 current_line = subsequent_lines_prefix.clone();
26352 current_line_len = subsequent_lines_prefix_len;
26353 } else if have_preceding_whitespace {
26354 continue;
26355 } else if current_line_len + 1 > wrap_column
26356 && current_line_len != current_prefix_len
26357 {
26358 wrapped_text.push_str(current_line.trim_end());
26359 wrapped_text.push('\n');
26360 is_first_line = false;
26361 current_line = subsequent_lines_prefix.clone();
26362 current_line_len = subsequent_lines_prefix_len;
26363 } else if current_line_len != current_prefix_len {
26364 current_line.push(' ');
26365 current_line_len += 1;
26366 }
26367 }
26368 }
26369 }
26370
26371 if !current_line.is_empty() {
26372 wrapped_text.push_str(¤t_line);
26373 }
26374 wrapped_text
26375}
26376
26377#[test]
26378fn test_wrap_with_prefix() {
26379 assert_eq!(
26380 wrap_with_prefix(
26381 "# ".to_string(),
26382 "# ".to_string(),
26383 "abcdefg".to_string(),
26384 4,
26385 NonZeroU32::new(4).unwrap(),
26386 false,
26387 ),
26388 "# abcdefg"
26389 );
26390 assert_eq!(
26391 wrap_with_prefix(
26392 "".to_string(),
26393 "".to_string(),
26394 "\thello world".to_string(),
26395 8,
26396 NonZeroU32::new(4).unwrap(),
26397 false,
26398 ),
26399 "hello\nworld"
26400 );
26401 assert_eq!(
26402 wrap_with_prefix(
26403 "// ".to_string(),
26404 "// ".to_string(),
26405 "xx \nyy zz aa bb cc".to_string(),
26406 12,
26407 NonZeroU32::new(4).unwrap(),
26408 false,
26409 ),
26410 "// xx yy zz\n// aa bb cc"
26411 );
26412 assert_eq!(
26413 wrap_with_prefix(
26414 String::new(),
26415 String::new(),
26416 "这是什么 \n 钢笔".to_string(),
26417 3,
26418 NonZeroU32::new(4).unwrap(),
26419 false,
26420 ),
26421 "这是什\n么 钢\n笔"
26422 );
26423 assert_eq!(
26424 wrap_with_prefix(
26425 String::new(),
26426 String::new(),
26427 format!("foo{}bar", '\u{2009}'), // thin space
26428 80,
26429 NonZeroU32::new(4).unwrap(),
26430 false,
26431 ),
26432 format!("foo{}bar", '\u{2009}')
26433 );
26434}
26435
26436pub trait CollaborationHub {
26437 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26438 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26439 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26440}
26441
26442impl CollaborationHub for Entity<Project> {
26443 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26444 self.read(cx).collaborators()
26445 }
26446
26447 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26448 self.read(cx).user_store().read(cx).participant_indices()
26449 }
26450
26451 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26452 let this = self.read(cx);
26453 let user_ids = this.collaborators().values().map(|c| c.user_id);
26454 this.user_store().read(cx).participant_names(user_ids, cx)
26455 }
26456}
26457
26458pub trait SemanticsProvider {
26459 fn hover(
26460 &self,
26461 buffer: &Entity<Buffer>,
26462 position: text::Anchor,
26463 cx: &mut App,
26464 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26465
26466 fn inline_values(
26467 &self,
26468 buffer_handle: Entity<Buffer>,
26469 range: Range<text::Anchor>,
26470 cx: &mut App,
26471 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26472
26473 fn applicable_inlay_chunks(
26474 &self,
26475 buffer: &Entity<Buffer>,
26476 ranges: &[Range<text::Anchor>],
26477 cx: &mut App,
26478 ) -> Vec<Range<BufferRow>>;
26479
26480 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26481
26482 fn inlay_hints(
26483 &self,
26484 invalidate: InvalidationStrategy,
26485 buffer: Entity<Buffer>,
26486 ranges: Vec<Range<text::Anchor>>,
26487 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26488 cx: &mut App,
26489 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26490
26491 fn semantic_tokens(
26492 &self,
26493 buffer: Entity<Buffer>,
26494 refresh: Option<RefreshForServer>,
26495 cx: &mut App,
26496 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>;
26497
26498 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26499
26500 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26501
26502 fn document_highlights(
26503 &self,
26504 buffer: &Entity<Buffer>,
26505 position: text::Anchor,
26506 cx: &mut App,
26507 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26508
26509 fn definitions(
26510 &self,
26511 buffer: &Entity<Buffer>,
26512 position: text::Anchor,
26513 kind: GotoDefinitionKind,
26514 cx: &mut App,
26515 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26516
26517 fn range_for_rename(
26518 &self,
26519 buffer: &Entity<Buffer>,
26520 position: text::Anchor,
26521 cx: &mut App,
26522 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26523
26524 fn perform_rename(
26525 &self,
26526 buffer: &Entity<Buffer>,
26527 position: text::Anchor,
26528 new_name: String,
26529 cx: &mut App,
26530 ) -> Option<Task<Result<ProjectTransaction>>>;
26531}
26532
26533pub trait CompletionProvider {
26534 fn completions(
26535 &self,
26536 excerpt_id: ExcerptId,
26537 buffer: &Entity<Buffer>,
26538 buffer_position: text::Anchor,
26539 trigger: CompletionContext,
26540 window: &mut Window,
26541 cx: &mut Context<Editor>,
26542 ) -> Task<Result<Vec<CompletionResponse>>>;
26543
26544 fn resolve_completions(
26545 &self,
26546 _buffer: Entity<Buffer>,
26547 _completion_indices: Vec<usize>,
26548 _completions: Rc<RefCell<Box<[Completion]>>>,
26549 _cx: &mut Context<Editor>,
26550 ) -> Task<Result<bool>> {
26551 Task::ready(Ok(false))
26552 }
26553
26554 fn apply_additional_edits_for_completion(
26555 &self,
26556 _buffer: Entity<Buffer>,
26557 _completions: Rc<RefCell<Box<[Completion]>>>,
26558 _completion_index: usize,
26559 _push_to_history: bool,
26560 _cx: &mut Context<Editor>,
26561 ) -> Task<Result<Option<language::Transaction>>> {
26562 Task::ready(Ok(None))
26563 }
26564
26565 fn is_completion_trigger(
26566 &self,
26567 buffer: &Entity<Buffer>,
26568 position: language::Anchor,
26569 text: &str,
26570 trigger_in_words: bool,
26571 cx: &mut Context<Editor>,
26572 ) -> bool;
26573
26574 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26575
26576 fn sort_completions(&self) -> bool {
26577 true
26578 }
26579
26580 fn filter_completions(&self) -> bool {
26581 true
26582 }
26583
26584 fn show_snippets(&self) -> bool {
26585 false
26586 }
26587}
26588
26589pub trait CodeActionProvider {
26590 fn id(&self) -> Arc<str>;
26591
26592 fn code_actions(
26593 &self,
26594 buffer: &Entity<Buffer>,
26595 range: Range<text::Anchor>,
26596 window: &mut Window,
26597 cx: &mut App,
26598 ) -> Task<Result<Vec<CodeAction>>>;
26599
26600 fn apply_code_action(
26601 &self,
26602 buffer_handle: Entity<Buffer>,
26603 action: CodeAction,
26604 excerpt_id: ExcerptId,
26605 push_to_history: bool,
26606 window: &mut Window,
26607 cx: &mut App,
26608 ) -> Task<Result<ProjectTransaction>>;
26609}
26610
26611impl CodeActionProvider for Entity<Project> {
26612 fn id(&self) -> Arc<str> {
26613 "project".into()
26614 }
26615
26616 fn code_actions(
26617 &self,
26618 buffer: &Entity<Buffer>,
26619 range: Range<text::Anchor>,
26620 _window: &mut Window,
26621 cx: &mut App,
26622 ) -> Task<Result<Vec<CodeAction>>> {
26623 self.update(cx, |project, cx| {
26624 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26625 let code_actions = project.code_actions(buffer, range, None, cx);
26626 cx.background_spawn(async move {
26627 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26628 Ok(code_lens_actions
26629 .context("code lens fetch")?
26630 .into_iter()
26631 .flatten()
26632 .chain(
26633 code_actions
26634 .context("code action fetch")?
26635 .into_iter()
26636 .flatten(),
26637 )
26638 .collect())
26639 })
26640 })
26641 }
26642
26643 fn apply_code_action(
26644 &self,
26645 buffer_handle: Entity<Buffer>,
26646 action: CodeAction,
26647 _excerpt_id: ExcerptId,
26648 push_to_history: bool,
26649 _window: &mut Window,
26650 cx: &mut App,
26651 ) -> Task<Result<ProjectTransaction>> {
26652 self.update(cx, |project, cx| {
26653 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26654 })
26655 }
26656}
26657
26658fn snippet_completions(
26659 project: &Project,
26660 buffer: &Entity<Buffer>,
26661 buffer_anchor: text::Anchor,
26662 classifier: CharClassifier,
26663 cx: &mut App,
26664) -> Task<Result<CompletionResponse>> {
26665 let languages = buffer.read(cx).languages_at(buffer_anchor);
26666 let snippet_store = project.snippets().read(cx);
26667
26668 let scopes: Vec<_> = languages
26669 .iter()
26670 .filter_map(|language| {
26671 let language_name = language.lsp_id();
26672 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26673
26674 if snippets.is_empty() {
26675 None
26676 } else {
26677 Some((language.default_scope(), snippets))
26678 }
26679 })
26680 .collect();
26681
26682 if scopes.is_empty() {
26683 return Task::ready(Ok(CompletionResponse {
26684 completions: vec![],
26685 display_options: CompletionDisplayOptions::default(),
26686 is_incomplete: false,
26687 }));
26688 }
26689
26690 let snapshot = buffer.read(cx).text_snapshot();
26691 let executor = cx.background_executor().clone();
26692
26693 cx.background_spawn(async move {
26694 let is_word_char = |c| classifier.is_word(c);
26695
26696 let mut is_incomplete = false;
26697 let mut completions: Vec<Completion> = Vec::new();
26698
26699 const MAX_PREFIX_LEN: usize = 128;
26700 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26701 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26702 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26703
26704 let max_buffer_window: String = snapshot
26705 .text_for_range(window_start..buffer_offset)
26706 .collect();
26707
26708 if max_buffer_window.is_empty() {
26709 return Ok(CompletionResponse {
26710 completions: vec![],
26711 display_options: CompletionDisplayOptions::default(),
26712 is_incomplete: true,
26713 });
26714 }
26715
26716 for (_scope, snippets) in scopes.into_iter() {
26717 // Sort snippets by word count to match longer snippet prefixes first.
26718 let mut sorted_snippet_candidates = snippets
26719 .iter()
26720 .enumerate()
26721 .flat_map(|(snippet_ix, snippet)| {
26722 snippet
26723 .prefix
26724 .iter()
26725 .enumerate()
26726 .map(move |(prefix_ix, prefix)| {
26727 let word_count =
26728 snippet_candidate_suffixes(prefix, is_word_char).count();
26729 ((snippet_ix, prefix_ix), prefix, word_count)
26730 })
26731 })
26732 .collect_vec();
26733 sorted_snippet_candidates
26734 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26735
26736 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26737
26738 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26739 .take(
26740 sorted_snippet_candidates
26741 .first()
26742 .map(|(_, _, word_count)| *word_count)
26743 .unwrap_or_default(),
26744 )
26745 .collect_vec();
26746
26747 const MAX_RESULTS: usize = 100;
26748 // Each match also remembers how many characters from the buffer it consumed
26749 let mut matches: Vec<(StringMatch, usize)> = vec![];
26750
26751 let mut snippet_list_cutoff_index = 0;
26752 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26753 let word_count = buffer_index + 1;
26754 // Increase `snippet_list_cutoff_index` until we have all of the
26755 // snippets with sufficiently many words.
26756 while sorted_snippet_candidates
26757 .get(snippet_list_cutoff_index)
26758 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26759 *snippet_word_count >= word_count
26760 })
26761 {
26762 snippet_list_cutoff_index += 1;
26763 }
26764
26765 // Take only the candidates with at least `word_count` many words
26766 let snippet_candidates_at_word_len =
26767 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26768
26769 let candidates = snippet_candidates_at_word_len
26770 .iter()
26771 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26772 .enumerate() // index in `sorted_snippet_candidates`
26773 // First char must match
26774 .filter(|(_ix, prefix)| {
26775 itertools::equal(
26776 prefix
26777 .chars()
26778 .next()
26779 .into_iter()
26780 .flat_map(|c| c.to_lowercase()),
26781 buffer_window
26782 .chars()
26783 .next()
26784 .into_iter()
26785 .flat_map(|c| c.to_lowercase()),
26786 )
26787 })
26788 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26789 .collect::<Vec<StringMatchCandidate>>();
26790
26791 matches.extend(
26792 fuzzy::match_strings(
26793 &candidates,
26794 &buffer_window,
26795 buffer_window.chars().any(|c| c.is_uppercase()),
26796 true,
26797 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26798 &Default::default(),
26799 executor.clone(),
26800 )
26801 .await
26802 .into_iter()
26803 .map(|string_match| (string_match, buffer_window.len())),
26804 );
26805
26806 if matches.len() >= MAX_RESULTS {
26807 break;
26808 }
26809 }
26810
26811 let to_lsp = |point: &text::Anchor| {
26812 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26813 point_to_lsp(end)
26814 };
26815 let lsp_end = to_lsp(&buffer_anchor);
26816
26817 if matches.len() >= MAX_RESULTS {
26818 is_incomplete = true;
26819 }
26820
26821 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26822 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26823 sorted_snippet_candidates[string_match.candidate_id];
26824 let snippet = &snippets[snippet_index];
26825 let start = buffer_offset - buffer_window_len;
26826 let start = snapshot.anchor_before(start);
26827 let range = start..buffer_anchor;
26828 let lsp_start = to_lsp(&start);
26829 let lsp_range = lsp::Range {
26830 start: lsp_start,
26831 end: lsp_end,
26832 };
26833 Completion {
26834 replace_range: range,
26835 new_text: snippet.body.clone(),
26836 source: CompletionSource::Lsp {
26837 insert_range: None,
26838 server_id: LanguageServerId(usize::MAX),
26839 resolved: true,
26840 lsp_completion: Box::new(lsp::CompletionItem {
26841 label: snippet.prefix.first().unwrap().clone(),
26842 kind: Some(CompletionItemKind::SNIPPET),
26843 label_details: snippet.description.as_ref().map(|description| {
26844 lsp::CompletionItemLabelDetails {
26845 detail: Some(description.clone()),
26846 description: None,
26847 }
26848 }),
26849 insert_text_format: Some(InsertTextFormat::SNIPPET),
26850 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26851 lsp::InsertReplaceEdit {
26852 new_text: snippet.body.clone(),
26853 insert: lsp_range,
26854 replace: lsp_range,
26855 },
26856 )),
26857 filter_text: Some(snippet.body.clone()),
26858 sort_text: Some(char::MAX.to_string()),
26859 ..lsp::CompletionItem::default()
26860 }),
26861 lsp_defaults: None,
26862 },
26863 label: CodeLabel {
26864 text: matching_prefix.clone(),
26865 runs: Vec::new(),
26866 filter_range: 0..matching_prefix.len(),
26867 },
26868 icon_path: None,
26869 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26870 single_line: snippet.name.clone().into(),
26871 plain_text: snippet
26872 .description
26873 .clone()
26874 .map(|description| description.into()),
26875 }),
26876 insert_text_mode: None,
26877 confirm: None,
26878 match_start: Some(start),
26879 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26880 }
26881 }));
26882 }
26883
26884 Ok(CompletionResponse {
26885 completions,
26886 display_options: CompletionDisplayOptions::default(),
26887 is_incomplete,
26888 })
26889 })
26890}
26891
26892impl CompletionProvider for Entity<Project> {
26893 fn completions(
26894 &self,
26895 _excerpt_id: ExcerptId,
26896 buffer: &Entity<Buffer>,
26897 buffer_position: text::Anchor,
26898 options: CompletionContext,
26899 _window: &mut Window,
26900 cx: &mut Context<Editor>,
26901 ) -> Task<Result<Vec<CompletionResponse>>> {
26902 self.update(cx, |project, cx| {
26903 let task = project.completions(buffer, buffer_position, options, cx);
26904 cx.background_spawn(task)
26905 })
26906 }
26907
26908 fn resolve_completions(
26909 &self,
26910 buffer: Entity<Buffer>,
26911 completion_indices: Vec<usize>,
26912 completions: Rc<RefCell<Box<[Completion]>>>,
26913 cx: &mut Context<Editor>,
26914 ) -> Task<Result<bool>> {
26915 self.update(cx, |project, cx| {
26916 project.lsp_store().update(cx, |lsp_store, cx| {
26917 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26918 })
26919 })
26920 }
26921
26922 fn apply_additional_edits_for_completion(
26923 &self,
26924 buffer: Entity<Buffer>,
26925 completions: Rc<RefCell<Box<[Completion]>>>,
26926 completion_index: usize,
26927 push_to_history: bool,
26928 cx: &mut Context<Editor>,
26929 ) -> Task<Result<Option<language::Transaction>>> {
26930 self.update(cx, |project, cx| {
26931 project.lsp_store().update(cx, |lsp_store, cx| {
26932 lsp_store.apply_additional_edits_for_completion(
26933 buffer,
26934 completions,
26935 completion_index,
26936 push_to_history,
26937 cx,
26938 )
26939 })
26940 })
26941 }
26942
26943 fn is_completion_trigger(
26944 &self,
26945 buffer: &Entity<Buffer>,
26946 position: language::Anchor,
26947 text: &str,
26948 trigger_in_words: bool,
26949 cx: &mut Context<Editor>,
26950 ) -> bool {
26951 let mut chars = text.chars();
26952 let char = if let Some(char) = chars.next() {
26953 char
26954 } else {
26955 return false;
26956 };
26957 if chars.next().is_some() {
26958 return false;
26959 }
26960
26961 let buffer = buffer.read(cx);
26962 let snapshot = buffer.snapshot();
26963 let classifier = snapshot
26964 .char_classifier_at(position)
26965 .scope_context(Some(CharScopeContext::Completion));
26966 if trigger_in_words && classifier.is_word(char) {
26967 return true;
26968 }
26969
26970 buffer.completion_triggers().contains(text)
26971 }
26972
26973 fn show_snippets(&self) -> bool {
26974 true
26975 }
26976}
26977
26978impl SemanticsProvider for Entity<Project> {
26979 fn hover(
26980 &self,
26981 buffer: &Entity<Buffer>,
26982 position: text::Anchor,
26983 cx: &mut App,
26984 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26985 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26986 }
26987
26988 fn document_highlights(
26989 &self,
26990 buffer: &Entity<Buffer>,
26991 position: text::Anchor,
26992 cx: &mut App,
26993 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26994 Some(self.update(cx, |project, cx| {
26995 project.document_highlights(buffer, position, cx)
26996 }))
26997 }
26998
26999 fn definitions(
27000 &self,
27001 buffer: &Entity<Buffer>,
27002 position: text::Anchor,
27003 kind: GotoDefinitionKind,
27004 cx: &mut App,
27005 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27006 Some(self.update(cx, |project, cx| match kind {
27007 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27008 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27009 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27010 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27011 }))
27012 }
27013
27014 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27015 self.update(cx, |project, cx| {
27016 if project
27017 .active_debug_session(cx)
27018 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27019 {
27020 return true;
27021 }
27022
27023 buffer.update(cx, |buffer, cx| {
27024 project.any_language_server_supports_inlay_hints(buffer, cx)
27025 })
27026 })
27027 }
27028
27029 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27030 self.update(cx, |project, cx| {
27031 buffer.update(cx, |buffer, cx| {
27032 project.any_language_server_supports_semantic_tokens(buffer, cx)
27033 })
27034 })
27035 }
27036
27037 fn inline_values(
27038 &self,
27039 buffer_handle: Entity<Buffer>,
27040 range: Range<text::Anchor>,
27041 cx: &mut App,
27042 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27043 self.update(cx, |project, cx| {
27044 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27045
27046 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27047 })
27048 }
27049
27050 fn applicable_inlay_chunks(
27051 &self,
27052 buffer: &Entity<Buffer>,
27053 ranges: &[Range<text::Anchor>],
27054 cx: &mut App,
27055 ) -> Vec<Range<BufferRow>> {
27056 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27057 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27058 })
27059 }
27060
27061 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27062 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
27063 lsp_store.invalidate_inlay_hints(for_buffers)
27064 });
27065 }
27066
27067 fn inlay_hints(
27068 &self,
27069 invalidate: InvalidationStrategy,
27070 buffer: Entity<Buffer>,
27071 ranges: Vec<Range<text::Anchor>>,
27072 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27073 cx: &mut App,
27074 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27075 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27076 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27077 }))
27078 }
27079
27080 fn semantic_tokens(
27081 &self,
27082 buffer: Entity<Buffer>,
27083 refresh: Option<RefreshForServer>,
27084 cx: &mut App,
27085 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>> {
27086 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27087 lsp_store.semantic_tokens(buffer, refresh, cx)
27088 })
27089 }
27090
27091 fn range_for_rename(
27092 &self,
27093 buffer: &Entity<Buffer>,
27094 position: text::Anchor,
27095 cx: &mut App,
27096 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27097 Some(self.update(cx, |project, cx| {
27098 let buffer = buffer.clone();
27099 let task = project.prepare_rename(buffer.clone(), position, cx);
27100 cx.spawn(async move |_, cx| {
27101 Ok(match task.await? {
27102 PrepareRenameResponse::Success(range) => Some(range),
27103 PrepareRenameResponse::InvalidPosition => None,
27104 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27105 // Fallback on using TreeSitter info to determine identifier range
27106 buffer.read_with(cx, |buffer, _| {
27107 let snapshot = buffer.snapshot();
27108 let (range, kind) = snapshot.surrounding_word(position, None);
27109 if kind != Some(CharKind::Word) {
27110 return None;
27111 }
27112 Some(
27113 snapshot.anchor_before(range.start)
27114 ..snapshot.anchor_after(range.end),
27115 )
27116 })
27117 }
27118 })
27119 })
27120 }))
27121 }
27122
27123 fn perform_rename(
27124 &self,
27125 buffer: &Entity<Buffer>,
27126 position: text::Anchor,
27127 new_name: String,
27128 cx: &mut App,
27129 ) -> Option<Task<Result<ProjectTransaction>>> {
27130 Some(self.update(cx, |project, cx| {
27131 project.perform_rename(buffer.clone(), position, new_name, cx)
27132 }))
27133 }
27134}
27135
27136fn consume_contiguous_rows(
27137 contiguous_row_selections: &mut Vec<Selection<Point>>,
27138 selection: &Selection<Point>,
27139 display_map: &DisplaySnapshot,
27140 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27141) -> (MultiBufferRow, MultiBufferRow) {
27142 contiguous_row_selections.push(selection.clone());
27143 let start_row = starting_row(selection, display_map);
27144 let mut end_row = ending_row(selection, display_map);
27145
27146 while let Some(next_selection) = selections.peek() {
27147 if next_selection.start.row <= end_row.0 {
27148 end_row = ending_row(next_selection, display_map);
27149 contiguous_row_selections.push(selections.next().unwrap().clone());
27150 } else {
27151 break;
27152 }
27153 }
27154 (start_row, end_row)
27155}
27156
27157fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27158 if selection.start.column > 0 {
27159 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27160 } else {
27161 MultiBufferRow(selection.start.row)
27162 }
27163}
27164
27165fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27166 if next_selection.end.column > 0 || next_selection.is_empty() {
27167 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27168 } else {
27169 MultiBufferRow(next_selection.end.row)
27170 }
27171}
27172
27173impl EditorSnapshot {
27174 pub fn remote_selections_in_range<'a>(
27175 &'a self,
27176 range: &'a Range<Anchor>,
27177 collaboration_hub: &dyn CollaborationHub,
27178 cx: &'a App,
27179 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27180 let participant_names = collaboration_hub.user_names(cx);
27181 let participant_indices = collaboration_hub.user_participant_indices(cx);
27182 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27183 let collaborators_by_replica_id = collaborators_by_peer_id
27184 .values()
27185 .map(|collaborator| (collaborator.replica_id, collaborator))
27186 .collect::<HashMap<_, _>>();
27187 self.buffer_snapshot()
27188 .selections_in_range(range, false)
27189 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27190 if replica_id == ReplicaId::AGENT {
27191 Some(RemoteSelection {
27192 replica_id,
27193 selection,
27194 cursor_shape,
27195 line_mode,
27196 collaborator_id: CollaboratorId::Agent,
27197 user_name: Some("Agent".into()),
27198 color: cx.theme().players().agent(),
27199 })
27200 } else {
27201 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27202 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27203 let user_name = participant_names.get(&collaborator.user_id).cloned();
27204 Some(RemoteSelection {
27205 replica_id,
27206 selection,
27207 cursor_shape,
27208 line_mode,
27209 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27210 user_name,
27211 color: if let Some(index) = participant_index {
27212 cx.theme().players().color_for_participant(index.0)
27213 } else {
27214 cx.theme().players().absent()
27215 },
27216 })
27217 }
27218 })
27219 }
27220
27221 pub fn hunks_for_ranges(
27222 &self,
27223 ranges: impl IntoIterator<Item = Range<Point>>,
27224 ) -> Vec<MultiBufferDiffHunk> {
27225 let mut hunks = Vec::new();
27226 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27227 HashMap::default();
27228 for query_range in ranges {
27229 let query_rows =
27230 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27231 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27232 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27233 ) {
27234 // Include deleted hunks that are adjacent to the query range, because
27235 // otherwise they would be missed.
27236 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27237 if hunk.status().is_deleted() {
27238 intersects_range |= hunk.row_range.start == query_rows.end;
27239 intersects_range |= hunk.row_range.end == query_rows.start;
27240 }
27241 if intersects_range {
27242 if !processed_buffer_rows
27243 .entry(hunk.buffer_id)
27244 .or_default()
27245 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27246 {
27247 continue;
27248 }
27249 hunks.push(hunk);
27250 }
27251 }
27252 }
27253
27254 hunks
27255 }
27256
27257 fn display_diff_hunks_for_rows<'a>(
27258 &'a self,
27259 display_rows: Range<DisplayRow>,
27260 folded_buffers: &'a HashSet<BufferId>,
27261 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27262 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27263 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27264
27265 self.buffer_snapshot()
27266 .diff_hunks_in_range(buffer_start..buffer_end)
27267 .filter_map(|hunk| {
27268 if folded_buffers.contains(&hunk.buffer_id)
27269 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27270 {
27271 return None;
27272 }
27273
27274 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27275 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27276 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27277 let line_len = self.buffer_snapshot().line_len(last_row);
27278 Point::new(last_row.0, line_len)
27279 } else {
27280 Point::new(hunk.row_range.end.0, 0)
27281 };
27282
27283 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27284 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27285
27286 let display_hunk = if hunk_display_start.column() != 0 {
27287 DisplayDiffHunk::Folded {
27288 display_row: hunk_display_start.row(),
27289 }
27290 } else {
27291 let mut end_row = hunk_display_end.row();
27292 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27293 end_row.0 += 1;
27294 }
27295 let is_created_file = hunk.is_created_file();
27296
27297 DisplayDiffHunk::Unfolded {
27298 status: hunk.status(),
27299 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27300 ..hunk.diff_base_byte_range.end.0,
27301 word_diffs: hunk.word_diffs,
27302 display_row_range: hunk_display_start.row()..end_row,
27303 multi_buffer_range: Anchor::range_in_buffer(
27304 hunk.excerpt_id,
27305 hunk.buffer_range,
27306 ),
27307 is_created_file,
27308 }
27309 };
27310
27311 Some(display_hunk)
27312 })
27313 }
27314
27315 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27316 self.display_snapshot
27317 .buffer_snapshot()
27318 .language_at(position)
27319 }
27320
27321 pub fn is_focused(&self) -> bool {
27322 self.is_focused
27323 }
27324
27325 pub fn placeholder_text(&self) -> Option<String> {
27326 self.placeholder_display_snapshot
27327 .as_ref()
27328 .map(|display_map| display_map.text())
27329 }
27330
27331 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27332 self.scroll_anchor.scroll_position(&self.display_snapshot)
27333 }
27334
27335 pub fn gutter_dimensions(
27336 &self,
27337 font_id: FontId,
27338 font_size: Pixels,
27339 style: &EditorStyle,
27340 window: &mut Window,
27341 cx: &App,
27342 ) -> GutterDimensions {
27343 if self.show_gutter
27344 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27345 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27346 {
27347 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27348 matches!(
27349 ProjectSettings::get_global(cx).git.git_gutter,
27350 GitGutterSetting::TrackedFiles
27351 )
27352 });
27353 let gutter_settings = EditorSettings::get_global(cx).gutter;
27354 let show_line_numbers = self
27355 .show_line_numbers
27356 .unwrap_or(gutter_settings.line_numbers);
27357 let line_gutter_width = if show_line_numbers {
27358 // Avoid flicker-like gutter resizes when the line number gains another digit by
27359 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27360 let min_width_for_number_on_gutter =
27361 ch_advance * gutter_settings.min_line_number_digits as f32;
27362 self.max_line_number_width(style, window)
27363 .max(min_width_for_number_on_gutter)
27364 } else {
27365 0.0.into()
27366 };
27367
27368 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27369 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27370
27371 let git_blame_entries_width =
27372 self.git_blame_gutter_max_author_length
27373 .map(|max_author_length| {
27374 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27375 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27376
27377 /// The number of characters to dedicate to gaps and margins.
27378 const SPACING_WIDTH: usize = 4;
27379
27380 let max_char_count = max_author_length.min(renderer.max_author_length())
27381 + ::git::SHORT_SHA_LENGTH
27382 + MAX_RELATIVE_TIMESTAMP.len()
27383 + SPACING_WIDTH;
27384
27385 ch_advance * max_char_count
27386 });
27387
27388 let is_singleton = self.buffer_snapshot().is_singleton();
27389
27390 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27391 left_padding += if !is_singleton {
27392 ch_width * 4.0
27393 } else if show_runnables || show_breakpoints {
27394 ch_width * 3.0
27395 } else if show_git_gutter && show_line_numbers {
27396 ch_width * 2.0
27397 } else if show_git_gutter || show_line_numbers {
27398 ch_width
27399 } else {
27400 px(0.)
27401 };
27402
27403 let shows_folds = is_singleton && gutter_settings.folds;
27404
27405 let right_padding = if shows_folds && show_line_numbers {
27406 ch_width * 4.0
27407 } else if shows_folds || (!is_singleton && show_line_numbers) {
27408 ch_width * 3.0
27409 } else if show_line_numbers {
27410 ch_width
27411 } else {
27412 px(0.)
27413 };
27414
27415 GutterDimensions {
27416 left_padding,
27417 right_padding,
27418 width: line_gutter_width + left_padding + right_padding,
27419 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27420 git_blame_entries_width,
27421 }
27422 } else if self.offset_content {
27423 GutterDimensions::default_with_margin(font_id, font_size, cx)
27424 } else {
27425 GutterDimensions::default()
27426 }
27427 }
27428
27429 pub fn render_crease_toggle(
27430 &self,
27431 buffer_row: MultiBufferRow,
27432 row_contains_cursor: bool,
27433 editor: Entity<Editor>,
27434 window: &mut Window,
27435 cx: &mut App,
27436 ) -> Option<AnyElement> {
27437 let folded = self.is_line_folded(buffer_row);
27438 let mut is_foldable = false;
27439
27440 if let Some(crease) = self
27441 .crease_snapshot
27442 .query_row(buffer_row, self.buffer_snapshot())
27443 {
27444 is_foldable = true;
27445 match crease {
27446 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27447 if let Some(render_toggle) = render_toggle {
27448 let toggle_callback =
27449 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27450 if folded {
27451 editor.update(cx, |editor, cx| {
27452 editor.fold_at(buffer_row, window, cx)
27453 });
27454 } else {
27455 editor.update(cx, |editor, cx| {
27456 editor.unfold_at(buffer_row, window, cx)
27457 });
27458 }
27459 });
27460 return Some((render_toggle)(
27461 buffer_row,
27462 folded,
27463 toggle_callback,
27464 window,
27465 cx,
27466 ));
27467 }
27468 }
27469 }
27470 }
27471
27472 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27473
27474 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27475 Some(
27476 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27477 .toggle_state(folded)
27478 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27479 if folded {
27480 this.unfold_at(buffer_row, window, cx);
27481 } else {
27482 this.fold_at(buffer_row, window, cx);
27483 }
27484 }))
27485 .into_any_element(),
27486 )
27487 } else {
27488 None
27489 }
27490 }
27491
27492 pub fn render_crease_trailer(
27493 &self,
27494 buffer_row: MultiBufferRow,
27495 window: &mut Window,
27496 cx: &mut App,
27497 ) -> Option<AnyElement> {
27498 let folded = self.is_line_folded(buffer_row);
27499 if let Crease::Inline { render_trailer, .. } = self
27500 .crease_snapshot
27501 .query_row(buffer_row, self.buffer_snapshot())?
27502 {
27503 let render_trailer = render_trailer.as_ref()?;
27504 Some(render_trailer(buffer_row, folded, window, cx))
27505 } else {
27506 None
27507 }
27508 }
27509
27510 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27511 let digit_count = self.widest_line_number().ilog10() + 1;
27512 column_pixels(style, digit_count as usize, window)
27513 }
27514
27515 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27516 ///
27517 /// This is positive if `base` is before `line`.
27518 fn relative_line_delta(
27519 &self,
27520 current_selection_head: DisplayRow,
27521 first_visible_row: DisplayRow,
27522 consider_wrapped_lines: bool,
27523 ) -> i64 {
27524 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27525 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27526
27527 if consider_wrapped_lines {
27528 let wrap_snapshot = self.wrap_snapshot();
27529 let base_wrap_row = wrap_snapshot
27530 .make_wrap_point(current_selection_head, Bias::Left)
27531 .row();
27532 let wrap_row = wrap_snapshot
27533 .make_wrap_point(first_visible_row, Bias::Left)
27534 .row();
27535
27536 wrap_row.0 as i64 - base_wrap_row.0 as i64
27537 } else {
27538 let fold_snapshot = self.fold_snapshot();
27539 let base_fold_row = fold_snapshot
27540 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27541 .row();
27542 let fold_row = fold_snapshot
27543 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27544 .row();
27545
27546 fold_row as i64 - base_fold_row as i64
27547 }
27548 }
27549
27550 /// Returns the unsigned relative line number to display for each row in `rows`.
27551 ///
27552 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27553 pub fn calculate_relative_line_numbers(
27554 &self,
27555 rows: &Range<DisplayRow>,
27556 current_selection_head: DisplayRow,
27557 count_wrapped_lines: bool,
27558 ) -> HashMap<DisplayRow, u32> {
27559 let initial_offset =
27560 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27561
27562 self.row_infos(rows.start)
27563 .take(rows.len())
27564 .enumerate()
27565 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27566 .filter(|(_row, row_info)| {
27567 row_info.buffer_row.is_some()
27568 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27569 })
27570 .enumerate()
27571 .filter_map(|(i, (row, row_info))| {
27572 // We want to ensure here that the current line has absolute
27573 // numbering, even if we are in a soft-wrapped line. With the
27574 // exception that if we are in a deleted line, we should number this
27575 // relative with 0, as otherwise it would have no line number at all
27576 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27577
27578 (relative_line_number != 0
27579 || row_info
27580 .diff_status
27581 .is_some_and(|status| status.is_deleted()))
27582 .then_some((row, relative_line_number))
27583 })
27584 .collect()
27585 }
27586}
27587
27588pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27589 let font_size = style.text.font_size.to_pixels(window.rem_size());
27590 let layout = window.text_system().shape_line(
27591 SharedString::from(" ".repeat(column)),
27592 font_size,
27593 &[TextRun {
27594 len: column,
27595 font: style.text.font(),
27596 color: Hsla::default(),
27597 ..Default::default()
27598 }],
27599 None,
27600 );
27601
27602 layout.width
27603}
27604
27605impl Deref for EditorSnapshot {
27606 type Target = DisplaySnapshot;
27607
27608 fn deref(&self) -> &Self::Target {
27609 &self.display_snapshot
27610 }
27611}
27612
27613#[derive(Clone, Debug, PartialEq, Eq)]
27614pub enum EditorEvent {
27615 /// Emitted when the stored review comments change (added, removed, or updated).
27616 ReviewCommentsChanged {
27617 /// The new total count of review comments.
27618 total_count: usize,
27619 },
27620 InputIgnored {
27621 text: Arc<str>,
27622 },
27623 InputHandled {
27624 utf16_range_to_replace: Option<Range<isize>>,
27625 text: Arc<str>,
27626 },
27627 ExcerptsAdded {
27628 buffer: Entity<Buffer>,
27629 predecessor: ExcerptId,
27630 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27631 },
27632 ExcerptsRemoved {
27633 ids: Vec<ExcerptId>,
27634 removed_buffer_ids: Vec<BufferId>,
27635 },
27636 BufferFoldToggled {
27637 ids: Vec<ExcerptId>,
27638 folded: bool,
27639 },
27640 ExcerptsEdited {
27641 ids: Vec<ExcerptId>,
27642 },
27643 ExcerptsExpanded {
27644 ids: Vec<ExcerptId>,
27645 },
27646 ExpandExcerptsRequested {
27647 excerpt_ids: Vec<ExcerptId>,
27648 lines: u32,
27649 direction: ExpandExcerptDirection,
27650 },
27651 StageOrUnstageRequested {
27652 stage: bool,
27653 hunks: Vec<MultiBufferDiffHunk>,
27654 },
27655 OpenExcerptsRequested {
27656 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27657 split: bool,
27658 },
27659 RestoreRequested {
27660 hunks: Vec<MultiBufferDiffHunk>,
27661 },
27662 BufferEdited,
27663 Edited {
27664 transaction_id: clock::Lamport,
27665 },
27666 Reparsed(BufferId),
27667 Focused,
27668 FocusedIn,
27669 Blurred,
27670 DirtyChanged,
27671 Saved,
27672 TitleChanged,
27673 SelectionsChanged {
27674 local: bool,
27675 },
27676 ScrollPositionChanged {
27677 local: bool,
27678 autoscroll: bool,
27679 },
27680 TransactionUndone {
27681 transaction_id: clock::Lamport,
27682 },
27683 TransactionBegun {
27684 transaction_id: clock::Lamport,
27685 },
27686 CursorShapeChanged,
27687 BreadcrumbsChanged,
27688 PushedToNavHistory {
27689 anchor: Anchor,
27690 is_deactivate: bool,
27691 },
27692}
27693
27694impl EventEmitter<EditorEvent> for Editor {}
27695
27696impl Focusable for Editor {
27697 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27698 self.focus_handle.clone()
27699 }
27700}
27701
27702impl Render for Editor {
27703 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27704 EditorElement::new(&cx.entity(), self.create_style(cx))
27705 }
27706}
27707
27708impl EntityInputHandler for Editor {
27709 fn text_for_range(
27710 &mut self,
27711 range_utf16: Range<usize>,
27712 adjusted_range: &mut Option<Range<usize>>,
27713 _: &mut Window,
27714 cx: &mut Context<Self>,
27715 ) -> Option<String> {
27716 let snapshot = self.buffer.read(cx).read(cx);
27717 let start = snapshot.clip_offset_utf16(
27718 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27719 Bias::Left,
27720 );
27721 let end = snapshot.clip_offset_utf16(
27722 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27723 Bias::Right,
27724 );
27725 if (start.0.0..end.0.0) != range_utf16 {
27726 adjusted_range.replace(start.0.0..end.0.0);
27727 }
27728 Some(snapshot.text_for_range(start..end).collect())
27729 }
27730
27731 fn selected_text_range(
27732 &mut self,
27733 ignore_disabled_input: bool,
27734 _: &mut Window,
27735 cx: &mut Context<Self>,
27736 ) -> Option<UTF16Selection> {
27737 // Prevent the IME menu from appearing when holding down an alphabetic key
27738 // while input is disabled.
27739 if !ignore_disabled_input && !self.input_enabled {
27740 return None;
27741 }
27742
27743 let selection = self
27744 .selections
27745 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27746 let range = selection.range();
27747
27748 Some(UTF16Selection {
27749 range: range.start.0.0..range.end.0.0,
27750 reversed: selection.reversed,
27751 })
27752 }
27753
27754 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27755 let snapshot = self.buffer.read(cx).read(cx);
27756 let range = self
27757 .text_highlights(HighlightKey::InputComposition, cx)?
27758 .1
27759 .first()?;
27760 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27761 }
27762
27763 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27764 self.clear_highlights(HighlightKey::InputComposition, cx);
27765 self.ime_transaction.take();
27766 }
27767
27768 fn replace_text_in_range(
27769 &mut self,
27770 range_utf16: Option<Range<usize>>,
27771 text: &str,
27772 window: &mut Window,
27773 cx: &mut Context<Self>,
27774 ) {
27775 if !self.input_enabled {
27776 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27777 return;
27778 }
27779
27780 self.transact(window, cx, |this, window, cx| {
27781 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27782 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27783 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27784 Some(this.selection_replacement_ranges(range_utf16, cx))
27785 } else {
27786 this.marked_text_ranges(cx)
27787 };
27788
27789 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27790 let newest_selection_id = this.selections.newest_anchor().id;
27791 this.selections
27792 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27793 .iter()
27794 .zip(ranges_to_replace.iter())
27795 .find_map(|(selection, range)| {
27796 if selection.id == newest_selection_id {
27797 Some(
27798 (range.start.0.0 as isize - selection.head().0.0 as isize)
27799 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27800 )
27801 } else {
27802 None
27803 }
27804 })
27805 });
27806
27807 cx.emit(EditorEvent::InputHandled {
27808 utf16_range_to_replace: range_to_replace,
27809 text: text.into(),
27810 });
27811
27812 if let Some(new_selected_ranges) = new_selected_ranges {
27813 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27814 selections.select_ranges(new_selected_ranges)
27815 });
27816 this.backspace(&Default::default(), window, cx);
27817 }
27818
27819 this.handle_input(text, window, cx);
27820 });
27821
27822 if let Some(transaction) = self.ime_transaction {
27823 self.buffer.update(cx, |buffer, cx| {
27824 buffer.group_until_transaction(transaction, cx);
27825 });
27826 }
27827
27828 self.unmark_text(window, cx);
27829 }
27830
27831 fn replace_and_mark_text_in_range(
27832 &mut self,
27833 range_utf16: Option<Range<usize>>,
27834 text: &str,
27835 new_selected_range_utf16: Option<Range<usize>>,
27836 window: &mut Window,
27837 cx: &mut Context<Self>,
27838 ) {
27839 if !self.input_enabled {
27840 return;
27841 }
27842
27843 let transaction = self.transact(window, cx, |this, window, cx| {
27844 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27845 let snapshot = this.buffer.read(cx).read(cx);
27846 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27847 for marked_range in &mut marked_ranges {
27848 marked_range.end = marked_range.start + relative_range_utf16.end;
27849 marked_range.start += relative_range_utf16.start;
27850 marked_range.start =
27851 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27852 marked_range.end =
27853 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27854 }
27855 }
27856 Some(marked_ranges)
27857 } else if let Some(range_utf16) = range_utf16 {
27858 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27859 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27860 Some(this.selection_replacement_ranges(range_utf16, cx))
27861 } else {
27862 None
27863 };
27864
27865 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27866 let newest_selection_id = this.selections.newest_anchor().id;
27867 this.selections
27868 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27869 .iter()
27870 .zip(ranges_to_replace.iter())
27871 .find_map(|(selection, range)| {
27872 if selection.id == newest_selection_id {
27873 Some(
27874 (range.start.0.0 as isize - selection.head().0.0 as isize)
27875 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27876 )
27877 } else {
27878 None
27879 }
27880 })
27881 });
27882
27883 cx.emit(EditorEvent::InputHandled {
27884 utf16_range_to_replace: range_to_replace,
27885 text: text.into(),
27886 });
27887
27888 if let Some(ranges) = ranges_to_replace {
27889 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27890 s.select_ranges(ranges)
27891 });
27892 }
27893
27894 let marked_ranges = {
27895 let snapshot = this.buffer.read(cx).read(cx);
27896 this.selections
27897 .disjoint_anchors_arc()
27898 .iter()
27899 .map(|selection| {
27900 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27901 })
27902 .collect::<Vec<_>>()
27903 };
27904
27905 if text.is_empty() {
27906 this.unmark_text(window, cx);
27907 } else {
27908 this.highlight_text(
27909 HighlightKey::InputComposition,
27910 marked_ranges.clone(),
27911 HighlightStyle {
27912 underline: Some(UnderlineStyle {
27913 thickness: px(1.),
27914 color: None,
27915 wavy: false,
27916 }),
27917 ..Default::default()
27918 },
27919 cx,
27920 );
27921 }
27922
27923 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27924 let use_autoclose = this.use_autoclose;
27925 let use_auto_surround = this.use_auto_surround;
27926 this.set_use_autoclose(false);
27927 this.set_use_auto_surround(false);
27928 this.handle_input(text, window, cx);
27929 this.set_use_autoclose(use_autoclose);
27930 this.set_use_auto_surround(use_auto_surround);
27931
27932 if let Some(new_selected_range) = new_selected_range_utf16 {
27933 let snapshot = this.buffer.read(cx).read(cx);
27934 let new_selected_ranges = marked_ranges
27935 .into_iter()
27936 .map(|marked_range| {
27937 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27938 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27939 insertion_start.0 + new_selected_range.start,
27940 ));
27941 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27942 insertion_start.0 + new_selected_range.end,
27943 ));
27944 snapshot.clip_offset_utf16(new_start, Bias::Left)
27945 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27946 })
27947 .collect::<Vec<_>>();
27948
27949 drop(snapshot);
27950 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27951 selections.select_ranges(new_selected_ranges)
27952 });
27953 }
27954 });
27955
27956 self.ime_transaction = self.ime_transaction.or(transaction);
27957 if let Some(transaction) = self.ime_transaction {
27958 self.buffer.update(cx, |buffer, cx| {
27959 buffer.group_until_transaction(transaction, cx);
27960 });
27961 }
27962
27963 if self
27964 .text_highlights(HighlightKey::InputComposition, cx)
27965 .is_none()
27966 {
27967 self.ime_transaction.take();
27968 }
27969 }
27970
27971 fn bounds_for_range(
27972 &mut self,
27973 range_utf16: Range<usize>,
27974 element_bounds: gpui::Bounds<Pixels>,
27975 window: &mut Window,
27976 cx: &mut Context<Self>,
27977 ) -> Option<gpui::Bounds<Pixels>> {
27978 let text_layout_details = self.text_layout_details(window, cx);
27979 let CharacterDimensions {
27980 em_width,
27981 em_advance,
27982 line_height,
27983 } = self.character_dimensions(window, cx);
27984
27985 let snapshot = self.snapshot(window, cx);
27986 let scroll_position = snapshot.scroll_position();
27987 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27988
27989 let start =
27990 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27991 let x = Pixels::from(
27992 ScrollOffset::from(
27993 snapshot.x_for_display_point(start, &text_layout_details)
27994 + self.gutter_dimensions.full_width(),
27995 ) - scroll_left,
27996 );
27997 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27998
27999 Some(Bounds {
28000 origin: element_bounds.origin + point(x, y),
28001 size: size(em_width, line_height),
28002 })
28003 }
28004
28005 fn character_index_for_point(
28006 &mut self,
28007 point: gpui::Point<Pixels>,
28008 _window: &mut Window,
28009 _cx: &mut Context<Self>,
28010 ) -> Option<usize> {
28011 let position_map = self.last_position_map.as_ref()?;
28012 if !position_map.text_hitbox.contains(&point) {
28013 return None;
28014 }
28015 let display_point = position_map.point_for_position(point).previous_valid;
28016 let anchor = position_map
28017 .snapshot
28018 .display_point_to_anchor(display_point, Bias::Left);
28019 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28020 Some(utf16_offset.0.0)
28021 }
28022
28023 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28024 self.input_enabled
28025 }
28026}
28027
28028trait SelectionExt {
28029 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28030 fn spanned_rows(
28031 &self,
28032 include_end_if_at_line_start: bool,
28033 map: &DisplaySnapshot,
28034 ) -> Range<MultiBufferRow>;
28035}
28036
28037impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28038 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28039 let start = self
28040 .start
28041 .to_point(map.buffer_snapshot())
28042 .to_display_point(map);
28043 let end = self
28044 .end
28045 .to_point(map.buffer_snapshot())
28046 .to_display_point(map);
28047 if self.reversed {
28048 end..start
28049 } else {
28050 start..end
28051 }
28052 }
28053
28054 fn spanned_rows(
28055 &self,
28056 include_end_if_at_line_start: bool,
28057 map: &DisplaySnapshot,
28058 ) -> Range<MultiBufferRow> {
28059 let start = self.start.to_point(map.buffer_snapshot());
28060 let mut end = self.end.to_point(map.buffer_snapshot());
28061 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28062 end.row -= 1;
28063 }
28064
28065 let buffer_start = map.prev_line_boundary(start).0;
28066 let buffer_end = map.next_line_boundary(end).0;
28067 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28068 }
28069}
28070
28071impl<T: InvalidationRegion> InvalidationStack<T> {
28072 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28073 where
28074 S: Clone + ToOffset,
28075 {
28076 while let Some(region) = self.last() {
28077 let all_selections_inside_invalidation_ranges =
28078 if selections.len() == region.ranges().len() {
28079 selections
28080 .iter()
28081 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28082 .all(|(selection, invalidation_range)| {
28083 let head = selection.head().to_offset(buffer);
28084 invalidation_range.start <= head && invalidation_range.end >= head
28085 })
28086 } else {
28087 false
28088 };
28089
28090 if all_selections_inside_invalidation_ranges {
28091 break;
28092 } else {
28093 self.pop();
28094 }
28095 }
28096 }
28097}
28098
28099#[derive(Clone)]
28100struct ErasedEditorImpl(Entity<Editor>);
28101
28102impl ui_input::ErasedEditor for ErasedEditorImpl {
28103 fn text(&self, cx: &App) -> String {
28104 self.0.read(cx).text(cx)
28105 }
28106
28107 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28108 self.0.update(cx, |this, cx| {
28109 this.set_text(text, window, cx);
28110 })
28111 }
28112
28113 fn clear(&self, window: &mut Window, cx: &mut App) {
28114 self.0.update(cx, |this, cx| this.clear(window, cx));
28115 }
28116
28117 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28118 self.0.update(cx, |this, cx| {
28119 this.set_placeholder_text(text, window, cx);
28120 });
28121 }
28122
28123 fn focus_handle(&self, cx: &App) -> FocusHandle {
28124 self.0.read(cx).focus_handle(cx)
28125 }
28126
28127 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28128 let settings = ThemeSettings::get_global(cx);
28129 let theme_color = cx.theme().colors();
28130
28131 let text_style = TextStyle {
28132 font_family: settings.ui_font.family.clone(),
28133 font_features: settings.ui_font.features.clone(),
28134 font_size: rems(0.875).into(),
28135 font_weight: settings.buffer_font.weight,
28136 font_style: FontStyle::Normal,
28137 line_height: relative(1.2),
28138 color: theme_color.text,
28139 ..Default::default()
28140 };
28141 let editor_style = EditorStyle {
28142 background: theme_color.ghost_element_background,
28143 local_player: cx.theme().players().local(),
28144 syntax: cx.theme().syntax().clone(),
28145 text: text_style,
28146 ..Default::default()
28147 };
28148 EditorElement::new(&self.0, editor_style).into_any()
28149 }
28150
28151 fn as_any(&self) -> &dyn Any {
28152 &self.0
28153 }
28154
28155 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28156 self.0.update(cx, |editor, cx| {
28157 let editor_offset = editor.buffer().read(cx).len(cx);
28158 editor.change_selections(
28159 SelectionEffects::scroll(Autoscroll::Next),
28160 window,
28161 cx,
28162 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28163 );
28164 });
28165 }
28166
28167 fn subscribe(
28168 &self,
28169 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28170 window: &mut Window,
28171 cx: &mut App,
28172 ) -> Subscription {
28173 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28174 let event = match event {
28175 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28176 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28177 _ => return,
28178 };
28179 (callback)(event, window, cx);
28180 })
28181 }
28182
28183 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28184 self.0.update(cx, |editor, cx| {
28185 editor.set_masked(masked, cx);
28186 });
28187 }
28188}
28189impl<T> Default for InvalidationStack<T> {
28190 fn default() -> Self {
28191 Self(Default::default())
28192 }
28193}
28194
28195impl<T> Deref for InvalidationStack<T> {
28196 type Target = Vec<T>;
28197
28198 fn deref(&self) -> &Self::Target {
28199 &self.0
28200 }
28201}
28202
28203impl<T> DerefMut for InvalidationStack<T> {
28204 fn deref_mut(&mut self) -> &mut Self::Target {
28205 &mut self.0
28206 }
28207}
28208
28209impl InvalidationRegion for SnippetState {
28210 fn ranges(&self) -> &[Range<Anchor>] {
28211 &self.ranges[self.active_index]
28212 }
28213}
28214
28215fn edit_prediction_edit_text(
28216 current_snapshot: &BufferSnapshot,
28217 edits: &[(Range<Anchor>, impl AsRef<str>)],
28218 edit_preview: &EditPreview,
28219 include_deletions: bool,
28220 cx: &App,
28221) -> HighlightedText {
28222 let edits = edits
28223 .iter()
28224 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28225 .collect::<Vec<_>>();
28226
28227 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28228}
28229
28230fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28231 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
28232 // Just show the raw edit text with basic styling
28233 let mut text = String::new();
28234 let mut highlights = Vec::new();
28235
28236 let insertion_highlight_style = HighlightStyle {
28237 color: Some(cx.theme().colors().text),
28238 ..Default::default()
28239 };
28240
28241 for (_, edit_text) in edits {
28242 let start_offset = text.len();
28243 text.push_str(edit_text);
28244 let end_offset = text.len();
28245
28246 if start_offset < end_offset {
28247 highlights.push((start_offset..end_offset, insertion_highlight_style));
28248 }
28249 }
28250
28251 HighlightedText {
28252 text: text.into(),
28253 highlights,
28254 }
28255}
28256
28257pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28258 match severity {
28259 lsp::DiagnosticSeverity::ERROR => colors.error,
28260 lsp::DiagnosticSeverity::WARNING => colors.warning,
28261 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28262 lsp::DiagnosticSeverity::HINT => colors.info,
28263 _ => colors.ignored,
28264 }
28265}
28266
28267pub fn styled_runs_for_code_label<'a>(
28268 label: &'a CodeLabel,
28269 syntax_theme: &'a theme::SyntaxTheme,
28270 local_player: &'a theme::PlayerColor,
28271) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28272 let fade_out = HighlightStyle {
28273 fade_out: Some(0.35),
28274 ..Default::default()
28275 };
28276
28277 let mut prev_end = label.filter_range.end;
28278 label
28279 .runs
28280 .iter()
28281 .enumerate()
28282 .flat_map(move |(ix, (range, highlight_id))| {
28283 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28284 HighlightStyle {
28285 color: Some(local_player.cursor),
28286 ..Default::default()
28287 }
28288 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28289 HighlightStyle {
28290 background_color: Some(local_player.selection),
28291 ..Default::default()
28292 }
28293 } else if let Some(style) = highlight_id.style(syntax_theme) {
28294 style
28295 } else {
28296 return Default::default();
28297 };
28298 let muted_style = style.highlight(fade_out);
28299
28300 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28301 if range.start >= label.filter_range.end {
28302 if range.start > prev_end {
28303 runs.push((prev_end..range.start, fade_out));
28304 }
28305 runs.push((range.clone(), muted_style));
28306 } else if range.end <= label.filter_range.end {
28307 runs.push((range.clone(), style));
28308 } else {
28309 runs.push((range.start..label.filter_range.end, style));
28310 runs.push((label.filter_range.end..range.end, muted_style));
28311 }
28312 prev_end = cmp::max(prev_end, range.end);
28313
28314 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28315 runs.push((prev_end..label.text.len(), fade_out));
28316 }
28317
28318 runs
28319 })
28320}
28321
28322pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28323 let mut prev_index = 0;
28324 let mut prev_codepoint: Option<char> = None;
28325 text.char_indices()
28326 .chain([(text.len(), '\0')])
28327 .filter_map(move |(index, codepoint)| {
28328 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28329 let is_boundary = index == text.len()
28330 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28331 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28332 if is_boundary {
28333 let chunk = &text[prev_index..index];
28334 prev_index = index;
28335 Some(chunk)
28336 } else {
28337 None
28338 }
28339 })
28340}
28341
28342/// Given a string of text immediately before the cursor, iterates over possible
28343/// strings a snippet could match to. More precisely: returns an iterator over
28344/// suffixes of `text` created by splitting at word boundaries (before & after
28345/// every non-word character).
28346///
28347/// Shorter suffixes are returned first.
28348pub(crate) fn snippet_candidate_suffixes(
28349 text: &str,
28350 is_word_char: impl Fn(char) -> bool,
28351) -> impl std::iter::Iterator<Item = &str> {
28352 let mut prev_index = text.len();
28353 let mut prev_codepoint = None;
28354 text.char_indices()
28355 .rev()
28356 .chain([(0, '\0')])
28357 .filter_map(move |(index, codepoint)| {
28358 let prev_index = std::mem::replace(&mut prev_index, index);
28359 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28360 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28361 None
28362 } else {
28363 let chunk = &text[prev_index..]; // go to end of string
28364 Some(chunk)
28365 }
28366 })
28367}
28368
28369pub trait RangeToAnchorExt: Sized {
28370 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28371
28372 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28373 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28374 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28375 }
28376}
28377
28378impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28379 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28380 let start_offset = self.start.to_offset(snapshot);
28381 let end_offset = self.end.to_offset(snapshot);
28382 if start_offset == end_offset {
28383 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28384 } else {
28385 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28386 }
28387 }
28388}
28389
28390pub trait RowExt {
28391 fn as_f64(&self) -> f64;
28392
28393 fn next_row(&self) -> Self;
28394
28395 fn previous_row(&self) -> Self;
28396
28397 fn minus(&self, other: Self) -> u32;
28398}
28399
28400impl RowExt for DisplayRow {
28401 fn as_f64(&self) -> f64 {
28402 self.0 as _
28403 }
28404
28405 fn next_row(&self) -> Self {
28406 Self(self.0 + 1)
28407 }
28408
28409 fn previous_row(&self) -> Self {
28410 Self(self.0.saturating_sub(1))
28411 }
28412
28413 fn minus(&self, other: Self) -> u32 {
28414 self.0 - other.0
28415 }
28416}
28417
28418impl RowExt for MultiBufferRow {
28419 fn as_f64(&self) -> f64 {
28420 self.0 as _
28421 }
28422
28423 fn next_row(&self) -> Self {
28424 Self(self.0 + 1)
28425 }
28426
28427 fn previous_row(&self) -> Self {
28428 Self(self.0.saturating_sub(1))
28429 }
28430
28431 fn minus(&self, other: Self) -> u32 {
28432 self.0 - other.0
28433 }
28434}
28435
28436trait RowRangeExt {
28437 type Row;
28438
28439 fn len(&self) -> usize;
28440
28441 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28442}
28443
28444impl RowRangeExt for Range<MultiBufferRow> {
28445 type Row = MultiBufferRow;
28446
28447 fn len(&self) -> usize {
28448 (self.end.0 - self.start.0) as usize
28449 }
28450
28451 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28452 (self.start.0..self.end.0).map(MultiBufferRow)
28453 }
28454}
28455
28456impl RowRangeExt for Range<DisplayRow> {
28457 type Row = DisplayRow;
28458
28459 fn len(&self) -> usize {
28460 (self.end.0 - self.start.0) as usize
28461 }
28462
28463 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28464 (self.start.0..self.end.0).map(DisplayRow)
28465 }
28466}
28467
28468/// If select range has more than one line, we
28469/// just point the cursor to range.start.
28470fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28471 if range.start.row == range.end.row {
28472 range
28473 } else {
28474 range.start..range.start
28475 }
28476}
28477pub struct KillRing(ClipboardItem);
28478impl Global for KillRing {}
28479
28480const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28481
28482enum BreakpointPromptEditAction {
28483 Log,
28484 Condition,
28485 HitCondition,
28486}
28487
28488struct BreakpointPromptEditor {
28489 pub(crate) prompt: Entity<Editor>,
28490 editor: WeakEntity<Editor>,
28491 breakpoint_anchor: Anchor,
28492 breakpoint: Breakpoint,
28493 edit_action: BreakpointPromptEditAction,
28494 block_ids: HashSet<CustomBlockId>,
28495 editor_margins: Arc<Mutex<EditorMargins>>,
28496 _subscriptions: Vec<Subscription>,
28497}
28498
28499impl BreakpointPromptEditor {
28500 const MAX_LINES: u8 = 4;
28501
28502 fn new(
28503 editor: WeakEntity<Editor>,
28504 breakpoint_anchor: Anchor,
28505 breakpoint: Breakpoint,
28506 edit_action: BreakpointPromptEditAction,
28507 window: &mut Window,
28508 cx: &mut Context<Self>,
28509 ) -> Self {
28510 let base_text = match edit_action {
28511 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28512 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28513 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28514 }
28515 .map(|msg| msg.to_string())
28516 .unwrap_or_default();
28517
28518 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28519 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28520
28521 let prompt = cx.new(|cx| {
28522 let mut prompt = Editor::new(
28523 EditorMode::AutoHeight {
28524 min_lines: 1,
28525 max_lines: Some(Self::MAX_LINES as usize),
28526 },
28527 buffer,
28528 None,
28529 window,
28530 cx,
28531 );
28532 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28533 prompt.set_show_cursor_when_unfocused(false, cx);
28534 prompt.set_placeholder_text(
28535 match edit_action {
28536 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28537 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28538 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28539 },
28540 window,
28541 cx,
28542 );
28543
28544 prompt
28545 });
28546
28547 Self {
28548 prompt,
28549 editor,
28550 breakpoint_anchor,
28551 breakpoint,
28552 edit_action,
28553 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28554 block_ids: Default::default(),
28555 _subscriptions: vec![],
28556 }
28557 }
28558
28559 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28560 self.block_ids.extend(block_ids)
28561 }
28562
28563 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28564 if let Some(editor) = self.editor.upgrade() {
28565 let message = self
28566 .prompt
28567 .read(cx)
28568 .buffer
28569 .read(cx)
28570 .as_singleton()
28571 .expect("A multi buffer in breakpoint prompt isn't possible")
28572 .read(cx)
28573 .as_rope()
28574 .to_string();
28575
28576 editor.update(cx, |editor, cx| {
28577 editor.edit_breakpoint_at_anchor(
28578 self.breakpoint_anchor,
28579 self.breakpoint.clone(),
28580 match self.edit_action {
28581 BreakpointPromptEditAction::Log => {
28582 BreakpointEditAction::EditLogMessage(message.into())
28583 }
28584 BreakpointPromptEditAction::Condition => {
28585 BreakpointEditAction::EditCondition(message.into())
28586 }
28587 BreakpointPromptEditAction::HitCondition => {
28588 BreakpointEditAction::EditHitCondition(message.into())
28589 }
28590 },
28591 cx,
28592 );
28593
28594 editor.remove_blocks(self.block_ids.clone(), None, cx);
28595 cx.focus_self(window);
28596 });
28597 }
28598 }
28599
28600 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28601 self.editor
28602 .update(cx, |editor, cx| {
28603 editor.remove_blocks(self.block_ids.clone(), None, cx);
28604 window.focus(&editor.focus_handle, cx);
28605 })
28606 .log_err();
28607 }
28608
28609 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28610 let settings = ThemeSettings::get_global(cx);
28611 let text_style = TextStyle {
28612 color: if self.prompt.read(cx).read_only(cx) {
28613 cx.theme().colors().text_disabled
28614 } else {
28615 cx.theme().colors().text
28616 },
28617 font_family: settings.buffer_font.family.clone(),
28618 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28619 font_size: settings.buffer_font_size(cx).into(),
28620 font_weight: settings.buffer_font.weight,
28621 line_height: relative(settings.buffer_line_height.value()),
28622 ..Default::default()
28623 };
28624 EditorElement::new(
28625 &self.prompt,
28626 EditorStyle {
28627 background: cx.theme().colors().editor_background,
28628 local_player: cx.theme().players().local(),
28629 text: text_style,
28630 ..Default::default()
28631 },
28632 )
28633 }
28634}
28635
28636impl Render for BreakpointPromptEditor {
28637 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28638 let editor_margins = *self.editor_margins.lock();
28639 let gutter_dimensions = editor_margins.gutter;
28640 h_flex()
28641 .key_context("Editor")
28642 .bg(cx.theme().colors().editor_background)
28643 .border_y_1()
28644 .border_color(cx.theme().status().info_border)
28645 .size_full()
28646 .py(window.line_height() / 2.5)
28647 .on_action(cx.listener(Self::confirm))
28648 .on_action(cx.listener(Self::cancel))
28649 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28650 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28651 }
28652}
28653
28654impl Focusable for BreakpointPromptEditor {
28655 fn focus_handle(&self, cx: &App) -> FocusHandle {
28656 self.prompt.focus_handle(cx)
28657 }
28658}
28659
28660fn all_edits_insertions_or_deletions(
28661 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28662 snapshot: &MultiBufferSnapshot,
28663) -> bool {
28664 let mut all_insertions = true;
28665 let mut all_deletions = true;
28666
28667 for (range, new_text) in edits.iter() {
28668 let range_is_empty = range.to_offset(snapshot).is_empty();
28669 let text_is_empty = new_text.is_empty();
28670
28671 if range_is_empty != text_is_empty {
28672 if range_is_empty {
28673 all_deletions = false;
28674 } else {
28675 all_insertions = false;
28676 }
28677 } else {
28678 return false;
28679 }
28680
28681 if !all_insertions && !all_deletions {
28682 return false;
28683 }
28684 }
28685 all_insertions || all_deletions
28686}
28687
28688struct MissingEditPredictionKeybindingTooltip;
28689
28690impl Render for MissingEditPredictionKeybindingTooltip {
28691 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28692 ui::tooltip_container(cx, |container, cx| {
28693 container
28694 .flex_shrink_0()
28695 .max_w_80()
28696 .min_h(rems_from_px(124.))
28697 .justify_between()
28698 .child(
28699 v_flex()
28700 .flex_1()
28701 .text_ui_sm(cx)
28702 .child(Label::new("Conflict with Accept Keybinding"))
28703 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28704 )
28705 .child(
28706 h_flex()
28707 .pb_1()
28708 .gap_1()
28709 .items_end()
28710 .w_full()
28711 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28712 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28713 }))
28714 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28715 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28716 })),
28717 )
28718 })
28719 }
28720}
28721
28722#[derive(Debug, Clone, Copy, PartialEq)]
28723pub struct LineHighlight {
28724 pub background: Background,
28725 pub border: Option<gpui::Hsla>,
28726 pub include_gutter: bool,
28727 pub type_id: Option<TypeId>,
28728}
28729
28730struct LineManipulationResult {
28731 pub new_text: String,
28732 pub line_count_before: usize,
28733 pub line_count_after: usize,
28734}
28735
28736fn render_diff_hunk_controls(
28737 row: u32,
28738 status: &DiffHunkStatus,
28739 hunk_range: Range<Anchor>,
28740 is_created_file: bool,
28741 line_height: Pixels,
28742 editor: &Entity<Editor>,
28743 _window: &mut Window,
28744 cx: &mut App,
28745) -> AnyElement {
28746 h_flex()
28747 .h(line_height)
28748 .mr_1()
28749 .gap_1()
28750 .px_0p5()
28751 .pb_1()
28752 .border_x_1()
28753 .border_b_1()
28754 .border_color(cx.theme().colors().border_variant)
28755 .rounded_b_lg()
28756 .bg(cx.theme().colors().editor_background)
28757 .gap_1()
28758 .block_mouse_except_scroll()
28759 .shadow_md()
28760 .child(if status.has_secondary_hunk() {
28761 Button::new(("stage", row as u64), "Stage")
28762 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28763 .tooltip({
28764 let focus_handle = editor.focus_handle(cx);
28765 move |_window, cx| {
28766 Tooltip::for_action_in(
28767 "Stage Hunk",
28768 &::git::ToggleStaged,
28769 &focus_handle,
28770 cx,
28771 )
28772 }
28773 })
28774 .on_click({
28775 let editor = editor.clone();
28776 move |_event, _window, cx| {
28777 editor.update(cx, |editor, cx| {
28778 editor.stage_or_unstage_diff_hunks(
28779 true,
28780 vec![hunk_range.start..hunk_range.start],
28781 cx,
28782 );
28783 });
28784 }
28785 })
28786 } else {
28787 Button::new(("unstage", row as u64), "Unstage")
28788 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28789 .tooltip({
28790 let focus_handle = editor.focus_handle(cx);
28791 move |_window, cx| {
28792 Tooltip::for_action_in(
28793 "Unstage Hunk",
28794 &::git::ToggleStaged,
28795 &focus_handle,
28796 cx,
28797 )
28798 }
28799 })
28800 .on_click({
28801 let editor = editor.clone();
28802 move |_event, _window, cx| {
28803 editor.update(cx, |editor, cx| {
28804 editor.stage_or_unstage_diff_hunks(
28805 false,
28806 vec![hunk_range.start..hunk_range.start],
28807 cx,
28808 );
28809 });
28810 }
28811 })
28812 })
28813 .child(
28814 Button::new(("restore", row as u64), "Restore")
28815 .tooltip({
28816 let focus_handle = editor.focus_handle(cx);
28817 move |_window, cx| {
28818 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28819 }
28820 })
28821 .on_click({
28822 let editor = editor.clone();
28823 move |_event, window, cx| {
28824 editor.update(cx, |editor, cx| {
28825 let snapshot = editor.snapshot(window, cx);
28826 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28827 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28828 });
28829 }
28830 })
28831 .disabled(is_created_file),
28832 )
28833 .when(
28834 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28835 |el| {
28836 el.child(
28837 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28838 .shape(IconButtonShape::Square)
28839 .icon_size(IconSize::Small)
28840 // .disabled(!has_multiple_hunks)
28841 .tooltip({
28842 let focus_handle = editor.focus_handle(cx);
28843 move |_window, cx| {
28844 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28845 }
28846 })
28847 .on_click({
28848 let editor = editor.clone();
28849 move |_event, window, cx| {
28850 editor.update(cx, |editor, cx| {
28851 let snapshot = editor.snapshot(window, cx);
28852 let position =
28853 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28854 editor.go_to_hunk_before_or_after_position(
28855 &snapshot,
28856 position,
28857 Direction::Next,
28858 window,
28859 cx,
28860 );
28861 editor.expand_selected_diff_hunks(cx);
28862 });
28863 }
28864 }),
28865 )
28866 .child(
28867 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28868 .shape(IconButtonShape::Square)
28869 .icon_size(IconSize::Small)
28870 // .disabled(!has_multiple_hunks)
28871 .tooltip({
28872 let focus_handle = editor.focus_handle(cx);
28873 move |_window, cx| {
28874 Tooltip::for_action_in(
28875 "Previous Hunk",
28876 &GoToPreviousHunk,
28877 &focus_handle,
28878 cx,
28879 )
28880 }
28881 })
28882 .on_click({
28883 let editor = editor.clone();
28884 move |_event, window, cx| {
28885 editor.update(cx, |editor, cx| {
28886 let snapshot = editor.snapshot(window, cx);
28887 let point =
28888 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28889 editor.go_to_hunk_before_or_after_position(
28890 &snapshot,
28891 point,
28892 Direction::Prev,
28893 window,
28894 cx,
28895 );
28896 editor.expand_selected_diff_hunks(cx);
28897 });
28898 }
28899 }),
28900 )
28901 },
28902 )
28903 .into_any_element()
28904}
28905
28906pub fn multibuffer_context_lines(cx: &App) -> u32 {
28907 EditorSettings::try_get(cx)
28908 .map(|settings| settings.excerpt_context_lines)
28909 .unwrap_or(2)
28910 .min(32)
28911}