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(window, 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 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
20269 return;
20270 }
20271
20272 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20273 self.display_map.update(cx, |display_map, cx| {
20274 display_map.fold_buffers([buffer_id], cx)
20275 });
20276
20277 let snapshot = self.display_snapshot(cx);
20278 self.selections.change_with(&snapshot, |selections| {
20279 selections.remove_selections_from_buffer(buffer_id);
20280 });
20281
20282 cx.emit(EditorEvent::BufferFoldToggled {
20283 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
20284 folded: true,
20285 });
20286 cx.notify();
20287 }
20288
20289 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20290 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20291 return;
20292 }
20293 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20294 self.display_map.update(cx, |display_map, cx| {
20295 display_map.unfold_buffers([buffer_id], cx);
20296 });
20297 cx.emit(EditorEvent::BufferFoldToggled {
20298 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20299 folded: false,
20300 });
20301 cx.notify();
20302 }
20303
20304 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20305 self.display_map.read(cx).is_buffer_folded(buffer)
20306 }
20307
20308 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20309 self.display_map.read(cx).folded_buffers()
20310 }
20311
20312 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20313 self.display_map.update(cx, |display_map, cx| {
20314 display_map.disable_header_for_buffer(buffer_id, cx);
20315 });
20316 cx.notify();
20317 }
20318
20319 /// Removes any folds with the given ranges.
20320 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20321 &mut self,
20322 ranges: &[Range<T>],
20323 type_id: TypeId,
20324 auto_scroll: bool,
20325 cx: &mut Context<Self>,
20326 ) {
20327 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20328 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20329 });
20330 self.folds_did_change(cx);
20331 }
20332
20333 fn remove_folds_with<T: ToOffset + Clone>(
20334 &mut self,
20335 ranges: &[Range<T>],
20336 auto_scroll: bool,
20337 cx: &mut Context<Self>,
20338 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20339 ) {
20340 if ranges.is_empty() {
20341 return;
20342 }
20343
20344 let mut buffers_affected = HashSet::default();
20345 let multi_buffer = self.buffer().read(cx);
20346 for range in ranges {
20347 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20348 buffers_affected.insert(buffer.read(cx).remote_id());
20349 };
20350 }
20351
20352 self.display_map.update(cx, update);
20353
20354 if auto_scroll {
20355 self.request_autoscroll(Autoscroll::fit(), cx);
20356 }
20357
20358 cx.notify();
20359 self.scrollbar_marker_state.dirty = true;
20360 self.active_indent_guides_state.dirty = true;
20361 }
20362
20363 pub fn update_renderer_widths(
20364 &mut self,
20365 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20366 cx: &mut Context<Self>,
20367 ) -> bool {
20368 self.display_map
20369 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20370 }
20371
20372 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20373 self.display_map.read(cx).fold_placeholder.clone()
20374 }
20375
20376 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20377 self.buffer.update(cx, |buffer, cx| {
20378 buffer.set_all_diff_hunks_expanded(cx);
20379 });
20380 }
20381
20382 pub fn expand_all_diff_hunks(
20383 &mut self,
20384 _: &ExpandAllDiffHunks,
20385 _window: &mut Window,
20386 cx: &mut Context<Self>,
20387 ) {
20388 self.buffer.update(cx, |buffer, cx| {
20389 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20390 });
20391 }
20392
20393 pub fn collapse_all_diff_hunks(
20394 &mut self,
20395 _: &CollapseAllDiffHunks,
20396 _window: &mut Window,
20397 cx: &mut Context<Self>,
20398 ) {
20399 self.buffer.update(cx, |buffer, cx| {
20400 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20401 });
20402 }
20403
20404 pub fn toggle_selected_diff_hunks(
20405 &mut self,
20406 _: &ToggleSelectedDiffHunks,
20407 _window: &mut Window,
20408 cx: &mut Context<Self>,
20409 ) {
20410 let ranges: Vec<_> = self
20411 .selections
20412 .disjoint_anchors()
20413 .iter()
20414 .map(|s| s.range())
20415 .collect();
20416 self.toggle_diff_hunks_in_ranges(ranges, cx);
20417 }
20418
20419 pub fn diff_hunks_in_ranges<'a>(
20420 &'a self,
20421 ranges: &'a [Range<Anchor>],
20422 buffer: &'a MultiBufferSnapshot,
20423 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20424 ranges.iter().flat_map(move |range| {
20425 let end_excerpt_id = range.end.excerpt_id;
20426 let range = range.to_point(buffer);
20427 let mut peek_end = range.end;
20428 if range.end.row < buffer.max_row().0 {
20429 peek_end = Point::new(range.end.row + 1, 0);
20430 }
20431 buffer
20432 .diff_hunks_in_range(range.start..peek_end)
20433 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20434 })
20435 }
20436
20437 pub fn has_stageable_diff_hunks_in_ranges(
20438 &self,
20439 ranges: &[Range<Anchor>],
20440 snapshot: &MultiBufferSnapshot,
20441 ) -> bool {
20442 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20443 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20444 }
20445
20446 pub fn toggle_staged_selected_diff_hunks(
20447 &mut self,
20448 _: &::git::ToggleStaged,
20449 _: &mut Window,
20450 cx: &mut Context<Self>,
20451 ) {
20452 let snapshot = self.buffer.read(cx).snapshot(cx);
20453 let ranges: Vec<_> = self
20454 .selections
20455 .disjoint_anchors()
20456 .iter()
20457 .map(|s| s.range())
20458 .collect();
20459 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20460 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20461 }
20462
20463 pub fn set_render_diff_hunk_controls(
20464 &mut self,
20465 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20466 cx: &mut Context<Self>,
20467 ) {
20468 self.render_diff_hunk_controls = render_diff_hunk_controls;
20469 cx.notify();
20470 }
20471
20472 pub fn stage_and_next(
20473 &mut self,
20474 _: &::git::StageAndNext,
20475 window: &mut Window,
20476 cx: &mut Context<Self>,
20477 ) {
20478 self.do_stage_or_unstage_and_next(true, window, cx);
20479 }
20480
20481 pub fn unstage_and_next(
20482 &mut self,
20483 _: &::git::UnstageAndNext,
20484 window: &mut Window,
20485 cx: &mut Context<Self>,
20486 ) {
20487 self.do_stage_or_unstage_and_next(false, window, cx);
20488 }
20489
20490 pub fn stage_or_unstage_diff_hunks(
20491 &mut self,
20492 stage: bool,
20493 ranges: Vec<Range<Anchor>>,
20494 cx: &mut Context<Self>,
20495 ) {
20496 if self.delegate_stage_and_restore {
20497 let snapshot = self.buffer.read(cx).snapshot(cx);
20498 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20499 if !hunks.is_empty() {
20500 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20501 }
20502 return;
20503 }
20504 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20505 cx.spawn(async move |this, cx| {
20506 task.await?;
20507 this.update(cx, |this, cx| {
20508 let snapshot = this.buffer.read(cx).snapshot(cx);
20509 let chunk_by = this
20510 .diff_hunks_in_ranges(&ranges, &snapshot)
20511 .chunk_by(|hunk| hunk.buffer_id);
20512 for (buffer_id, hunks) in &chunk_by {
20513 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20514 }
20515 })
20516 })
20517 .detach_and_log_err(cx);
20518 }
20519
20520 fn save_buffers_for_ranges_if_needed(
20521 &mut self,
20522 ranges: &[Range<Anchor>],
20523 cx: &mut Context<Editor>,
20524 ) -> Task<Result<()>> {
20525 let multibuffer = self.buffer.read(cx);
20526 let snapshot = multibuffer.read(cx);
20527 let buffer_ids: HashSet<_> = ranges
20528 .iter()
20529 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20530 .collect();
20531 drop(snapshot);
20532
20533 let mut buffers = HashSet::default();
20534 for buffer_id in buffer_ids {
20535 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20536 let buffer = buffer_entity.read(cx);
20537 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20538 {
20539 buffers.insert(buffer_entity);
20540 }
20541 }
20542 }
20543
20544 if let Some(project) = &self.project {
20545 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20546 } else {
20547 Task::ready(Ok(()))
20548 }
20549 }
20550
20551 fn do_stage_or_unstage_and_next(
20552 &mut self,
20553 stage: bool,
20554 window: &mut Window,
20555 cx: &mut Context<Self>,
20556 ) {
20557 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20558
20559 if ranges.iter().any(|range| range.start != range.end) {
20560 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20561 return;
20562 }
20563
20564 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20565 let snapshot = self.snapshot(window, cx);
20566 let position = self
20567 .selections
20568 .newest::<Point>(&snapshot.display_snapshot)
20569 .head();
20570 let mut row = snapshot
20571 .buffer_snapshot()
20572 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20573 .find(|hunk| hunk.row_range.start.0 > position.row)
20574 .map(|hunk| hunk.row_range.start);
20575
20576 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20577 // Outside of the project diff editor, wrap around to the beginning.
20578 if !all_diff_hunks_expanded {
20579 row = row.or_else(|| {
20580 snapshot
20581 .buffer_snapshot()
20582 .diff_hunks_in_range(Point::zero()..position)
20583 .find(|hunk| hunk.row_range.end.0 < position.row)
20584 .map(|hunk| hunk.row_range.start)
20585 });
20586 }
20587
20588 if let Some(row) = row {
20589 let destination = Point::new(row.0, 0);
20590 let autoscroll = Autoscroll::center();
20591
20592 self.unfold_ranges(&[destination..destination], false, false, cx);
20593 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20594 s.select_ranges([destination..destination]);
20595 });
20596 }
20597 }
20598
20599 pub(crate) fn do_stage_or_unstage(
20600 &self,
20601 stage: bool,
20602 buffer_id: BufferId,
20603 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20604 cx: &mut App,
20605 ) -> Option<()> {
20606 let project = self.project()?;
20607 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20608 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20609 let buffer_snapshot = buffer.read(cx).snapshot();
20610 let file_exists = buffer_snapshot
20611 .file()
20612 .is_some_and(|file| file.disk_state().exists());
20613 diff.update(cx, |diff, cx| {
20614 diff.stage_or_unstage_hunks(
20615 stage,
20616 &hunks
20617 .map(|hunk| buffer_diff::DiffHunk {
20618 buffer_range: hunk.buffer_range,
20619 // We don't need to pass in word diffs here because they're only used for rendering and
20620 // this function changes internal state
20621 base_word_diffs: Vec::default(),
20622 buffer_word_diffs: Vec::default(),
20623 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20624 ..hunk.diff_base_byte_range.end.0,
20625 secondary_status: hunk.status.secondary,
20626 range: Point::zero()..Point::zero(), // unused
20627 })
20628 .collect::<Vec<_>>(),
20629 &buffer_snapshot,
20630 file_exists,
20631 cx,
20632 )
20633 });
20634 None
20635 }
20636
20637 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20638 let ranges: Vec<_> = self
20639 .selections
20640 .disjoint_anchors()
20641 .iter()
20642 .map(|s| s.range())
20643 .collect();
20644 self.buffer
20645 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20646 }
20647
20648 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20649 self.buffer.update(cx, |buffer, cx| {
20650 let ranges = vec![Anchor::min()..Anchor::max()];
20651 if !buffer.all_diff_hunks_expanded()
20652 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20653 {
20654 buffer.collapse_diff_hunks(ranges, cx);
20655 true
20656 } else {
20657 false
20658 }
20659 })
20660 }
20661
20662 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20663 if self.buffer.read(cx).all_diff_hunks_expanded() {
20664 return true;
20665 }
20666 let ranges = vec![Anchor::min()..Anchor::max()];
20667 self.buffer
20668 .read(cx)
20669 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20670 }
20671
20672 fn toggle_diff_hunks_in_ranges(
20673 &mut self,
20674 ranges: Vec<Range<Anchor>>,
20675 cx: &mut Context<Editor>,
20676 ) {
20677 self.buffer.update(cx, |buffer, cx| {
20678 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20679 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20680 })
20681 }
20682
20683 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20684 self.buffer.update(cx, |buffer, cx| {
20685 let snapshot = buffer.snapshot(cx);
20686 let excerpt_id = range.end.excerpt_id;
20687 let point_range = range.to_point(&snapshot);
20688 let expand = !buffer.single_hunk_is_expanded(range, cx);
20689 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20690 })
20691 }
20692
20693 pub(crate) fn apply_all_diff_hunks(
20694 &mut self,
20695 _: &ApplyAllDiffHunks,
20696 window: &mut Window,
20697 cx: &mut Context<Self>,
20698 ) {
20699 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20700
20701 let buffers = self.buffer.read(cx).all_buffers();
20702 for branch_buffer in buffers {
20703 branch_buffer.update(cx, |branch_buffer, cx| {
20704 branch_buffer.merge_into_base(Vec::new(), cx);
20705 });
20706 }
20707
20708 if let Some(project) = self.project.clone() {
20709 self.save(
20710 SaveOptions {
20711 format: true,
20712 autosave: false,
20713 },
20714 project,
20715 window,
20716 cx,
20717 )
20718 .detach_and_log_err(cx);
20719 }
20720 }
20721
20722 pub(crate) fn apply_selected_diff_hunks(
20723 &mut self,
20724 _: &ApplyDiffHunk,
20725 window: &mut Window,
20726 cx: &mut Context<Self>,
20727 ) {
20728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20729 let snapshot = self.snapshot(window, cx);
20730 let hunks = snapshot.hunks_for_ranges(
20731 self.selections
20732 .all(&snapshot.display_snapshot)
20733 .into_iter()
20734 .map(|selection| selection.range()),
20735 );
20736 let mut ranges_by_buffer = HashMap::default();
20737 self.transact(window, cx, |editor, _window, cx| {
20738 for hunk in hunks {
20739 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20740 ranges_by_buffer
20741 .entry(buffer.clone())
20742 .or_insert_with(Vec::new)
20743 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20744 }
20745 }
20746
20747 for (buffer, ranges) in ranges_by_buffer {
20748 buffer.update(cx, |buffer, cx| {
20749 buffer.merge_into_base(ranges, cx);
20750 });
20751 }
20752 });
20753
20754 if let Some(project) = self.project.clone() {
20755 self.save(
20756 SaveOptions {
20757 format: true,
20758 autosave: false,
20759 },
20760 project,
20761 window,
20762 cx,
20763 )
20764 .detach_and_log_err(cx);
20765 }
20766 }
20767
20768 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20769 if hovered != self.gutter_hovered {
20770 self.gutter_hovered = hovered;
20771 cx.notify();
20772 }
20773 }
20774
20775 pub fn insert_blocks(
20776 &mut self,
20777 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20778 autoscroll: Option<Autoscroll>,
20779 cx: &mut Context<Self>,
20780 ) -> Vec<CustomBlockId> {
20781 let blocks = self
20782 .display_map
20783 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20784 if let Some(autoscroll) = autoscroll {
20785 self.request_autoscroll(autoscroll, cx);
20786 }
20787 cx.notify();
20788 blocks
20789 }
20790
20791 pub fn resize_blocks(
20792 &mut self,
20793 heights: HashMap<CustomBlockId, u32>,
20794 autoscroll: Option<Autoscroll>,
20795 cx: &mut Context<Self>,
20796 ) {
20797 self.display_map
20798 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20799 if let Some(autoscroll) = autoscroll {
20800 self.request_autoscroll(autoscroll, cx);
20801 }
20802 cx.notify();
20803 }
20804
20805 pub fn replace_blocks(
20806 &mut self,
20807 renderers: HashMap<CustomBlockId, RenderBlock>,
20808 autoscroll: Option<Autoscroll>,
20809 cx: &mut Context<Self>,
20810 ) {
20811 self.display_map
20812 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20813 if let Some(autoscroll) = autoscroll {
20814 self.request_autoscroll(autoscroll, cx);
20815 }
20816 cx.notify();
20817 }
20818
20819 pub fn remove_blocks(
20820 &mut self,
20821 block_ids: HashSet<CustomBlockId>,
20822 autoscroll: Option<Autoscroll>,
20823 cx: &mut Context<Self>,
20824 ) {
20825 self.display_map.update(cx, |display_map, cx| {
20826 display_map.remove_blocks(block_ids, cx)
20827 });
20828 if let Some(autoscroll) = autoscroll {
20829 self.request_autoscroll(autoscroll, cx);
20830 }
20831 cx.notify();
20832 }
20833
20834 pub fn row_for_block(
20835 &self,
20836 block_id: CustomBlockId,
20837 cx: &mut Context<Self>,
20838 ) -> Option<DisplayRow> {
20839 self.display_map
20840 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20841 }
20842
20843 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20844 self.focused_block = Some(focused_block);
20845 }
20846
20847 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20848 self.focused_block.take()
20849 }
20850
20851 pub fn insert_creases(
20852 &mut self,
20853 creases: impl IntoIterator<Item = Crease<Anchor>>,
20854 cx: &mut Context<Self>,
20855 ) -> Vec<CreaseId> {
20856 self.display_map
20857 .update(cx, |map, cx| map.insert_creases(creases, cx))
20858 }
20859
20860 pub fn remove_creases(
20861 &mut self,
20862 ids: impl IntoIterator<Item = CreaseId>,
20863 cx: &mut Context<Self>,
20864 ) -> Vec<(CreaseId, Range<Anchor>)> {
20865 self.display_map
20866 .update(cx, |map, cx| map.remove_creases(ids, cx))
20867 }
20868
20869 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20870 self.display_map
20871 .update(cx, |map, cx| map.snapshot(cx))
20872 .longest_row()
20873 }
20874
20875 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20876 self.display_map
20877 .update(cx, |map, cx| map.snapshot(cx))
20878 .max_point()
20879 }
20880
20881 pub fn text(&self, cx: &App) -> String {
20882 self.buffer.read(cx).read(cx).text()
20883 }
20884
20885 pub fn is_empty(&self, cx: &App) -> bool {
20886 self.buffer.read(cx).read(cx).is_empty()
20887 }
20888
20889 pub fn text_option(&self, cx: &App) -> Option<String> {
20890 let text = self.text(cx);
20891 let text = text.trim();
20892
20893 if text.is_empty() {
20894 return None;
20895 }
20896
20897 Some(text.to_string())
20898 }
20899
20900 pub fn set_text(
20901 &mut self,
20902 text: impl Into<Arc<str>>,
20903 window: &mut Window,
20904 cx: &mut Context<Self>,
20905 ) {
20906 self.transact(window, cx, |this, _, cx| {
20907 this.buffer
20908 .read(cx)
20909 .as_singleton()
20910 .expect("you can only call set_text on editors for singleton buffers")
20911 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20912 });
20913 }
20914
20915 pub fn display_text(&self, cx: &mut App) -> String {
20916 self.display_map
20917 .update(cx, |map, cx| map.snapshot(cx))
20918 .text()
20919 }
20920
20921 fn create_minimap(
20922 &self,
20923 minimap_settings: MinimapSettings,
20924 window: &mut Window,
20925 cx: &mut Context<Self>,
20926 ) -> Option<Entity<Self>> {
20927 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20928 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20929 }
20930
20931 fn initialize_new_minimap(
20932 &self,
20933 minimap_settings: MinimapSettings,
20934 window: &mut Window,
20935 cx: &mut Context<Self>,
20936 ) -> Entity<Self> {
20937 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20938 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20939
20940 let mut minimap = Editor::new_internal(
20941 EditorMode::Minimap {
20942 parent: cx.weak_entity(),
20943 },
20944 self.buffer.clone(),
20945 None,
20946 Some(self.display_map.clone()),
20947 window,
20948 cx,
20949 );
20950 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20951 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
20952 minimap.scroll_manager.clone_state(
20953 &self.scroll_manager,
20954 &my_snapshot,
20955 &minimap_snapshot,
20956 cx,
20957 );
20958 minimap.set_text_style_refinement(TextStyleRefinement {
20959 font_size: Some(MINIMAP_FONT_SIZE),
20960 font_weight: Some(MINIMAP_FONT_WEIGHT),
20961 font_family: Some(MINIMAP_FONT_FAMILY),
20962 ..Default::default()
20963 });
20964 minimap.update_minimap_configuration(minimap_settings, cx);
20965 cx.new(|_| minimap)
20966 }
20967
20968 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20969 let current_line_highlight = minimap_settings
20970 .current_line_highlight
20971 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20972 self.set_current_line_highlight(Some(current_line_highlight));
20973 }
20974
20975 pub fn minimap(&self) -> Option<&Entity<Self>> {
20976 self.minimap
20977 .as_ref()
20978 .filter(|_| self.minimap_visibility.visible())
20979 }
20980
20981 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20982 let mut wrap_guides = smallvec![];
20983
20984 if self.show_wrap_guides == Some(false) {
20985 return wrap_guides;
20986 }
20987
20988 let settings = self.buffer.read(cx).language_settings(cx);
20989 if settings.show_wrap_guides {
20990 match self.soft_wrap_mode(cx) {
20991 SoftWrap::Column(soft_wrap) => {
20992 wrap_guides.push((soft_wrap as usize, true));
20993 }
20994 SoftWrap::Bounded(soft_wrap) => {
20995 wrap_guides.push((soft_wrap as usize, true));
20996 }
20997 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20998 }
20999 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21000 }
21001
21002 wrap_guides
21003 }
21004
21005 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21006 let settings = self.buffer.read(cx).language_settings(cx);
21007 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21008 match mode {
21009 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21010 SoftWrap::None
21011 }
21012 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21013 language_settings::SoftWrap::PreferredLineLength => {
21014 SoftWrap::Column(settings.preferred_line_length)
21015 }
21016 language_settings::SoftWrap::Bounded => {
21017 SoftWrap::Bounded(settings.preferred_line_length)
21018 }
21019 }
21020 }
21021
21022 pub fn set_soft_wrap_mode(
21023 &mut self,
21024 mode: language_settings::SoftWrap,
21025 cx: &mut Context<Self>,
21026 ) {
21027 self.soft_wrap_mode_override = Some(mode);
21028 cx.notify();
21029 }
21030
21031 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21032 self.hard_wrap = hard_wrap;
21033 cx.notify();
21034 }
21035
21036 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21037 self.text_style_refinement = Some(style);
21038 }
21039
21040 /// called by the Element so we know what style we were most recently rendered with.
21041 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21042 // We intentionally do not inform the display map about the minimap style
21043 // so that wrapping is not recalculated and stays consistent for the editor
21044 // and its linked minimap.
21045 if !self.mode.is_minimap() {
21046 let font = style.text.font();
21047 let font_size = style.text.font_size.to_pixels(window.rem_size());
21048 let display_map = self
21049 .placeholder_display_map
21050 .as_ref()
21051 .filter(|_| self.is_empty(cx))
21052 .unwrap_or(&self.display_map);
21053
21054 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21055 }
21056 self.style = Some(style);
21057 }
21058
21059 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21060 if self.style.is_none() {
21061 self.style = Some(self.create_style(cx));
21062 }
21063 self.style.as_ref().unwrap()
21064 }
21065
21066 // Called by the element. This method is not designed to be called outside of the editor
21067 // element's layout code because it does not notify when rewrapping is computed synchronously.
21068 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21069 if self.is_empty(cx) {
21070 self.placeholder_display_map
21071 .as_ref()
21072 .map_or(false, |display_map| {
21073 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21074 })
21075 } else {
21076 self.display_map
21077 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21078 }
21079 }
21080
21081 pub fn set_soft_wrap(&mut self) {
21082 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21083 }
21084
21085 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21086 if self.soft_wrap_mode_override.is_some() {
21087 self.soft_wrap_mode_override.take();
21088 } else {
21089 let soft_wrap = match self.soft_wrap_mode(cx) {
21090 SoftWrap::GitDiff => return,
21091 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21092 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21093 language_settings::SoftWrap::None
21094 }
21095 };
21096 self.soft_wrap_mode_override = Some(soft_wrap);
21097 }
21098 cx.notify();
21099 }
21100
21101 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21102 let Some(workspace) = self.workspace() else {
21103 return;
21104 };
21105 let fs = workspace.read(cx).app_state().fs.clone();
21106 let current_show = TabBarSettings::get_global(cx).show;
21107 update_settings_file(fs, cx, move |setting, _| {
21108 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21109 });
21110 }
21111
21112 pub fn toggle_indent_guides(
21113 &mut self,
21114 _: &ToggleIndentGuides,
21115 _: &mut Window,
21116 cx: &mut Context<Self>,
21117 ) {
21118 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21119 self.buffer
21120 .read(cx)
21121 .language_settings(cx)
21122 .indent_guides
21123 .enabled
21124 });
21125 self.show_indent_guides = Some(!currently_enabled);
21126 cx.notify();
21127 }
21128
21129 fn should_show_indent_guides(&self) -> Option<bool> {
21130 self.show_indent_guides
21131 }
21132
21133 pub fn disable_indent_guides_for_buffer(
21134 &mut self,
21135 buffer_id: BufferId,
21136 cx: &mut Context<Self>,
21137 ) {
21138 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21139 cx.notify();
21140 }
21141
21142 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21143 self.buffers_with_disabled_indent_guides
21144 .contains(&buffer_id)
21145 }
21146
21147 pub fn toggle_line_numbers(
21148 &mut self,
21149 _: &ToggleLineNumbers,
21150 _: &mut Window,
21151 cx: &mut Context<Self>,
21152 ) {
21153 let mut editor_settings = EditorSettings::get_global(cx).clone();
21154 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21155 EditorSettings::override_global(editor_settings, cx);
21156 }
21157
21158 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21159 if let Some(show_line_numbers) = self.show_line_numbers {
21160 return show_line_numbers;
21161 }
21162 EditorSettings::get_global(cx).gutter.line_numbers
21163 }
21164
21165 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21166 match (
21167 self.use_relative_line_numbers,
21168 EditorSettings::get_global(cx).relative_line_numbers,
21169 ) {
21170 (None, setting) => setting,
21171 (Some(false), _) => RelativeLineNumbers::Disabled,
21172 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21173 (Some(true), _) => RelativeLineNumbers::Enabled,
21174 }
21175 }
21176
21177 pub fn toggle_relative_line_numbers(
21178 &mut self,
21179 _: &ToggleRelativeLineNumbers,
21180 _: &mut Window,
21181 cx: &mut Context<Self>,
21182 ) {
21183 let is_relative = self.relative_line_numbers(cx);
21184 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21185 }
21186
21187 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21188 self.use_relative_line_numbers = is_relative;
21189 cx.notify();
21190 }
21191
21192 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21193 self.show_gutter = show_gutter;
21194 cx.notify();
21195 }
21196
21197 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21198 self.show_scrollbars = ScrollbarAxes {
21199 horizontal: show,
21200 vertical: show,
21201 };
21202 cx.notify();
21203 }
21204
21205 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21206 self.show_scrollbars.vertical = show;
21207 cx.notify();
21208 }
21209
21210 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21211 self.show_scrollbars.horizontal = show;
21212 cx.notify();
21213 }
21214
21215 pub fn set_minimap_visibility(
21216 &mut self,
21217 minimap_visibility: MinimapVisibility,
21218 window: &mut Window,
21219 cx: &mut Context<Self>,
21220 ) {
21221 if self.minimap_visibility != minimap_visibility {
21222 if minimap_visibility.visible() && self.minimap.is_none() {
21223 let minimap_settings = EditorSettings::get_global(cx).minimap;
21224 self.minimap =
21225 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21226 }
21227 self.minimap_visibility = minimap_visibility;
21228 cx.notify();
21229 }
21230 }
21231
21232 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21233 self.set_show_scrollbars(false, cx);
21234 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21235 }
21236
21237 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21238 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21239 }
21240
21241 /// Normally the text in full mode and auto height editors is padded on the
21242 /// left side by roughly half a character width for improved hit testing.
21243 ///
21244 /// Use this method to disable this for cases where this is not wanted (e.g.
21245 /// if you want to align the editor text with some other text above or below)
21246 /// or if you want to add this padding to single-line editors.
21247 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21248 self.offset_content = offset_content;
21249 cx.notify();
21250 }
21251
21252 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21253 self.show_line_numbers = Some(show_line_numbers);
21254 cx.notify();
21255 }
21256
21257 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21258 self.disable_expand_excerpt_buttons = true;
21259 cx.notify();
21260 }
21261
21262 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21263 self.number_deleted_lines = number;
21264 cx.notify();
21265 }
21266
21267 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21268 self.delegate_expand_excerpts = delegate;
21269 }
21270
21271 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21272 self.delegate_stage_and_restore = delegate;
21273 }
21274
21275 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21276 self.delegate_open_excerpts = delegate;
21277 }
21278
21279 pub fn set_on_local_selections_changed(
21280 &mut self,
21281 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21282 ) {
21283 self.on_local_selections_changed = callback;
21284 }
21285
21286 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21287 self.suppress_selection_callback = suppress;
21288 }
21289
21290 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21291 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21292 cx.notify();
21293 }
21294
21295 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21296 self.show_code_actions = Some(show_code_actions);
21297 cx.notify();
21298 }
21299
21300 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21301 self.show_runnables = Some(show_runnables);
21302 cx.notify();
21303 }
21304
21305 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21306 self.show_breakpoints = Some(show_breakpoints);
21307 cx.notify();
21308 }
21309
21310 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21311 self.show_diff_review_button = show;
21312 cx.notify();
21313 }
21314
21315 pub fn show_diff_review_button(&self) -> bool {
21316 self.show_diff_review_button
21317 }
21318
21319 pub fn render_diff_review_button(
21320 &self,
21321 display_row: DisplayRow,
21322 width: Pixels,
21323 cx: &mut Context<Self>,
21324 ) -> impl IntoElement {
21325 let text_color = cx.theme().colors().text;
21326 let icon_color = cx.theme().colors().icon_accent;
21327
21328 h_flex()
21329 .id("diff_review_button")
21330 .cursor_pointer()
21331 .w(width - px(1.))
21332 .h(relative(0.9))
21333 .justify_center()
21334 .rounded_sm()
21335 .border_1()
21336 .border_color(text_color.opacity(0.1))
21337 .bg(text_color.opacity(0.15))
21338 .hover(|s| {
21339 s.bg(icon_color.opacity(0.4))
21340 .border_color(icon_color.opacity(0.5))
21341 })
21342 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21343 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21344 .on_mouse_down(
21345 gpui::MouseButton::Left,
21346 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21347 editor.start_diff_review_drag(display_row, window, cx);
21348 }),
21349 )
21350 }
21351
21352 pub fn start_diff_review_drag(
21353 &mut self,
21354 display_row: DisplayRow,
21355 window: &mut Window,
21356 cx: &mut Context<Self>,
21357 ) {
21358 let snapshot = self.snapshot(window, cx);
21359 let point = snapshot
21360 .display_snapshot
21361 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21362 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21363 self.diff_review_drag_state = Some(DiffReviewDragState {
21364 start_anchor: anchor,
21365 current_anchor: anchor,
21366 });
21367 cx.notify();
21368 }
21369
21370 pub fn update_diff_review_drag(
21371 &mut self,
21372 display_row: DisplayRow,
21373 window: &mut Window,
21374 cx: &mut Context<Self>,
21375 ) {
21376 if self.diff_review_drag_state.is_none() {
21377 return;
21378 }
21379 let snapshot = self.snapshot(window, cx);
21380 let point = snapshot
21381 .display_snapshot
21382 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21383 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21384 if let Some(drag_state) = &mut self.diff_review_drag_state {
21385 drag_state.current_anchor = anchor;
21386 cx.notify();
21387 }
21388 }
21389
21390 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21391 if let Some(drag_state) = self.diff_review_drag_state.take() {
21392 let snapshot = self.snapshot(window, cx);
21393 let range = drag_state.row_range(&snapshot.display_snapshot);
21394 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21395 }
21396 cx.notify();
21397 }
21398
21399 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21400 self.diff_review_drag_state = None;
21401 cx.notify();
21402 }
21403
21404 /// Calculates the appropriate block height for the diff review overlay.
21405 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21406 /// and 2 lines per comment when expanded.
21407 fn calculate_overlay_height(
21408 &self,
21409 hunk_key: &DiffHunkKey,
21410 comments_expanded: bool,
21411 snapshot: &MultiBufferSnapshot,
21412 ) -> u32 {
21413 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21414 let base_height: u32 = 2; // Input row with avatar and buttons
21415
21416 if comment_count == 0 {
21417 base_height
21418 } else if comments_expanded {
21419 // Header (1 line) + 2 lines per comment
21420 base_height + 1 + (comment_count as u32 * 2)
21421 } else {
21422 // Just header when collapsed
21423 base_height + 1
21424 }
21425 }
21426
21427 pub fn show_diff_review_overlay(
21428 &mut self,
21429 display_range: Range<DisplayRow>,
21430 window: &mut Window,
21431 cx: &mut Context<Self>,
21432 ) {
21433 let Range { start, end } = display_range.sorted();
21434
21435 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21436 let editor_snapshot = self.snapshot(window, cx);
21437
21438 // Convert display rows to multibuffer points
21439 let start_point = editor_snapshot
21440 .display_snapshot
21441 .display_point_to_point(start.as_display_point(), Bias::Left);
21442 let end_point = editor_snapshot
21443 .display_snapshot
21444 .display_point_to_point(end.as_display_point(), Bias::Left);
21445 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21446
21447 // Create anchor range for the selected lines (start of first line to end of last line)
21448 let line_end = Point::new(
21449 end_point.row,
21450 buffer_snapshot.line_len(end_multi_buffer_row),
21451 );
21452 let anchor_range =
21453 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21454
21455 // Compute the hunk key for this display row
21456 let file_path = buffer_snapshot
21457 .file_at(start_point)
21458 .map(|file: &Arc<dyn language::File>| file.path().clone())
21459 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21460 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21461 let new_hunk_key = DiffHunkKey {
21462 file_path,
21463 hunk_start_anchor,
21464 };
21465
21466 // Check if we already have an overlay for this hunk
21467 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21468 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21469 }) {
21470 // Just focus the existing overlay's prompt editor
21471 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21472 window.focus(&focus_handle, cx);
21473 return;
21474 }
21475
21476 // Dismiss overlays that have no comments for their hunks
21477 self.dismiss_overlays_without_comments(cx);
21478
21479 // Get the current user's avatar URI from the project's user_store
21480 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21481 let user_store = project.read(cx).user_store();
21482 user_store
21483 .read(cx)
21484 .current_user()
21485 .map(|user| user.avatar_uri.clone())
21486 });
21487
21488 // Create anchor at the end of the last row so the block appears immediately below it
21489 // Use multibuffer coordinates for anchor creation
21490 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21491 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21492
21493 // Use the hunk key we already computed
21494 let hunk_key = new_hunk_key;
21495
21496 // Create the prompt editor for the review input
21497 let prompt_editor = cx.new(|cx| {
21498 let mut editor = Editor::single_line(window, cx);
21499 editor.set_placeholder_text("Add a review comment...", window, cx);
21500 editor
21501 });
21502
21503 // Register the Newline action on the prompt editor to submit the review
21504 let parent_editor = cx.entity().downgrade();
21505 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21506 prompt_editor.register_action({
21507 let parent_editor = parent_editor.clone();
21508 move |_: &crate::actions::Newline, window, cx| {
21509 if let Some(editor) = parent_editor.upgrade() {
21510 editor.update(cx, |editor, cx| {
21511 editor.submit_diff_review_comment(window, cx);
21512 });
21513 }
21514 }
21515 })
21516 });
21517
21518 // Calculate initial height based on existing comments for this hunk
21519 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21520
21521 // Create the overlay block
21522 let prompt_editor_for_render = prompt_editor.clone();
21523 let hunk_key_for_render = hunk_key.clone();
21524 let editor_handle = cx.entity().downgrade();
21525 let block = BlockProperties {
21526 style: BlockStyle::Sticky,
21527 placement: BlockPlacement::Below(anchor),
21528 height: Some(initial_height),
21529 render: Arc::new(move |cx| {
21530 Self::render_diff_review_overlay(
21531 &prompt_editor_for_render,
21532 &hunk_key_for_render,
21533 &editor_handle,
21534 cx,
21535 )
21536 }),
21537 priority: 0,
21538 };
21539
21540 let block_ids = self.insert_blocks([block], None, cx);
21541 let Some(block_id) = block_ids.into_iter().next() else {
21542 log::error!("Failed to insert diff review overlay block");
21543 return;
21544 };
21545
21546 self.diff_review_overlays.push(DiffReviewOverlay {
21547 anchor_range,
21548 block_id,
21549 prompt_editor: prompt_editor.clone(),
21550 hunk_key,
21551 comments_expanded: true,
21552 inline_edit_editors: HashMap::default(),
21553 inline_edit_subscriptions: HashMap::default(),
21554 user_avatar_uri,
21555 _subscription: subscription,
21556 });
21557
21558 // Focus the prompt editor
21559 let focus_handle = prompt_editor.focus_handle(cx);
21560 window.focus(&focus_handle, cx);
21561
21562 cx.notify();
21563 }
21564
21565 /// Dismisses all diff review overlays.
21566 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21567 if self.diff_review_overlays.is_empty() {
21568 return;
21569 }
21570 let block_ids: HashSet<_> = self
21571 .diff_review_overlays
21572 .drain(..)
21573 .map(|overlay| overlay.block_id)
21574 .collect();
21575 self.remove_blocks(block_ids, None, cx);
21576 cx.notify();
21577 }
21578
21579 /// Dismisses overlays that have no comments stored for their hunks.
21580 /// Keeps overlays that have at least one comment.
21581 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21582 let snapshot = self.buffer.read(cx).snapshot(cx);
21583
21584 // First, compute which overlays have comments (to avoid borrow issues with retain)
21585 let overlays_with_comments: Vec<bool> = self
21586 .diff_review_overlays
21587 .iter()
21588 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21589 .collect();
21590
21591 // Now collect block IDs to remove and retain overlays
21592 let mut block_ids_to_remove = HashSet::default();
21593 let mut index = 0;
21594 self.diff_review_overlays.retain(|overlay| {
21595 let has_comments = overlays_with_comments[index];
21596 index += 1;
21597 if !has_comments {
21598 block_ids_to_remove.insert(overlay.block_id);
21599 }
21600 has_comments
21601 });
21602
21603 if !block_ids_to_remove.is_empty() {
21604 self.remove_blocks(block_ids_to_remove, None, cx);
21605 cx.notify();
21606 }
21607 }
21608
21609 /// Refreshes the diff review overlay block to update its height and render function.
21610 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21611 fn refresh_diff_review_overlay_height(
21612 &mut self,
21613 hunk_key: &DiffHunkKey,
21614 _window: &mut Window,
21615 cx: &mut Context<Self>,
21616 ) {
21617 // Extract all needed data from overlay first to avoid borrow conflicts
21618 let snapshot = self.buffer.read(cx).snapshot(cx);
21619 let (comments_expanded, block_id, prompt_editor) = {
21620 let Some(overlay) = self
21621 .diff_review_overlays
21622 .iter()
21623 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21624 else {
21625 return;
21626 };
21627
21628 (
21629 overlay.comments_expanded,
21630 overlay.block_id,
21631 overlay.prompt_editor.clone(),
21632 )
21633 };
21634
21635 // Calculate new height
21636 let snapshot = self.buffer.read(cx).snapshot(cx);
21637 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21638
21639 // Update the block height using resize_blocks (avoids flicker)
21640 let mut heights = HashMap::default();
21641 heights.insert(block_id, new_height);
21642 self.resize_blocks(heights, None, cx);
21643
21644 // Update the render function using replace_blocks (avoids flicker)
21645 let hunk_key_for_render = hunk_key.clone();
21646 let editor_handle = cx.entity().downgrade();
21647 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21648 Arc::new(move |cx| {
21649 Self::render_diff_review_overlay(
21650 &prompt_editor,
21651 &hunk_key_for_render,
21652 &editor_handle,
21653 cx,
21654 )
21655 });
21656
21657 let mut renderers = HashMap::default();
21658 renderers.insert(block_id, render);
21659 self.replace_blocks(renderers, None, cx);
21660 }
21661
21662 /// Action handler for SubmitDiffReviewComment.
21663 pub fn submit_diff_review_comment_action(
21664 &mut self,
21665 _: &SubmitDiffReviewComment,
21666 window: &mut Window,
21667 cx: &mut Context<Self>,
21668 ) {
21669 self.submit_diff_review_comment(window, cx);
21670 }
21671
21672 /// Stores the diff review comment locally.
21673 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21674 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21675 // Find the overlay that currently has focus
21676 let overlay_index = self
21677 .diff_review_overlays
21678 .iter()
21679 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21680 let Some(overlay_index) = overlay_index else {
21681 return;
21682 };
21683 let overlay = &self.diff_review_overlays[overlay_index];
21684
21685 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21686 if comment_text.is_empty() {
21687 return;
21688 }
21689
21690 let anchor_range = overlay.anchor_range.clone();
21691 let hunk_key = overlay.hunk_key.clone();
21692
21693 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21694
21695 // Clear the prompt editor but keep the overlay open
21696 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21697 overlay.prompt_editor.update(cx, |editor, cx| {
21698 editor.clear(window, cx);
21699 });
21700 }
21701
21702 // Refresh the overlay to update the block height for the new comment
21703 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21704
21705 cx.notify();
21706 }
21707
21708 /// Returns the prompt editor for the diff review overlay, if one is active.
21709 /// This is primarily used for testing.
21710 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21711 self.diff_review_overlays
21712 .first()
21713 .map(|overlay| &overlay.prompt_editor)
21714 }
21715
21716 /// Returns the line range for the first diff review overlay, if one is active.
21717 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21718 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21719 let overlay = self.diff_review_overlays.first()?;
21720 let snapshot = self.buffer.read(cx).snapshot(cx);
21721 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21722 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21723 let start_row = snapshot
21724 .point_to_buffer_point(start_point)
21725 .map(|(_, p, _)| p.row)
21726 .unwrap_or(start_point.row);
21727 let end_row = snapshot
21728 .point_to_buffer_point(end_point)
21729 .map(|(_, p, _)| p.row)
21730 .unwrap_or(end_point.row);
21731 Some((start_row, end_row))
21732 }
21733
21734 /// Sets whether the comments section is expanded in the diff review overlay.
21735 /// This is primarily used for testing.
21736 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21737 for overlay in &mut self.diff_review_overlays {
21738 overlay.comments_expanded = expanded;
21739 }
21740 cx.notify();
21741 }
21742
21743 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21744 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21745 a.file_path == b.file_path
21746 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21747 }
21748
21749 /// Returns comments for a specific hunk, ordered by creation time.
21750 pub fn comments_for_hunk<'a>(
21751 &'a self,
21752 key: &DiffHunkKey,
21753 snapshot: &MultiBufferSnapshot,
21754 ) -> &'a [StoredReviewComment] {
21755 let key_point = key.hunk_start_anchor.to_point(snapshot);
21756 self.stored_review_comments
21757 .iter()
21758 .find(|(k, _)| {
21759 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21760 })
21761 .map(|(_, comments)| comments.as_slice())
21762 .unwrap_or(&[])
21763 }
21764
21765 /// Returns the total count of stored review comments across all hunks.
21766 pub fn total_review_comment_count(&self) -> usize {
21767 self.stored_review_comments
21768 .iter()
21769 .map(|(_, v)| v.len())
21770 .sum()
21771 }
21772
21773 /// Returns the count of comments for a specific hunk.
21774 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
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(|(_, v)| v.len())
21782 .unwrap_or(0)
21783 }
21784
21785 /// Adds a new review comment to a specific hunk.
21786 pub fn add_review_comment(
21787 &mut self,
21788 hunk_key: DiffHunkKey,
21789 comment: String,
21790 anchor_range: Range<Anchor>,
21791 cx: &mut Context<Self>,
21792 ) -> usize {
21793 let id = self.next_review_comment_id;
21794 self.next_review_comment_id += 1;
21795
21796 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21797
21798 let snapshot = self.buffer.read(cx).snapshot(cx);
21799 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21800
21801 // Find existing entry for this hunk or add a new one
21802 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21803 k.file_path == hunk_key.file_path
21804 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21805 }) {
21806 comments.push(stored_comment);
21807 } else {
21808 self.stored_review_comments
21809 .push((hunk_key, vec![stored_comment]));
21810 }
21811
21812 cx.emit(EditorEvent::ReviewCommentsChanged {
21813 total_count: self.total_review_comment_count(),
21814 });
21815 cx.notify();
21816 id
21817 }
21818
21819 /// Removes a review comment by ID from any hunk.
21820 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21821 for (_, comments) in self.stored_review_comments.iter_mut() {
21822 if let Some(index) = comments.iter().position(|c| c.id == id) {
21823 comments.remove(index);
21824 cx.emit(EditorEvent::ReviewCommentsChanged {
21825 total_count: self.total_review_comment_count(),
21826 });
21827 cx.notify();
21828 return true;
21829 }
21830 }
21831 false
21832 }
21833
21834 /// Updates a review comment's text by ID.
21835 pub fn update_review_comment(
21836 &mut self,
21837 id: usize,
21838 new_comment: String,
21839 cx: &mut Context<Self>,
21840 ) -> bool {
21841 for (_, comments) in self.stored_review_comments.iter_mut() {
21842 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21843 comment.comment = new_comment;
21844 comment.is_editing = false;
21845 cx.emit(EditorEvent::ReviewCommentsChanged {
21846 total_count: self.total_review_comment_count(),
21847 });
21848 cx.notify();
21849 return true;
21850 }
21851 }
21852 false
21853 }
21854
21855 /// Sets a comment's editing state.
21856 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
21857 for (_, comments) in self.stored_review_comments.iter_mut() {
21858 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
21859 comment.is_editing = is_editing;
21860 cx.notify();
21861 return;
21862 }
21863 }
21864 }
21865
21866 /// Takes all stored comments from all hunks, clearing the storage.
21867 /// Returns a Vec of (hunk_key, comments) pairs.
21868 pub fn take_all_review_comments(
21869 &mut self,
21870 cx: &mut Context<Self>,
21871 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
21872 // Dismiss all overlays when taking comments (e.g., when sending to agent)
21873 self.dismiss_all_diff_review_overlays(cx);
21874 let comments = std::mem::take(&mut self.stored_review_comments);
21875 // Reset the ID counter since all comments have been taken
21876 self.next_review_comment_id = 0;
21877 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
21878 cx.notify();
21879 comments
21880 }
21881
21882 /// Removes review comments whose anchors are no longer valid or whose
21883 /// associated diff hunks no longer exist.
21884 ///
21885 /// This should be called when the buffer changes to prevent orphaned comments
21886 /// from accumulating.
21887 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
21888 let snapshot = self.buffer.read(cx).snapshot(cx);
21889 let original_count = self.total_review_comment_count();
21890
21891 // Remove comments with invalid hunk anchors
21892 self.stored_review_comments
21893 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
21894
21895 // Also clean up individual comments with invalid anchor ranges
21896 for (_, comments) in &mut self.stored_review_comments {
21897 comments.retain(|comment| {
21898 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
21899 });
21900 }
21901
21902 // Remove empty hunk entries
21903 self.stored_review_comments
21904 .retain(|(_, comments)| !comments.is_empty());
21905
21906 let new_count = self.total_review_comment_count();
21907 if new_count != original_count {
21908 cx.emit(EditorEvent::ReviewCommentsChanged {
21909 total_count: new_count,
21910 });
21911 cx.notify();
21912 }
21913 }
21914
21915 /// Toggles the expanded state of the comments section in the overlay.
21916 pub fn toggle_review_comments_expanded(
21917 &mut self,
21918 _: &ToggleReviewCommentsExpanded,
21919 window: &mut Window,
21920 cx: &mut Context<Self>,
21921 ) {
21922 // Find the overlay that currently has focus, or use the first one
21923 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
21924 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
21925 overlay.comments_expanded = !overlay.comments_expanded;
21926 Some(overlay.hunk_key.clone())
21927 } else {
21928 None
21929 }
21930 });
21931
21932 // If no focused overlay found, toggle the first one
21933 let hunk_key = overlay_info.or_else(|| {
21934 self.diff_review_overlays.first_mut().map(|overlay| {
21935 overlay.comments_expanded = !overlay.comments_expanded;
21936 overlay.hunk_key.clone()
21937 })
21938 });
21939
21940 if let Some(hunk_key) = hunk_key {
21941 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21942 cx.notify();
21943 }
21944 }
21945
21946 /// Handles the EditReviewComment action - sets a comment into editing mode.
21947 pub fn edit_review_comment(
21948 &mut self,
21949 action: &EditReviewComment,
21950 window: &mut Window,
21951 cx: &mut Context<Self>,
21952 ) {
21953 let comment_id = action.id;
21954
21955 // Set the comment to editing mode
21956 self.set_comment_editing(comment_id, true, cx);
21957
21958 // Find the overlay that contains this comment and create an inline editor if needed
21959 // First, find which hunk this comment belongs to
21960 let hunk_key = self
21961 .stored_review_comments
21962 .iter()
21963 .find_map(|(key, comments)| {
21964 if comments.iter().any(|c| c.id == comment_id) {
21965 Some(key.clone())
21966 } else {
21967 None
21968 }
21969 });
21970
21971 let snapshot = self.buffer.read(cx).snapshot(cx);
21972 if let Some(hunk_key) = hunk_key {
21973 if let Some(overlay) = self
21974 .diff_review_overlays
21975 .iter_mut()
21976 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
21977 {
21978 if let std::collections::hash_map::Entry::Vacant(entry) =
21979 overlay.inline_edit_editors.entry(comment_id)
21980 {
21981 // Find the comment text
21982 let comment_text = self
21983 .stored_review_comments
21984 .iter()
21985 .flat_map(|(_, comments)| comments)
21986 .find(|c| c.id == comment_id)
21987 .map(|c| c.comment.clone())
21988 .unwrap_or_default();
21989
21990 // Create inline editor
21991 let parent_editor = cx.entity().downgrade();
21992 let inline_editor = cx.new(|cx| {
21993 let mut editor = Editor::single_line(window, cx);
21994 editor.set_text(&*comment_text, window, cx);
21995 // Select all text for easy replacement
21996 editor.select_all(&crate::actions::SelectAll, window, cx);
21997 editor
21998 });
21999
22000 // Register the Newline action to confirm the edit
22001 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22002 inline_editor.register_action({
22003 let parent_editor = parent_editor.clone();
22004 move |_: &crate::actions::Newline, window, cx| {
22005 if let Some(editor) = parent_editor.upgrade() {
22006 editor.update(cx, |editor, cx| {
22007 editor.confirm_edit_review_comment(comment_id, window, cx);
22008 });
22009 }
22010 }
22011 })
22012 });
22013
22014 // Store the subscription to keep the action handler alive
22015 overlay
22016 .inline_edit_subscriptions
22017 .insert(comment_id, subscription);
22018
22019 // Focus the inline editor
22020 let focus_handle = inline_editor.focus_handle(cx);
22021 window.focus(&focus_handle, cx);
22022
22023 entry.insert(inline_editor);
22024 }
22025 }
22026 }
22027
22028 cx.notify();
22029 }
22030
22031 /// Confirms an inline edit of a review comment.
22032 pub fn confirm_edit_review_comment(
22033 &mut self,
22034 comment_id: usize,
22035 _window: &mut Window,
22036 cx: &mut Context<Self>,
22037 ) {
22038 // Get the new text from the inline editor
22039 // Find the overlay containing this comment's inline editor
22040 let snapshot = self.buffer.read(cx).snapshot(cx);
22041 let hunk_key = self
22042 .stored_review_comments
22043 .iter()
22044 .find_map(|(key, comments)| {
22045 if comments.iter().any(|c| c.id == comment_id) {
22046 Some(key.clone())
22047 } else {
22048 None
22049 }
22050 });
22051
22052 let new_text = hunk_key
22053 .as_ref()
22054 .and_then(|hunk_key| {
22055 self.diff_review_overlays
22056 .iter()
22057 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22058 })
22059 .as_ref()
22060 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22061 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22062
22063 if let Some(new_text) = new_text {
22064 if !new_text.is_empty() {
22065 self.update_review_comment(comment_id, new_text, cx);
22066 }
22067 }
22068
22069 // Remove the inline editor and its subscription
22070 if let Some(hunk_key) = hunk_key {
22071 if let Some(overlay) = self
22072 .diff_review_overlays
22073 .iter_mut()
22074 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22075 {
22076 overlay.inline_edit_editors.remove(&comment_id);
22077 overlay.inline_edit_subscriptions.remove(&comment_id);
22078 }
22079 }
22080
22081 // Clear editing state
22082 self.set_comment_editing(comment_id, false, cx);
22083 }
22084
22085 /// Cancels an inline edit of a review comment.
22086 pub fn cancel_edit_review_comment(
22087 &mut self,
22088 comment_id: usize,
22089 _window: &mut Window,
22090 cx: &mut Context<Self>,
22091 ) {
22092 // Find which hunk this comment belongs to
22093 let hunk_key = self
22094 .stored_review_comments
22095 .iter()
22096 .find_map(|(key, comments)| {
22097 if comments.iter().any(|c| c.id == comment_id) {
22098 Some(key.clone())
22099 } else {
22100 None
22101 }
22102 });
22103
22104 // Remove the inline editor and its subscription
22105 if let Some(hunk_key) = hunk_key {
22106 let snapshot = self.buffer.read(cx).snapshot(cx);
22107 if let Some(overlay) = self
22108 .diff_review_overlays
22109 .iter_mut()
22110 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22111 {
22112 overlay.inline_edit_editors.remove(&comment_id);
22113 overlay.inline_edit_subscriptions.remove(&comment_id);
22114 }
22115 }
22116
22117 // Clear editing state
22118 self.set_comment_editing(comment_id, false, cx);
22119 }
22120
22121 /// Action handler for ConfirmEditReviewComment.
22122 pub fn confirm_edit_review_comment_action(
22123 &mut self,
22124 action: &ConfirmEditReviewComment,
22125 window: &mut Window,
22126 cx: &mut Context<Self>,
22127 ) {
22128 self.confirm_edit_review_comment(action.id, window, cx);
22129 }
22130
22131 /// Action handler for CancelEditReviewComment.
22132 pub fn cancel_edit_review_comment_action(
22133 &mut self,
22134 action: &CancelEditReviewComment,
22135 window: &mut Window,
22136 cx: &mut Context<Self>,
22137 ) {
22138 self.cancel_edit_review_comment(action.id, window, cx);
22139 }
22140
22141 /// Handles the DeleteReviewComment action - removes a comment.
22142 pub fn delete_review_comment(
22143 &mut self,
22144 action: &DeleteReviewComment,
22145 window: &mut Window,
22146 cx: &mut Context<Self>,
22147 ) {
22148 // Get the hunk key before removing the comment
22149 // Find the hunk key from the comment itself
22150 let comment_id = action.id;
22151 let hunk_key = self
22152 .stored_review_comments
22153 .iter()
22154 .find_map(|(key, comments)| {
22155 if comments.iter().any(|c| c.id == comment_id) {
22156 Some(key.clone())
22157 } else {
22158 None
22159 }
22160 });
22161
22162 // Also get it from the overlay for refresh purposes
22163 let overlay_hunk_key = self
22164 .diff_review_overlays
22165 .first()
22166 .map(|o| o.hunk_key.clone());
22167
22168 self.remove_review_comment(action.id, cx);
22169
22170 // Refresh the overlay height after removing a comment
22171 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22172 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22173 }
22174 }
22175
22176 fn render_diff_review_overlay(
22177 prompt_editor: &Entity<Editor>,
22178 hunk_key: &DiffHunkKey,
22179 editor_handle: &WeakEntity<Editor>,
22180 cx: &mut BlockContext,
22181 ) -> AnyElement {
22182 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22183 if ranges.is_empty() {
22184 return None;
22185 }
22186 let formatted: Vec<String> = ranges
22187 .iter()
22188 .map(|(start, end)| {
22189 let start_line = start + 1;
22190 let end_line = end + 1;
22191 if start_line == end_line {
22192 format!("Line {start_line}")
22193 } else {
22194 format!("Lines {start_line}-{end_line}")
22195 }
22196 })
22197 .collect();
22198 // Don't show label for single line in single excerpt
22199 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22200 return None;
22201 }
22202 Some(formatted.join(" ⋯ "))
22203 }
22204
22205 let theme = cx.theme();
22206 let colors = theme.colors();
22207
22208 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22209 editor_handle
22210 .upgrade()
22211 .map(|editor| {
22212 let editor = editor.read(cx);
22213 let snapshot = editor.buffer().read(cx).snapshot(cx);
22214 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22215 let (expanded, editors, avatar_uri, line_ranges) = editor
22216 .diff_review_overlays
22217 .iter()
22218 .find(|overlay| {
22219 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22220 })
22221 .map(|o| {
22222 let start_point = o.anchor_range.start.to_point(&snapshot);
22223 let end_point = o.anchor_range.end.to_point(&snapshot);
22224 // Get line ranges per excerpt to detect discontinuities
22225 let buffer_ranges =
22226 snapshot.range_to_buffer_ranges(start_point..end_point);
22227 let ranges: Vec<(u32, u32)> = buffer_ranges
22228 .iter()
22229 .map(|(buffer, range, _)| {
22230 let start = buffer.offset_to_point(range.start.0).row;
22231 let end = buffer.offset_to_point(range.end.0).row;
22232 (start, end)
22233 })
22234 .collect();
22235 (
22236 o.comments_expanded,
22237 o.inline_edit_editors.clone(),
22238 o.user_avatar_uri.clone(),
22239 if ranges.is_empty() {
22240 None
22241 } else {
22242 Some(ranges)
22243 },
22244 )
22245 })
22246 .unwrap_or((true, HashMap::default(), None, None));
22247 (comments, expanded, editors, avatar_uri, line_ranges)
22248 })
22249 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22250
22251 let comment_count = comments.len();
22252 let avatar_size = px(20.);
22253 let action_icon_size = IconSize::XSmall;
22254
22255 v_flex()
22256 .w_full()
22257 .bg(colors.editor_background)
22258 .border_b_1()
22259 .border_color(colors.border)
22260 .px_2()
22261 .pb_2()
22262 .gap_2()
22263 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22264 .when_some(line_ranges, |el, ranges| {
22265 let label = format_line_ranges(&ranges);
22266 if let Some(label) = label {
22267 el.child(
22268 h_flex()
22269 .w_full()
22270 .px_2()
22271 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22272 )
22273 } else {
22274 el
22275 }
22276 })
22277 // Top row: editable input with user's avatar
22278 .child(
22279 h_flex()
22280 .w_full()
22281 .items_center()
22282 .gap_2()
22283 .px_2()
22284 .py_1p5()
22285 .rounded_md()
22286 .bg(colors.surface_background)
22287 .child(
22288 div()
22289 .size(avatar_size)
22290 .flex_shrink_0()
22291 .rounded_full()
22292 .overflow_hidden()
22293 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22294 Avatar::new(avatar_uri.clone())
22295 .size(avatar_size)
22296 .into_any_element()
22297 } else {
22298 Icon::new(IconName::Person)
22299 .size(IconSize::Small)
22300 .color(ui::Color::Muted)
22301 .into_any_element()
22302 }),
22303 )
22304 .child(
22305 div()
22306 .flex_1()
22307 .border_1()
22308 .border_color(colors.border)
22309 .rounded_md()
22310 .bg(colors.editor_background)
22311 .px_2()
22312 .py_1()
22313 .child(prompt_editor.clone()),
22314 )
22315 .child(
22316 h_flex()
22317 .flex_shrink_0()
22318 .gap_1()
22319 .child(
22320 IconButton::new("diff-review-close", IconName::Close)
22321 .icon_color(ui::Color::Muted)
22322 .icon_size(action_icon_size)
22323 .tooltip(Tooltip::text("Close"))
22324 .on_click(|_, window, cx| {
22325 window
22326 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22327 }),
22328 )
22329 .child(
22330 IconButton::new("diff-review-add", IconName::Return)
22331 .icon_color(ui::Color::Muted)
22332 .icon_size(action_icon_size)
22333 .tooltip(Tooltip::text("Add comment"))
22334 .on_click(|_, window, cx| {
22335 window.dispatch_action(
22336 Box::new(crate::actions::SubmitDiffReviewComment),
22337 cx,
22338 );
22339 }),
22340 ),
22341 ),
22342 )
22343 // Expandable comments section (only shown when there are comments)
22344 .when(comment_count > 0, |el| {
22345 el.child(Self::render_comments_section(
22346 comments,
22347 comments_expanded,
22348 inline_editors,
22349 user_avatar_uri,
22350 avatar_size,
22351 action_icon_size,
22352 colors,
22353 ))
22354 })
22355 .into_any_element()
22356 }
22357
22358 fn render_comments_section(
22359 comments: Vec<StoredReviewComment>,
22360 expanded: bool,
22361 inline_editors: HashMap<usize, Entity<Editor>>,
22362 user_avatar_uri: Option<SharedUri>,
22363 avatar_size: Pixels,
22364 action_icon_size: IconSize,
22365 colors: &theme::ThemeColors,
22366 ) -> impl IntoElement {
22367 let comment_count = comments.len();
22368
22369 v_flex()
22370 .w_full()
22371 .gap_1()
22372 // Header with expand/collapse toggle
22373 .child(
22374 h_flex()
22375 .id("review-comments-header")
22376 .w_full()
22377 .items_center()
22378 .gap_1()
22379 .px_2()
22380 .py_1()
22381 .cursor_pointer()
22382 .rounded_md()
22383 .hover(|style| style.bg(colors.ghost_element_hover))
22384 .on_click(|_, window: &mut Window, cx| {
22385 window.dispatch_action(
22386 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22387 cx,
22388 );
22389 })
22390 .child(
22391 Icon::new(if expanded {
22392 IconName::ChevronDown
22393 } else {
22394 IconName::ChevronRight
22395 })
22396 .size(IconSize::Small)
22397 .color(ui::Color::Muted),
22398 )
22399 .child(
22400 Label::new(format!(
22401 "{} Comment{}",
22402 comment_count,
22403 if comment_count == 1 { "" } else { "s" }
22404 ))
22405 .size(LabelSize::Small)
22406 .color(Color::Muted),
22407 ),
22408 )
22409 // Comments list (when expanded)
22410 .when(expanded, |el| {
22411 el.children(comments.into_iter().map(|comment| {
22412 let inline_editor = inline_editors.get(&comment.id).cloned();
22413 Self::render_comment_row(
22414 comment,
22415 inline_editor,
22416 user_avatar_uri.clone(),
22417 avatar_size,
22418 action_icon_size,
22419 colors,
22420 )
22421 }))
22422 })
22423 }
22424
22425 fn render_comment_row(
22426 comment: StoredReviewComment,
22427 inline_editor: Option<Entity<Editor>>,
22428 user_avatar_uri: Option<SharedUri>,
22429 avatar_size: Pixels,
22430 action_icon_size: IconSize,
22431 colors: &theme::ThemeColors,
22432 ) -> impl IntoElement {
22433 let comment_id = comment.id;
22434 let is_editing = inline_editor.is_some();
22435
22436 h_flex()
22437 .w_full()
22438 .items_center()
22439 .gap_2()
22440 .px_2()
22441 .py_1p5()
22442 .rounded_md()
22443 .bg(colors.surface_background)
22444 .child(
22445 div()
22446 .size(avatar_size)
22447 .flex_shrink_0()
22448 .rounded_full()
22449 .overflow_hidden()
22450 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22451 Avatar::new(avatar_uri.clone())
22452 .size(avatar_size)
22453 .into_any_element()
22454 } else {
22455 Icon::new(IconName::Person)
22456 .size(IconSize::Small)
22457 .color(ui::Color::Muted)
22458 .into_any_element()
22459 }),
22460 )
22461 .child(if let Some(editor) = inline_editor {
22462 // Inline edit mode: show an editable text field
22463 div()
22464 .flex_1()
22465 .border_1()
22466 .border_color(colors.border)
22467 .rounded_md()
22468 .bg(colors.editor_background)
22469 .px_2()
22470 .py_1()
22471 .child(editor)
22472 .into_any_element()
22473 } else {
22474 // Display mode: show the comment text
22475 div()
22476 .flex_1()
22477 .text_sm()
22478 .text_color(colors.text)
22479 .child(comment.comment)
22480 .into_any_element()
22481 })
22482 .child(if is_editing {
22483 // Editing mode: show close and confirm buttons
22484 h_flex()
22485 .gap_1()
22486 .child(
22487 IconButton::new(
22488 format!("diff-review-cancel-edit-{comment_id}"),
22489 IconName::Close,
22490 )
22491 .icon_color(ui::Color::Muted)
22492 .icon_size(action_icon_size)
22493 .tooltip(Tooltip::text("Cancel"))
22494 .on_click(move |_, window, cx| {
22495 window.dispatch_action(
22496 Box::new(crate::actions::CancelEditReviewComment {
22497 id: comment_id,
22498 }),
22499 cx,
22500 );
22501 }),
22502 )
22503 .child(
22504 IconButton::new(
22505 format!("diff-review-confirm-edit-{comment_id}"),
22506 IconName::Return,
22507 )
22508 .icon_color(ui::Color::Muted)
22509 .icon_size(action_icon_size)
22510 .tooltip(Tooltip::text("Confirm"))
22511 .on_click(move |_, window, cx| {
22512 window.dispatch_action(
22513 Box::new(crate::actions::ConfirmEditReviewComment {
22514 id: comment_id,
22515 }),
22516 cx,
22517 );
22518 }),
22519 )
22520 .into_any_element()
22521 } else {
22522 // Display mode: no action buttons for now (edit/delete not yet implemented)
22523 gpui::Empty.into_any_element()
22524 })
22525 }
22526
22527 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22528 if self.display_map.read(cx).masked != masked {
22529 self.display_map.update(cx, |map, _| map.masked = masked);
22530 }
22531 cx.notify()
22532 }
22533
22534 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22535 self.show_wrap_guides = Some(show_wrap_guides);
22536 cx.notify();
22537 }
22538
22539 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22540 self.show_indent_guides = Some(show_indent_guides);
22541 cx.notify();
22542 }
22543
22544 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22545 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22546 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22547 && let Some(dir) = file.abs_path(cx).parent()
22548 {
22549 return Some(dir.to_owned());
22550 }
22551 }
22552
22553 None
22554 }
22555
22556 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22557 self.active_excerpt(cx)?
22558 .1
22559 .read(cx)
22560 .file()
22561 .and_then(|f| f.as_local())
22562 }
22563
22564 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22565 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22566 let buffer = buffer.read(cx);
22567 if let Some(project_path) = buffer.project_path(cx) {
22568 let project = self.project()?.read(cx);
22569 project.absolute_path(&project_path, cx)
22570 } else {
22571 buffer
22572 .file()
22573 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22574 }
22575 })
22576 }
22577
22578 pub fn reveal_in_finder(
22579 &mut self,
22580 _: &RevealInFileManager,
22581 _window: &mut Window,
22582 cx: &mut Context<Self>,
22583 ) {
22584 if let Some(path) = self.target_file_abs_path(cx) {
22585 if let Some(project) = self.project() {
22586 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22587 } else {
22588 cx.reveal_path(&path);
22589 }
22590 }
22591 }
22592
22593 pub fn copy_path(
22594 &mut self,
22595 _: &zed_actions::workspace::CopyPath,
22596 _window: &mut Window,
22597 cx: &mut Context<Self>,
22598 ) {
22599 if let Some(path) = self.target_file_abs_path(cx)
22600 && let Some(path) = path.to_str()
22601 {
22602 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22603 } else {
22604 cx.propagate();
22605 }
22606 }
22607
22608 pub fn copy_relative_path(
22609 &mut self,
22610 _: &zed_actions::workspace::CopyRelativePath,
22611 _window: &mut Window,
22612 cx: &mut Context<Self>,
22613 ) {
22614 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22615 let project = self.project()?.read(cx);
22616 let path = buffer.read(cx).file()?.path();
22617 let path = path.display(project.path_style(cx));
22618 Some(path)
22619 }) {
22620 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22621 } else {
22622 cx.propagate();
22623 }
22624 }
22625
22626 /// Returns the project path for the editor's buffer, if any buffer is
22627 /// opened in the editor.
22628 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22629 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22630 buffer.read(cx).project_path(cx)
22631 } else {
22632 None
22633 }
22634 }
22635
22636 // Returns true if the editor handled a go-to-line request
22637 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22638 maybe!({
22639 let breakpoint_store = self.breakpoint_store.as_ref()?;
22640
22641 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
22642 else {
22643 self.clear_row_highlights::<ActiveDebugLine>();
22644 return None;
22645 };
22646
22647 let position = active_stack_frame.position;
22648 let buffer_id = position.buffer_id?;
22649 let snapshot = self
22650 .project
22651 .as_ref()?
22652 .read(cx)
22653 .buffer_for_id(buffer_id, cx)?
22654 .read(cx)
22655 .snapshot();
22656
22657 let mut handled = false;
22658 for (id, ExcerptRange { context, .. }) in
22659 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22660 {
22661 if context.start.cmp(&position, &snapshot).is_ge()
22662 || context.end.cmp(&position, &snapshot).is_lt()
22663 {
22664 continue;
22665 }
22666 let snapshot = self.buffer.read(cx).snapshot(cx);
22667 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22668
22669 handled = true;
22670 self.clear_row_highlights::<ActiveDebugLine>();
22671
22672 self.go_to_line::<ActiveDebugLine>(
22673 multibuffer_anchor,
22674 Some(cx.theme().colors().editor_debugger_active_line_background),
22675 window,
22676 cx,
22677 );
22678
22679 cx.notify();
22680 }
22681
22682 handled.then_some(())
22683 })
22684 .is_some()
22685 }
22686
22687 pub fn copy_file_name_without_extension(
22688 &mut self,
22689 _: &CopyFileNameWithoutExtension,
22690 _: &mut Window,
22691 cx: &mut Context<Self>,
22692 ) {
22693 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22694 let file = buffer.read(cx).file()?;
22695 file.path().file_stem()
22696 }) {
22697 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22698 }
22699 }
22700
22701 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22702 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22703 let file = buffer.read(cx).file()?;
22704 Some(file.file_name(cx))
22705 }) {
22706 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22707 }
22708 }
22709
22710 pub fn toggle_git_blame(
22711 &mut self,
22712 _: &::git::Blame,
22713 window: &mut Window,
22714 cx: &mut Context<Self>,
22715 ) {
22716 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22717
22718 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22719 self.start_git_blame(true, window, cx);
22720 }
22721
22722 cx.notify();
22723 }
22724
22725 pub fn toggle_git_blame_inline(
22726 &mut self,
22727 _: &ToggleGitBlameInline,
22728 window: &mut Window,
22729 cx: &mut Context<Self>,
22730 ) {
22731 self.toggle_git_blame_inline_internal(true, window, cx);
22732 cx.notify();
22733 }
22734
22735 pub fn open_git_blame_commit(
22736 &mut self,
22737 _: &OpenGitBlameCommit,
22738 window: &mut Window,
22739 cx: &mut Context<Self>,
22740 ) {
22741 self.open_git_blame_commit_internal(window, cx);
22742 }
22743
22744 fn open_git_blame_commit_internal(
22745 &mut self,
22746 window: &mut Window,
22747 cx: &mut Context<Self>,
22748 ) -> Option<()> {
22749 let blame = self.blame.as_ref()?;
22750 let snapshot = self.snapshot(window, cx);
22751 let cursor = self
22752 .selections
22753 .newest::<Point>(&snapshot.display_snapshot)
22754 .head();
22755 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22756 let (_, blame_entry) = blame
22757 .update(cx, |blame, cx| {
22758 blame
22759 .blame_for_rows(
22760 &[RowInfo {
22761 buffer_id: Some(buffer.remote_id()),
22762 buffer_row: Some(point.row),
22763 ..Default::default()
22764 }],
22765 cx,
22766 )
22767 .next()
22768 })
22769 .flatten()?;
22770 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22771 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22772 let workspace = self.workspace()?.downgrade();
22773 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22774 None
22775 }
22776
22777 pub fn git_blame_inline_enabled(&self) -> bool {
22778 self.git_blame_inline_enabled
22779 }
22780
22781 pub fn toggle_selection_menu(
22782 &mut self,
22783 _: &ToggleSelectionMenu,
22784 _: &mut Window,
22785 cx: &mut Context<Self>,
22786 ) {
22787 self.show_selection_menu = self
22788 .show_selection_menu
22789 .map(|show_selections_menu| !show_selections_menu)
22790 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22791
22792 cx.notify();
22793 }
22794
22795 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22796 self.show_selection_menu
22797 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22798 }
22799
22800 fn start_git_blame(
22801 &mut self,
22802 user_triggered: bool,
22803 window: &mut Window,
22804 cx: &mut Context<Self>,
22805 ) {
22806 if let Some(project) = self.project() {
22807 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22808 && buffer.read(cx).file().is_none()
22809 {
22810 return;
22811 }
22812
22813 let focused = self.focus_handle(cx).contains_focused(window, cx);
22814
22815 let project = project.clone();
22816 let blame = cx
22817 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
22818 self.blame_subscription =
22819 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
22820 self.blame = Some(blame);
22821 }
22822 }
22823
22824 fn toggle_git_blame_inline_internal(
22825 &mut self,
22826 user_triggered: bool,
22827 window: &mut Window,
22828 cx: &mut Context<Self>,
22829 ) {
22830 if self.git_blame_inline_enabled {
22831 self.git_blame_inline_enabled = false;
22832 self.show_git_blame_inline = false;
22833 self.show_git_blame_inline_delay_task.take();
22834 } else {
22835 self.git_blame_inline_enabled = true;
22836 self.start_git_blame_inline(user_triggered, window, cx);
22837 }
22838
22839 cx.notify();
22840 }
22841
22842 fn start_git_blame_inline(
22843 &mut self,
22844 user_triggered: bool,
22845 window: &mut Window,
22846 cx: &mut Context<Self>,
22847 ) {
22848 self.start_git_blame(user_triggered, window, cx);
22849
22850 if ProjectSettings::get_global(cx)
22851 .git
22852 .inline_blame_delay()
22853 .is_some()
22854 {
22855 self.start_inline_blame_timer(window, cx);
22856 } else {
22857 self.show_git_blame_inline = true
22858 }
22859 }
22860
22861 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
22862 self.blame.as_ref()
22863 }
22864
22865 pub fn show_git_blame_gutter(&self) -> bool {
22866 self.show_git_blame_gutter
22867 }
22868
22869 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
22870 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
22871 }
22872
22873 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
22874 self.show_git_blame_inline
22875 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
22876 && !self.newest_selection_head_on_empty_line(cx)
22877 && self.has_blame_entries(cx)
22878 }
22879
22880 fn has_blame_entries(&self, cx: &App) -> bool {
22881 self.blame()
22882 .is_some_and(|blame| blame.read(cx).has_generated_entries())
22883 }
22884
22885 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
22886 let cursor_anchor = self.selections.newest_anchor().head();
22887
22888 let snapshot = self.buffer.read(cx).snapshot(cx);
22889 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
22890
22891 snapshot.line_len(buffer_row) == 0
22892 }
22893
22894 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
22895 let buffer_and_selection = maybe!({
22896 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
22897 let selection_range = selection.range();
22898
22899 let multi_buffer = self.buffer().read(cx);
22900 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
22901 let buffer_ranges = multi_buffer_snapshot
22902 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
22903
22904 let (buffer, range, _) = if selection.reversed {
22905 buffer_ranges.first()
22906 } else {
22907 buffer_ranges.last()
22908 }?;
22909
22910 let buffer_range = range.to_point(buffer);
22911
22912 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
22913 return Some((
22914 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22915 buffer_range.start.row..buffer_range.end.row,
22916 ));
22917 };
22918
22919 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
22920 let start =
22921 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
22922 let end =
22923 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
22924
22925 Some((
22926 multi_buffer.buffer(buffer.remote_id()).unwrap(),
22927 start.row..end.row,
22928 ))
22929 });
22930
22931 let Some((buffer, selection)) = buffer_and_selection else {
22932 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
22933 };
22934
22935 let Some(project) = self.project() else {
22936 return Task::ready(Err(anyhow!("editor does not have project")));
22937 };
22938
22939 project.update(cx, |project, cx| {
22940 project.get_permalink_to_line(&buffer, selection, cx)
22941 })
22942 }
22943
22944 pub fn copy_permalink_to_line(
22945 &mut self,
22946 _: &CopyPermalinkToLine,
22947 window: &mut Window,
22948 cx: &mut Context<Self>,
22949 ) {
22950 let permalink_task = self.get_permalink_to_line(cx);
22951 let workspace = self.workspace();
22952
22953 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
22954 Ok(permalink) => {
22955 cx.update(|_, cx| {
22956 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
22957 })
22958 .ok();
22959 }
22960 Err(err) => {
22961 let message = format!("Failed to copy permalink: {err}");
22962
22963 anyhow::Result::<()>::Err(err).log_err();
22964
22965 if let Some(workspace) = workspace {
22966 workspace
22967 .update_in(cx, |workspace, _, cx| {
22968 struct CopyPermalinkToLine;
22969
22970 workspace.show_toast(
22971 Toast::new(
22972 NotificationId::unique::<CopyPermalinkToLine>(),
22973 message,
22974 ),
22975 cx,
22976 )
22977 })
22978 .ok();
22979 }
22980 }
22981 })
22982 .detach();
22983 }
22984
22985 pub fn copy_file_location(
22986 &mut self,
22987 _: &CopyFileLocation,
22988 _: &mut Window,
22989 cx: &mut Context<Self>,
22990 ) {
22991 let selection = self
22992 .selections
22993 .newest::<Point>(&self.display_snapshot(cx))
22994 .start
22995 .row
22996 + 1;
22997 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22998 let project = self.project()?.read(cx);
22999 let file = buffer.read(cx).file()?;
23000 let path = file.path().display(project.path_style(cx));
23001
23002 Some(format!("{path}:{selection}"))
23003 }) {
23004 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23005 }
23006 }
23007
23008 pub fn open_permalink_to_line(
23009 &mut self,
23010 _: &OpenPermalinkToLine,
23011 window: &mut Window,
23012 cx: &mut Context<Self>,
23013 ) {
23014 let permalink_task = self.get_permalink_to_line(cx);
23015 let workspace = self.workspace();
23016
23017 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23018 Ok(permalink) => {
23019 cx.update(|_, cx| {
23020 cx.open_url(permalink.as_ref());
23021 })
23022 .ok();
23023 }
23024 Err(err) => {
23025 let message = format!("Failed to open permalink: {err}");
23026
23027 anyhow::Result::<()>::Err(err).log_err();
23028
23029 if let Some(workspace) = workspace {
23030 workspace.update(cx, |workspace, cx| {
23031 struct OpenPermalinkToLine;
23032
23033 workspace.show_toast(
23034 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23035 cx,
23036 )
23037 });
23038 }
23039 }
23040 })
23041 .detach();
23042 }
23043
23044 pub fn insert_uuid_v4(
23045 &mut self,
23046 _: &InsertUuidV4,
23047 window: &mut Window,
23048 cx: &mut Context<Self>,
23049 ) {
23050 self.insert_uuid(UuidVersion::V4, window, cx);
23051 }
23052
23053 pub fn insert_uuid_v7(
23054 &mut self,
23055 _: &InsertUuidV7,
23056 window: &mut Window,
23057 cx: &mut Context<Self>,
23058 ) {
23059 self.insert_uuid(UuidVersion::V7, window, cx);
23060 }
23061
23062 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23063 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23064 self.transact(window, cx, |this, window, cx| {
23065 let edits = this
23066 .selections
23067 .all::<Point>(&this.display_snapshot(cx))
23068 .into_iter()
23069 .map(|selection| {
23070 let uuid = match version {
23071 UuidVersion::V4 => uuid::Uuid::new_v4(),
23072 UuidVersion::V7 => uuid::Uuid::now_v7(),
23073 };
23074
23075 (selection.range(), uuid.to_string())
23076 });
23077 this.edit(edits, cx);
23078 this.refresh_edit_prediction(true, false, window, cx);
23079 });
23080 }
23081
23082 pub fn open_selections_in_multibuffer(
23083 &mut self,
23084 _: &OpenSelectionsInMultibuffer,
23085 window: &mut Window,
23086 cx: &mut Context<Self>,
23087 ) {
23088 let multibuffer = self.buffer.read(cx);
23089
23090 let Some(buffer) = multibuffer.as_singleton() else {
23091 return;
23092 };
23093
23094 let Some(workspace) = self.workspace() else {
23095 return;
23096 };
23097
23098 let title = multibuffer.title(cx).to_string();
23099
23100 let locations = self
23101 .selections
23102 .all_anchors(&self.display_snapshot(cx))
23103 .iter()
23104 .map(|selection| {
23105 (
23106 buffer.clone(),
23107 (selection.start.text_anchor..selection.end.text_anchor)
23108 .to_point(buffer.read(cx)),
23109 )
23110 })
23111 .into_group_map();
23112
23113 cx.spawn_in(window, async move |_, cx| {
23114 workspace.update_in(cx, |workspace, window, cx| {
23115 Self::open_locations_in_multibuffer(
23116 workspace,
23117 locations,
23118 format!("Selections for '{title}'"),
23119 false,
23120 false,
23121 MultibufferSelectionMode::All,
23122 window,
23123 cx,
23124 );
23125 })
23126 })
23127 .detach();
23128 }
23129
23130 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23131 /// last highlight added will be used.
23132 ///
23133 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23134 pub fn highlight_rows<T: 'static>(
23135 &mut self,
23136 range: Range<Anchor>,
23137 color: Hsla,
23138 options: RowHighlightOptions,
23139 cx: &mut Context<Self>,
23140 ) {
23141 let snapshot = self.buffer().read(cx).snapshot(cx);
23142 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23143 let ix = row_highlights.binary_search_by(|highlight| {
23144 Ordering::Equal
23145 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23146 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23147 });
23148
23149 if let Err(mut ix) = ix {
23150 let index = post_inc(&mut self.highlight_order);
23151
23152 // If this range intersects with the preceding highlight, then merge it with
23153 // the preceding highlight. Otherwise insert a new highlight.
23154 let mut merged = false;
23155 if ix > 0 {
23156 let prev_highlight = &mut row_highlights[ix - 1];
23157 if prev_highlight
23158 .range
23159 .end
23160 .cmp(&range.start, &snapshot)
23161 .is_ge()
23162 {
23163 ix -= 1;
23164 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23165 prev_highlight.range.end = range.end;
23166 }
23167 merged = true;
23168 prev_highlight.index = index;
23169 prev_highlight.color = color;
23170 prev_highlight.options = options;
23171 }
23172 }
23173
23174 if !merged {
23175 row_highlights.insert(
23176 ix,
23177 RowHighlight {
23178 range,
23179 index,
23180 color,
23181 options,
23182 type_id: TypeId::of::<T>(),
23183 },
23184 );
23185 }
23186
23187 // If any of the following highlights intersect with this one, merge them.
23188 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23189 let highlight = &row_highlights[ix];
23190 if next_highlight
23191 .range
23192 .start
23193 .cmp(&highlight.range.end, &snapshot)
23194 .is_le()
23195 {
23196 if next_highlight
23197 .range
23198 .end
23199 .cmp(&highlight.range.end, &snapshot)
23200 .is_gt()
23201 {
23202 row_highlights[ix].range.end = next_highlight.range.end;
23203 }
23204 row_highlights.remove(ix + 1);
23205 } else {
23206 break;
23207 }
23208 }
23209 }
23210 }
23211
23212 /// Remove any highlighted row ranges of the given type that intersect the
23213 /// given ranges.
23214 pub fn remove_highlighted_rows<T: 'static>(
23215 &mut self,
23216 ranges_to_remove: Vec<Range<Anchor>>,
23217 cx: &mut Context<Self>,
23218 ) {
23219 let snapshot = self.buffer().read(cx).snapshot(cx);
23220 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23221 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23222 row_highlights.retain(|highlight| {
23223 while let Some(range_to_remove) = ranges_to_remove.peek() {
23224 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23225 Ordering::Less | Ordering::Equal => {
23226 ranges_to_remove.next();
23227 }
23228 Ordering::Greater => {
23229 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23230 Ordering::Less | Ordering::Equal => {
23231 return false;
23232 }
23233 Ordering::Greater => break,
23234 }
23235 }
23236 }
23237 }
23238
23239 true
23240 })
23241 }
23242
23243 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23244 pub fn clear_row_highlights<T: 'static>(&mut self) {
23245 self.highlighted_rows.remove(&TypeId::of::<T>());
23246 }
23247
23248 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23249 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23250 self.highlighted_rows
23251 .get(&TypeId::of::<T>())
23252 .map_or(&[] as &[_], |vec| vec.as_slice())
23253 .iter()
23254 .map(|highlight| (highlight.range.clone(), highlight.color))
23255 }
23256
23257 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23258 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23259 /// Allows to ignore certain kinds of highlights.
23260 pub fn highlighted_display_rows(
23261 &self,
23262 window: &mut Window,
23263 cx: &mut App,
23264 ) -> BTreeMap<DisplayRow, LineHighlight> {
23265 let snapshot = self.snapshot(window, cx);
23266 let mut used_highlight_orders = HashMap::default();
23267 self.highlighted_rows
23268 .iter()
23269 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23270 .fold(
23271 BTreeMap::<DisplayRow, LineHighlight>::new(),
23272 |mut unique_rows, highlight| {
23273 let start = highlight.range.start.to_display_point(&snapshot);
23274 let end = highlight.range.end.to_display_point(&snapshot);
23275 let start_row = start.row().0;
23276 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23277 {
23278 end.row().0.saturating_sub(1)
23279 } else {
23280 end.row().0
23281 };
23282 for row in start_row..=end_row {
23283 let used_index =
23284 used_highlight_orders.entry(row).or_insert(highlight.index);
23285 if highlight.index >= *used_index {
23286 *used_index = highlight.index;
23287 unique_rows.insert(
23288 DisplayRow(row),
23289 LineHighlight {
23290 include_gutter: highlight.options.include_gutter,
23291 border: None,
23292 background: highlight.color.into(),
23293 type_id: Some(highlight.type_id),
23294 },
23295 );
23296 }
23297 }
23298 unique_rows
23299 },
23300 )
23301 }
23302
23303 pub fn highlighted_display_row_for_autoscroll(
23304 &self,
23305 snapshot: &DisplaySnapshot,
23306 ) -> Option<DisplayRow> {
23307 self.highlighted_rows
23308 .values()
23309 .flat_map(|highlighted_rows| highlighted_rows.iter())
23310 .filter_map(|highlight| {
23311 if highlight.options.autoscroll {
23312 Some(highlight.range.start.to_display_point(snapshot).row())
23313 } else {
23314 None
23315 }
23316 })
23317 .min()
23318 }
23319
23320 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23321 self.highlight_background(
23322 HighlightKey::SearchWithinRange,
23323 ranges,
23324 |_, colors| colors.colors().editor_document_highlight_read_background,
23325 cx,
23326 )
23327 }
23328
23329 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23330 self.breadcrumb_header = Some(new_header);
23331 }
23332
23333 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23334 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23335 }
23336
23337 pub fn highlight_background(
23338 &mut self,
23339 key: HighlightKey,
23340 ranges: &[Range<Anchor>],
23341 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23342 cx: &mut Context<Self>,
23343 ) {
23344 self.background_highlights
23345 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23346 self.scrollbar_marker_state.dirty = true;
23347 cx.notify();
23348 }
23349
23350 pub fn highlight_background_key(
23351 &mut self,
23352 key: HighlightKey,
23353 ranges: &[Range<Anchor>],
23354 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23355 cx: &mut Context<Self>,
23356 ) {
23357 self.background_highlights
23358 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23359 self.scrollbar_marker_state.dirty = true;
23360 cx.notify();
23361 }
23362
23363 pub fn clear_background_highlights(
23364 &mut self,
23365 key: HighlightKey,
23366 cx: &mut Context<Self>,
23367 ) -> Option<BackgroundHighlight> {
23368 let text_highlights = self.background_highlights.remove(&key)?;
23369 if !text_highlights.1.is_empty() {
23370 self.scrollbar_marker_state.dirty = true;
23371 cx.notify();
23372 }
23373 Some(text_highlights)
23374 }
23375
23376 pub fn highlight_gutter<T: 'static>(
23377 &mut self,
23378 ranges: impl Into<Vec<Range<Anchor>>>,
23379 color_fetcher: fn(&App) -> Hsla,
23380 cx: &mut Context<Self>,
23381 ) {
23382 self.gutter_highlights
23383 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23384 cx.notify();
23385 }
23386
23387 pub fn clear_gutter_highlights<T: 'static>(
23388 &mut self,
23389 cx: &mut Context<Self>,
23390 ) -> Option<GutterHighlight> {
23391 cx.notify();
23392 self.gutter_highlights.remove(&TypeId::of::<T>())
23393 }
23394
23395 pub fn insert_gutter_highlight<T: 'static>(
23396 &mut self,
23397 range: Range<Anchor>,
23398 color_fetcher: fn(&App) -> Hsla,
23399 cx: &mut Context<Self>,
23400 ) {
23401 let snapshot = self.buffer().read(cx).snapshot(cx);
23402 let mut highlights = self
23403 .gutter_highlights
23404 .remove(&TypeId::of::<T>())
23405 .map(|(_, highlights)| highlights)
23406 .unwrap_or_default();
23407 let ix = highlights.binary_search_by(|highlight| {
23408 Ordering::Equal
23409 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23410 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23411 });
23412 if let Err(ix) = ix {
23413 highlights.insert(ix, range);
23414 }
23415 self.gutter_highlights
23416 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23417 }
23418
23419 pub fn remove_gutter_highlights<T: 'static>(
23420 &mut self,
23421 ranges_to_remove: Vec<Range<Anchor>>,
23422 cx: &mut Context<Self>,
23423 ) {
23424 let snapshot = self.buffer().read(cx).snapshot(cx);
23425 let Some((color_fetcher, mut gutter_highlights)) =
23426 self.gutter_highlights.remove(&TypeId::of::<T>())
23427 else {
23428 return;
23429 };
23430 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23431 gutter_highlights.retain(|highlight| {
23432 while let Some(range_to_remove) = ranges_to_remove.peek() {
23433 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23434 Ordering::Less | Ordering::Equal => {
23435 ranges_to_remove.next();
23436 }
23437 Ordering::Greater => {
23438 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23439 Ordering::Less | Ordering::Equal => {
23440 return false;
23441 }
23442 Ordering::Greater => break,
23443 }
23444 }
23445 }
23446 }
23447
23448 true
23449 });
23450 self.gutter_highlights
23451 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23452 }
23453
23454 #[cfg(feature = "test-support")]
23455 pub fn all_text_highlights(
23456 &self,
23457 window: &mut Window,
23458 cx: &mut Context<Self>,
23459 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23460 let snapshot = self.snapshot(window, cx);
23461 self.display_map.update(cx, |display_map, _| {
23462 display_map
23463 .all_text_highlights()
23464 .map(|(_, highlight)| {
23465 let (style, ranges) = highlight.as_ref();
23466 (
23467 *style,
23468 ranges
23469 .iter()
23470 .map(|range| range.clone().to_display_points(&snapshot))
23471 .collect(),
23472 )
23473 })
23474 .collect()
23475 })
23476 }
23477
23478 #[cfg(feature = "test-support")]
23479 pub fn all_text_background_highlights(
23480 &self,
23481 window: &mut Window,
23482 cx: &mut Context<Self>,
23483 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23484 let snapshot = self.snapshot(window, cx);
23485 let buffer = &snapshot.buffer_snapshot();
23486 let start = buffer.anchor_before(MultiBufferOffset(0));
23487 let end = buffer.anchor_after(buffer.len());
23488 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23489 }
23490
23491 #[cfg(any(test, feature = "test-support"))]
23492 pub fn sorted_background_highlights_in_range(
23493 &self,
23494 search_range: Range<Anchor>,
23495 display_snapshot: &DisplaySnapshot,
23496 theme: &Theme,
23497 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23498 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23499 res.sort_by(|a, b| {
23500 a.0.start
23501 .cmp(&b.0.start)
23502 .then_with(|| a.0.end.cmp(&b.0.end))
23503 .then_with(|| a.1.cmp(&b.1))
23504 });
23505 res
23506 }
23507
23508 #[cfg(feature = "test-support")]
23509 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23510 let snapshot = self.buffer().read(cx).snapshot(cx);
23511
23512 let highlights = self
23513 .background_highlights
23514 .get(&HighlightKey::BufferSearchHighlights);
23515
23516 if let Some((_color, ranges)) = highlights {
23517 ranges
23518 .iter()
23519 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23520 .collect_vec()
23521 } else {
23522 vec![]
23523 }
23524 }
23525
23526 fn document_highlights_for_position<'a>(
23527 &'a self,
23528 position: Anchor,
23529 buffer: &'a MultiBufferSnapshot,
23530 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23531 let read_highlights = self
23532 .background_highlights
23533 .get(&HighlightKey::DocumentHighlightRead)
23534 .map(|h| &h.1);
23535 let write_highlights = self
23536 .background_highlights
23537 .get(&HighlightKey::DocumentHighlightWrite)
23538 .map(|h| &h.1);
23539 let left_position = position.bias_left(buffer);
23540 let right_position = position.bias_right(buffer);
23541 read_highlights
23542 .into_iter()
23543 .chain(write_highlights)
23544 .flat_map(move |ranges| {
23545 let start_ix = match ranges.binary_search_by(|probe| {
23546 let cmp = probe.end.cmp(&left_position, buffer);
23547 if cmp.is_ge() {
23548 Ordering::Greater
23549 } else {
23550 Ordering::Less
23551 }
23552 }) {
23553 Ok(i) | Err(i) => i,
23554 };
23555
23556 ranges[start_ix..]
23557 .iter()
23558 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23559 })
23560 }
23561
23562 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23563 self.background_highlights
23564 .get(&key)
23565 .is_some_and(|(_, highlights)| !highlights.is_empty())
23566 }
23567
23568 /// Returns all background highlights for a given range.
23569 ///
23570 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23571 pub fn background_highlights_in_range(
23572 &self,
23573 search_range: Range<Anchor>,
23574 display_snapshot: &DisplaySnapshot,
23575 theme: &Theme,
23576 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23577 let mut results = Vec::new();
23578 for (color_fetcher, ranges) in self.background_highlights.values() {
23579 let start_ix = match ranges.binary_search_by(|probe| {
23580 let cmp = probe
23581 .end
23582 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23583 if cmp.is_gt() {
23584 Ordering::Greater
23585 } else {
23586 Ordering::Less
23587 }
23588 }) {
23589 Ok(i) | Err(i) => i,
23590 };
23591 for (index, range) in ranges[start_ix..].iter().enumerate() {
23592 if range
23593 .start
23594 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23595 .is_ge()
23596 {
23597 break;
23598 }
23599
23600 let color = color_fetcher(&(start_ix + index), theme);
23601 let start = range.start.to_display_point(display_snapshot);
23602 let end = range.end.to_display_point(display_snapshot);
23603 results.push((start..end, color))
23604 }
23605 }
23606 results
23607 }
23608
23609 pub fn gutter_highlights_in_range(
23610 &self,
23611 search_range: Range<Anchor>,
23612 display_snapshot: &DisplaySnapshot,
23613 cx: &App,
23614 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23615 let mut results = Vec::new();
23616 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23617 let color = color_fetcher(cx);
23618 let start_ix = match ranges.binary_search_by(|probe| {
23619 let cmp = probe
23620 .end
23621 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23622 if cmp.is_gt() {
23623 Ordering::Greater
23624 } else {
23625 Ordering::Less
23626 }
23627 }) {
23628 Ok(i) | Err(i) => i,
23629 };
23630 for range in &ranges[start_ix..] {
23631 if range
23632 .start
23633 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23634 .is_ge()
23635 {
23636 break;
23637 }
23638
23639 let start = range.start.to_display_point(display_snapshot);
23640 let end = range.end.to_display_point(display_snapshot);
23641 results.push((start..end, color))
23642 }
23643 }
23644 results
23645 }
23646
23647 /// Get the text ranges corresponding to the redaction query
23648 pub fn redacted_ranges(
23649 &self,
23650 search_range: Range<Anchor>,
23651 display_snapshot: &DisplaySnapshot,
23652 cx: &App,
23653 ) -> Vec<Range<DisplayPoint>> {
23654 display_snapshot
23655 .buffer_snapshot()
23656 .redacted_ranges(search_range, |file| {
23657 if let Some(file) = file {
23658 file.is_private()
23659 && EditorSettings::get(
23660 Some(SettingsLocation {
23661 worktree_id: file.worktree_id(cx),
23662 path: file.path().as_ref(),
23663 }),
23664 cx,
23665 )
23666 .redact_private_values
23667 } else {
23668 false
23669 }
23670 })
23671 .map(|range| {
23672 range.start.to_display_point(display_snapshot)
23673 ..range.end.to_display_point(display_snapshot)
23674 })
23675 .collect()
23676 }
23677
23678 pub fn highlight_text_key(
23679 &mut self,
23680 key: HighlightKey,
23681 ranges: Vec<Range<Anchor>>,
23682 style: HighlightStyle,
23683 merge: bool,
23684 cx: &mut Context<Self>,
23685 ) {
23686 self.display_map.update(cx, |map, cx| {
23687 map.highlight_text(key, ranges, style, merge, cx);
23688 });
23689 cx.notify();
23690 }
23691
23692 pub fn highlight_text(
23693 &mut self,
23694 key: HighlightKey,
23695 ranges: Vec<Range<Anchor>>,
23696 style: HighlightStyle,
23697 cx: &mut Context<Self>,
23698 ) {
23699 self.display_map.update(cx, |map, cx| {
23700 map.highlight_text(key, ranges, style, false, cx)
23701 });
23702 cx.notify();
23703 }
23704
23705 pub fn text_highlights<'a>(
23706 &'a self,
23707 key: HighlightKey,
23708 cx: &'a App,
23709 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23710 self.display_map.read(cx).text_highlights(key)
23711 }
23712
23713 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23714 let cleared = self
23715 .display_map
23716 .update(cx, |map, _| map.clear_highlights(key));
23717 if cleared {
23718 cx.notify();
23719 }
23720 }
23721
23722 pub fn clear_highlights_with(
23723 &mut self,
23724 f: impl FnMut(&HighlightKey) -> bool,
23725 cx: &mut Context<Self>,
23726 ) {
23727 let cleared = self
23728 .display_map
23729 .update(cx, |map, _| map.clear_highlights_with(f));
23730 if cleared {
23731 cx.notify();
23732 }
23733 }
23734
23735 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23736 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23737 && self.focus_handle.is_focused(window)
23738 }
23739
23740 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23741 self.show_cursor_when_unfocused = is_enabled;
23742 cx.notify();
23743 }
23744
23745 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23746 cx.notify();
23747 }
23748
23749 fn on_debug_session_event(
23750 &mut self,
23751 _session: Entity<Session>,
23752 event: &SessionEvent,
23753 cx: &mut Context<Self>,
23754 ) {
23755 if let SessionEvent::InvalidateInlineValue = event {
23756 self.refresh_inline_values(cx);
23757 }
23758 }
23759
23760 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23761 let Some(project) = self.project.clone() else {
23762 return;
23763 };
23764
23765 if !self.inline_value_cache.enabled {
23766 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23767 self.splice_inlays(&inlays, Vec::new(), cx);
23768 return;
23769 }
23770
23771 let current_execution_position = self
23772 .highlighted_rows
23773 .get(&TypeId::of::<ActiveDebugLine>())
23774 .and_then(|lines| lines.last().map(|line| line.range.end));
23775
23776 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23777 let inline_values = editor
23778 .update(cx, |editor, cx| {
23779 let Some(current_execution_position) = current_execution_position else {
23780 return Some(Task::ready(Ok(Vec::new())));
23781 };
23782
23783 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23784 let snapshot = buffer.snapshot(cx);
23785
23786 let excerpt = snapshot.excerpt_containing(
23787 current_execution_position..current_execution_position,
23788 )?;
23789
23790 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23791 })?;
23792
23793 let range =
23794 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23795
23796 project.inline_values(buffer, range, cx)
23797 })
23798 .ok()
23799 .flatten()?
23800 .await
23801 .context("refreshing debugger inlays")
23802 .log_err()?;
23803
23804 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23805
23806 for (buffer_id, inline_value) in inline_values
23807 .into_iter()
23808 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23809 {
23810 buffer_inline_values
23811 .entry(buffer_id)
23812 .or_default()
23813 .push(inline_value);
23814 }
23815
23816 editor
23817 .update(cx, |editor, cx| {
23818 let snapshot = editor.buffer.read(cx).snapshot(cx);
23819 let mut new_inlays = Vec::default();
23820
23821 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23822 let buffer_id = buffer_snapshot.remote_id();
23823 buffer_inline_values
23824 .get(&buffer_id)
23825 .into_iter()
23826 .flatten()
23827 .for_each(|hint| {
23828 let inlay = Inlay::debugger(
23829 post_inc(&mut editor.next_inlay_id),
23830 Anchor::in_buffer(excerpt_id, hint.position),
23831 hint.text(),
23832 );
23833 if !inlay.text().chars().contains(&'\n') {
23834 new_inlays.push(inlay);
23835 }
23836 });
23837 }
23838
23839 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
23840 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
23841
23842 editor.splice_inlays(&inlay_ids, new_inlays, cx);
23843 })
23844 .ok()?;
23845 Some(())
23846 });
23847 }
23848
23849 fn on_buffer_event(
23850 &mut self,
23851 multibuffer: &Entity<MultiBuffer>,
23852 event: &multi_buffer::Event,
23853 window: &mut Window,
23854 cx: &mut Context<Self>,
23855 ) {
23856 match event {
23857 multi_buffer::Event::Edited { edited_buffer } => {
23858 self.scrollbar_marker_state.dirty = true;
23859 self.active_indent_guides_state.dirty = true;
23860 self.refresh_active_diagnostics(cx);
23861 self.refresh_code_actions(window, cx);
23862 self.refresh_single_line_folds(window, cx);
23863 self.refresh_matching_bracket_highlights(window, cx);
23864 self.refresh_outline_symbols(cx);
23865 self.refresh_sticky_headers(&self.snapshot(window, cx), cx);
23866 if self.has_active_edit_prediction() {
23867 self.update_visible_edit_prediction(window, cx);
23868 }
23869
23870 // Clean up orphaned review comments after edits
23871 self.cleanup_orphaned_review_comments(cx);
23872
23873 if let Some(buffer) = edited_buffer {
23874 if buffer.read(cx).file().is_none() {
23875 cx.emit(EditorEvent::TitleChanged);
23876 }
23877
23878 if self.project.is_some() {
23879 let buffer_id = buffer.read(cx).remote_id();
23880 self.register_buffer(buffer_id, cx);
23881 self.update_lsp_data(Some(buffer_id), window, cx);
23882 self.refresh_inlay_hints(
23883 InlayHintRefreshReason::BufferEdited(buffer_id),
23884 cx,
23885 );
23886 }
23887 }
23888
23889 cx.emit(EditorEvent::BufferEdited);
23890 cx.emit(SearchEvent::MatchesInvalidated);
23891
23892 let Some(project) = &self.project else { return };
23893 let (telemetry, is_via_ssh) = {
23894 let project = project.read(cx);
23895 let telemetry = project.client().telemetry().clone();
23896 let is_via_ssh = project.is_via_remote_server();
23897 (telemetry, is_via_ssh)
23898 };
23899 telemetry.log_edit_event("editor", is_via_ssh);
23900 }
23901 multi_buffer::Event::ExcerptsAdded {
23902 buffer,
23903 predecessor,
23904 excerpts,
23905 } => {
23906 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23907 let buffer_id = buffer.read(cx).remote_id();
23908 if self.buffer.read(cx).diff_for(buffer_id).is_none()
23909 && let Some(project) = &self.project
23910 {
23911 update_uncommitted_diff_for_buffer(
23912 cx.entity(),
23913 project,
23914 [buffer.clone()],
23915 self.buffer.clone(),
23916 cx,
23917 )
23918 .detach();
23919 }
23920 self.semantic_token_state
23921 .invalidate_buffer(&buffer.read(cx).remote_id());
23922 self.update_lsp_data(Some(buffer_id), window, cx);
23923 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23924 self.colorize_brackets(false, cx);
23925 self.refresh_selected_text_highlights(true, window, cx);
23926 cx.emit(EditorEvent::ExcerptsAdded {
23927 buffer: buffer.clone(),
23928 predecessor: *predecessor,
23929 excerpts: excerpts.clone(),
23930 });
23931 }
23932 multi_buffer::Event::ExcerptsRemoved {
23933 ids,
23934 removed_buffer_ids,
23935 } => {
23936 if let Some(inlay_hints) = &mut self.inlay_hints {
23937 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
23938 }
23939 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
23940 for buffer_id in removed_buffer_ids {
23941 self.registered_buffers.remove(buffer_id);
23942 self.tasks
23943 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
23944 self.semantic_token_state.invalidate_buffer(buffer_id);
23945 self.display_map.update(cx, |display_map, cx| {
23946 display_map.invalidate_semantic_highlights(*buffer_id);
23947 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
23948 });
23949 }
23950 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23951 cx.emit(EditorEvent::ExcerptsRemoved {
23952 ids: ids.clone(),
23953 removed_buffer_ids: removed_buffer_ids.clone(),
23954 });
23955 }
23956 multi_buffer::Event::ExcerptsEdited {
23957 excerpt_ids,
23958 buffer_ids,
23959 } => {
23960 self.display_map.update(cx, |map, cx| {
23961 map.unfold_buffers(buffer_ids.iter().copied(), cx)
23962 });
23963 cx.emit(EditorEvent::ExcerptsEdited {
23964 ids: excerpt_ids.clone(),
23965 });
23966 }
23967 multi_buffer::Event::ExcerptsExpanded { ids } => {
23968 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
23969 self.refresh_document_highlights(cx);
23970 let snapshot = multibuffer.read(cx).snapshot(cx);
23971 for id in ids {
23972 self.fetched_tree_sitter_chunks.remove(id);
23973 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
23974 self.semantic_token_state
23975 .invalidate_buffer(&buffer.remote_id());
23976 }
23977 }
23978 self.colorize_brackets(false, cx);
23979 self.update_lsp_data(None, window, cx);
23980 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
23981 }
23982 multi_buffer::Event::Reparsed(buffer_id) => {
23983 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23984 self.refresh_selected_text_highlights(true, window, cx);
23985 self.colorize_brackets(true, cx);
23986 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23987
23988 cx.emit(EditorEvent::Reparsed(*buffer_id));
23989 }
23990 multi_buffer::Event::DiffHunksToggled => {
23991 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
23992 }
23993 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
23994 if !is_fresh_language {
23995 self.registered_buffers.remove(&buffer_id);
23996 }
23997 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
23998 cx.emit(EditorEvent::Reparsed(*buffer_id));
23999 cx.notify();
24000 }
24001 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24002 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24003 multi_buffer::Event::FileHandleChanged
24004 | multi_buffer::Event::Reloaded
24005 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24006 multi_buffer::Event::DiagnosticsUpdated => {
24007 self.update_diagnostics_state(window, cx);
24008 }
24009 _ => {}
24010 };
24011 }
24012
24013 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24014 if !self.diagnostics_enabled() {
24015 return;
24016 }
24017 self.refresh_active_diagnostics(cx);
24018 self.refresh_inline_diagnostics(true, window, cx);
24019 self.scrollbar_marker_state.dirty = true;
24020 cx.notify();
24021 }
24022
24023 pub fn start_temporary_diff_override(&mut self) {
24024 self.load_diff_task.take();
24025 self.temporary_diff_override = true;
24026 }
24027
24028 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24029 self.temporary_diff_override = false;
24030 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24031 self.buffer.update(cx, |buffer, cx| {
24032 buffer.set_all_diff_hunks_collapsed(cx);
24033 });
24034
24035 if let Some(project) = self.project.clone() {
24036 self.load_diff_task = Some(
24037 update_uncommitted_diff_for_buffer(
24038 cx.entity(),
24039 &project,
24040 self.buffer.read(cx).all_buffers(),
24041 self.buffer.clone(),
24042 cx,
24043 )
24044 .shared(),
24045 );
24046 }
24047 }
24048
24049 fn on_display_map_changed(
24050 &mut self,
24051 _: Entity<DisplayMap>,
24052 _: &mut Window,
24053 cx: &mut Context<Self>,
24054 ) {
24055 cx.notify();
24056 }
24057
24058 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24059 if !self.mode.is_full() {
24060 return None;
24061 }
24062
24063 let theme_settings = theme::ThemeSettings::get_global(cx);
24064 let theme = cx.theme();
24065 let accent_colors = theme.accents().clone();
24066
24067 let accent_overrides = theme_settings
24068 .theme_overrides
24069 .get(theme.name.as_ref())
24070 .map(|theme_style| &theme_style.accents)
24071 .into_iter()
24072 .flatten()
24073 .chain(
24074 theme_settings
24075 .experimental_theme_overrides
24076 .as_ref()
24077 .map(|overrides| &overrides.accents)
24078 .into_iter()
24079 .flatten(),
24080 )
24081 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24082 .collect();
24083
24084 Some(AccentData {
24085 colors: accent_colors,
24086 overrides: accent_overrides,
24087 })
24088 }
24089
24090 fn fetch_applicable_language_settings(
24091 &self,
24092 cx: &App,
24093 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24094 if !self.mode.is_full() {
24095 return HashMap::default();
24096 }
24097
24098 self.buffer().read(cx).all_buffers().into_iter().fold(
24099 HashMap::default(),
24100 |mut acc, buffer| {
24101 let buffer = buffer.read(cx);
24102 let language = buffer.language().map(|language| language.name());
24103 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24104 let file = buffer.file();
24105 v.insert(language_settings(language, file, cx).into_owned());
24106 }
24107 acc
24108 },
24109 )
24110 }
24111
24112 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24113 let new_language_settings = self.fetch_applicable_language_settings(cx);
24114 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24115 self.applicable_language_settings = new_language_settings;
24116
24117 let new_accents = self.fetch_accent_data(cx);
24118 let accents_changed = new_accents != self.accent_data;
24119 self.accent_data = new_accents;
24120
24121 if self.diagnostics_enabled() {
24122 let new_severity = EditorSettings::get_global(cx)
24123 .diagnostics_max_severity
24124 .unwrap_or(DiagnosticSeverity::Hint);
24125 self.set_max_diagnostics_severity(new_severity, cx);
24126 }
24127 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24128 self.update_edit_prediction_settings(cx);
24129 self.refresh_edit_prediction(true, false, window, cx);
24130 self.refresh_inline_values(cx);
24131
24132 let old_cursor_shape = self.cursor_shape;
24133 let old_show_breadcrumbs = self.show_breadcrumbs;
24134
24135 {
24136 let editor_settings = EditorSettings::get_global(cx);
24137 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24138 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24139 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24140 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24141 }
24142
24143 if old_cursor_shape != self.cursor_shape {
24144 cx.emit(EditorEvent::CursorShapeChanged);
24145 }
24146
24147 if old_show_breadcrumbs != self.show_breadcrumbs {
24148 cx.emit(EditorEvent::BreadcrumbsChanged);
24149 }
24150
24151 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24152 let project_settings = ProjectSettings::get_global(cx);
24153 (
24154 project_settings.session.restore_unsaved_buffers,
24155 project_settings.diagnostics.inline.enabled,
24156 project_settings.git.inline_blame.enabled,
24157 )
24158 };
24159 self.buffer_serialization = self
24160 .should_serialize_buffer()
24161 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24162
24163 if self.mode.is_full() {
24164 if self.show_inline_diagnostics != show_inline_diagnostics {
24165 self.show_inline_diagnostics = show_inline_diagnostics;
24166 self.refresh_inline_diagnostics(false, window, cx);
24167 }
24168
24169 if self.git_blame_inline_enabled != inline_blame_enabled {
24170 self.toggle_git_blame_inline_internal(false, window, cx);
24171 }
24172
24173 let minimap_settings = EditorSettings::get_global(cx).minimap;
24174 if self.minimap_visibility != MinimapVisibility::Disabled {
24175 if self.minimap_visibility.settings_visibility()
24176 != minimap_settings.minimap_enabled()
24177 {
24178 self.set_minimap_visibility(
24179 MinimapVisibility::for_mode(self.mode(), cx),
24180 window,
24181 cx,
24182 );
24183 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24184 minimap_entity.update(cx, |minimap_editor, cx| {
24185 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24186 })
24187 }
24188 }
24189
24190 if language_settings_changed || accents_changed {
24191 self.colorize_brackets(true, cx);
24192 }
24193
24194 if language_settings_changed {
24195 self.clear_disabled_lsp_folding_ranges(window, cx);
24196 }
24197
24198 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24199 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24200 }) {
24201 if !inlay_splice.is_empty() {
24202 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24203 }
24204 self.refresh_colors_for_visible_range(None, window, cx);
24205 }
24206
24207 self.refresh_inlay_hints(
24208 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24209 self.selections.newest_anchor().head(),
24210 &self.buffer.read(cx).snapshot(cx),
24211 cx,
24212 )),
24213 cx,
24214 );
24215
24216 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24217 .global_lsp_settings
24218 .semantic_token_rules
24219 .clone();
24220 let semantic_token_rules_changed = self
24221 .semantic_token_state
24222 .update_rules(new_semantic_token_rules);
24223 if language_settings_changed || semantic_token_rules_changed {
24224 self.refresh_semantic_token_highlights(cx);
24225 }
24226 }
24227
24228 cx.notify();
24229 }
24230
24231 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24232 if !self.mode.is_full() {
24233 return;
24234 }
24235
24236 let new_accents = self.fetch_accent_data(cx);
24237 if new_accents != self.accent_data {
24238 self.accent_data = new_accents;
24239 self.colorize_brackets(true, cx);
24240 }
24241
24242 self.refresh_semantic_token_highlights(cx);
24243 }
24244
24245 pub fn set_searchable(&mut self, searchable: bool) {
24246 self.searchable = searchable;
24247 }
24248
24249 pub fn searchable(&self) -> bool {
24250 self.searchable
24251 }
24252
24253 pub fn open_excerpts_in_split(
24254 &mut self,
24255 _: &OpenExcerptsSplit,
24256 window: &mut Window,
24257 cx: &mut Context<Self>,
24258 ) {
24259 self.open_excerpts_common(None, true, window, cx)
24260 }
24261
24262 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24263 self.open_excerpts_common(None, false, window, cx)
24264 }
24265
24266 pub(crate) fn open_excerpts_common(
24267 &mut self,
24268 jump_data: Option<JumpData>,
24269 split: bool,
24270 window: &mut Window,
24271 cx: &mut Context<Self>,
24272 ) {
24273 if self.buffer.read(cx).is_singleton() {
24274 cx.propagate();
24275 return;
24276 }
24277
24278 let mut new_selections_by_buffer = HashMap::default();
24279 match &jump_data {
24280 Some(JumpData::MultiBufferPoint {
24281 excerpt_id,
24282 position,
24283 anchor,
24284 line_offset_from_top,
24285 }) => {
24286 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24287 if let Some(buffer) = multi_buffer_snapshot
24288 .buffer_id_for_excerpt(*excerpt_id)
24289 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24290 {
24291 let buffer_snapshot = buffer.read(cx).snapshot();
24292 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24293 language::ToPoint::to_point(anchor, &buffer_snapshot)
24294 } else {
24295 buffer_snapshot.clip_point(*position, Bias::Left)
24296 };
24297 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24298 new_selections_by_buffer.insert(
24299 buffer,
24300 (
24301 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24302 Some(*line_offset_from_top),
24303 ),
24304 );
24305 }
24306 }
24307 Some(JumpData::MultiBufferRow {
24308 row,
24309 line_offset_from_top,
24310 }) => {
24311 let point = MultiBufferPoint::new(row.0, 0);
24312 if let Some((buffer, buffer_point, _)) =
24313 self.buffer.read(cx).point_to_buffer_point(point, cx)
24314 {
24315 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24316 new_selections_by_buffer
24317 .entry(buffer)
24318 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24319 .0
24320 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24321 }
24322 }
24323 None => {
24324 let selections = self
24325 .selections
24326 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24327 let multi_buffer = self.buffer.read(cx);
24328 for selection in selections {
24329 for (snapshot, range, _, anchor) in multi_buffer
24330 .snapshot(cx)
24331 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24332 {
24333 if let Some(anchor) = anchor {
24334 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24335 else {
24336 continue;
24337 };
24338 let offset = text::ToOffset::to_offset(
24339 &anchor.text_anchor,
24340 &buffer_handle.read(cx).snapshot(),
24341 );
24342 let range = BufferOffset(offset)..BufferOffset(offset);
24343 new_selections_by_buffer
24344 .entry(buffer_handle)
24345 .or_insert((Vec::new(), None))
24346 .0
24347 .push(range)
24348 } else {
24349 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24350 else {
24351 continue;
24352 };
24353 new_selections_by_buffer
24354 .entry(buffer_handle)
24355 .or_insert((Vec::new(), None))
24356 .0
24357 .push(range)
24358 }
24359 }
24360 }
24361 }
24362 }
24363
24364 if self.delegate_open_excerpts {
24365 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24366 .into_iter()
24367 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24368 .collect();
24369 if !selections_by_buffer.is_empty() {
24370 cx.emit(EditorEvent::OpenExcerptsRequested {
24371 selections_by_buffer,
24372 split,
24373 });
24374 }
24375 return;
24376 }
24377
24378 let Some(workspace) = self.workspace() else {
24379 cx.propagate();
24380 return;
24381 };
24382
24383 new_selections_by_buffer
24384 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24385
24386 if new_selections_by_buffer.is_empty() {
24387 return;
24388 }
24389
24390 Self::open_buffers_in_workspace(
24391 workspace.downgrade(),
24392 new_selections_by_buffer,
24393 split,
24394 window,
24395 cx,
24396 );
24397 }
24398
24399 pub(crate) fn open_buffers_in_workspace(
24400 workspace: WeakEntity<Workspace>,
24401 new_selections_by_buffer: HashMap<
24402 Entity<language::Buffer>,
24403 (Vec<Range<BufferOffset>>, Option<u32>),
24404 >,
24405 split: bool,
24406 window: &mut Window,
24407 cx: &mut App,
24408 ) {
24409 // We defer the pane interaction because we ourselves are a workspace item
24410 // and activating a new item causes the pane to call a method on us reentrantly,
24411 // which panics if we're on the stack.
24412 window.defer(cx, move |window, cx| {
24413 workspace
24414 .update(cx, |workspace, cx| {
24415 let pane = if split {
24416 workspace.adjacent_pane(window, cx)
24417 } else {
24418 workspace.active_pane().clone()
24419 };
24420
24421 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24422 let buffer_read = buffer.read(cx);
24423 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24424 (true, project::File::from_dyn(Some(file)).is_some())
24425 } else {
24426 (false, false)
24427 };
24428
24429 // If project file is none workspace.open_project_item will fail to open the excerpt
24430 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24431 // so we check if there's a tab match in that case first
24432 let editor = (!has_file || !is_project_file)
24433 .then(|| {
24434 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24435 // so `workspace.open_project_item` will never find them, always opening a new editor.
24436 // Instead, we try to activate the existing editor in the pane first.
24437 let (editor, pane_item_index, pane_item_id) =
24438 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24439 let editor = item.downcast::<Editor>()?;
24440 let singleton_buffer =
24441 editor.read(cx).buffer().read(cx).as_singleton()?;
24442 if singleton_buffer == buffer {
24443 Some((editor, i, item.item_id()))
24444 } else {
24445 None
24446 }
24447 })?;
24448 pane.update(cx, |pane, cx| {
24449 pane.activate_item(pane_item_index, true, true, window, cx);
24450 if !PreviewTabsSettings::get_global(cx)
24451 .enable_preview_from_multibuffer
24452 {
24453 pane.unpreview_item_if_preview(pane_item_id);
24454 }
24455 });
24456 Some(editor)
24457 })
24458 .flatten()
24459 .unwrap_or_else(|| {
24460 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24461 .enable_keep_preview_on_code_navigation;
24462 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24463 .enable_preview_from_multibuffer;
24464 workspace.open_project_item::<Self>(
24465 pane.clone(),
24466 buffer,
24467 true,
24468 true,
24469 keep_old_preview,
24470 allow_new_preview,
24471 window,
24472 cx,
24473 )
24474 });
24475
24476 editor.update(cx, |editor, cx| {
24477 if has_file && !is_project_file {
24478 editor.set_read_only(true);
24479 }
24480 let autoscroll = match scroll_offset {
24481 Some(scroll_offset) => {
24482 Autoscroll::top_relative(scroll_offset as usize)
24483 }
24484 None => Autoscroll::newest(),
24485 };
24486 let nav_history = editor.nav_history.take();
24487 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24488 let Some((&excerpt_id, _, buffer_snapshot)) =
24489 multibuffer_snapshot.as_singleton()
24490 else {
24491 return;
24492 };
24493 editor.change_selections(
24494 SelectionEffects::scroll(autoscroll),
24495 window,
24496 cx,
24497 |s| {
24498 s.select_ranges(ranges.into_iter().map(|range| {
24499 let range = buffer_snapshot.anchor_before(range.start)
24500 ..buffer_snapshot.anchor_after(range.end);
24501 multibuffer_snapshot
24502 .anchor_range_in_excerpt(excerpt_id, range)
24503 .unwrap()
24504 }));
24505 },
24506 );
24507 editor.nav_history = nav_history;
24508 });
24509 }
24510 })
24511 .ok();
24512 });
24513 }
24514
24515 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24516 let snapshot = self.buffer.read(cx).read(cx);
24517 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24518 Some(
24519 ranges
24520 .iter()
24521 .map(move |range| {
24522 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24523 })
24524 .collect(),
24525 )
24526 }
24527
24528 fn selection_replacement_ranges(
24529 &self,
24530 range: Range<MultiBufferOffsetUtf16>,
24531 cx: &mut App,
24532 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24533 let selections = self
24534 .selections
24535 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24536 let newest_selection = selections
24537 .iter()
24538 .max_by_key(|selection| selection.id)
24539 .unwrap();
24540 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24541 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24542 let snapshot = self.buffer.read(cx).read(cx);
24543 selections
24544 .into_iter()
24545 .map(|mut selection| {
24546 selection.start.0.0 =
24547 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24548 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24549 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24550 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24551 })
24552 .collect()
24553 }
24554
24555 fn report_editor_event(
24556 &self,
24557 reported_event: ReportEditorEvent,
24558 file_extension: Option<String>,
24559 cx: &App,
24560 ) {
24561 if cfg!(any(test, feature = "test-support")) {
24562 return;
24563 }
24564
24565 let Some(project) = &self.project else { return };
24566
24567 // If None, we are in a file without an extension
24568 let file = self
24569 .buffer
24570 .read(cx)
24571 .as_singleton()
24572 .and_then(|b| b.read(cx).file());
24573 let file_extension = file_extension.or(file
24574 .as_ref()
24575 .and_then(|file| Path::new(file.file_name(cx)).extension())
24576 .and_then(|e| e.to_str())
24577 .map(|a| a.to_string()));
24578
24579 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24580 .map(|vim_mode| vim_mode.0)
24581 .unwrap_or(false);
24582
24583 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24584 let copilot_enabled = edit_predictions_provider
24585 == language::language_settings::EditPredictionProvider::Copilot;
24586 let copilot_enabled_for_language = self
24587 .buffer
24588 .read(cx)
24589 .language_settings(cx)
24590 .show_edit_predictions;
24591
24592 let project = project.read(cx);
24593 let event_type = reported_event.event_type();
24594
24595 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24596 telemetry::event!(
24597 event_type,
24598 type = if auto_saved {"autosave"} else {"manual"},
24599 file_extension,
24600 vim_mode,
24601 copilot_enabled,
24602 copilot_enabled_for_language,
24603 edit_predictions_provider,
24604 is_via_ssh = project.is_via_remote_server(),
24605 );
24606 } else {
24607 telemetry::event!(
24608 event_type,
24609 file_extension,
24610 vim_mode,
24611 copilot_enabled,
24612 copilot_enabled_for_language,
24613 edit_predictions_provider,
24614 is_via_ssh = project.is_via_remote_server(),
24615 );
24616 };
24617 }
24618
24619 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24620 /// with each line being an array of {text, highlight} objects.
24621 fn copy_highlight_json(
24622 &mut self,
24623 _: &CopyHighlightJson,
24624 window: &mut Window,
24625 cx: &mut Context<Self>,
24626 ) {
24627 #[derive(Serialize)]
24628 struct Chunk<'a> {
24629 text: String,
24630 highlight: Option<&'a str>,
24631 }
24632
24633 let snapshot = self.buffer.read(cx).snapshot(cx);
24634 let range = self
24635 .selected_text_range(false, window, cx)
24636 .and_then(|selection| {
24637 if selection.range.is_empty() {
24638 None
24639 } else {
24640 Some(
24641 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24642 selection.range.start,
24643 )))
24644 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24645 selection.range.end,
24646 ))),
24647 )
24648 }
24649 })
24650 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24651
24652 let chunks = snapshot.chunks(range, true);
24653 let mut lines = Vec::new();
24654 let mut line: VecDeque<Chunk> = VecDeque::new();
24655
24656 let Some(style) = self.style.as_ref() else {
24657 return;
24658 };
24659
24660 for chunk in chunks {
24661 let highlight = chunk
24662 .syntax_highlight_id
24663 .and_then(|id| id.name(&style.syntax));
24664 let mut chunk_lines = chunk.text.split('\n').peekable();
24665 while let Some(text) = chunk_lines.next() {
24666 let mut merged_with_last_token = false;
24667 if let Some(last_token) = line.back_mut()
24668 && last_token.highlight == highlight
24669 {
24670 last_token.text.push_str(text);
24671 merged_with_last_token = true;
24672 }
24673
24674 if !merged_with_last_token {
24675 line.push_back(Chunk {
24676 text: text.into(),
24677 highlight,
24678 });
24679 }
24680
24681 if chunk_lines.peek().is_some() {
24682 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24683 line.pop_front();
24684 }
24685 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24686 line.pop_back();
24687 }
24688
24689 lines.push(mem::take(&mut line));
24690 }
24691 }
24692 }
24693
24694 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24695 return;
24696 };
24697 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24698 }
24699
24700 pub fn open_context_menu(
24701 &mut self,
24702 _: &OpenContextMenu,
24703 window: &mut Window,
24704 cx: &mut Context<Self>,
24705 ) {
24706 self.request_autoscroll(Autoscroll::newest(), cx);
24707 let position = self
24708 .selections
24709 .newest_display(&self.display_snapshot(cx))
24710 .start;
24711 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24712 }
24713
24714 pub fn replay_insert_event(
24715 &mut self,
24716 text: &str,
24717 relative_utf16_range: Option<Range<isize>>,
24718 window: &mut Window,
24719 cx: &mut Context<Self>,
24720 ) {
24721 if !self.input_enabled {
24722 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24723 return;
24724 }
24725 if let Some(relative_utf16_range) = relative_utf16_range {
24726 let selections = self
24727 .selections
24728 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24729 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24730 let new_ranges = selections.into_iter().map(|range| {
24731 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24732 range
24733 .head()
24734 .0
24735 .0
24736 .saturating_add_signed(relative_utf16_range.start),
24737 ));
24738 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24739 range
24740 .head()
24741 .0
24742 .0
24743 .saturating_add_signed(relative_utf16_range.end),
24744 ));
24745 start..end
24746 });
24747 s.select_ranges(new_ranges);
24748 });
24749 }
24750
24751 self.handle_input(text, window, cx);
24752 }
24753
24754 pub fn is_focused(&self, window: &Window) -> bool {
24755 self.focus_handle.is_focused(window)
24756 }
24757
24758 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24759 cx.emit(EditorEvent::Focused);
24760
24761 if let Some(descendant) = self
24762 .last_focused_descendant
24763 .take()
24764 .and_then(|descendant| descendant.upgrade())
24765 {
24766 window.focus(&descendant, cx);
24767 } else {
24768 if let Some(blame) = self.blame.as_ref() {
24769 blame.update(cx, GitBlame::focus)
24770 }
24771
24772 self.blink_manager.update(cx, BlinkManager::enable);
24773 self.show_cursor_names(window, cx);
24774 self.buffer.update(cx, |buffer, cx| {
24775 buffer.finalize_last_transaction(cx);
24776 if self.leader_id.is_none() {
24777 buffer.set_active_selections(
24778 &self.selections.disjoint_anchors_arc(),
24779 self.selections.line_mode(),
24780 self.cursor_shape,
24781 cx,
24782 );
24783 }
24784 });
24785
24786 if let Some(position_map) = self.last_position_map.clone() {
24787 EditorElement::mouse_moved(
24788 self,
24789 &MouseMoveEvent {
24790 position: window.mouse_position(),
24791 pressed_button: None,
24792 modifiers: window.modifiers(),
24793 },
24794 &position_map,
24795 None,
24796 window,
24797 cx,
24798 );
24799 }
24800 }
24801 }
24802
24803 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24804 cx.emit(EditorEvent::FocusedIn)
24805 }
24806
24807 fn handle_focus_out(
24808 &mut self,
24809 event: FocusOutEvent,
24810 _window: &mut Window,
24811 cx: &mut Context<Self>,
24812 ) {
24813 if event.blurred != self.focus_handle {
24814 self.last_focused_descendant = Some(event.blurred);
24815 }
24816 self.selection_drag_state = SelectionDragState::None;
24817 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24818 }
24819
24820 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24821 self.blink_manager.update(cx, BlinkManager::disable);
24822 self.buffer
24823 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
24824
24825 if let Some(blame) = self.blame.as_ref() {
24826 blame.update(cx, GitBlame::blur)
24827 }
24828 if !self.hover_state.focused(window, cx) {
24829 hide_hover(self, cx);
24830 }
24831 if !self
24832 .context_menu
24833 .borrow()
24834 .as_ref()
24835 .is_some_and(|context_menu| context_menu.focused(window, cx))
24836 {
24837 self.hide_context_menu(window, cx);
24838 }
24839 self.take_active_edit_prediction(cx);
24840 cx.emit(EditorEvent::Blurred);
24841 cx.notify();
24842 }
24843
24844 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24845 let mut pending: String = window
24846 .pending_input_keystrokes()
24847 .into_iter()
24848 .flatten()
24849 .filter_map(|keystroke| keystroke.key_char.clone())
24850 .collect();
24851
24852 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
24853 pending = "".to_string();
24854 }
24855
24856 let existing_pending = self
24857 .text_highlights(HighlightKey::PendingInput, cx)
24858 .map(|(_, ranges)| ranges.to_vec());
24859 if existing_pending.is_none() && pending.is_empty() {
24860 return;
24861 }
24862 let transaction =
24863 self.transact(window, cx, |this, window, cx| {
24864 let selections = this
24865 .selections
24866 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
24867 let edits = selections
24868 .iter()
24869 .map(|selection| (selection.end..selection.end, pending.clone()));
24870 this.edit(edits, cx);
24871 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24872 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
24873 sel.start + ix * pending.len()..sel.end + ix * pending.len()
24874 }));
24875 });
24876 if let Some(existing_ranges) = existing_pending {
24877 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
24878 this.edit(edits, cx);
24879 }
24880 });
24881
24882 let snapshot = self.snapshot(window, cx);
24883 let ranges = self
24884 .selections
24885 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
24886 .into_iter()
24887 .map(|selection| {
24888 snapshot.buffer_snapshot().anchor_after(selection.end)
24889 ..snapshot
24890 .buffer_snapshot()
24891 .anchor_before(selection.end + pending.len())
24892 })
24893 .collect();
24894
24895 if pending.is_empty() {
24896 self.clear_highlights(HighlightKey::PendingInput, cx);
24897 } else {
24898 self.highlight_text(
24899 HighlightKey::PendingInput,
24900 ranges,
24901 HighlightStyle {
24902 underline: Some(UnderlineStyle {
24903 thickness: px(1.),
24904 color: None,
24905 wavy: false,
24906 }),
24907 ..Default::default()
24908 },
24909 cx,
24910 );
24911 }
24912
24913 self.ime_transaction = self.ime_transaction.or(transaction);
24914 if let Some(transaction) = self.ime_transaction {
24915 self.buffer.update(cx, |buffer, cx| {
24916 buffer.group_until_transaction(transaction, cx);
24917 });
24918 }
24919
24920 if self
24921 .text_highlights(HighlightKey::PendingInput, cx)
24922 .is_none()
24923 {
24924 self.ime_transaction.take();
24925 }
24926 }
24927
24928 pub fn register_action_renderer(
24929 &mut self,
24930 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
24931 ) -> Subscription {
24932 let id = self.next_editor_action_id.post_inc();
24933 self.editor_actions
24934 .borrow_mut()
24935 .insert(id, Box::new(listener));
24936
24937 let editor_actions = self.editor_actions.clone();
24938 Subscription::new(move || {
24939 editor_actions.borrow_mut().remove(&id);
24940 })
24941 }
24942
24943 pub fn register_action<A: Action>(
24944 &mut self,
24945 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
24946 ) -> Subscription {
24947 let id = self.next_editor_action_id.post_inc();
24948 let listener = Arc::new(listener);
24949 self.editor_actions.borrow_mut().insert(
24950 id,
24951 Box::new(move |_, window, _| {
24952 let listener = listener.clone();
24953 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
24954 let action = action.downcast_ref().unwrap();
24955 if phase == DispatchPhase::Bubble {
24956 listener(action, window, cx)
24957 }
24958 })
24959 }),
24960 );
24961
24962 let editor_actions = self.editor_actions.clone();
24963 Subscription::new(move || {
24964 editor_actions.borrow_mut().remove(&id);
24965 })
24966 }
24967
24968 pub fn file_header_size(&self) -> u32 {
24969 FILE_HEADER_HEIGHT
24970 }
24971
24972 pub fn restore(
24973 &mut self,
24974 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
24975 window: &mut Window,
24976 cx: &mut Context<Self>,
24977 ) {
24978 self.buffer().update(cx, |multi_buffer, cx| {
24979 for (buffer_id, changes) in revert_changes {
24980 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
24981 buffer.update(cx, |buffer, cx| {
24982 buffer.edit(
24983 changes
24984 .into_iter()
24985 .map(|(range, text)| (range, text.to_string())),
24986 None,
24987 cx,
24988 );
24989 });
24990 }
24991 }
24992 });
24993 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24994 selections.refresh()
24995 });
24996 }
24997
24998 pub fn to_pixel_point(
24999 &mut self,
25000 source: Anchor,
25001 editor_snapshot: &EditorSnapshot,
25002 window: &mut Window,
25003 cx: &mut App,
25004 ) -> Option<gpui::Point<Pixels>> {
25005 let source_point = source.to_display_point(editor_snapshot);
25006 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25007 }
25008
25009 pub fn display_to_pixel_point(
25010 &mut self,
25011 source: DisplayPoint,
25012 editor_snapshot: &EditorSnapshot,
25013 window: &mut Window,
25014 cx: &mut App,
25015 ) -> Option<gpui::Point<Pixels>> {
25016 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25017 let text_layout_details = self.text_layout_details(window, cx);
25018 let scroll_top = text_layout_details
25019 .scroll_anchor
25020 .scroll_position(editor_snapshot)
25021 .y;
25022
25023 if source.row().as_f64() < scroll_top.floor() {
25024 return None;
25025 }
25026 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25027 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25028 Some(gpui::Point::new(source_x, source_y))
25029 }
25030
25031 pub fn has_visible_completions_menu(&self) -> bool {
25032 !self.edit_prediction_preview_is_active()
25033 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25034 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25035 })
25036 }
25037
25038 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25039 if self.mode.is_minimap() {
25040 return;
25041 }
25042 self.addons
25043 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25044 }
25045
25046 pub fn unregister_addon<T: Addon>(&mut self) {
25047 self.addons.remove(&std::any::TypeId::of::<T>());
25048 }
25049
25050 pub fn addon<T: Addon>(&self) -> Option<&T> {
25051 let type_id = std::any::TypeId::of::<T>();
25052 self.addons
25053 .get(&type_id)
25054 .and_then(|item| item.to_any().downcast_ref::<T>())
25055 }
25056
25057 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25058 let type_id = std::any::TypeId::of::<T>();
25059 self.addons
25060 .get_mut(&type_id)
25061 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25062 }
25063
25064 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25065 let text_layout_details = self.text_layout_details(window, cx);
25066 let style = &text_layout_details.editor_style;
25067 let font_id = window.text_system().resolve_font(&style.text.font());
25068 let font_size = style.text.font_size.to_pixels(window.rem_size());
25069 let line_height = style.text.line_height_in_pixels(window.rem_size());
25070 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25071 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25072
25073 CharacterDimensions {
25074 em_width,
25075 em_advance,
25076 line_height,
25077 }
25078 }
25079
25080 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25081 self.load_diff_task.clone()
25082 }
25083
25084 fn read_metadata_from_db(
25085 &mut self,
25086 item_id: u64,
25087 workspace_id: WorkspaceId,
25088 window: &mut Window,
25089 cx: &mut Context<Editor>,
25090 ) {
25091 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25092 && !self.mode.is_minimap()
25093 && WorkspaceSettings::get(None, cx).restore_on_startup
25094 != RestoreOnStartupBehavior::EmptyTab
25095 {
25096 let buffer_snapshot = OnceCell::new();
25097
25098 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25099 && !folds.is_empty()
25100 {
25101 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25102 let snapshot_len = snapshot.len().0;
25103
25104 // Helper: search for fingerprint in buffer, return offset if found
25105 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25106 // Ensure we start at a character boundary (defensive)
25107 let search_start = snapshot
25108 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25109 .0;
25110 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25111
25112 let mut byte_offset = search_start;
25113 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25114 if byte_offset > search_end {
25115 break;
25116 }
25117 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25118 return Some(byte_offset);
25119 }
25120 byte_offset += ch.len_utf8();
25121 }
25122 None
25123 };
25124
25125 // Track search position to handle duplicate fingerprints correctly.
25126 // Folds are stored in document order, so we advance after each match.
25127 let mut search_start = 0usize;
25128
25129 let valid_folds: Vec<_> = folds
25130 .into_iter()
25131 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25132 // Skip folds without fingerprints (old data before migration)
25133 let sfp = start_fp?;
25134 let efp = end_fp?;
25135 let efp_len = efp.len();
25136
25137 // Fast path: check if fingerprints match at stored offsets
25138 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25139 let start_matches = stored_start < snapshot_len
25140 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25141 let efp_check_pos = stored_end.saturating_sub(efp_len);
25142 let end_matches = efp_check_pos >= stored_start
25143 && stored_end <= snapshot_len
25144 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25145
25146 let (new_start, new_end) = if start_matches && end_matches {
25147 // Offsets unchanged, use stored values
25148 (stored_start, stored_end)
25149 } else if sfp == efp {
25150 // Short fold: identical fingerprints can only match once per search
25151 // Use stored fold length to compute new_end
25152 let new_start = find_fingerprint(&sfp, search_start)?;
25153 let fold_len = stored_end - stored_start;
25154 let new_end = new_start + fold_len;
25155 (new_start, new_end)
25156 } else {
25157 // Slow path: search for fingerprints in buffer
25158 let new_start = find_fingerprint(&sfp, search_start)?;
25159 // Search for end_fp after start, then add efp_len to get actual fold end
25160 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25161 let new_end = efp_pos + efp_len;
25162 (new_start, new_end)
25163 };
25164
25165 // Advance search position for next fold
25166 search_start = new_end;
25167
25168 // Validate fold makes sense (end must be after start)
25169 if new_end <= new_start {
25170 return None;
25171 }
25172
25173 Some(
25174 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25175 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25176 )
25177 })
25178 .collect();
25179
25180 if !valid_folds.is_empty() {
25181 self.fold_ranges(valid_folds, false, window, cx);
25182
25183 // Migrate folds to current entity_id before workspace cleanup runs.
25184 // Entity IDs change between sessions, but workspace cleanup deletes
25185 // old editor rows (cascading to folds) based on current entity IDs.
25186 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
25187 if new_editor_id != item_id {
25188 cx.spawn(async move |_, _| {
25189 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
25190 .await
25191 .log_err();
25192 })
25193 .detach();
25194 }
25195 }
25196 }
25197
25198 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25199 && !selections.is_empty()
25200 {
25201 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25202 // skip adding the initial selection to selection history
25203 self.selection_history.mode = SelectionHistoryMode::Skipping;
25204 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25205 s.select_ranges(selections.into_iter().map(|(start, end)| {
25206 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25207 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25208 }));
25209 });
25210 self.selection_history.mode = SelectionHistoryMode::Normal;
25211 };
25212 }
25213
25214 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25215 }
25216
25217 fn update_lsp_data(
25218 &mut self,
25219 for_buffer: Option<BufferId>,
25220 window: &mut Window,
25221 cx: &mut Context<'_, Self>,
25222 ) {
25223 if !self.enable_lsp_data {
25224 return;
25225 }
25226
25227 if let Some(buffer_id) = for_buffer {
25228 self.pull_diagnostics(buffer_id, window, cx);
25229 self.update_semantic_tokens(Some(buffer_id), None, cx);
25230 } else {
25231 self.refresh_semantic_token_highlights(cx);
25232 }
25233 self.refresh_colors_for_visible_range(for_buffer, window, cx);
25234 self.refresh_folding_ranges(for_buffer, window, cx);
25235 }
25236
25237 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25238 if !self.mode().is_full() {
25239 return;
25240 }
25241 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25242 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25243 }
25244 }
25245
25246 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25247 if !self.mode().is_full() {
25248 return;
25249 }
25250
25251 if !self.registered_buffers.contains_key(&buffer_id)
25252 && let Some(project) = self.project.as_ref()
25253 {
25254 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25255 project.update(cx, |project, cx| {
25256 self.registered_buffers.insert(
25257 buffer_id,
25258 project.register_buffer_with_language_servers(&buffer, cx),
25259 );
25260 });
25261 } else {
25262 self.registered_buffers.remove(&buffer_id);
25263 }
25264 }
25265 }
25266
25267 fn create_style(&self, cx: &App) -> EditorStyle {
25268 let settings = ThemeSettings::get_global(cx);
25269
25270 let mut text_style = match self.mode {
25271 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25272 color: cx.theme().colors().editor_foreground,
25273 font_family: settings.ui_font.family.clone(),
25274 font_features: settings.ui_font.features.clone(),
25275 font_fallbacks: settings.ui_font.fallbacks.clone(),
25276 font_size: rems(0.875).into(),
25277 font_weight: settings.ui_font.weight,
25278 line_height: relative(settings.buffer_line_height.value()),
25279 ..Default::default()
25280 },
25281 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25282 color: cx.theme().colors().editor_foreground,
25283 font_family: settings.buffer_font.family.clone(),
25284 font_features: settings.buffer_font.features.clone(),
25285 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25286 font_size: settings.buffer_font_size(cx).into(),
25287 font_weight: settings.buffer_font.weight,
25288 line_height: relative(settings.buffer_line_height.value()),
25289 ..Default::default()
25290 },
25291 };
25292 if let Some(text_style_refinement) = &self.text_style_refinement {
25293 text_style.refine(text_style_refinement)
25294 }
25295
25296 let background = match self.mode {
25297 EditorMode::SingleLine => cx.theme().system().transparent,
25298 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25299 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25300 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25301 };
25302
25303 EditorStyle {
25304 background,
25305 border: cx.theme().colors().border,
25306 local_player: cx.theme().players().local(),
25307 text: text_style,
25308 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25309 syntax: cx.theme().syntax().clone(),
25310 status: cx.theme().status().clone(),
25311 inlay_hints_style: make_inlay_hints_style(cx),
25312 edit_prediction_styles: make_suggestion_styles(cx),
25313 unnecessary_code_fade: settings.unnecessary_code_fade,
25314 show_underlines: self.diagnostics_enabled(),
25315 }
25316 }
25317 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25318 let multibuffer = self.buffer().read(cx);
25319 let is_singleton = multibuffer.is_singleton();
25320 let (buffer_id, symbols) = self.outline_symbols.as_ref()?;
25321 let buffer = multibuffer.buffer(*buffer_id)?;
25322
25323 let buffer = buffer.read(cx);
25324 let settings = ThemeSettings::get_global(cx);
25325 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25326 let mut breadcrumbs = if is_singleton {
25327 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25328 buffer
25329 .snapshot()
25330 .resolve_file_path(
25331 self.project
25332 .as_ref()
25333 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25334 .unwrap_or_default(),
25335 cx,
25336 )
25337 .unwrap_or_else(|| {
25338 if multibuffer.is_singleton() {
25339 multibuffer.title(cx).to_string()
25340 } else {
25341 "untitled".to_string()
25342 }
25343 })
25344 });
25345 vec![BreadcrumbText {
25346 text,
25347 highlights: None,
25348 font: Some(settings.buffer_font.clone()),
25349 }]
25350 } else {
25351 vec![]
25352 };
25353
25354 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25355 text: symbol.text.clone(),
25356 highlights: Some(symbol.highlight_ranges.clone()),
25357 font: Some(settings.buffer_font.clone()),
25358 }));
25359 Some(breadcrumbs)
25360 }
25361
25362 fn disable_lsp_data(&mut self) {
25363 self.enable_lsp_data = false;
25364 }
25365
25366 fn disable_runnables(&mut self) {
25367 self.enable_runnables = false;
25368 }
25369}
25370
25371fn edit_for_markdown_paste<'a>(
25372 buffer: &MultiBufferSnapshot,
25373 range: Range<MultiBufferOffset>,
25374 to_insert: &'a str,
25375 url: Option<url::Url>,
25376) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25377 if url.is_none() {
25378 return (range, Cow::Borrowed(to_insert));
25379 };
25380
25381 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25382
25383 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25384 Cow::Borrowed(to_insert)
25385 } else {
25386 Cow::Owned(format!("[{old_text}]({to_insert})"))
25387 };
25388 (range, new_text)
25389}
25390
25391fn process_completion_for_edit(
25392 completion: &Completion,
25393 intent: CompletionIntent,
25394 buffer: &Entity<Buffer>,
25395 cursor_position: &text::Anchor,
25396 cx: &mut Context<Editor>,
25397) -> CompletionEdit {
25398 let buffer = buffer.read(cx);
25399 let buffer_snapshot = buffer.snapshot();
25400 let (snippet, new_text) = if completion.is_snippet() {
25401 let mut snippet_source = completion.new_text.clone();
25402 // Workaround for typescript language server issues so that methods don't expand within
25403 // strings and functions with type expressions. The previous point is used because the query
25404 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25405 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25406 let previous_point = if previous_point.column > 0 {
25407 cursor_position.to_previous_offset(&buffer_snapshot)
25408 } else {
25409 cursor_position.to_offset(&buffer_snapshot)
25410 };
25411 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25412 && scope.prefers_label_for_snippet_in_completion()
25413 && let Some(label) = completion.label()
25414 && matches!(
25415 completion.kind(),
25416 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25417 )
25418 {
25419 snippet_source = label;
25420 }
25421 match Snippet::parse(&snippet_source).log_err() {
25422 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25423 None => (None, completion.new_text.clone()),
25424 }
25425 } else {
25426 (None, completion.new_text.clone())
25427 };
25428
25429 let mut range_to_replace = {
25430 let replace_range = &completion.replace_range;
25431 if let CompletionSource::Lsp {
25432 insert_range: Some(insert_range),
25433 ..
25434 } = &completion.source
25435 {
25436 debug_assert_eq!(
25437 insert_range.start, replace_range.start,
25438 "insert_range and replace_range should start at the same position"
25439 );
25440 debug_assert!(
25441 insert_range
25442 .start
25443 .cmp(cursor_position, &buffer_snapshot)
25444 .is_le(),
25445 "insert_range should start before or at cursor position"
25446 );
25447 debug_assert!(
25448 replace_range
25449 .start
25450 .cmp(cursor_position, &buffer_snapshot)
25451 .is_le(),
25452 "replace_range should start before or at cursor position"
25453 );
25454
25455 let should_replace = match intent {
25456 CompletionIntent::CompleteWithInsert => false,
25457 CompletionIntent::CompleteWithReplace => true,
25458 CompletionIntent::Complete | CompletionIntent::Compose => {
25459 let insert_mode =
25460 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25461 .completions
25462 .lsp_insert_mode;
25463 match insert_mode {
25464 LspInsertMode::Insert => false,
25465 LspInsertMode::Replace => true,
25466 LspInsertMode::ReplaceSubsequence => {
25467 let mut text_to_replace = buffer.chars_for_range(
25468 buffer.anchor_before(replace_range.start)
25469 ..buffer.anchor_after(replace_range.end),
25470 );
25471 let mut current_needle = text_to_replace.next();
25472 for haystack_ch in completion.label.text.chars() {
25473 if let Some(needle_ch) = current_needle
25474 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25475 {
25476 current_needle = text_to_replace.next();
25477 }
25478 }
25479 current_needle.is_none()
25480 }
25481 LspInsertMode::ReplaceSuffix => {
25482 if replace_range
25483 .end
25484 .cmp(cursor_position, &buffer_snapshot)
25485 .is_gt()
25486 {
25487 let range_after_cursor = *cursor_position..replace_range.end;
25488 let text_after_cursor = buffer
25489 .text_for_range(
25490 buffer.anchor_before(range_after_cursor.start)
25491 ..buffer.anchor_after(range_after_cursor.end),
25492 )
25493 .collect::<String>()
25494 .to_ascii_lowercase();
25495 completion
25496 .label
25497 .text
25498 .to_ascii_lowercase()
25499 .ends_with(&text_after_cursor)
25500 } else {
25501 true
25502 }
25503 }
25504 }
25505 }
25506 };
25507
25508 if should_replace {
25509 replace_range.clone()
25510 } else {
25511 insert_range.clone()
25512 }
25513 } else {
25514 replace_range.clone()
25515 }
25516 };
25517
25518 if range_to_replace
25519 .end
25520 .cmp(cursor_position, &buffer_snapshot)
25521 .is_lt()
25522 {
25523 range_to_replace.end = *cursor_position;
25524 }
25525
25526 let replace_range = range_to_replace.to_offset(buffer);
25527 CompletionEdit {
25528 new_text,
25529 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25530 snippet,
25531 }
25532}
25533
25534struct CompletionEdit {
25535 new_text: String,
25536 replace_range: Range<BufferOffset>,
25537 snippet: Option<Snippet>,
25538}
25539
25540fn comment_delimiter_for_newline(
25541 start_point: &Point,
25542 buffer: &MultiBufferSnapshot,
25543 language: &LanguageScope,
25544) -> Option<Arc<str>> {
25545 let delimiters = language.line_comment_prefixes();
25546 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25547 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25548
25549 let num_of_whitespaces = snapshot
25550 .chars_for_range(range.clone())
25551 .take_while(|c| c.is_whitespace())
25552 .count();
25553 let comment_candidate = snapshot
25554 .chars_for_range(range.clone())
25555 .skip(num_of_whitespaces)
25556 .take(max_len_of_delimiter)
25557 .collect::<String>();
25558 let (delimiter, trimmed_len) = delimiters
25559 .iter()
25560 .filter_map(|delimiter| {
25561 let prefix = delimiter.trim_end();
25562 if comment_candidate.starts_with(prefix) {
25563 Some((delimiter, prefix.len()))
25564 } else {
25565 None
25566 }
25567 })
25568 .max_by_key(|(_, len)| *len)?;
25569
25570 if let Some(BlockCommentConfig {
25571 start: block_start, ..
25572 }) = language.block_comment()
25573 {
25574 let block_start_trimmed = block_start.trim_end();
25575 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25576 let line_content = snapshot
25577 .chars_for_range(range)
25578 .skip(num_of_whitespaces)
25579 .take(block_start_trimmed.len())
25580 .collect::<String>();
25581
25582 if line_content.starts_with(block_start_trimmed) {
25583 return None;
25584 }
25585 }
25586 }
25587
25588 let cursor_is_placed_after_comment_marker =
25589 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25590 if cursor_is_placed_after_comment_marker {
25591 Some(delimiter.clone())
25592 } else {
25593 None
25594 }
25595}
25596
25597fn documentation_delimiter_for_newline(
25598 start_point: &Point,
25599 buffer: &MultiBufferSnapshot,
25600 language: &LanguageScope,
25601 newline_config: &mut NewlineConfig,
25602) -> Option<Arc<str>> {
25603 let BlockCommentConfig {
25604 start: start_tag,
25605 end: end_tag,
25606 prefix: delimiter,
25607 tab_size: len,
25608 } = language.documentation_comment()?;
25609 let is_within_block_comment = buffer
25610 .language_scope_at(*start_point)
25611 .is_some_and(|scope| scope.override_name() == Some("comment"));
25612 if !is_within_block_comment {
25613 return None;
25614 }
25615
25616 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25617
25618 let num_of_whitespaces = snapshot
25619 .chars_for_range(range.clone())
25620 .take_while(|c| c.is_whitespace())
25621 .count();
25622
25623 // 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.
25624 let column = start_point.column;
25625 let cursor_is_after_start_tag = {
25626 let start_tag_len = start_tag.len();
25627 let start_tag_line = snapshot
25628 .chars_for_range(range.clone())
25629 .skip(num_of_whitespaces)
25630 .take(start_tag_len)
25631 .collect::<String>();
25632 if start_tag_line.starts_with(start_tag.as_ref()) {
25633 num_of_whitespaces + start_tag_len <= column as usize
25634 } else {
25635 false
25636 }
25637 };
25638
25639 let cursor_is_after_delimiter = {
25640 let delimiter_trim = delimiter.trim_end();
25641 let delimiter_line = snapshot
25642 .chars_for_range(range.clone())
25643 .skip(num_of_whitespaces)
25644 .take(delimiter_trim.len())
25645 .collect::<String>();
25646 if delimiter_line.starts_with(delimiter_trim) {
25647 num_of_whitespaces + delimiter_trim.len() <= column as usize
25648 } else {
25649 false
25650 }
25651 };
25652
25653 let mut needs_extra_line = false;
25654 let mut extra_line_additional_indent = IndentSize::spaces(0);
25655
25656 let cursor_is_before_end_tag_if_exists = {
25657 let mut char_position = 0u32;
25658 let mut end_tag_offset = None;
25659
25660 'outer: for chunk in snapshot.text_for_range(range) {
25661 if let Some(byte_pos) = chunk.find(&**end_tag) {
25662 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25663 end_tag_offset = Some(char_position + chars_before_match);
25664 break 'outer;
25665 }
25666 char_position += chunk.chars().count() as u32;
25667 }
25668
25669 if let Some(end_tag_offset) = end_tag_offset {
25670 let cursor_is_before_end_tag = column <= end_tag_offset;
25671 if cursor_is_after_start_tag {
25672 if cursor_is_before_end_tag {
25673 needs_extra_line = true;
25674 }
25675 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
25676 if cursor_is_at_start_of_end_tag {
25677 extra_line_additional_indent.len = *len;
25678 }
25679 }
25680 cursor_is_before_end_tag
25681 } else {
25682 true
25683 }
25684 };
25685
25686 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
25687 && cursor_is_before_end_tag_if_exists
25688 {
25689 let additional_indent = if cursor_is_after_start_tag {
25690 IndentSize::spaces(*len)
25691 } else {
25692 IndentSize::spaces(0)
25693 };
25694
25695 *newline_config = NewlineConfig::Newline {
25696 additional_indent,
25697 extra_line_additional_indent: if needs_extra_line {
25698 Some(extra_line_additional_indent)
25699 } else {
25700 None
25701 },
25702 prevent_auto_indent: true,
25703 };
25704 Some(delimiter.clone())
25705 } else {
25706 None
25707 }
25708}
25709
25710const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
25711
25712fn list_delimiter_for_newline(
25713 start_point: &Point,
25714 buffer: &MultiBufferSnapshot,
25715 language: &LanguageScope,
25716 newline_config: &mut NewlineConfig,
25717) -> Option<Arc<str>> {
25718 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25719
25720 let num_of_whitespaces = snapshot
25721 .chars_for_range(range.clone())
25722 .take_while(|c| c.is_whitespace())
25723 .count();
25724
25725 let task_list_entries: Vec<_> = language
25726 .task_list()
25727 .into_iter()
25728 .flat_map(|config| {
25729 config
25730 .prefixes
25731 .iter()
25732 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
25733 })
25734 .collect();
25735 let unordered_list_entries: Vec<_> = language
25736 .unordered_list()
25737 .iter()
25738 .map(|marker| (marker.as_ref(), marker.as_ref()))
25739 .collect();
25740
25741 let all_entries: Vec<_> = task_list_entries
25742 .into_iter()
25743 .chain(unordered_list_entries)
25744 .collect();
25745
25746 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
25747 let candidate: String = snapshot
25748 .chars_for_range(range.clone())
25749 .skip(num_of_whitespaces)
25750 .take(max_prefix_len)
25751 .collect();
25752
25753 if let Some((prefix, continuation)) = all_entries
25754 .iter()
25755 .filter(|(prefix, _)| candidate.starts_with(*prefix))
25756 .max_by_key(|(prefix, _)| prefix.len())
25757 {
25758 let end_of_prefix = num_of_whitespaces + prefix.len();
25759 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25760 let has_content_after_marker = snapshot
25761 .chars_for_range(range)
25762 .skip(end_of_prefix)
25763 .any(|c| !c.is_whitespace());
25764
25765 if has_content_after_marker && cursor_is_after_prefix {
25766 return Some((*continuation).into());
25767 }
25768
25769 if start_point.column as usize == end_of_prefix {
25770 if num_of_whitespaces == 0 {
25771 *newline_config = NewlineConfig::ClearCurrentLine;
25772 } else {
25773 *newline_config = NewlineConfig::UnindentCurrentLine {
25774 continuation: (*continuation).into(),
25775 };
25776 }
25777 }
25778
25779 return None;
25780 }
25781 }
25782
25783 let candidate: String = snapshot
25784 .chars_for_range(range.clone())
25785 .skip(num_of_whitespaces)
25786 .take(ORDERED_LIST_MAX_MARKER_LEN)
25787 .collect();
25788
25789 for ordered_config in language.ordered_list() {
25790 let regex = match Regex::new(&ordered_config.pattern) {
25791 Ok(r) => r,
25792 Err(_) => continue,
25793 };
25794
25795 if let Some(captures) = regex.captures(&candidate) {
25796 let full_match = captures.get(0)?;
25797 let marker_len = full_match.len();
25798 let end_of_prefix = num_of_whitespaces + marker_len;
25799 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
25800
25801 let has_content_after_marker = snapshot
25802 .chars_for_range(range)
25803 .skip(end_of_prefix)
25804 .any(|c| !c.is_whitespace());
25805
25806 if has_content_after_marker && cursor_is_after_prefix {
25807 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
25808 let continuation = ordered_config
25809 .format
25810 .replace("{1}", &(number + 1).to_string());
25811 return Some(continuation.into());
25812 }
25813
25814 if start_point.column as usize == end_of_prefix {
25815 let continuation = ordered_config.format.replace("{1}", "1");
25816 if num_of_whitespaces == 0 {
25817 *newline_config = NewlineConfig::ClearCurrentLine;
25818 } else {
25819 *newline_config = NewlineConfig::UnindentCurrentLine {
25820 continuation: continuation.into(),
25821 };
25822 }
25823 }
25824
25825 return None;
25826 }
25827 }
25828
25829 None
25830}
25831
25832fn is_list_prefix_row(
25833 row: MultiBufferRow,
25834 buffer: &MultiBufferSnapshot,
25835 language: &LanguageScope,
25836) -> bool {
25837 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
25838 return false;
25839 };
25840
25841 let num_of_whitespaces = snapshot
25842 .chars_for_range(range.clone())
25843 .take_while(|c| c.is_whitespace())
25844 .count();
25845
25846 let task_list_prefixes: Vec<_> = language
25847 .task_list()
25848 .into_iter()
25849 .flat_map(|config| {
25850 config
25851 .prefixes
25852 .iter()
25853 .map(|p| p.as_ref())
25854 .collect::<Vec<_>>()
25855 })
25856 .collect();
25857 let unordered_list_markers: Vec<_> = language
25858 .unordered_list()
25859 .iter()
25860 .map(|marker| marker.as_ref())
25861 .collect();
25862 let all_prefixes: Vec<_> = task_list_prefixes
25863 .into_iter()
25864 .chain(unordered_list_markers)
25865 .collect();
25866 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
25867 let candidate: String = snapshot
25868 .chars_for_range(range.clone())
25869 .skip(num_of_whitespaces)
25870 .take(max_prefix_len)
25871 .collect();
25872 if all_prefixes
25873 .iter()
25874 .any(|prefix| candidate.starts_with(*prefix))
25875 {
25876 return true;
25877 }
25878 }
25879
25880 let ordered_list_candidate: String = snapshot
25881 .chars_for_range(range)
25882 .skip(num_of_whitespaces)
25883 .take(ORDERED_LIST_MAX_MARKER_LEN)
25884 .collect();
25885 for ordered_config in language.ordered_list() {
25886 let regex = match Regex::new(&ordered_config.pattern) {
25887 Ok(r) => r,
25888 Err(_) => continue,
25889 };
25890 if let Some(captures) = regex.captures(&ordered_list_candidate) {
25891 return captures.get(0).is_some();
25892 }
25893 }
25894
25895 false
25896}
25897
25898#[derive(Debug)]
25899enum NewlineConfig {
25900 /// Insert newline with optional additional indent and optional extra blank line
25901 Newline {
25902 additional_indent: IndentSize,
25903 extra_line_additional_indent: Option<IndentSize>,
25904 prevent_auto_indent: bool,
25905 },
25906 /// Clear the current line
25907 ClearCurrentLine,
25908 /// Unindent the current line and add continuation
25909 UnindentCurrentLine { continuation: Arc<str> },
25910}
25911
25912impl NewlineConfig {
25913 fn has_extra_line(&self) -> bool {
25914 matches!(
25915 self,
25916 Self::Newline {
25917 extra_line_additional_indent: Some(_),
25918 ..
25919 }
25920 )
25921 }
25922
25923 fn insert_extra_newline_brackets(
25924 buffer: &MultiBufferSnapshot,
25925 range: Range<MultiBufferOffset>,
25926 language: &language::LanguageScope,
25927 ) -> bool {
25928 let leading_whitespace_len = buffer
25929 .reversed_chars_at(range.start)
25930 .take_while(|c| c.is_whitespace() && *c != '\n')
25931 .map(|c| c.len_utf8())
25932 .sum::<usize>();
25933 let trailing_whitespace_len = buffer
25934 .chars_at(range.end)
25935 .take_while(|c| c.is_whitespace() && *c != '\n')
25936 .map(|c| c.len_utf8())
25937 .sum::<usize>();
25938 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
25939
25940 language.brackets().any(|(pair, enabled)| {
25941 let pair_start = pair.start.trim_end();
25942 let pair_end = pair.end.trim_start();
25943
25944 enabled
25945 && pair.newline
25946 && buffer.contains_str_at(range.end, pair_end)
25947 && buffer.contains_str_at(
25948 range.start.saturating_sub_usize(pair_start.len()),
25949 pair_start,
25950 )
25951 })
25952 }
25953
25954 fn insert_extra_newline_tree_sitter(
25955 buffer: &MultiBufferSnapshot,
25956 range: Range<MultiBufferOffset>,
25957 ) -> bool {
25958 let (buffer, range) = match buffer
25959 .range_to_buffer_ranges(range.start..=range.end)
25960 .as_slice()
25961 {
25962 [(buffer, range, _)] => (*buffer, range.clone()),
25963 _ => return false,
25964 };
25965 let pair = {
25966 let mut result: Option<BracketMatch<usize>> = None;
25967
25968 for pair in buffer
25969 .all_bracket_ranges(range.start.0..range.end.0)
25970 .filter(move |pair| {
25971 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
25972 })
25973 {
25974 let len = pair.close_range.end - pair.open_range.start;
25975
25976 if let Some(existing) = &result {
25977 let existing_len = existing.close_range.end - existing.open_range.start;
25978 if len > existing_len {
25979 continue;
25980 }
25981 }
25982
25983 result = Some(pair);
25984 }
25985
25986 result
25987 };
25988 let Some(pair) = pair else {
25989 return false;
25990 };
25991 pair.newline_only
25992 && buffer
25993 .chars_for_range(pair.open_range.end..range.start.0)
25994 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
25995 .all(|c| c.is_whitespace() && c != '\n')
25996 }
25997}
25998
25999fn update_uncommitted_diff_for_buffer(
26000 editor: Entity<Editor>,
26001 project: &Entity<Project>,
26002 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26003 buffer: Entity<MultiBuffer>,
26004 cx: &mut App,
26005) -> Task<()> {
26006 let mut tasks = Vec::new();
26007 project.update(cx, |project, cx| {
26008 for buffer in buffers {
26009 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26010 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26011 }
26012 }
26013 });
26014 cx.spawn(async move |cx| {
26015 let diffs = future::join_all(tasks).await;
26016 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26017 return;
26018 }
26019
26020 buffer.update(cx, |buffer, cx| {
26021 for diff in diffs.into_iter().flatten() {
26022 buffer.add_diff(diff, cx);
26023 }
26024 });
26025 })
26026}
26027
26028fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26029 let tab_size = tab_size.get() as usize;
26030 let mut width = offset;
26031
26032 for ch in text.chars() {
26033 width += if ch == '\t' {
26034 tab_size - (width % tab_size)
26035 } else {
26036 1
26037 };
26038 }
26039
26040 width - offset
26041}
26042
26043#[cfg(test)]
26044mod tests {
26045 use super::*;
26046
26047 #[test]
26048 fn test_string_size_with_expanded_tabs() {
26049 let nz = |val| NonZeroU32::new(val).unwrap();
26050 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26051 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26052 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26053 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26054 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26055 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26056 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26057 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26058 }
26059}
26060
26061/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26062struct WordBreakingTokenizer<'a> {
26063 input: &'a str,
26064}
26065
26066impl<'a> WordBreakingTokenizer<'a> {
26067 fn new(input: &'a str) -> Self {
26068 Self { input }
26069 }
26070}
26071
26072fn is_char_ideographic(ch: char) -> bool {
26073 use unicode_script::Script::*;
26074 use unicode_script::UnicodeScript;
26075 matches!(ch.script(), Han | Tangut | Yi)
26076}
26077
26078fn is_grapheme_ideographic(text: &str) -> bool {
26079 text.chars().any(is_char_ideographic)
26080}
26081
26082fn is_grapheme_whitespace(text: &str) -> bool {
26083 text.chars().any(|x| x.is_whitespace())
26084}
26085
26086fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26087 text.chars()
26088 .next()
26089 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26090}
26091
26092#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26093enum WordBreakToken<'a> {
26094 Word { token: &'a str, grapheme_len: usize },
26095 InlineWhitespace { token: &'a str, grapheme_len: usize },
26096 Newline,
26097}
26098
26099impl<'a> Iterator for WordBreakingTokenizer<'a> {
26100 /// Yields a span, the count of graphemes in the token, and whether it was
26101 /// whitespace. Note that it also breaks at word boundaries.
26102 type Item = WordBreakToken<'a>;
26103
26104 fn next(&mut self) -> Option<Self::Item> {
26105 use unicode_segmentation::UnicodeSegmentation;
26106 if self.input.is_empty() {
26107 return None;
26108 }
26109
26110 let mut iter = self.input.graphemes(true).peekable();
26111 let mut offset = 0;
26112 let mut grapheme_len = 0;
26113 if let Some(first_grapheme) = iter.next() {
26114 let is_newline = first_grapheme == "\n";
26115 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26116 offset += first_grapheme.len();
26117 grapheme_len += 1;
26118 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26119 if let Some(grapheme) = iter.peek().copied()
26120 && should_stay_with_preceding_ideograph(grapheme)
26121 {
26122 offset += grapheme.len();
26123 grapheme_len += 1;
26124 }
26125 } else {
26126 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26127 let mut next_word_bound = words.peek().copied();
26128 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26129 next_word_bound = words.next();
26130 }
26131 while let Some(grapheme) = iter.peek().copied() {
26132 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26133 break;
26134 };
26135 if is_grapheme_whitespace(grapheme) != is_whitespace
26136 || (grapheme == "\n") != is_newline
26137 {
26138 break;
26139 };
26140 offset += grapheme.len();
26141 grapheme_len += 1;
26142 iter.next();
26143 }
26144 }
26145 let token = &self.input[..offset];
26146 self.input = &self.input[offset..];
26147 if token == "\n" {
26148 Some(WordBreakToken::Newline)
26149 } else if is_whitespace {
26150 Some(WordBreakToken::InlineWhitespace {
26151 token,
26152 grapheme_len,
26153 })
26154 } else {
26155 Some(WordBreakToken::Word {
26156 token,
26157 grapheme_len,
26158 })
26159 }
26160 } else {
26161 None
26162 }
26163 }
26164}
26165
26166#[test]
26167fn test_word_breaking_tokenizer() {
26168 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26169 ("", &[]),
26170 (" ", &[whitespace(" ", 2)]),
26171 ("Ʒ", &[word("Ʒ", 1)]),
26172 ("Ǽ", &[word("Ǽ", 1)]),
26173 ("⋑", &[word("⋑", 1)]),
26174 ("⋑⋑", &[word("⋑⋑", 2)]),
26175 (
26176 "原理,进而",
26177 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26178 ),
26179 (
26180 "hello world",
26181 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26182 ),
26183 (
26184 "hello, world",
26185 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26186 ),
26187 (
26188 " hello world",
26189 &[
26190 whitespace(" ", 2),
26191 word("hello", 5),
26192 whitespace(" ", 1),
26193 word("world", 5),
26194 ],
26195 ),
26196 (
26197 "这是什么 \n 钢笔",
26198 &[
26199 word("这", 1),
26200 word("是", 1),
26201 word("什", 1),
26202 word("么", 1),
26203 whitespace(" ", 1),
26204 newline(),
26205 whitespace(" ", 1),
26206 word("钢", 1),
26207 word("笔", 1),
26208 ],
26209 ),
26210 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26211 ];
26212
26213 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26214 WordBreakToken::Word {
26215 token,
26216 grapheme_len,
26217 }
26218 }
26219
26220 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26221 WordBreakToken::InlineWhitespace {
26222 token,
26223 grapheme_len,
26224 }
26225 }
26226
26227 fn newline() -> WordBreakToken<'static> {
26228 WordBreakToken::Newline
26229 }
26230
26231 for (input, result) in tests {
26232 assert_eq!(
26233 WordBreakingTokenizer::new(input)
26234 .collect::<Vec<_>>()
26235 .as_slice(),
26236 *result,
26237 );
26238 }
26239}
26240
26241fn wrap_with_prefix(
26242 first_line_prefix: String,
26243 subsequent_lines_prefix: String,
26244 unwrapped_text: String,
26245 wrap_column: usize,
26246 tab_size: NonZeroU32,
26247 preserve_existing_whitespace: bool,
26248) -> String {
26249 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26250 let subsequent_lines_prefix_len =
26251 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26252 let mut wrapped_text = String::new();
26253 let mut current_line = first_line_prefix;
26254 let mut is_first_line = true;
26255
26256 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26257 let mut current_line_len = first_line_prefix_len;
26258 let mut in_whitespace = false;
26259 for token in tokenizer {
26260 let have_preceding_whitespace = in_whitespace;
26261 match token {
26262 WordBreakToken::Word {
26263 token,
26264 grapheme_len,
26265 } => {
26266 in_whitespace = false;
26267 let current_prefix_len = if is_first_line {
26268 first_line_prefix_len
26269 } else {
26270 subsequent_lines_prefix_len
26271 };
26272 if current_line_len + grapheme_len > wrap_column
26273 && current_line_len != current_prefix_len
26274 {
26275 wrapped_text.push_str(current_line.trim_end());
26276 wrapped_text.push('\n');
26277 is_first_line = false;
26278 current_line = subsequent_lines_prefix.clone();
26279 current_line_len = subsequent_lines_prefix_len;
26280 }
26281 current_line.push_str(token);
26282 current_line_len += grapheme_len;
26283 }
26284 WordBreakToken::InlineWhitespace {
26285 mut token,
26286 mut grapheme_len,
26287 } => {
26288 in_whitespace = true;
26289 if have_preceding_whitespace && !preserve_existing_whitespace {
26290 continue;
26291 }
26292 if !preserve_existing_whitespace {
26293 // Keep a single whitespace grapheme as-is
26294 if let Some(first) =
26295 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26296 {
26297 token = first;
26298 } else {
26299 token = " ";
26300 }
26301 grapheme_len = 1;
26302 }
26303 let current_prefix_len = if is_first_line {
26304 first_line_prefix_len
26305 } else {
26306 subsequent_lines_prefix_len
26307 };
26308 if current_line_len + grapheme_len > wrap_column {
26309 wrapped_text.push_str(current_line.trim_end());
26310 wrapped_text.push('\n');
26311 is_first_line = false;
26312 current_line = subsequent_lines_prefix.clone();
26313 current_line_len = subsequent_lines_prefix_len;
26314 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26315 current_line.push_str(token);
26316 current_line_len += grapheme_len;
26317 }
26318 }
26319 WordBreakToken::Newline => {
26320 in_whitespace = true;
26321 let current_prefix_len = if is_first_line {
26322 first_line_prefix_len
26323 } else {
26324 subsequent_lines_prefix_len
26325 };
26326 if preserve_existing_whitespace {
26327 wrapped_text.push_str(current_line.trim_end());
26328 wrapped_text.push('\n');
26329 is_first_line = false;
26330 current_line = subsequent_lines_prefix.clone();
26331 current_line_len = subsequent_lines_prefix_len;
26332 } else if have_preceding_whitespace {
26333 continue;
26334 } else if current_line_len + 1 > wrap_column
26335 && current_line_len != current_prefix_len
26336 {
26337 wrapped_text.push_str(current_line.trim_end());
26338 wrapped_text.push('\n');
26339 is_first_line = false;
26340 current_line = subsequent_lines_prefix.clone();
26341 current_line_len = subsequent_lines_prefix_len;
26342 } else if current_line_len != current_prefix_len {
26343 current_line.push(' ');
26344 current_line_len += 1;
26345 }
26346 }
26347 }
26348 }
26349
26350 if !current_line.is_empty() {
26351 wrapped_text.push_str(¤t_line);
26352 }
26353 wrapped_text
26354}
26355
26356#[test]
26357fn test_wrap_with_prefix() {
26358 assert_eq!(
26359 wrap_with_prefix(
26360 "# ".to_string(),
26361 "# ".to_string(),
26362 "abcdefg".to_string(),
26363 4,
26364 NonZeroU32::new(4).unwrap(),
26365 false,
26366 ),
26367 "# abcdefg"
26368 );
26369 assert_eq!(
26370 wrap_with_prefix(
26371 "".to_string(),
26372 "".to_string(),
26373 "\thello world".to_string(),
26374 8,
26375 NonZeroU32::new(4).unwrap(),
26376 false,
26377 ),
26378 "hello\nworld"
26379 );
26380 assert_eq!(
26381 wrap_with_prefix(
26382 "// ".to_string(),
26383 "// ".to_string(),
26384 "xx \nyy zz aa bb cc".to_string(),
26385 12,
26386 NonZeroU32::new(4).unwrap(),
26387 false,
26388 ),
26389 "// xx yy zz\n// aa bb cc"
26390 );
26391 assert_eq!(
26392 wrap_with_prefix(
26393 String::new(),
26394 String::new(),
26395 "这是什么 \n 钢笔".to_string(),
26396 3,
26397 NonZeroU32::new(4).unwrap(),
26398 false,
26399 ),
26400 "这是什\n么 钢\n笔"
26401 );
26402 assert_eq!(
26403 wrap_with_prefix(
26404 String::new(),
26405 String::new(),
26406 format!("foo{}bar", '\u{2009}'), // thin space
26407 80,
26408 NonZeroU32::new(4).unwrap(),
26409 false,
26410 ),
26411 format!("foo{}bar", '\u{2009}')
26412 );
26413}
26414
26415pub trait CollaborationHub {
26416 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26417 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26418 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26419}
26420
26421impl CollaborationHub for Entity<Project> {
26422 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26423 self.read(cx).collaborators()
26424 }
26425
26426 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26427 self.read(cx).user_store().read(cx).participant_indices()
26428 }
26429
26430 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26431 let this = self.read(cx);
26432 let user_ids = this.collaborators().values().map(|c| c.user_id);
26433 this.user_store().read(cx).participant_names(user_ids, cx)
26434 }
26435}
26436
26437pub trait SemanticsProvider {
26438 fn hover(
26439 &self,
26440 buffer: &Entity<Buffer>,
26441 position: text::Anchor,
26442 cx: &mut App,
26443 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26444
26445 fn inline_values(
26446 &self,
26447 buffer_handle: Entity<Buffer>,
26448 range: Range<text::Anchor>,
26449 cx: &mut App,
26450 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26451
26452 fn applicable_inlay_chunks(
26453 &self,
26454 buffer: &Entity<Buffer>,
26455 ranges: &[Range<text::Anchor>],
26456 cx: &mut App,
26457 ) -> Vec<Range<BufferRow>>;
26458
26459 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26460
26461 fn inlay_hints(
26462 &self,
26463 invalidate: InvalidationStrategy,
26464 buffer: Entity<Buffer>,
26465 ranges: Vec<Range<text::Anchor>>,
26466 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26467 cx: &mut App,
26468 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26469
26470 fn semantic_tokens(
26471 &self,
26472 buffer: Entity<Buffer>,
26473 refresh: Option<RefreshForServer>,
26474 cx: &mut App,
26475 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>;
26476
26477 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26478
26479 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26480
26481 fn document_highlights(
26482 &self,
26483 buffer: &Entity<Buffer>,
26484 position: text::Anchor,
26485 cx: &mut App,
26486 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26487
26488 fn definitions(
26489 &self,
26490 buffer: &Entity<Buffer>,
26491 position: text::Anchor,
26492 kind: GotoDefinitionKind,
26493 cx: &mut App,
26494 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26495
26496 fn range_for_rename(
26497 &self,
26498 buffer: &Entity<Buffer>,
26499 position: text::Anchor,
26500 cx: &mut App,
26501 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26502
26503 fn perform_rename(
26504 &self,
26505 buffer: &Entity<Buffer>,
26506 position: text::Anchor,
26507 new_name: String,
26508 cx: &mut App,
26509 ) -> Option<Task<Result<ProjectTransaction>>>;
26510}
26511
26512pub trait CompletionProvider {
26513 fn completions(
26514 &self,
26515 excerpt_id: ExcerptId,
26516 buffer: &Entity<Buffer>,
26517 buffer_position: text::Anchor,
26518 trigger: CompletionContext,
26519 window: &mut Window,
26520 cx: &mut Context<Editor>,
26521 ) -> Task<Result<Vec<CompletionResponse>>>;
26522
26523 fn resolve_completions(
26524 &self,
26525 _buffer: Entity<Buffer>,
26526 _completion_indices: Vec<usize>,
26527 _completions: Rc<RefCell<Box<[Completion]>>>,
26528 _cx: &mut Context<Editor>,
26529 ) -> Task<Result<bool>> {
26530 Task::ready(Ok(false))
26531 }
26532
26533 fn apply_additional_edits_for_completion(
26534 &self,
26535 _buffer: Entity<Buffer>,
26536 _completions: Rc<RefCell<Box<[Completion]>>>,
26537 _completion_index: usize,
26538 _push_to_history: bool,
26539 _cx: &mut Context<Editor>,
26540 ) -> Task<Result<Option<language::Transaction>>> {
26541 Task::ready(Ok(None))
26542 }
26543
26544 fn is_completion_trigger(
26545 &self,
26546 buffer: &Entity<Buffer>,
26547 position: language::Anchor,
26548 text: &str,
26549 trigger_in_words: bool,
26550 cx: &mut Context<Editor>,
26551 ) -> bool;
26552
26553 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26554
26555 fn sort_completions(&self) -> bool {
26556 true
26557 }
26558
26559 fn filter_completions(&self) -> bool {
26560 true
26561 }
26562
26563 fn show_snippets(&self) -> bool {
26564 false
26565 }
26566}
26567
26568pub trait CodeActionProvider {
26569 fn id(&self) -> Arc<str>;
26570
26571 fn code_actions(
26572 &self,
26573 buffer: &Entity<Buffer>,
26574 range: Range<text::Anchor>,
26575 window: &mut Window,
26576 cx: &mut App,
26577 ) -> Task<Result<Vec<CodeAction>>>;
26578
26579 fn apply_code_action(
26580 &self,
26581 buffer_handle: Entity<Buffer>,
26582 action: CodeAction,
26583 excerpt_id: ExcerptId,
26584 push_to_history: bool,
26585 window: &mut Window,
26586 cx: &mut App,
26587 ) -> Task<Result<ProjectTransaction>>;
26588}
26589
26590impl CodeActionProvider for Entity<Project> {
26591 fn id(&self) -> Arc<str> {
26592 "project".into()
26593 }
26594
26595 fn code_actions(
26596 &self,
26597 buffer: &Entity<Buffer>,
26598 range: Range<text::Anchor>,
26599 _window: &mut Window,
26600 cx: &mut App,
26601 ) -> Task<Result<Vec<CodeAction>>> {
26602 self.update(cx, |project, cx| {
26603 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26604 let code_actions = project.code_actions(buffer, range, None, cx);
26605 cx.background_spawn(async move {
26606 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26607 Ok(code_lens_actions
26608 .context("code lens fetch")?
26609 .into_iter()
26610 .flatten()
26611 .chain(
26612 code_actions
26613 .context("code action fetch")?
26614 .into_iter()
26615 .flatten(),
26616 )
26617 .collect())
26618 })
26619 })
26620 }
26621
26622 fn apply_code_action(
26623 &self,
26624 buffer_handle: Entity<Buffer>,
26625 action: CodeAction,
26626 _excerpt_id: ExcerptId,
26627 push_to_history: bool,
26628 _window: &mut Window,
26629 cx: &mut App,
26630 ) -> Task<Result<ProjectTransaction>> {
26631 self.update(cx, |project, cx| {
26632 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26633 })
26634 }
26635}
26636
26637fn snippet_completions(
26638 project: &Project,
26639 buffer: &Entity<Buffer>,
26640 buffer_anchor: text::Anchor,
26641 classifier: CharClassifier,
26642 cx: &mut App,
26643) -> Task<Result<CompletionResponse>> {
26644 let languages = buffer.read(cx).languages_at(buffer_anchor);
26645 let snippet_store = project.snippets().read(cx);
26646
26647 let scopes: Vec<_> = languages
26648 .iter()
26649 .filter_map(|language| {
26650 let language_name = language.lsp_id();
26651 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26652
26653 if snippets.is_empty() {
26654 None
26655 } else {
26656 Some((language.default_scope(), snippets))
26657 }
26658 })
26659 .collect();
26660
26661 if scopes.is_empty() {
26662 return Task::ready(Ok(CompletionResponse {
26663 completions: vec![],
26664 display_options: CompletionDisplayOptions::default(),
26665 is_incomplete: false,
26666 }));
26667 }
26668
26669 let snapshot = buffer.read(cx).text_snapshot();
26670 let executor = cx.background_executor().clone();
26671
26672 cx.background_spawn(async move {
26673 let is_word_char = |c| classifier.is_word(c);
26674
26675 let mut is_incomplete = false;
26676 let mut completions: Vec<Completion> = Vec::new();
26677
26678 const MAX_PREFIX_LEN: usize = 128;
26679 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
26680 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
26681 let window_start = snapshot.clip_offset(window_start, Bias::Left);
26682
26683 let max_buffer_window: String = snapshot
26684 .text_for_range(window_start..buffer_offset)
26685 .collect();
26686
26687 if max_buffer_window.is_empty() {
26688 return Ok(CompletionResponse {
26689 completions: vec![],
26690 display_options: CompletionDisplayOptions::default(),
26691 is_incomplete: true,
26692 });
26693 }
26694
26695 for (_scope, snippets) in scopes.into_iter() {
26696 // Sort snippets by word count to match longer snippet prefixes first.
26697 let mut sorted_snippet_candidates = snippets
26698 .iter()
26699 .enumerate()
26700 .flat_map(|(snippet_ix, snippet)| {
26701 snippet
26702 .prefix
26703 .iter()
26704 .enumerate()
26705 .map(move |(prefix_ix, prefix)| {
26706 let word_count =
26707 snippet_candidate_suffixes(prefix, is_word_char).count();
26708 ((snippet_ix, prefix_ix), prefix, word_count)
26709 })
26710 })
26711 .collect_vec();
26712 sorted_snippet_candidates
26713 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
26714
26715 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
26716
26717 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
26718 .take(
26719 sorted_snippet_candidates
26720 .first()
26721 .map(|(_, _, word_count)| *word_count)
26722 .unwrap_or_default(),
26723 )
26724 .collect_vec();
26725
26726 const MAX_RESULTS: usize = 100;
26727 // Each match also remembers how many characters from the buffer it consumed
26728 let mut matches: Vec<(StringMatch, usize)> = vec![];
26729
26730 let mut snippet_list_cutoff_index = 0;
26731 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
26732 let word_count = buffer_index + 1;
26733 // Increase `snippet_list_cutoff_index` until we have all of the
26734 // snippets with sufficiently many words.
26735 while sorted_snippet_candidates
26736 .get(snippet_list_cutoff_index)
26737 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
26738 *snippet_word_count >= word_count
26739 })
26740 {
26741 snippet_list_cutoff_index += 1;
26742 }
26743
26744 // Take only the candidates with at least `word_count` many words
26745 let snippet_candidates_at_word_len =
26746 &sorted_snippet_candidates[..snippet_list_cutoff_index];
26747
26748 let candidates = snippet_candidates_at_word_len
26749 .iter()
26750 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
26751 .enumerate() // index in `sorted_snippet_candidates`
26752 // First char must match
26753 .filter(|(_ix, prefix)| {
26754 itertools::equal(
26755 prefix
26756 .chars()
26757 .next()
26758 .into_iter()
26759 .flat_map(|c| c.to_lowercase()),
26760 buffer_window
26761 .chars()
26762 .next()
26763 .into_iter()
26764 .flat_map(|c| c.to_lowercase()),
26765 )
26766 })
26767 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
26768 .collect::<Vec<StringMatchCandidate>>();
26769
26770 matches.extend(
26771 fuzzy::match_strings(
26772 &candidates,
26773 &buffer_window,
26774 buffer_window.chars().any(|c| c.is_uppercase()),
26775 true,
26776 MAX_RESULTS - matches.len(), // always prioritize longer snippets
26777 &Default::default(),
26778 executor.clone(),
26779 )
26780 .await
26781 .into_iter()
26782 .map(|string_match| (string_match, buffer_window.len())),
26783 );
26784
26785 if matches.len() >= MAX_RESULTS {
26786 break;
26787 }
26788 }
26789
26790 let to_lsp = |point: &text::Anchor| {
26791 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
26792 point_to_lsp(end)
26793 };
26794 let lsp_end = to_lsp(&buffer_anchor);
26795
26796 if matches.len() >= MAX_RESULTS {
26797 is_incomplete = true;
26798 }
26799
26800 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
26801 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
26802 sorted_snippet_candidates[string_match.candidate_id];
26803 let snippet = &snippets[snippet_index];
26804 let start = buffer_offset - buffer_window_len;
26805 let start = snapshot.anchor_before(start);
26806 let range = start..buffer_anchor;
26807 let lsp_start = to_lsp(&start);
26808 let lsp_range = lsp::Range {
26809 start: lsp_start,
26810 end: lsp_end,
26811 };
26812 Completion {
26813 replace_range: range,
26814 new_text: snippet.body.clone(),
26815 source: CompletionSource::Lsp {
26816 insert_range: None,
26817 server_id: LanguageServerId(usize::MAX),
26818 resolved: true,
26819 lsp_completion: Box::new(lsp::CompletionItem {
26820 label: snippet.prefix.first().unwrap().clone(),
26821 kind: Some(CompletionItemKind::SNIPPET),
26822 label_details: snippet.description.as_ref().map(|description| {
26823 lsp::CompletionItemLabelDetails {
26824 detail: Some(description.clone()),
26825 description: None,
26826 }
26827 }),
26828 insert_text_format: Some(InsertTextFormat::SNIPPET),
26829 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26830 lsp::InsertReplaceEdit {
26831 new_text: snippet.body.clone(),
26832 insert: lsp_range,
26833 replace: lsp_range,
26834 },
26835 )),
26836 filter_text: Some(snippet.body.clone()),
26837 sort_text: Some(char::MAX.to_string()),
26838 ..lsp::CompletionItem::default()
26839 }),
26840 lsp_defaults: None,
26841 },
26842 label: CodeLabel {
26843 text: matching_prefix.clone(),
26844 runs: Vec::new(),
26845 filter_range: 0..matching_prefix.len(),
26846 },
26847 icon_path: None,
26848 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
26849 single_line: snippet.name.clone().into(),
26850 plain_text: snippet
26851 .description
26852 .clone()
26853 .map(|description| description.into()),
26854 }),
26855 insert_text_mode: None,
26856 confirm: None,
26857 match_start: Some(start),
26858 snippet_deduplication_key: Some((snippet_index, prefix_index)),
26859 }
26860 }));
26861 }
26862
26863 Ok(CompletionResponse {
26864 completions,
26865 display_options: CompletionDisplayOptions::default(),
26866 is_incomplete,
26867 })
26868 })
26869}
26870
26871impl CompletionProvider for Entity<Project> {
26872 fn completions(
26873 &self,
26874 _excerpt_id: ExcerptId,
26875 buffer: &Entity<Buffer>,
26876 buffer_position: text::Anchor,
26877 options: CompletionContext,
26878 _window: &mut Window,
26879 cx: &mut Context<Editor>,
26880 ) -> Task<Result<Vec<CompletionResponse>>> {
26881 self.update(cx, |project, cx| {
26882 let task = project.completions(buffer, buffer_position, options, cx);
26883 cx.background_spawn(task)
26884 })
26885 }
26886
26887 fn resolve_completions(
26888 &self,
26889 buffer: Entity<Buffer>,
26890 completion_indices: Vec<usize>,
26891 completions: Rc<RefCell<Box<[Completion]>>>,
26892 cx: &mut Context<Editor>,
26893 ) -> Task<Result<bool>> {
26894 self.update(cx, |project, cx| {
26895 project.lsp_store().update(cx, |lsp_store, cx| {
26896 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
26897 })
26898 })
26899 }
26900
26901 fn apply_additional_edits_for_completion(
26902 &self,
26903 buffer: Entity<Buffer>,
26904 completions: Rc<RefCell<Box<[Completion]>>>,
26905 completion_index: usize,
26906 push_to_history: bool,
26907 cx: &mut Context<Editor>,
26908 ) -> Task<Result<Option<language::Transaction>>> {
26909 self.update(cx, |project, cx| {
26910 project.lsp_store().update(cx, |lsp_store, cx| {
26911 lsp_store.apply_additional_edits_for_completion(
26912 buffer,
26913 completions,
26914 completion_index,
26915 push_to_history,
26916 cx,
26917 )
26918 })
26919 })
26920 }
26921
26922 fn is_completion_trigger(
26923 &self,
26924 buffer: &Entity<Buffer>,
26925 position: language::Anchor,
26926 text: &str,
26927 trigger_in_words: bool,
26928 cx: &mut Context<Editor>,
26929 ) -> bool {
26930 let mut chars = text.chars();
26931 let char = if let Some(char) = chars.next() {
26932 char
26933 } else {
26934 return false;
26935 };
26936 if chars.next().is_some() {
26937 return false;
26938 }
26939
26940 let buffer = buffer.read(cx);
26941 let snapshot = buffer.snapshot();
26942 let classifier = snapshot
26943 .char_classifier_at(position)
26944 .scope_context(Some(CharScopeContext::Completion));
26945 if trigger_in_words && classifier.is_word(char) {
26946 return true;
26947 }
26948
26949 buffer.completion_triggers().contains(text)
26950 }
26951
26952 fn show_snippets(&self) -> bool {
26953 true
26954 }
26955}
26956
26957impl SemanticsProvider for Entity<Project> {
26958 fn hover(
26959 &self,
26960 buffer: &Entity<Buffer>,
26961 position: text::Anchor,
26962 cx: &mut App,
26963 ) -> Option<Task<Option<Vec<project::Hover>>>> {
26964 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
26965 }
26966
26967 fn document_highlights(
26968 &self,
26969 buffer: &Entity<Buffer>,
26970 position: text::Anchor,
26971 cx: &mut App,
26972 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
26973 Some(self.update(cx, |project, cx| {
26974 project.document_highlights(buffer, position, cx)
26975 }))
26976 }
26977
26978 fn definitions(
26979 &self,
26980 buffer: &Entity<Buffer>,
26981 position: text::Anchor,
26982 kind: GotoDefinitionKind,
26983 cx: &mut App,
26984 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
26985 Some(self.update(cx, |project, cx| match kind {
26986 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
26987 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
26988 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
26989 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
26990 }))
26991 }
26992
26993 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
26994 self.update(cx, |project, cx| {
26995 if project
26996 .active_debug_session(cx)
26997 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
26998 {
26999 return true;
27000 }
27001
27002 buffer.update(cx, |buffer, cx| {
27003 project.any_language_server_supports_inlay_hints(buffer, cx)
27004 })
27005 })
27006 }
27007
27008 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27009 self.update(cx, |project, cx| {
27010 buffer.update(cx, |buffer, cx| {
27011 project.any_language_server_supports_semantic_tokens(buffer, cx)
27012 })
27013 })
27014 }
27015
27016 fn inline_values(
27017 &self,
27018 buffer_handle: Entity<Buffer>,
27019 range: Range<text::Anchor>,
27020 cx: &mut App,
27021 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27022 self.update(cx, |project, cx| {
27023 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27024
27025 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27026 })
27027 }
27028
27029 fn applicable_inlay_chunks(
27030 &self,
27031 buffer: &Entity<Buffer>,
27032 ranges: &[Range<text::Anchor>],
27033 cx: &mut App,
27034 ) -> Vec<Range<BufferRow>> {
27035 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27036 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27037 })
27038 }
27039
27040 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27041 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
27042 lsp_store.invalidate_inlay_hints(for_buffers)
27043 });
27044 }
27045
27046 fn inlay_hints(
27047 &self,
27048 invalidate: InvalidationStrategy,
27049 buffer: Entity<Buffer>,
27050 ranges: Vec<Range<text::Anchor>>,
27051 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27052 cx: &mut App,
27053 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27054 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27055 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27056 }))
27057 }
27058
27059 fn semantic_tokens(
27060 &self,
27061 buffer: Entity<Buffer>,
27062 refresh: Option<RefreshForServer>,
27063 cx: &mut App,
27064 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>> {
27065 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27066 lsp_store.semantic_tokens(buffer, refresh, cx)
27067 })
27068 }
27069
27070 fn range_for_rename(
27071 &self,
27072 buffer: &Entity<Buffer>,
27073 position: text::Anchor,
27074 cx: &mut App,
27075 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27076 Some(self.update(cx, |project, cx| {
27077 let buffer = buffer.clone();
27078 let task = project.prepare_rename(buffer.clone(), position, cx);
27079 cx.spawn(async move |_, cx| {
27080 Ok(match task.await? {
27081 PrepareRenameResponse::Success(range) => Some(range),
27082 PrepareRenameResponse::InvalidPosition => None,
27083 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27084 // Fallback on using TreeSitter info to determine identifier range
27085 buffer.read_with(cx, |buffer, _| {
27086 let snapshot = buffer.snapshot();
27087 let (range, kind) = snapshot.surrounding_word(position, None);
27088 if kind != Some(CharKind::Word) {
27089 return None;
27090 }
27091 Some(
27092 snapshot.anchor_before(range.start)
27093 ..snapshot.anchor_after(range.end),
27094 )
27095 })
27096 }
27097 })
27098 })
27099 }))
27100 }
27101
27102 fn perform_rename(
27103 &self,
27104 buffer: &Entity<Buffer>,
27105 position: text::Anchor,
27106 new_name: String,
27107 cx: &mut App,
27108 ) -> Option<Task<Result<ProjectTransaction>>> {
27109 Some(self.update(cx, |project, cx| {
27110 project.perform_rename(buffer.clone(), position, new_name, cx)
27111 }))
27112 }
27113}
27114
27115fn consume_contiguous_rows(
27116 contiguous_row_selections: &mut Vec<Selection<Point>>,
27117 selection: &Selection<Point>,
27118 display_map: &DisplaySnapshot,
27119 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27120) -> (MultiBufferRow, MultiBufferRow) {
27121 contiguous_row_selections.push(selection.clone());
27122 let start_row = starting_row(selection, display_map);
27123 let mut end_row = ending_row(selection, display_map);
27124
27125 while let Some(next_selection) = selections.peek() {
27126 if next_selection.start.row <= end_row.0 {
27127 end_row = ending_row(next_selection, display_map);
27128 contiguous_row_selections.push(selections.next().unwrap().clone());
27129 } else {
27130 break;
27131 }
27132 }
27133 (start_row, end_row)
27134}
27135
27136fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27137 if selection.start.column > 0 {
27138 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27139 } else {
27140 MultiBufferRow(selection.start.row)
27141 }
27142}
27143
27144fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27145 if next_selection.end.column > 0 || next_selection.is_empty() {
27146 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27147 } else {
27148 MultiBufferRow(next_selection.end.row)
27149 }
27150}
27151
27152impl EditorSnapshot {
27153 pub fn remote_selections_in_range<'a>(
27154 &'a self,
27155 range: &'a Range<Anchor>,
27156 collaboration_hub: &dyn CollaborationHub,
27157 cx: &'a App,
27158 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27159 let participant_names = collaboration_hub.user_names(cx);
27160 let participant_indices = collaboration_hub.user_participant_indices(cx);
27161 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27162 let collaborators_by_replica_id = collaborators_by_peer_id
27163 .values()
27164 .map(|collaborator| (collaborator.replica_id, collaborator))
27165 .collect::<HashMap<_, _>>();
27166 self.buffer_snapshot()
27167 .selections_in_range(range, false)
27168 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27169 if replica_id == ReplicaId::AGENT {
27170 Some(RemoteSelection {
27171 replica_id,
27172 selection,
27173 cursor_shape,
27174 line_mode,
27175 collaborator_id: CollaboratorId::Agent,
27176 user_name: Some("Agent".into()),
27177 color: cx.theme().players().agent(),
27178 })
27179 } else {
27180 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27181 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27182 let user_name = participant_names.get(&collaborator.user_id).cloned();
27183 Some(RemoteSelection {
27184 replica_id,
27185 selection,
27186 cursor_shape,
27187 line_mode,
27188 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27189 user_name,
27190 color: if let Some(index) = participant_index {
27191 cx.theme().players().color_for_participant(index.0)
27192 } else {
27193 cx.theme().players().absent()
27194 },
27195 })
27196 }
27197 })
27198 }
27199
27200 pub fn hunks_for_ranges(
27201 &self,
27202 ranges: impl IntoIterator<Item = Range<Point>>,
27203 ) -> Vec<MultiBufferDiffHunk> {
27204 let mut hunks = Vec::new();
27205 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27206 HashMap::default();
27207 for query_range in ranges {
27208 let query_rows =
27209 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27210 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27211 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27212 ) {
27213 // Include deleted hunks that are adjacent to the query range, because
27214 // otherwise they would be missed.
27215 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27216 if hunk.status().is_deleted() {
27217 intersects_range |= hunk.row_range.start == query_rows.end;
27218 intersects_range |= hunk.row_range.end == query_rows.start;
27219 }
27220 if intersects_range {
27221 if !processed_buffer_rows
27222 .entry(hunk.buffer_id)
27223 .or_default()
27224 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27225 {
27226 continue;
27227 }
27228 hunks.push(hunk);
27229 }
27230 }
27231 }
27232
27233 hunks
27234 }
27235
27236 fn display_diff_hunks_for_rows<'a>(
27237 &'a self,
27238 display_rows: Range<DisplayRow>,
27239 folded_buffers: &'a HashSet<BufferId>,
27240 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27241 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27242 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27243
27244 self.buffer_snapshot()
27245 .diff_hunks_in_range(buffer_start..buffer_end)
27246 .filter_map(|hunk| {
27247 if folded_buffers.contains(&hunk.buffer_id)
27248 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27249 {
27250 return None;
27251 }
27252
27253 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27254 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27255 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27256 let line_len = self.buffer_snapshot().line_len(last_row);
27257 Point::new(last_row.0, line_len)
27258 } else {
27259 Point::new(hunk.row_range.end.0, 0)
27260 };
27261
27262 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27263 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27264
27265 let display_hunk = if hunk_display_start.column() != 0 {
27266 DisplayDiffHunk::Folded {
27267 display_row: hunk_display_start.row(),
27268 }
27269 } else {
27270 let mut end_row = hunk_display_end.row();
27271 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27272 end_row.0 += 1;
27273 }
27274 let is_created_file = hunk.is_created_file();
27275
27276 DisplayDiffHunk::Unfolded {
27277 status: hunk.status(),
27278 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27279 ..hunk.diff_base_byte_range.end.0,
27280 word_diffs: hunk.word_diffs,
27281 display_row_range: hunk_display_start.row()..end_row,
27282 multi_buffer_range: Anchor::range_in_buffer(
27283 hunk.excerpt_id,
27284 hunk.buffer_range,
27285 ),
27286 is_created_file,
27287 }
27288 };
27289
27290 Some(display_hunk)
27291 })
27292 }
27293
27294 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27295 self.display_snapshot
27296 .buffer_snapshot()
27297 .language_at(position)
27298 }
27299
27300 pub fn is_focused(&self) -> bool {
27301 self.is_focused
27302 }
27303
27304 pub fn placeholder_text(&self) -> Option<String> {
27305 self.placeholder_display_snapshot
27306 .as_ref()
27307 .map(|display_map| display_map.text())
27308 }
27309
27310 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27311 self.scroll_anchor.scroll_position(&self.display_snapshot)
27312 }
27313
27314 pub fn gutter_dimensions(
27315 &self,
27316 font_id: FontId,
27317 font_size: Pixels,
27318 style: &EditorStyle,
27319 window: &mut Window,
27320 cx: &App,
27321 ) -> GutterDimensions {
27322 if self.show_gutter
27323 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27324 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27325 {
27326 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27327 matches!(
27328 ProjectSettings::get_global(cx).git.git_gutter,
27329 GitGutterSetting::TrackedFiles
27330 )
27331 });
27332 let gutter_settings = EditorSettings::get_global(cx).gutter;
27333 let show_line_numbers = self
27334 .show_line_numbers
27335 .unwrap_or(gutter_settings.line_numbers);
27336 let line_gutter_width = if show_line_numbers {
27337 // Avoid flicker-like gutter resizes when the line number gains another digit by
27338 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27339 let min_width_for_number_on_gutter =
27340 ch_advance * gutter_settings.min_line_number_digits as f32;
27341 self.max_line_number_width(style, window)
27342 .max(min_width_for_number_on_gutter)
27343 } else {
27344 0.0.into()
27345 };
27346
27347 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27348 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27349
27350 let git_blame_entries_width =
27351 self.git_blame_gutter_max_author_length
27352 .map(|max_author_length| {
27353 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27354 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27355
27356 /// The number of characters to dedicate to gaps and margins.
27357 const SPACING_WIDTH: usize = 4;
27358
27359 let max_char_count = max_author_length.min(renderer.max_author_length())
27360 + ::git::SHORT_SHA_LENGTH
27361 + MAX_RELATIVE_TIMESTAMP.len()
27362 + SPACING_WIDTH;
27363
27364 ch_advance * max_char_count
27365 });
27366
27367 let is_singleton = self.buffer_snapshot().is_singleton();
27368
27369 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27370 left_padding += if !is_singleton {
27371 ch_width * 4.0
27372 } else if show_runnables || show_breakpoints {
27373 ch_width * 3.0
27374 } else if show_git_gutter && show_line_numbers {
27375 ch_width * 2.0
27376 } else if show_git_gutter || show_line_numbers {
27377 ch_width
27378 } else {
27379 px(0.)
27380 };
27381
27382 let shows_folds = is_singleton && gutter_settings.folds;
27383
27384 let right_padding = if shows_folds && show_line_numbers {
27385 ch_width * 4.0
27386 } else if shows_folds || (!is_singleton && show_line_numbers) {
27387 ch_width * 3.0
27388 } else if show_line_numbers {
27389 ch_width
27390 } else {
27391 px(0.)
27392 };
27393
27394 GutterDimensions {
27395 left_padding,
27396 right_padding,
27397 width: line_gutter_width + left_padding + right_padding,
27398 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27399 git_blame_entries_width,
27400 }
27401 } else if self.offset_content {
27402 GutterDimensions::default_with_margin(font_id, font_size, cx)
27403 } else {
27404 GutterDimensions::default()
27405 }
27406 }
27407
27408 pub fn render_crease_toggle(
27409 &self,
27410 buffer_row: MultiBufferRow,
27411 row_contains_cursor: bool,
27412 editor: Entity<Editor>,
27413 window: &mut Window,
27414 cx: &mut App,
27415 ) -> Option<AnyElement> {
27416 let folded = self.is_line_folded(buffer_row);
27417 let mut is_foldable = false;
27418
27419 if let Some(crease) = self
27420 .crease_snapshot
27421 .query_row(buffer_row, self.buffer_snapshot())
27422 {
27423 is_foldable = true;
27424 match crease {
27425 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27426 if let Some(render_toggle) = render_toggle {
27427 let toggle_callback =
27428 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27429 if folded {
27430 editor.update(cx, |editor, cx| {
27431 editor.fold_at(buffer_row, window, cx)
27432 });
27433 } else {
27434 editor.update(cx, |editor, cx| {
27435 editor.unfold_at(buffer_row, window, cx)
27436 });
27437 }
27438 });
27439 return Some((render_toggle)(
27440 buffer_row,
27441 folded,
27442 toggle_callback,
27443 window,
27444 cx,
27445 ));
27446 }
27447 }
27448 }
27449 }
27450
27451 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27452
27453 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27454 Some(
27455 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27456 .toggle_state(folded)
27457 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27458 if folded {
27459 this.unfold_at(buffer_row, window, cx);
27460 } else {
27461 this.fold_at(buffer_row, window, cx);
27462 }
27463 }))
27464 .into_any_element(),
27465 )
27466 } else {
27467 None
27468 }
27469 }
27470
27471 pub fn render_crease_trailer(
27472 &self,
27473 buffer_row: MultiBufferRow,
27474 window: &mut Window,
27475 cx: &mut App,
27476 ) -> Option<AnyElement> {
27477 let folded = self.is_line_folded(buffer_row);
27478 if let Crease::Inline { render_trailer, .. } = self
27479 .crease_snapshot
27480 .query_row(buffer_row, self.buffer_snapshot())?
27481 {
27482 let render_trailer = render_trailer.as_ref()?;
27483 Some(render_trailer(buffer_row, folded, window, cx))
27484 } else {
27485 None
27486 }
27487 }
27488
27489 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27490 let digit_count = self.widest_line_number().ilog10() + 1;
27491 column_pixels(style, digit_count as usize, window)
27492 }
27493
27494 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27495 ///
27496 /// This is positive if `base` is before `line`.
27497 fn relative_line_delta(
27498 &self,
27499 current_selection_head: DisplayRow,
27500 first_visible_row: DisplayRow,
27501 consider_wrapped_lines: bool,
27502 ) -> i64 {
27503 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27504 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27505
27506 if consider_wrapped_lines {
27507 let wrap_snapshot = self.wrap_snapshot();
27508 let base_wrap_row = wrap_snapshot
27509 .make_wrap_point(current_selection_head, Bias::Left)
27510 .row();
27511 let wrap_row = wrap_snapshot
27512 .make_wrap_point(first_visible_row, Bias::Left)
27513 .row();
27514
27515 wrap_row.0 as i64 - base_wrap_row.0 as i64
27516 } else {
27517 let fold_snapshot = self.fold_snapshot();
27518 let base_fold_row = fold_snapshot
27519 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27520 .row();
27521 let fold_row = fold_snapshot
27522 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27523 .row();
27524
27525 fold_row as i64 - base_fold_row as i64
27526 }
27527 }
27528
27529 /// Returns the unsigned relative line number to display for each row in `rows`.
27530 ///
27531 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27532 pub fn calculate_relative_line_numbers(
27533 &self,
27534 rows: &Range<DisplayRow>,
27535 current_selection_head: DisplayRow,
27536 count_wrapped_lines: bool,
27537 ) -> HashMap<DisplayRow, u32> {
27538 let initial_offset =
27539 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27540 let current_selection_point = current_selection_head.as_display_point().to_point(self);
27541
27542 self.row_infos(rows.start)
27543 .take(rows.len())
27544 .enumerate()
27545 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27546 .filter(|(_row, row_info)| {
27547 row_info.buffer_row.is_some()
27548 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27549 })
27550 .enumerate()
27551 .filter(|(_, (row, row_info))| {
27552 // We want to check here that
27553 // - the row is not the current selection head to ensure the current
27554 // line has absolute numbering
27555 // - similarly, should the selection head live in a soft-wrapped line
27556 // and we are not counting those, that the parent line keeps its
27557 // absolute number
27558 // - lastly, if we are in a deleted line, it is fine to number this
27559 // relative with 0, as otherwise it would have no line number at all
27560 (*row != current_selection_head
27561 && (count_wrapped_lines
27562 || row_info.buffer_row != Some(current_selection_point.row)))
27563 || row_info
27564 .diff_status
27565 .is_some_and(|status| status.is_deleted())
27566 })
27567 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
27568 .collect()
27569 }
27570}
27571
27572pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27573 let font_size = style.text.font_size.to_pixels(window.rem_size());
27574 let layout = window.text_system().shape_line(
27575 SharedString::from(" ".repeat(column)),
27576 font_size,
27577 &[TextRun {
27578 len: column,
27579 font: style.text.font(),
27580 color: Hsla::default(),
27581 ..Default::default()
27582 }],
27583 None,
27584 );
27585
27586 layout.width
27587}
27588
27589impl Deref for EditorSnapshot {
27590 type Target = DisplaySnapshot;
27591
27592 fn deref(&self) -> &Self::Target {
27593 &self.display_snapshot
27594 }
27595}
27596
27597#[derive(Clone, Debug, PartialEq, Eq)]
27598pub enum EditorEvent {
27599 /// Emitted when the stored review comments change (added, removed, or updated).
27600 ReviewCommentsChanged {
27601 /// The new total count of review comments.
27602 total_count: usize,
27603 },
27604 InputIgnored {
27605 text: Arc<str>,
27606 },
27607 InputHandled {
27608 utf16_range_to_replace: Option<Range<isize>>,
27609 text: Arc<str>,
27610 },
27611 ExcerptsAdded {
27612 buffer: Entity<Buffer>,
27613 predecessor: ExcerptId,
27614 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27615 },
27616 ExcerptsRemoved {
27617 ids: Vec<ExcerptId>,
27618 removed_buffer_ids: Vec<BufferId>,
27619 },
27620 BufferFoldToggled {
27621 ids: Vec<ExcerptId>,
27622 folded: bool,
27623 },
27624 ExcerptsEdited {
27625 ids: Vec<ExcerptId>,
27626 },
27627 ExcerptsExpanded {
27628 ids: Vec<ExcerptId>,
27629 },
27630 ExpandExcerptsRequested {
27631 excerpt_ids: Vec<ExcerptId>,
27632 lines: u32,
27633 direction: ExpandExcerptDirection,
27634 },
27635 StageOrUnstageRequested {
27636 stage: bool,
27637 hunks: Vec<MultiBufferDiffHunk>,
27638 },
27639 OpenExcerptsRequested {
27640 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27641 split: bool,
27642 },
27643 RestoreRequested {
27644 hunks: Vec<MultiBufferDiffHunk>,
27645 },
27646 BufferEdited,
27647 Edited {
27648 transaction_id: clock::Lamport,
27649 },
27650 Reparsed(BufferId),
27651 Focused,
27652 FocusedIn,
27653 Blurred,
27654 DirtyChanged,
27655 Saved,
27656 TitleChanged,
27657 SelectionsChanged {
27658 local: bool,
27659 },
27660 ScrollPositionChanged {
27661 local: bool,
27662 autoscroll: bool,
27663 },
27664 TransactionUndone {
27665 transaction_id: clock::Lamport,
27666 },
27667 TransactionBegun {
27668 transaction_id: clock::Lamport,
27669 },
27670 CursorShapeChanged,
27671 BreadcrumbsChanged,
27672 PushedToNavHistory {
27673 anchor: Anchor,
27674 is_deactivate: bool,
27675 },
27676}
27677
27678impl EventEmitter<EditorEvent> for Editor {}
27679
27680impl Focusable for Editor {
27681 fn focus_handle(&self, _cx: &App) -> FocusHandle {
27682 self.focus_handle.clone()
27683 }
27684}
27685
27686impl Render for Editor {
27687 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
27688 EditorElement::new(&cx.entity(), self.create_style(cx))
27689 }
27690}
27691
27692impl EntityInputHandler for Editor {
27693 fn text_for_range(
27694 &mut self,
27695 range_utf16: Range<usize>,
27696 adjusted_range: &mut Option<Range<usize>>,
27697 _: &mut Window,
27698 cx: &mut Context<Self>,
27699 ) -> Option<String> {
27700 let snapshot = self.buffer.read(cx).read(cx);
27701 let start = snapshot.clip_offset_utf16(
27702 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
27703 Bias::Left,
27704 );
27705 let end = snapshot.clip_offset_utf16(
27706 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
27707 Bias::Right,
27708 );
27709 if (start.0.0..end.0.0) != range_utf16 {
27710 adjusted_range.replace(start.0.0..end.0.0);
27711 }
27712 Some(snapshot.text_for_range(start..end).collect())
27713 }
27714
27715 fn selected_text_range(
27716 &mut self,
27717 ignore_disabled_input: bool,
27718 _: &mut Window,
27719 cx: &mut Context<Self>,
27720 ) -> Option<UTF16Selection> {
27721 // Prevent the IME menu from appearing when holding down an alphabetic key
27722 // while input is disabled.
27723 if !ignore_disabled_input && !self.input_enabled {
27724 return None;
27725 }
27726
27727 let selection = self
27728 .selections
27729 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
27730 let range = selection.range();
27731
27732 Some(UTF16Selection {
27733 range: range.start.0.0..range.end.0.0,
27734 reversed: selection.reversed,
27735 })
27736 }
27737
27738 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
27739 let snapshot = self.buffer.read(cx).read(cx);
27740 let range = self
27741 .text_highlights(HighlightKey::InputComposition, cx)?
27742 .1
27743 .first()?;
27744 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
27745 }
27746
27747 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
27748 self.clear_highlights(HighlightKey::InputComposition, cx);
27749 self.ime_transaction.take();
27750 }
27751
27752 fn replace_text_in_range(
27753 &mut self,
27754 range_utf16: Option<Range<usize>>,
27755 text: &str,
27756 window: &mut Window,
27757 cx: &mut Context<Self>,
27758 ) {
27759 if !self.input_enabled {
27760 cx.emit(EditorEvent::InputIgnored { text: text.into() });
27761 return;
27762 }
27763
27764 self.transact(window, cx, |this, window, cx| {
27765 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
27766 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27767 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27768 Some(this.selection_replacement_ranges(range_utf16, cx))
27769 } else {
27770 this.marked_text_ranges(cx)
27771 };
27772
27773 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
27774 let newest_selection_id = this.selections.newest_anchor().id;
27775 this.selections
27776 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27777 .iter()
27778 .zip(ranges_to_replace.iter())
27779 .find_map(|(selection, range)| {
27780 if selection.id == newest_selection_id {
27781 Some(
27782 (range.start.0.0 as isize - selection.head().0.0 as isize)
27783 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27784 )
27785 } else {
27786 None
27787 }
27788 })
27789 });
27790
27791 cx.emit(EditorEvent::InputHandled {
27792 utf16_range_to_replace: range_to_replace,
27793 text: text.into(),
27794 });
27795
27796 if let Some(new_selected_ranges) = new_selected_ranges {
27797 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27798 selections.select_ranges(new_selected_ranges)
27799 });
27800 this.backspace(&Default::default(), window, cx);
27801 }
27802
27803 this.handle_input(text, window, cx);
27804 });
27805
27806 if let Some(transaction) = self.ime_transaction {
27807 self.buffer.update(cx, |buffer, cx| {
27808 buffer.group_until_transaction(transaction, cx);
27809 });
27810 }
27811
27812 self.unmark_text(window, cx);
27813 }
27814
27815 fn replace_and_mark_text_in_range(
27816 &mut self,
27817 range_utf16: Option<Range<usize>>,
27818 text: &str,
27819 new_selected_range_utf16: Option<Range<usize>>,
27820 window: &mut Window,
27821 cx: &mut Context<Self>,
27822 ) {
27823 if !self.input_enabled {
27824 return;
27825 }
27826
27827 let transaction = self.transact(window, cx, |this, window, cx| {
27828 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
27829 let snapshot = this.buffer.read(cx).read(cx);
27830 if let Some(relative_range_utf16) = range_utf16.as_ref() {
27831 for marked_range in &mut marked_ranges {
27832 marked_range.end = marked_range.start + relative_range_utf16.end;
27833 marked_range.start += relative_range_utf16.start;
27834 marked_range.start =
27835 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
27836 marked_range.end =
27837 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
27838 }
27839 }
27840 Some(marked_ranges)
27841 } else if let Some(range_utf16) = range_utf16 {
27842 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
27843 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
27844 Some(this.selection_replacement_ranges(range_utf16, cx))
27845 } else {
27846 None
27847 };
27848
27849 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
27850 let newest_selection_id = this.selections.newest_anchor().id;
27851 this.selections
27852 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
27853 .iter()
27854 .zip(ranges_to_replace.iter())
27855 .find_map(|(selection, range)| {
27856 if selection.id == newest_selection_id {
27857 Some(
27858 (range.start.0.0 as isize - selection.head().0.0 as isize)
27859 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
27860 )
27861 } else {
27862 None
27863 }
27864 })
27865 });
27866
27867 cx.emit(EditorEvent::InputHandled {
27868 utf16_range_to_replace: range_to_replace,
27869 text: text.into(),
27870 });
27871
27872 if let Some(ranges) = ranges_to_replace {
27873 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27874 s.select_ranges(ranges)
27875 });
27876 }
27877
27878 let marked_ranges = {
27879 let snapshot = this.buffer.read(cx).read(cx);
27880 this.selections
27881 .disjoint_anchors_arc()
27882 .iter()
27883 .map(|selection| {
27884 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
27885 })
27886 .collect::<Vec<_>>()
27887 };
27888
27889 if text.is_empty() {
27890 this.unmark_text(window, cx);
27891 } else {
27892 this.highlight_text(
27893 HighlightKey::InputComposition,
27894 marked_ranges.clone(),
27895 HighlightStyle {
27896 underline: Some(UnderlineStyle {
27897 thickness: px(1.),
27898 color: None,
27899 wavy: false,
27900 }),
27901 ..Default::default()
27902 },
27903 cx,
27904 );
27905 }
27906
27907 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
27908 let use_autoclose = this.use_autoclose;
27909 let use_auto_surround = this.use_auto_surround;
27910 this.set_use_autoclose(false);
27911 this.set_use_auto_surround(false);
27912 this.handle_input(text, window, cx);
27913 this.set_use_autoclose(use_autoclose);
27914 this.set_use_auto_surround(use_auto_surround);
27915
27916 if let Some(new_selected_range) = new_selected_range_utf16 {
27917 let snapshot = this.buffer.read(cx).read(cx);
27918 let new_selected_ranges = marked_ranges
27919 .into_iter()
27920 .map(|marked_range| {
27921 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
27922 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
27923 insertion_start.0 + new_selected_range.start,
27924 ));
27925 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
27926 insertion_start.0 + new_selected_range.end,
27927 ));
27928 snapshot.clip_offset_utf16(new_start, Bias::Left)
27929 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
27930 })
27931 .collect::<Vec<_>>();
27932
27933 drop(snapshot);
27934 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27935 selections.select_ranges(new_selected_ranges)
27936 });
27937 }
27938 });
27939
27940 self.ime_transaction = self.ime_transaction.or(transaction);
27941 if let Some(transaction) = self.ime_transaction {
27942 self.buffer.update(cx, |buffer, cx| {
27943 buffer.group_until_transaction(transaction, cx);
27944 });
27945 }
27946
27947 if self
27948 .text_highlights(HighlightKey::InputComposition, cx)
27949 .is_none()
27950 {
27951 self.ime_transaction.take();
27952 }
27953 }
27954
27955 fn bounds_for_range(
27956 &mut self,
27957 range_utf16: Range<usize>,
27958 element_bounds: gpui::Bounds<Pixels>,
27959 window: &mut Window,
27960 cx: &mut Context<Self>,
27961 ) -> Option<gpui::Bounds<Pixels>> {
27962 let text_layout_details = self.text_layout_details(window, cx);
27963 let CharacterDimensions {
27964 em_width,
27965 em_advance,
27966 line_height,
27967 } = self.character_dimensions(window, cx);
27968
27969 let snapshot = self.snapshot(window, cx);
27970 let scroll_position = snapshot.scroll_position();
27971 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
27972
27973 let start =
27974 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
27975 let x = Pixels::from(
27976 ScrollOffset::from(
27977 snapshot.x_for_display_point(start, &text_layout_details)
27978 + self.gutter_dimensions.full_width(),
27979 ) - scroll_left,
27980 );
27981 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
27982
27983 Some(Bounds {
27984 origin: element_bounds.origin + point(x, y),
27985 size: size(em_width, line_height),
27986 })
27987 }
27988
27989 fn character_index_for_point(
27990 &mut self,
27991 point: gpui::Point<Pixels>,
27992 _window: &mut Window,
27993 _cx: &mut Context<Self>,
27994 ) -> Option<usize> {
27995 let position_map = self.last_position_map.as_ref()?;
27996 if !position_map.text_hitbox.contains(&point) {
27997 return None;
27998 }
27999 let display_point = position_map.point_for_position(point).previous_valid;
28000 let anchor = position_map
28001 .snapshot
28002 .display_point_to_anchor(display_point, Bias::Left);
28003 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28004 Some(utf16_offset.0.0)
28005 }
28006
28007 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28008 self.input_enabled
28009 }
28010}
28011
28012trait SelectionExt {
28013 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28014 fn spanned_rows(
28015 &self,
28016 include_end_if_at_line_start: bool,
28017 map: &DisplaySnapshot,
28018 ) -> Range<MultiBufferRow>;
28019}
28020
28021impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28022 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28023 let start = self
28024 .start
28025 .to_point(map.buffer_snapshot())
28026 .to_display_point(map);
28027 let end = self
28028 .end
28029 .to_point(map.buffer_snapshot())
28030 .to_display_point(map);
28031 if self.reversed {
28032 end..start
28033 } else {
28034 start..end
28035 }
28036 }
28037
28038 fn spanned_rows(
28039 &self,
28040 include_end_if_at_line_start: bool,
28041 map: &DisplaySnapshot,
28042 ) -> Range<MultiBufferRow> {
28043 let start = self.start.to_point(map.buffer_snapshot());
28044 let mut end = self.end.to_point(map.buffer_snapshot());
28045 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28046 end.row -= 1;
28047 }
28048
28049 let buffer_start = map.prev_line_boundary(start).0;
28050 let buffer_end = map.next_line_boundary(end).0;
28051 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28052 }
28053}
28054
28055impl<T: InvalidationRegion> InvalidationStack<T> {
28056 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28057 where
28058 S: Clone + ToOffset,
28059 {
28060 while let Some(region) = self.last() {
28061 let all_selections_inside_invalidation_ranges =
28062 if selections.len() == region.ranges().len() {
28063 selections
28064 .iter()
28065 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28066 .all(|(selection, invalidation_range)| {
28067 let head = selection.head().to_offset(buffer);
28068 invalidation_range.start <= head && invalidation_range.end >= head
28069 })
28070 } else {
28071 false
28072 };
28073
28074 if all_selections_inside_invalidation_ranges {
28075 break;
28076 } else {
28077 self.pop();
28078 }
28079 }
28080 }
28081}
28082
28083#[derive(Clone)]
28084struct ErasedEditorImpl(Entity<Editor>);
28085
28086impl ui_input::ErasedEditor for ErasedEditorImpl {
28087 fn text(&self, cx: &App) -> String {
28088 self.0.read(cx).text(cx)
28089 }
28090
28091 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28092 self.0.update(cx, |this, cx| {
28093 this.set_text(text, window, cx);
28094 })
28095 }
28096
28097 fn clear(&self, window: &mut Window, cx: &mut App) {
28098 self.0.update(cx, |this, cx| this.clear(window, cx));
28099 }
28100
28101 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28102 self.0.update(cx, |this, cx| {
28103 this.set_placeholder_text(text, window, cx);
28104 });
28105 }
28106
28107 fn focus_handle(&self, cx: &App) -> FocusHandle {
28108 self.0.read(cx).focus_handle(cx)
28109 }
28110
28111 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28112 let settings = ThemeSettings::get_global(cx);
28113 let theme_color = cx.theme().colors();
28114
28115 let text_style = TextStyle {
28116 font_family: settings.ui_font.family.clone(),
28117 font_features: settings.ui_font.features.clone(),
28118 font_size: rems(0.875).into(),
28119 font_weight: settings.buffer_font.weight,
28120 font_style: FontStyle::Normal,
28121 line_height: relative(1.2),
28122 color: theme_color.text,
28123 ..Default::default()
28124 };
28125 let editor_style = EditorStyle {
28126 background: theme_color.ghost_element_background,
28127 local_player: cx.theme().players().local(),
28128 syntax: cx.theme().syntax().clone(),
28129 text: text_style,
28130 ..Default::default()
28131 };
28132 EditorElement::new(&self.0, editor_style).into_any()
28133 }
28134
28135 fn as_any(&self) -> &dyn Any {
28136 &self.0
28137 }
28138
28139 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28140 self.0.update(cx, |editor, cx| {
28141 let editor_offset = editor.buffer().read(cx).len(cx);
28142 editor.change_selections(
28143 SelectionEffects::scroll(Autoscroll::Next),
28144 window,
28145 cx,
28146 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28147 );
28148 });
28149 }
28150
28151 fn subscribe(
28152 &self,
28153 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28154 window: &mut Window,
28155 cx: &mut App,
28156 ) -> Subscription {
28157 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28158 let event = match event {
28159 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28160 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28161 _ => return,
28162 };
28163 (callback)(event, window, cx);
28164 })
28165 }
28166
28167 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28168 self.0.update(cx, |editor, cx| {
28169 editor.set_masked(masked, cx);
28170 });
28171 }
28172}
28173impl<T> Default for InvalidationStack<T> {
28174 fn default() -> Self {
28175 Self(Default::default())
28176 }
28177}
28178
28179impl<T> Deref for InvalidationStack<T> {
28180 type Target = Vec<T>;
28181
28182 fn deref(&self) -> &Self::Target {
28183 &self.0
28184 }
28185}
28186
28187impl<T> DerefMut for InvalidationStack<T> {
28188 fn deref_mut(&mut self) -> &mut Self::Target {
28189 &mut self.0
28190 }
28191}
28192
28193impl InvalidationRegion for SnippetState {
28194 fn ranges(&self) -> &[Range<Anchor>] {
28195 &self.ranges[self.active_index]
28196 }
28197}
28198
28199fn edit_prediction_edit_text(
28200 current_snapshot: &BufferSnapshot,
28201 edits: &[(Range<Anchor>, impl AsRef<str>)],
28202 edit_preview: &EditPreview,
28203 include_deletions: bool,
28204 cx: &App,
28205) -> HighlightedText {
28206 let edits = edits
28207 .iter()
28208 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28209 .collect::<Vec<_>>();
28210
28211 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28212}
28213
28214fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28215 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
28216 // Just show the raw edit text with basic styling
28217 let mut text = String::new();
28218 let mut highlights = Vec::new();
28219
28220 let insertion_highlight_style = HighlightStyle {
28221 color: Some(cx.theme().colors().text),
28222 ..Default::default()
28223 };
28224
28225 for (_, edit_text) in edits {
28226 let start_offset = text.len();
28227 text.push_str(edit_text);
28228 let end_offset = text.len();
28229
28230 if start_offset < end_offset {
28231 highlights.push((start_offset..end_offset, insertion_highlight_style));
28232 }
28233 }
28234
28235 HighlightedText {
28236 text: text.into(),
28237 highlights,
28238 }
28239}
28240
28241pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28242 match severity {
28243 lsp::DiagnosticSeverity::ERROR => colors.error,
28244 lsp::DiagnosticSeverity::WARNING => colors.warning,
28245 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28246 lsp::DiagnosticSeverity::HINT => colors.info,
28247 _ => colors.ignored,
28248 }
28249}
28250
28251pub fn styled_runs_for_code_label<'a>(
28252 label: &'a CodeLabel,
28253 syntax_theme: &'a theme::SyntaxTheme,
28254 local_player: &'a theme::PlayerColor,
28255) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28256 let fade_out = HighlightStyle {
28257 fade_out: Some(0.35),
28258 ..Default::default()
28259 };
28260
28261 let mut prev_end = label.filter_range.end;
28262 label
28263 .runs
28264 .iter()
28265 .enumerate()
28266 .flat_map(move |(ix, (range, highlight_id))| {
28267 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28268 HighlightStyle {
28269 color: Some(local_player.cursor),
28270 ..Default::default()
28271 }
28272 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28273 HighlightStyle {
28274 background_color: Some(local_player.selection),
28275 ..Default::default()
28276 }
28277 } else if let Some(style) = highlight_id.style(syntax_theme) {
28278 style
28279 } else {
28280 return Default::default();
28281 };
28282 let muted_style = style.highlight(fade_out);
28283
28284 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28285 if range.start >= label.filter_range.end {
28286 if range.start > prev_end {
28287 runs.push((prev_end..range.start, fade_out));
28288 }
28289 runs.push((range.clone(), muted_style));
28290 } else if range.end <= label.filter_range.end {
28291 runs.push((range.clone(), style));
28292 } else {
28293 runs.push((range.start..label.filter_range.end, style));
28294 runs.push((label.filter_range.end..range.end, muted_style));
28295 }
28296 prev_end = cmp::max(prev_end, range.end);
28297
28298 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28299 runs.push((prev_end..label.text.len(), fade_out));
28300 }
28301
28302 runs
28303 })
28304}
28305
28306pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28307 let mut prev_index = 0;
28308 let mut prev_codepoint: Option<char> = None;
28309 text.char_indices()
28310 .chain([(text.len(), '\0')])
28311 .filter_map(move |(index, codepoint)| {
28312 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28313 let is_boundary = index == text.len()
28314 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28315 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28316 if is_boundary {
28317 let chunk = &text[prev_index..index];
28318 prev_index = index;
28319 Some(chunk)
28320 } else {
28321 None
28322 }
28323 })
28324}
28325
28326/// Given a string of text immediately before the cursor, iterates over possible
28327/// strings a snippet could match to. More precisely: returns an iterator over
28328/// suffixes of `text` created by splitting at word boundaries (before & after
28329/// every non-word character).
28330///
28331/// Shorter suffixes are returned first.
28332pub(crate) fn snippet_candidate_suffixes(
28333 text: &str,
28334 is_word_char: impl Fn(char) -> bool,
28335) -> impl std::iter::Iterator<Item = &str> {
28336 let mut prev_index = text.len();
28337 let mut prev_codepoint = None;
28338 text.char_indices()
28339 .rev()
28340 .chain([(0, '\0')])
28341 .filter_map(move |(index, codepoint)| {
28342 let prev_index = std::mem::replace(&mut prev_index, index);
28343 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28344 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28345 None
28346 } else {
28347 let chunk = &text[prev_index..]; // go to end of string
28348 Some(chunk)
28349 }
28350 })
28351}
28352
28353pub trait RangeToAnchorExt: Sized {
28354 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28355
28356 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28357 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28358 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28359 }
28360}
28361
28362impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28363 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28364 let start_offset = self.start.to_offset(snapshot);
28365 let end_offset = self.end.to_offset(snapshot);
28366 if start_offset == end_offset {
28367 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28368 } else {
28369 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28370 }
28371 }
28372}
28373
28374pub trait RowExt {
28375 fn as_f64(&self) -> f64;
28376
28377 fn next_row(&self) -> Self;
28378
28379 fn previous_row(&self) -> Self;
28380
28381 fn minus(&self, other: Self) -> u32;
28382}
28383
28384impl RowExt for DisplayRow {
28385 fn as_f64(&self) -> f64 {
28386 self.0 as _
28387 }
28388
28389 fn next_row(&self) -> Self {
28390 Self(self.0 + 1)
28391 }
28392
28393 fn previous_row(&self) -> Self {
28394 Self(self.0.saturating_sub(1))
28395 }
28396
28397 fn minus(&self, other: Self) -> u32 {
28398 self.0 - other.0
28399 }
28400}
28401
28402impl RowExt for MultiBufferRow {
28403 fn as_f64(&self) -> f64 {
28404 self.0 as _
28405 }
28406
28407 fn next_row(&self) -> Self {
28408 Self(self.0 + 1)
28409 }
28410
28411 fn previous_row(&self) -> Self {
28412 Self(self.0.saturating_sub(1))
28413 }
28414
28415 fn minus(&self, other: Self) -> u32 {
28416 self.0 - other.0
28417 }
28418}
28419
28420trait RowRangeExt {
28421 type Row;
28422
28423 fn len(&self) -> usize;
28424
28425 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28426}
28427
28428impl RowRangeExt for Range<MultiBufferRow> {
28429 type Row = MultiBufferRow;
28430
28431 fn len(&self) -> usize {
28432 (self.end.0 - self.start.0) as usize
28433 }
28434
28435 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28436 (self.start.0..self.end.0).map(MultiBufferRow)
28437 }
28438}
28439
28440impl RowRangeExt for Range<DisplayRow> {
28441 type Row = DisplayRow;
28442
28443 fn len(&self) -> usize {
28444 (self.end.0 - self.start.0) as usize
28445 }
28446
28447 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28448 (self.start.0..self.end.0).map(DisplayRow)
28449 }
28450}
28451
28452/// If select range has more than one line, we
28453/// just point the cursor to range.start.
28454fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28455 if range.start.row == range.end.row {
28456 range
28457 } else {
28458 range.start..range.start
28459 }
28460}
28461pub struct KillRing(ClipboardItem);
28462impl Global for KillRing {}
28463
28464const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28465
28466enum BreakpointPromptEditAction {
28467 Log,
28468 Condition,
28469 HitCondition,
28470}
28471
28472struct BreakpointPromptEditor {
28473 pub(crate) prompt: Entity<Editor>,
28474 editor: WeakEntity<Editor>,
28475 breakpoint_anchor: Anchor,
28476 breakpoint: Breakpoint,
28477 edit_action: BreakpointPromptEditAction,
28478 block_ids: HashSet<CustomBlockId>,
28479 editor_margins: Arc<Mutex<EditorMargins>>,
28480 _subscriptions: Vec<Subscription>,
28481}
28482
28483impl BreakpointPromptEditor {
28484 const MAX_LINES: u8 = 4;
28485
28486 fn new(
28487 editor: WeakEntity<Editor>,
28488 breakpoint_anchor: Anchor,
28489 breakpoint: Breakpoint,
28490 edit_action: BreakpointPromptEditAction,
28491 window: &mut Window,
28492 cx: &mut Context<Self>,
28493 ) -> Self {
28494 let base_text = match edit_action {
28495 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28496 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28497 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28498 }
28499 .map(|msg| msg.to_string())
28500 .unwrap_or_default();
28501
28502 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28503 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28504
28505 let prompt = cx.new(|cx| {
28506 let mut prompt = Editor::new(
28507 EditorMode::AutoHeight {
28508 min_lines: 1,
28509 max_lines: Some(Self::MAX_LINES as usize),
28510 },
28511 buffer,
28512 None,
28513 window,
28514 cx,
28515 );
28516 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28517 prompt.set_show_cursor_when_unfocused(false, cx);
28518 prompt.set_placeholder_text(
28519 match edit_action {
28520 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28521 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28522 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28523 },
28524 window,
28525 cx,
28526 );
28527
28528 prompt
28529 });
28530
28531 Self {
28532 prompt,
28533 editor,
28534 breakpoint_anchor,
28535 breakpoint,
28536 edit_action,
28537 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28538 block_ids: Default::default(),
28539 _subscriptions: vec![],
28540 }
28541 }
28542
28543 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28544 self.block_ids.extend(block_ids)
28545 }
28546
28547 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28548 if let Some(editor) = self.editor.upgrade() {
28549 let message = self
28550 .prompt
28551 .read(cx)
28552 .buffer
28553 .read(cx)
28554 .as_singleton()
28555 .expect("A multi buffer in breakpoint prompt isn't possible")
28556 .read(cx)
28557 .as_rope()
28558 .to_string();
28559
28560 editor.update(cx, |editor, cx| {
28561 editor.edit_breakpoint_at_anchor(
28562 self.breakpoint_anchor,
28563 self.breakpoint.clone(),
28564 match self.edit_action {
28565 BreakpointPromptEditAction::Log => {
28566 BreakpointEditAction::EditLogMessage(message.into())
28567 }
28568 BreakpointPromptEditAction::Condition => {
28569 BreakpointEditAction::EditCondition(message.into())
28570 }
28571 BreakpointPromptEditAction::HitCondition => {
28572 BreakpointEditAction::EditHitCondition(message.into())
28573 }
28574 },
28575 cx,
28576 );
28577
28578 editor.remove_blocks(self.block_ids.clone(), None, cx);
28579 cx.focus_self(window);
28580 });
28581 }
28582 }
28583
28584 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28585 self.editor
28586 .update(cx, |editor, cx| {
28587 editor.remove_blocks(self.block_ids.clone(), None, cx);
28588 window.focus(&editor.focus_handle, cx);
28589 })
28590 .log_err();
28591 }
28592
28593 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28594 let settings = ThemeSettings::get_global(cx);
28595 let text_style = TextStyle {
28596 color: if self.prompt.read(cx).read_only(cx) {
28597 cx.theme().colors().text_disabled
28598 } else {
28599 cx.theme().colors().text
28600 },
28601 font_family: settings.buffer_font.family.clone(),
28602 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28603 font_size: settings.buffer_font_size(cx).into(),
28604 font_weight: settings.buffer_font.weight,
28605 line_height: relative(settings.buffer_line_height.value()),
28606 ..Default::default()
28607 };
28608 EditorElement::new(
28609 &self.prompt,
28610 EditorStyle {
28611 background: cx.theme().colors().editor_background,
28612 local_player: cx.theme().players().local(),
28613 text: text_style,
28614 ..Default::default()
28615 },
28616 )
28617 }
28618}
28619
28620impl Render for BreakpointPromptEditor {
28621 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28622 let editor_margins = *self.editor_margins.lock();
28623 let gutter_dimensions = editor_margins.gutter;
28624 h_flex()
28625 .key_context("Editor")
28626 .bg(cx.theme().colors().editor_background)
28627 .border_y_1()
28628 .border_color(cx.theme().status().info_border)
28629 .size_full()
28630 .py(window.line_height() / 2.5)
28631 .on_action(cx.listener(Self::confirm))
28632 .on_action(cx.listener(Self::cancel))
28633 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28634 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28635 }
28636}
28637
28638impl Focusable for BreakpointPromptEditor {
28639 fn focus_handle(&self, cx: &App) -> FocusHandle {
28640 self.prompt.focus_handle(cx)
28641 }
28642}
28643
28644fn all_edits_insertions_or_deletions(
28645 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28646 snapshot: &MultiBufferSnapshot,
28647) -> bool {
28648 let mut all_insertions = true;
28649 let mut all_deletions = true;
28650
28651 for (range, new_text) in edits.iter() {
28652 let range_is_empty = range.to_offset(snapshot).is_empty();
28653 let text_is_empty = new_text.is_empty();
28654
28655 if range_is_empty != text_is_empty {
28656 if range_is_empty {
28657 all_deletions = false;
28658 } else {
28659 all_insertions = false;
28660 }
28661 } else {
28662 return false;
28663 }
28664
28665 if !all_insertions && !all_deletions {
28666 return false;
28667 }
28668 }
28669 all_insertions || all_deletions
28670}
28671
28672struct MissingEditPredictionKeybindingTooltip;
28673
28674impl Render for MissingEditPredictionKeybindingTooltip {
28675 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28676 ui::tooltip_container(cx, |container, cx| {
28677 container
28678 .flex_shrink_0()
28679 .max_w_80()
28680 .min_h(rems_from_px(124.))
28681 .justify_between()
28682 .child(
28683 v_flex()
28684 .flex_1()
28685 .text_ui_sm(cx)
28686 .child(Label::new("Conflict with Accept Keybinding"))
28687 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
28688 )
28689 .child(
28690 h_flex()
28691 .pb_1()
28692 .gap_1()
28693 .items_end()
28694 .w_full()
28695 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
28696 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
28697 }))
28698 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
28699 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
28700 })),
28701 )
28702 })
28703 }
28704}
28705
28706#[derive(Debug, Clone, Copy, PartialEq)]
28707pub struct LineHighlight {
28708 pub background: Background,
28709 pub border: Option<gpui::Hsla>,
28710 pub include_gutter: bool,
28711 pub type_id: Option<TypeId>,
28712}
28713
28714struct LineManipulationResult {
28715 pub new_text: String,
28716 pub line_count_before: usize,
28717 pub line_count_after: usize,
28718}
28719
28720fn render_diff_hunk_controls(
28721 row: u32,
28722 status: &DiffHunkStatus,
28723 hunk_range: Range<Anchor>,
28724 is_created_file: bool,
28725 line_height: Pixels,
28726 editor: &Entity<Editor>,
28727 _window: &mut Window,
28728 cx: &mut App,
28729) -> AnyElement {
28730 h_flex()
28731 .h(line_height)
28732 .mr_1()
28733 .gap_1()
28734 .px_0p5()
28735 .pb_1()
28736 .border_x_1()
28737 .border_b_1()
28738 .border_color(cx.theme().colors().border_variant)
28739 .rounded_b_lg()
28740 .bg(cx.theme().colors().editor_background)
28741 .gap_1()
28742 .block_mouse_except_scroll()
28743 .shadow_md()
28744 .child(if status.has_secondary_hunk() {
28745 Button::new(("stage", row as u64), "Stage")
28746 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28747 .tooltip({
28748 let focus_handle = editor.focus_handle(cx);
28749 move |_window, cx| {
28750 Tooltip::for_action_in(
28751 "Stage Hunk",
28752 &::git::ToggleStaged,
28753 &focus_handle,
28754 cx,
28755 )
28756 }
28757 })
28758 .on_click({
28759 let editor = editor.clone();
28760 move |_event, _window, cx| {
28761 editor.update(cx, |editor, cx| {
28762 editor.stage_or_unstage_diff_hunks(
28763 true,
28764 vec![hunk_range.start..hunk_range.start],
28765 cx,
28766 );
28767 });
28768 }
28769 })
28770 } else {
28771 Button::new(("unstage", row as u64), "Unstage")
28772 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
28773 .tooltip({
28774 let focus_handle = editor.focus_handle(cx);
28775 move |_window, cx| {
28776 Tooltip::for_action_in(
28777 "Unstage Hunk",
28778 &::git::ToggleStaged,
28779 &focus_handle,
28780 cx,
28781 )
28782 }
28783 })
28784 .on_click({
28785 let editor = editor.clone();
28786 move |_event, _window, cx| {
28787 editor.update(cx, |editor, cx| {
28788 editor.stage_or_unstage_diff_hunks(
28789 false,
28790 vec![hunk_range.start..hunk_range.start],
28791 cx,
28792 );
28793 });
28794 }
28795 })
28796 })
28797 .child(
28798 Button::new(("restore", row as u64), "Restore")
28799 .tooltip({
28800 let focus_handle = editor.focus_handle(cx);
28801 move |_window, cx| {
28802 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
28803 }
28804 })
28805 .on_click({
28806 let editor = editor.clone();
28807 move |_event, window, cx| {
28808 editor.update(cx, |editor, cx| {
28809 let snapshot = editor.snapshot(window, cx);
28810 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
28811 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
28812 });
28813 }
28814 })
28815 .disabled(is_created_file),
28816 )
28817 .when(
28818 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
28819 |el| {
28820 el.child(
28821 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
28822 .shape(IconButtonShape::Square)
28823 .icon_size(IconSize::Small)
28824 // .disabled(!has_multiple_hunks)
28825 .tooltip({
28826 let focus_handle = editor.focus_handle(cx);
28827 move |_window, cx| {
28828 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
28829 }
28830 })
28831 .on_click({
28832 let editor = editor.clone();
28833 move |_event, window, cx| {
28834 editor.update(cx, |editor, cx| {
28835 let snapshot = editor.snapshot(window, cx);
28836 let position =
28837 hunk_range.end.to_point(&snapshot.buffer_snapshot());
28838 editor.go_to_hunk_before_or_after_position(
28839 &snapshot,
28840 position,
28841 Direction::Next,
28842 window,
28843 cx,
28844 );
28845 editor.expand_selected_diff_hunks(cx);
28846 });
28847 }
28848 }),
28849 )
28850 .child(
28851 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
28852 .shape(IconButtonShape::Square)
28853 .icon_size(IconSize::Small)
28854 // .disabled(!has_multiple_hunks)
28855 .tooltip({
28856 let focus_handle = editor.focus_handle(cx);
28857 move |_window, cx| {
28858 Tooltip::for_action_in(
28859 "Previous Hunk",
28860 &GoToPreviousHunk,
28861 &focus_handle,
28862 cx,
28863 )
28864 }
28865 })
28866 .on_click({
28867 let editor = editor.clone();
28868 move |_event, window, cx| {
28869 editor.update(cx, |editor, cx| {
28870 let snapshot = editor.snapshot(window, cx);
28871 let point =
28872 hunk_range.start.to_point(&snapshot.buffer_snapshot());
28873 editor.go_to_hunk_before_or_after_position(
28874 &snapshot,
28875 point,
28876 Direction::Prev,
28877 window,
28878 cx,
28879 );
28880 editor.expand_selected_diff_hunks(cx);
28881 });
28882 }
28883 }),
28884 )
28885 },
28886 )
28887 .into_any_element()
28888}
28889
28890pub fn multibuffer_context_lines(cx: &App) -> u32 {
28891 EditorSettings::try_get(cx)
28892 .map(|settings| settings.excerpt_context_lines)
28893 .unwrap_or(2)
28894 .min(32)
28895}