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 document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod semantic_tokens;
42mod split;
43pub mod split_editor_view;
44pub mod tasks;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
137 TreeSitterOptions, WordsQuery,
138 language_settings::{
139 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
140 all_language_settings, language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::DB;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207 ThemeSettings, observe_buffer_font_size_adjustment,
208};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212};
213use ui_input::ErasedEditor;
214use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
215use workspace::{
216 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
217 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
218 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
219 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
220 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
221 searchable::SearchEvent,
222};
223use zed_actions::editor::{MoveDown, MoveUp};
224
225use crate::{
226 code_context_menus::CompletionsMenuSource,
227 editor_settings::MultiCursorModifier,
228 hover_links::{find_url, find_url_from_range},
229 inlays::{
230 InlineValueCache,
231 inlay_hints::{LspInlayHintData, inlay_hint_settings},
232 },
233 scroll::{ScrollOffset, ScrollPixelOffset},
234 selections_collection::resolve_selections_wrapping_blocks,
235 semantic_tokens::SemanticTokenState,
236 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
237};
238
239pub const FILE_HEADER_HEIGHT: u32 = 2;
240pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
241const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
242const MAX_LINE_LEN: usize = 1024;
243const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
244const MAX_SELECTION_HISTORY_LEN: usize = 1024;
245pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
246#[doc(hidden)]
247pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
248pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
249
250pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
251pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
252pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
253pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
254
255pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
256pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
257pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
258
259pub type RenderDiffHunkControlsFn = Arc<
260 dyn Fn(
261 u32,
262 &DiffHunkStatus,
263 Range<Anchor>,
264 bool,
265 Pixels,
266 &Entity<Editor>,
267 &mut Window,
268 &mut App,
269 ) -> AnyElement,
270>;
271
272enum ReportEditorEvent {
273 Saved { auto_saved: bool },
274 EditorOpened,
275 Closed,
276}
277
278impl ReportEditorEvent {
279 pub fn event_type(&self) -> &'static str {
280 match self {
281 Self::Saved { .. } => "Editor Saved",
282 Self::EditorOpened => "Editor Opened",
283 Self::Closed => "Editor Closed",
284 }
285 }
286}
287
288pub enum ActiveDebugLine {}
289pub enum DebugStackFrameLine {}
290
291pub enum ConflictsOuter {}
292pub enum ConflictsOurs {}
293pub enum ConflictsTheirs {}
294pub enum ConflictsOursMarker {}
295pub enum ConflictsTheirsMarker {}
296
297pub struct HunkAddedColor;
298pub struct HunkRemovedColor;
299
300#[derive(Debug, Copy, Clone, PartialEq, Eq)]
301pub enum Navigated {
302 Yes,
303 No,
304}
305
306impl Navigated {
307 pub fn from_bool(yes: bool) -> Navigated {
308 if yes { Navigated::Yes } else { Navigated::No }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Eq)]
313enum DisplayDiffHunk {
314 Folded {
315 display_row: DisplayRow,
316 },
317 Unfolded {
318 is_created_file: bool,
319 diff_base_byte_range: Range<usize>,
320 display_row_range: Range<DisplayRow>,
321 multi_buffer_range: Range<Anchor>,
322 status: DiffHunkStatus,
323 word_diffs: Vec<Range<MultiBufferOffset>>,
324 },
325}
326
327pub enum HideMouseCursorOrigin {
328 TypingAction,
329 MovementAction,
330}
331
332pub fn init(cx: &mut App) {
333 cx.set_global(GlobalBlameRenderer(Arc::new(())));
334 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
335
336 workspace::register_project_item::<Editor>(cx);
337 workspace::FollowableViewRegistry::register::<Editor>(cx);
338 workspace::register_serializable_item::<Editor>(cx);
339
340 cx.observe_new(
341 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
342 workspace.register_action(Editor::new_file);
343 workspace.register_action(Editor::new_file_split);
344 workspace.register_action(Editor::new_file_vertical);
345 workspace.register_action(Editor::new_file_horizontal);
346 workspace.register_action(Editor::cancel_language_server_work);
347 workspace.register_action(Editor::toggle_focus);
348 },
349 )
350 .detach();
351
352 cx.on_action(move |_: &workspace::NewFile, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 Editor::new_file(workspace, &Default::default(), window, cx)
361 },
362 )
363 .detach_and_log_err(cx);
364 }
365 })
366 .on_action(move |_: &workspace::NewWindow, cx| {
367 let app_state = workspace::AppState::global(cx);
368 if let Some(app_state) = app_state.upgrade() {
369 workspace::open_new(
370 Default::default(),
371 app_state,
372 cx,
373 |workspace, window, cx| {
374 cx.activate(true);
375 Editor::new_file(workspace, &Default::default(), window, cx)
376 },
377 )
378 .detach_and_log_err(cx);
379 }
380 });
381 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
382 Arc::new(ErasedEditorImpl(
383 cx.new(|cx| Editor::single_line(window, cx)),
384 )) as Arc<dyn ErasedEditor>
385 });
386 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 language_registry: Option<Arc<LanguageRegistry>>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 language_registry: Option<Arc<LanguageRegistry>>,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 mode: ColumnarMode,
452 goal_column: u32,
453 },
454 Extend {
455 position: DisplayPoint,
456 click_count: usize,
457 },
458 Update {
459 position: DisplayPoint,
460 goal_column: u32,
461 scroll_delta: gpui::Point<f32>,
462 },
463 End,
464}
465
466#[derive(Clone, Debug, PartialEq)]
467pub enum ColumnarMode {
468 FromMouse,
469 FromSelection,
470}
471
472#[derive(Clone, Debug)]
473pub enum SelectMode {
474 Character,
475 Word(Range<Anchor>),
476 Line(Range<Anchor>),
477 All,
478}
479
480#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
481pub enum SizingBehavior {
482 /// The editor will layout itself using `size_full` and will include the vertical
483 /// scroll margin as requested by user settings.
484 #[default]
485 Default,
486 /// The editor will layout itself using `size_full`, but will not have any
487 /// vertical overscroll.
488 ExcludeOverscrollMargin,
489 /// The editor will request a vertical size according to its content and will be
490 /// layouted without a vertical scroll margin.
491 SizeByContent,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// Determines the sizing behavior for this editor
507 sizing_behavior: SizingBehavior,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sizing_behavior: SizingBehavior::Default,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
596 let show_background = language_settings::language_settings(None, None, cx)
597 .inlay_hints
598 .show_background;
599
600 let mut style = cx.theme().syntax().get("hint");
601
602 if style.color.is_none() {
603 style.color = Some(cx.theme().status().hint);
604 }
605
606 if !show_background {
607 style.background_color = None;
608 return style;
609 }
610
611 if style.background_color.is_none() {
612 style.background_color = Some(cx.theme().status().hint_background);
613 }
614
615 style
616}
617
618pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
619 EditPredictionStyles {
620 insertion: HighlightStyle {
621 color: Some(cx.theme().status().predictive),
622 ..HighlightStyle::default()
623 },
624 whitespace: HighlightStyle {
625 background_color: Some(cx.theme().status().created_background),
626 ..HighlightStyle::default()
627 },
628 }
629}
630
631type CompletionId = usize;
632
633pub(crate) enum EditDisplayMode {
634 TabAccept,
635 DiffPopover,
636 Inline,
637}
638
639enum EditPrediction {
640 Edit {
641 edits: Vec<(Range<Anchor>, Arc<str>)>,
642 /// Predicted cursor position as (anchor, offset_from_anchor).
643 /// The anchor is in multibuffer coordinates; after applying edits,
644 /// resolve the anchor and add the offset to get the final cursor position.
645 cursor_position: Option<(Anchor, usize)>,
646 edit_preview: Option<EditPreview>,
647 display_mode: EditDisplayMode,
648 snapshot: BufferSnapshot,
649 },
650 /// Move to a specific location in the active editor
651 MoveWithin {
652 target: Anchor,
653 snapshot: BufferSnapshot,
654 },
655 /// Move to a specific location in a different editor (not the active one)
656 MoveOutside {
657 target: language::Anchor,
658 snapshot: BufferSnapshot,
659 },
660}
661
662struct EditPredictionState {
663 inlay_ids: Vec<InlayId>,
664 completion: EditPrediction,
665 completion_id: Option<SharedString>,
666 invalidation_range: Option<Range<Anchor>>,
667}
668
669enum EditPredictionSettings {
670 Disabled,
671 Enabled {
672 show_in_menu: bool,
673 preview_requires_modifier: bool,
674 },
675}
676
677#[derive(Debug, Clone)]
678struct InlineDiagnostic {
679 message: SharedString,
680 group_id: usize,
681 is_primary: bool,
682 start: Point,
683 severity: lsp::DiagnosticSeverity,
684}
685
686pub enum MenuEditPredictionsPolicy {
687 Never,
688 ByProvider,
689}
690
691pub enum EditPredictionPreview {
692 /// Modifier is not pressed
693 Inactive { released_too_fast: bool },
694 /// Modifier pressed
695 Active {
696 since: Instant,
697 previous_scroll_position: Option<SharedScrollAnchor>,
698 },
699}
700
701impl EditPredictionPreview {
702 pub fn released_too_fast(&self) -> bool {
703 match self {
704 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
705 EditPredictionPreview::Active { .. } => false,
706 }
707 }
708
709 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
710 if let EditPredictionPreview::Active {
711 previous_scroll_position,
712 ..
713 } = self
714 {
715 *previous_scroll_position = scroll_position;
716 }
717 }
718}
719
720pub struct ContextMenuOptions {
721 pub min_entries_visible: usize,
722 pub max_entries_visible: usize,
723 pub placement: Option<ContextMenuPlacement>,
724}
725
726#[derive(Debug, Clone, PartialEq, Eq)]
727pub enum ContextMenuPlacement {
728 Above,
729 Below,
730}
731
732#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
733struct EditorActionId(usize);
734
735impl EditorActionId {
736 pub fn post_inc(&mut self) -> Self {
737 let answer = self.0;
738
739 *self = Self(answer + 1);
740
741 Self(answer)
742 }
743}
744
745// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
746// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
747
748type BackgroundHighlight = (
749 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
750 Arc<[Range<Anchor>]>,
751);
752type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
753
754#[derive(Default)]
755struct ScrollbarMarkerState {
756 scrollbar_size: Size<Pixels>,
757 dirty: bool,
758 markers: Arc<[PaintQuad]>,
759 pending_refresh: Option<Task<Result<()>>>,
760}
761
762impl ScrollbarMarkerState {
763 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
764 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
765 }
766}
767
768#[derive(Clone, Copy, PartialEq, Eq)]
769pub enum MinimapVisibility {
770 Disabled,
771 Enabled {
772 /// The configuration currently present in the users settings.
773 setting_configuration: bool,
774 /// Whether to override the currently set visibility from the users setting.
775 toggle_override: bool,
776 },
777}
778
779impl MinimapVisibility {
780 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
781 if mode.is_full() {
782 Self::Enabled {
783 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
784 toggle_override: false,
785 }
786 } else {
787 Self::Disabled
788 }
789 }
790
791 fn hidden(&self) -> Self {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => Self::Enabled {
797 setting_configuration,
798 toggle_override: setting_configuration,
799 },
800 Self::Disabled => Self::Disabled,
801 }
802 }
803
804 fn disabled(&self) -> bool {
805 matches!(*self, Self::Disabled)
806 }
807
808 fn settings_visibility(&self) -> bool {
809 match *self {
810 Self::Enabled {
811 setting_configuration,
812 ..
813 } => setting_configuration,
814 _ => false,
815 }
816 }
817
818 fn visible(&self) -> bool {
819 match *self {
820 Self::Enabled {
821 setting_configuration,
822 toggle_override,
823 } => setting_configuration ^ toggle_override,
824 _ => false,
825 }
826 }
827
828 fn toggle_visibility(&self) -> Self {
829 match *self {
830 Self::Enabled {
831 toggle_override,
832 setting_configuration,
833 } => Self::Enabled {
834 setting_configuration,
835 toggle_override: !toggle_override,
836 },
837 Self::Disabled => Self::Disabled,
838 }
839 }
840}
841
842#[derive(Debug, Clone, Copy, PartialEq, Eq)]
843pub enum BufferSerialization {
844 All,
845 NonDirtyBuffers,
846}
847
848impl BufferSerialization {
849 fn new(restore_unsaved_buffers: bool) -> Self {
850 if restore_unsaved_buffers {
851 Self::All
852 } else {
853 Self::NonDirtyBuffers
854 }
855 }
856}
857
858#[derive(Clone, Debug)]
859struct RunnableTasks {
860 templates: Vec<(TaskSourceKind, TaskTemplate)>,
861 offset: multi_buffer::Anchor,
862 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
863 column: u32,
864 // Values of all named captures, including those starting with '_'
865 extra_variables: HashMap<String, String>,
866 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
867 context_range: Range<BufferOffset>,
868}
869
870impl RunnableTasks {
871 fn resolve<'a>(
872 &'a self,
873 cx: &'a task::TaskContext,
874 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
875 self.templates.iter().filter_map(|(kind, template)| {
876 template
877 .resolve_task(&kind.to_id_base(), cx)
878 .map(|task| (kind.clone(), task))
879 })
880 }
881}
882
883#[derive(Clone)]
884pub struct ResolvedTasks {
885 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
886 position: Anchor,
887}
888
889/// Addons allow storing per-editor state in other crates (e.g. Vim)
890pub trait Addon: 'static {
891 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
892
893 fn render_buffer_header_controls(
894 &self,
895 _: &ExcerptInfo,
896 _: &Window,
897 _: &App,
898 ) -> Option<AnyElement> {
899 None
900 }
901
902 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
903 None
904 }
905
906 fn to_any(&self) -> &dyn std::any::Any;
907
908 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
909 None
910 }
911}
912
913struct ChangeLocation {
914 current: Option<Vec<Anchor>>,
915 original: Vec<Anchor>,
916}
917impl ChangeLocation {
918 fn locations(&self) -> &[Anchor] {
919 self.current.as_ref().unwrap_or(&self.original)
920 }
921}
922
923/// A set of caret positions, registered when the editor was edited.
924pub struct ChangeList {
925 changes: Vec<ChangeLocation>,
926 /// Currently "selected" change.
927 position: Option<usize>,
928}
929
930impl ChangeList {
931 pub fn new() -> Self {
932 Self {
933 changes: Vec::new(),
934 position: None,
935 }
936 }
937
938 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
939 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
940 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
941 if self.changes.is_empty() {
942 return None;
943 }
944
945 let prev = self.position.unwrap_or(self.changes.len());
946 let next = if direction == Direction::Prev {
947 prev.saturating_sub(count)
948 } else {
949 (prev + count).min(self.changes.len() - 1)
950 };
951 self.position = Some(next);
952 self.changes.get(next).map(|change| change.locations())
953 }
954
955 /// Adds a new change to the list, resetting the change list position.
956 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
957 self.position.take();
958 if let Some(last) = self.changes.last_mut()
959 && group
960 {
961 last.current = Some(new_positions)
962 } else {
963 self.changes.push(ChangeLocation {
964 original: new_positions,
965 current: None,
966 });
967 }
968 }
969
970 pub fn last(&self) -> Option<&[Anchor]> {
971 self.changes.last().map(|change| change.locations())
972 }
973
974 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
975 self.changes.last().map(|change| change.original.as_slice())
976 }
977
978 pub fn invert_last_group(&mut self) {
979 if let Some(last) = self.changes.last_mut()
980 && let Some(current) = last.current.as_mut()
981 {
982 mem::swap(&mut last.original, current);
983 }
984 }
985}
986
987#[derive(Clone)]
988struct InlineBlamePopoverState {
989 scroll_handle: ScrollHandle,
990 commit_message: Option<ParsedCommitMessage>,
991 markdown: Entity<Markdown>,
992}
993
994struct InlineBlamePopover {
995 position: gpui::Point<Pixels>,
996 hide_task: Option<Task<()>>,
997 popover_bounds: Option<Bounds<Pixels>>,
998 popover_state: InlineBlamePopoverState,
999 keyboard_grace: bool,
1000}
1001
1002enum SelectionDragState {
1003 /// State when no drag related activity is detected.
1004 None,
1005 /// State when the mouse is down on a selection that is about to be dragged.
1006 ReadyToDrag {
1007 selection: Selection<Anchor>,
1008 click_position: gpui::Point<Pixels>,
1009 mouse_down_time: Instant,
1010 },
1011 /// State when the mouse is dragging the selection in the editor.
1012 Dragging {
1013 selection: Selection<Anchor>,
1014 drop_cursor: Selection<Anchor>,
1015 hide_drop_cursor: bool,
1016 },
1017}
1018
1019enum ColumnarSelectionState {
1020 FromMouse {
1021 selection_tail: Anchor,
1022 display_point: Option<DisplayPoint>,
1023 },
1024 FromSelection {
1025 selection_tail: Anchor,
1026 },
1027}
1028
1029/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1030/// a breakpoint on them.
1031#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1032struct PhantomBreakpointIndicator {
1033 display_row: DisplayRow,
1034 /// There's a small debounce between hovering over the line and showing the indicator.
1035 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1036 is_active: bool,
1037 collides_with_existing_breakpoint: bool,
1038}
1039
1040/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1041/// in diff view mode.
1042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1043pub(crate) struct PhantomDiffReviewIndicator {
1044 /// The starting anchor of the selection (or the only row if not dragging).
1045 pub start: Anchor,
1046 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1047 pub end: Anchor,
1048 /// There's a small debounce between hovering over the line and showing the indicator.
1049 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1050 pub is_active: bool,
1051}
1052
1053#[derive(Clone, Debug)]
1054pub(crate) struct DiffReviewDragState {
1055 pub start_anchor: Anchor,
1056 pub current_anchor: Anchor,
1057}
1058
1059impl DiffReviewDragState {
1060 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1061 let start = self.start_anchor.to_display_point(snapshot).row();
1062 let current = self.current_anchor.to_display_point(snapshot).row();
1063
1064 (start..=current).sorted()
1065 }
1066}
1067
1068/// Identifies a specific hunk in the diff buffer.
1069/// Used as a key to group comments by their location.
1070#[derive(Clone, Debug)]
1071pub struct DiffHunkKey {
1072 /// The file path (relative to worktree) this hunk belongs to.
1073 pub file_path: Arc<util::rel_path::RelPath>,
1074 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1075 pub hunk_start_anchor: Anchor,
1076}
1077
1078/// A review comment stored locally before being sent to the Agent panel.
1079#[derive(Clone)]
1080pub struct StoredReviewComment {
1081 /// Unique identifier for this comment (for edit/delete operations).
1082 pub id: usize,
1083 /// The comment text entered by the user.
1084 pub comment: String,
1085 /// Anchors for the code range being reviewed.
1086 pub range: Range<Anchor>,
1087 /// Timestamp when the comment was created (for chronological ordering).
1088 pub created_at: Instant,
1089 /// Whether this comment is currently being edited inline.
1090 pub is_editing: bool,
1091}
1092
1093impl StoredReviewComment {
1094 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1095 Self {
1096 id,
1097 comment,
1098 range: anchor_range,
1099 created_at: Instant::now(),
1100 is_editing: false,
1101 }
1102 }
1103}
1104
1105/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1106pub(crate) struct DiffReviewOverlay {
1107 pub anchor_range: Range<Anchor>,
1108 /// The block ID for the overlay.
1109 pub block_id: CustomBlockId,
1110 /// The editor entity for the review input.
1111 pub prompt_editor: Entity<Editor>,
1112 /// The hunk key this overlay belongs to.
1113 pub hunk_key: DiffHunkKey,
1114 /// Whether the comments section is expanded.
1115 pub comments_expanded: bool,
1116 /// Editors for comments currently being edited inline.
1117 /// Key: comment ID, Value: Editor entity for inline editing.
1118 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1119 /// Subscriptions for inline edit editors' action handlers.
1120 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1121 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1122 /// The current user's avatar URI for display in comment rows.
1123 pub user_avatar_uri: Option<SharedUri>,
1124 /// Subscription to keep the action handler alive.
1125 _subscription: Subscription,
1126}
1127
1128/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1129///
1130/// See the [module level documentation](self) for more information.
1131pub struct Editor {
1132 focus_handle: FocusHandle,
1133 last_focused_descendant: Option<WeakFocusHandle>,
1134 /// The text buffer being edited
1135 buffer: Entity<MultiBuffer>,
1136 /// Map of how text in the buffer should be displayed.
1137 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1138 pub display_map: Entity<DisplayMap>,
1139 placeholder_display_map: Option<Entity<DisplayMap>>,
1140 pub selections: SelectionsCollection,
1141 pub scroll_manager: ScrollManager,
1142 /// When inline assist editors are linked, they all render cursors because
1143 /// typing enters text into each of them, even the ones that aren't focused.
1144 pub(crate) show_cursor_when_unfocused: bool,
1145 columnar_selection_state: Option<ColumnarSelectionState>,
1146 add_selections_state: Option<AddSelectionsState>,
1147 select_next_state: Option<SelectNextState>,
1148 select_prev_state: Option<SelectNextState>,
1149 selection_history: SelectionHistory,
1150 defer_selection_effects: bool,
1151 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1152 autoclose_regions: Vec<AutocloseRegion>,
1153 snippet_stack: InvalidationStack<SnippetState>,
1154 select_syntax_node_history: SelectSyntaxNodeHistory,
1155 ime_transaction: Option<TransactionId>,
1156 pub diagnostics_max_severity: DiagnosticSeverity,
1157 active_diagnostics: ActiveDiagnostic,
1158 show_inline_diagnostics: bool,
1159 inline_diagnostics_update: Task<()>,
1160 inline_diagnostics_enabled: bool,
1161 diagnostics_enabled: bool,
1162 word_completions_enabled: bool,
1163 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1164 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1165 hard_wrap: Option<usize>,
1166 project: Option<Entity<Project>>,
1167 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1168 completion_provider: Option<Rc<dyn CompletionProvider>>,
1169 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1170 blink_manager: Entity<BlinkManager>,
1171 show_cursor_names: bool,
1172 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1173 pub show_local_selections: bool,
1174 mode: EditorMode,
1175 show_breadcrumbs: bool,
1176 show_gutter: bool,
1177 show_scrollbars: ScrollbarAxes,
1178 minimap_visibility: MinimapVisibility,
1179 offset_content: bool,
1180 disable_expand_excerpt_buttons: bool,
1181 delegate_expand_excerpts: bool,
1182 delegate_stage_and_restore: bool,
1183 delegate_open_excerpts: bool,
1184 enable_lsp_data: bool,
1185 enable_runnables: bool,
1186 show_line_numbers: Option<bool>,
1187 use_relative_line_numbers: Option<bool>,
1188 show_git_diff_gutter: Option<bool>,
1189 show_code_actions: Option<bool>,
1190 show_runnables: Option<bool>,
1191 show_breakpoints: Option<bool>,
1192 show_diff_review_button: bool,
1193 show_wrap_guides: Option<bool>,
1194 show_indent_guides: Option<bool>,
1195 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1196 highlight_order: usize,
1197 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1198 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1199 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1200 scrollbar_marker_state: ScrollbarMarkerState,
1201 active_indent_guides_state: ActiveIndentGuidesState,
1202 nav_history: Option<ItemNavHistory>,
1203 context_menu: RefCell<Option<CodeContextMenu>>,
1204 context_menu_options: Option<ContextMenuOptions>,
1205 mouse_context_menu: Option<MouseContextMenu>,
1206 completion_tasks: Vec<(CompletionId, Task<()>)>,
1207 inline_blame_popover: Option<InlineBlamePopover>,
1208 inline_blame_popover_show_task: Option<Task<()>>,
1209 signature_help_state: SignatureHelpState,
1210 auto_signature_help: Option<bool>,
1211 find_all_references_task_sources: Vec<Anchor>,
1212 next_completion_id: CompletionId,
1213 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1214 code_actions_task: Option<Task<Result<()>>>,
1215 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1216 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_complete: bool,
1218 document_highlights_task: Option<Task<()>>,
1219 linked_editing_range_task: Option<Task<Option<()>>>,
1220 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1221 pending_rename: Option<RenameState>,
1222 searchable: bool,
1223 cursor_shape: CursorShape,
1224 /// Whether the cursor is offset one character to the left when something is
1225 /// selected (needed for vim visual mode)
1226 cursor_offset_on_selection: bool,
1227 current_line_highlight: Option<CurrentLineHighlight>,
1228 /// Whether to collapse search match ranges to just their start position.
1229 /// When true, navigating to a match positions the cursor at the match
1230 /// without selecting the matched text.
1231 collapse_matches: bool,
1232 autoindent_mode: Option<AutoindentMode>,
1233 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1234 input_enabled: bool,
1235 use_modal_editing: bool,
1236 read_only: bool,
1237 leader_id: Option<CollaboratorId>,
1238 remote_id: Option<ViewId>,
1239 pub hover_state: HoverState,
1240 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1241 prev_pressure_stage: Option<PressureStage>,
1242 gutter_hovered: bool,
1243 hovered_link_state: Option<HoveredLinkState>,
1244 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1245 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1246 active_edit_prediction: Option<EditPredictionState>,
1247 /// Used to prevent flickering as the user types while the menu is open
1248 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1249 edit_prediction_settings: EditPredictionSettings,
1250 edit_predictions_hidden_for_vim_mode: bool,
1251 show_edit_predictions_override: Option<bool>,
1252 show_completions_on_input_override: Option<bool>,
1253 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1254 edit_prediction_preview: EditPredictionPreview,
1255 edit_prediction_indent_conflict: bool,
1256 edit_prediction_requires_modifier_in_indent_conflict: bool,
1257 next_inlay_id: usize,
1258 next_color_inlay_id: usize,
1259 _subscriptions: Vec<Subscription>,
1260 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1261 gutter_dimensions: GutterDimensions,
1262 style: Option<EditorStyle>,
1263 text_style_refinement: Option<TextStyleRefinement>,
1264 next_editor_action_id: EditorActionId,
1265 editor_actions: Rc<
1266 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1267 >,
1268 use_autoclose: bool,
1269 use_auto_surround: bool,
1270 auto_replace_emoji_shortcode: bool,
1271 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1272 show_git_blame_gutter: bool,
1273 show_git_blame_inline: bool,
1274 show_git_blame_inline_delay_task: Option<Task<()>>,
1275 git_blame_inline_enabled: bool,
1276 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1277 buffer_serialization: Option<BufferSerialization>,
1278 show_selection_menu: Option<bool>,
1279 blame: Option<Entity<GitBlame>>,
1280 blame_subscription: Option<Subscription>,
1281 custom_context_menu: Option<
1282 Box<
1283 dyn 'static
1284 + Fn(
1285 &mut Self,
1286 DisplayPoint,
1287 &mut Window,
1288 &mut Context<Self>,
1289 ) -> Option<Entity<ui::ContextMenu>>,
1290 >,
1291 >,
1292 last_bounds: Option<Bounds<Pixels>>,
1293 last_position_map: Option<Rc<PositionMap>>,
1294 expect_bounds_change: Option<Bounds<Pixels>>,
1295 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1296 tasks_update_task: Option<Task<()>>,
1297 breakpoint_store: Option<Entity<BreakpointStore>>,
1298 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1299 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1300 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1301 /// Active diff review overlays. Multiple overlays can be open simultaneously
1302 /// when hunks have comments stored.
1303 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1304 /// Stored review comments grouped by hunk.
1305 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1306 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1307 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1308 /// Counter for generating unique comment IDs.
1309 next_review_comment_id: usize,
1310 hovered_diff_hunk_row: Option<DisplayRow>,
1311 pull_diagnostics_task: Task<()>,
1312 in_project_search: bool,
1313 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1314 breadcrumb_header: Option<String>,
1315 focused_block: Option<FocusedBlock>,
1316 next_scroll_position: NextScrollCursorCenterTopBottom,
1317 addons: HashMap<TypeId, Box<dyn Addon>>,
1318 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1319 load_diff_task: Option<Shared<Task<()>>>,
1320 /// Whether we are temporarily displaying a diff other than git's
1321 temporary_diff_override: bool,
1322 selection_mark_mode: bool,
1323 toggle_fold_multiple_buffers: Task<()>,
1324 _scroll_cursor_center_top_bottom_task: Task<()>,
1325 serialize_selections: Task<()>,
1326 serialize_folds: Task<()>,
1327 mouse_cursor_hidden: bool,
1328 minimap: Option<Entity<Self>>,
1329 hide_mouse_mode: HideMouseMode,
1330 pub change_list: ChangeList,
1331 inline_value_cache: InlineValueCache,
1332 number_deleted_lines: bool,
1333
1334 selection_drag_state: SelectionDragState,
1335 colors: Option<LspColorData>,
1336 post_scroll_update: Task<()>,
1337 refresh_colors_task: Task<()>,
1338 use_document_folding_ranges: bool,
1339 refresh_folding_ranges_task: Task<()>,
1340 inlay_hints: Option<LspInlayHintData>,
1341 folding_newlines: Task<()>,
1342 select_next_is_case_sensitive: Option<bool>,
1343 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1344 on_local_selections_changed:
1345 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1346 suppress_selection_callback: bool,
1347 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1348 accent_data: Option<AccentData>,
1349 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1350 semantic_token_state: SemanticTokenState,
1351 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1352 refresh_document_symbols_task: Shared<Task<()>>,
1353 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1354 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1355 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1356 sticky_headers_task: Task<()>,
1357 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1358}
1359
1360#[derive(Debug, PartialEq)]
1361struct AccentData {
1362 colors: AccentColors,
1363 overrides: Vec<SharedString>,
1364}
1365
1366fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1367 if debounce_ms > 0 {
1368 Some(Duration::from_millis(debounce_ms))
1369 } else {
1370 None
1371 }
1372}
1373
1374#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1375enum NextScrollCursorCenterTopBottom {
1376 #[default]
1377 Center,
1378 Top,
1379 Bottom,
1380}
1381
1382impl NextScrollCursorCenterTopBottom {
1383 fn next(&self) -> Self {
1384 match self {
1385 Self::Center => Self::Top,
1386 Self::Top => Self::Bottom,
1387 Self::Bottom => Self::Center,
1388 }
1389 }
1390}
1391
1392#[derive(Clone)]
1393pub struct EditorSnapshot {
1394 pub mode: EditorMode,
1395 show_gutter: bool,
1396 offset_content: bool,
1397 show_line_numbers: Option<bool>,
1398 number_deleted_lines: bool,
1399 show_git_diff_gutter: Option<bool>,
1400 show_code_actions: Option<bool>,
1401 show_runnables: Option<bool>,
1402 show_breakpoints: Option<bool>,
1403 git_blame_gutter_max_author_length: Option<usize>,
1404 pub display_snapshot: DisplaySnapshot,
1405 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1406 is_focused: bool,
1407 scroll_anchor: SharedScrollAnchor,
1408 ongoing_scroll: OngoingScroll,
1409 current_line_highlight: CurrentLineHighlight,
1410 gutter_hovered: bool,
1411 semantic_tokens_enabled: bool,
1412}
1413
1414#[derive(Default, Debug, Clone, Copy)]
1415pub struct GutterDimensions {
1416 pub left_padding: Pixels,
1417 pub right_padding: Pixels,
1418 pub width: Pixels,
1419 pub margin: Pixels,
1420 pub git_blame_entries_width: Option<Pixels>,
1421}
1422
1423impl GutterDimensions {
1424 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1425 Self {
1426 margin: Self::default_gutter_margin(font_id, font_size, cx),
1427 ..Default::default()
1428 }
1429 }
1430
1431 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1432 -cx.text_system().descent(font_id, font_size)
1433 }
1434 /// The full width of the space taken up by the gutter.
1435 pub fn full_width(&self) -> Pixels {
1436 self.margin + self.width
1437 }
1438
1439 /// The width of the space reserved for the fold indicators,
1440 /// use alongside 'justify_end' and `gutter_width` to
1441 /// right align content with the line numbers
1442 pub fn fold_area_width(&self) -> Pixels {
1443 self.margin + self.right_padding
1444 }
1445}
1446
1447struct CharacterDimensions {
1448 em_width: Pixels,
1449 em_advance: Pixels,
1450 line_height: Pixels,
1451}
1452
1453#[derive(Debug)]
1454pub struct RemoteSelection {
1455 pub replica_id: ReplicaId,
1456 pub selection: Selection<Anchor>,
1457 pub cursor_shape: CursorShape,
1458 pub collaborator_id: CollaboratorId,
1459 pub line_mode: bool,
1460 pub user_name: Option<SharedString>,
1461 pub color: PlayerColor,
1462}
1463
1464#[derive(Clone, Debug)]
1465struct SelectionHistoryEntry {
1466 selections: Arc<[Selection<Anchor>]>,
1467 select_next_state: Option<SelectNextState>,
1468 select_prev_state: Option<SelectNextState>,
1469 add_selections_state: Option<AddSelectionsState>,
1470}
1471
1472#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1473enum SelectionHistoryMode {
1474 #[default]
1475 Normal,
1476 Undoing,
1477 Redoing,
1478 Skipping,
1479}
1480
1481#[derive(Clone, PartialEq, Eq, Hash)]
1482struct HoveredCursor {
1483 replica_id: ReplicaId,
1484 selection_id: usize,
1485}
1486
1487#[derive(Debug)]
1488/// SelectionEffects controls the side-effects of updating the selection.
1489///
1490/// The default behaviour does "what you mostly want":
1491/// - it pushes to the nav history if the cursor moved by >10 lines
1492/// - it re-triggers completion requests
1493/// - it scrolls to fit
1494///
1495/// You might want to modify these behaviours. For example when doing a "jump"
1496/// like go to definition, we always want to add to nav history; but when scrolling
1497/// in vim mode we never do.
1498///
1499/// Similarly, you might want to disable scrolling if you don't want the viewport to
1500/// move.
1501#[derive(Clone)]
1502pub struct SelectionEffects {
1503 nav_history: Option<bool>,
1504 completions: bool,
1505 scroll: Option<Autoscroll>,
1506}
1507
1508impl Default for SelectionEffects {
1509 fn default() -> Self {
1510 Self {
1511 nav_history: None,
1512 completions: true,
1513 scroll: Some(Autoscroll::fit()),
1514 }
1515 }
1516}
1517impl SelectionEffects {
1518 pub fn scroll(scroll: Autoscroll) -> Self {
1519 Self {
1520 scroll: Some(scroll),
1521 ..Default::default()
1522 }
1523 }
1524
1525 pub fn no_scroll() -> Self {
1526 Self {
1527 scroll: None,
1528 ..Default::default()
1529 }
1530 }
1531
1532 pub fn completions(self, completions: bool) -> Self {
1533 Self {
1534 completions,
1535 ..self
1536 }
1537 }
1538
1539 pub fn nav_history(self, nav_history: bool) -> Self {
1540 Self {
1541 nav_history: Some(nav_history),
1542 ..self
1543 }
1544 }
1545}
1546
1547struct DeferredSelectionEffectsState {
1548 changed: bool,
1549 effects: SelectionEffects,
1550 old_cursor_position: Anchor,
1551 history_entry: SelectionHistoryEntry,
1552}
1553
1554#[derive(Default)]
1555struct SelectionHistory {
1556 #[allow(clippy::type_complexity)]
1557 selections_by_transaction:
1558 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1559 mode: SelectionHistoryMode,
1560 undo_stack: VecDeque<SelectionHistoryEntry>,
1561 redo_stack: VecDeque<SelectionHistoryEntry>,
1562}
1563
1564impl SelectionHistory {
1565 #[track_caller]
1566 fn insert_transaction(
1567 &mut self,
1568 transaction_id: TransactionId,
1569 selections: Arc<[Selection<Anchor>]>,
1570 ) {
1571 if selections.is_empty() {
1572 log::error!(
1573 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1574 std::panic::Location::caller()
1575 );
1576 return;
1577 }
1578 self.selections_by_transaction
1579 .insert(transaction_id, (selections, None));
1580 }
1581
1582 #[allow(clippy::type_complexity)]
1583 fn transaction(
1584 &self,
1585 transaction_id: TransactionId,
1586 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1587 self.selections_by_transaction.get(&transaction_id)
1588 }
1589
1590 #[allow(clippy::type_complexity)]
1591 fn transaction_mut(
1592 &mut self,
1593 transaction_id: TransactionId,
1594 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1595 self.selections_by_transaction.get_mut(&transaction_id)
1596 }
1597
1598 fn push(&mut self, entry: SelectionHistoryEntry) {
1599 if !entry.selections.is_empty() {
1600 match self.mode {
1601 SelectionHistoryMode::Normal => {
1602 self.push_undo(entry);
1603 self.redo_stack.clear();
1604 }
1605 SelectionHistoryMode::Undoing => self.push_redo(entry),
1606 SelectionHistoryMode::Redoing => self.push_undo(entry),
1607 SelectionHistoryMode::Skipping => {}
1608 }
1609 }
1610 }
1611
1612 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1613 if self
1614 .undo_stack
1615 .back()
1616 .is_none_or(|e| e.selections != entry.selections)
1617 {
1618 self.undo_stack.push_back(entry);
1619 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1620 self.undo_stack.pop_front();
1621 }
1622 }
1623 }
1624
1625 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1626 if self
1627 .redo_stack
1628 .back()
1629 .is_none_or(|e| e.selections != entry.selections)
1630 {
1631 self.redo_stack.push_back(entry);
1632 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1633 self.redo_stack.pop_front();
1634 }
1635 }
1636 }
1637}
1638
1639#[derive(Clone, Copy)]
1640pub struct RowHighlightOptions {
1641 pub autoscroll: bool,
1642 pub include_gutter: bool,
1643}
1644
1645impl Default for RowHighlightOptions {
1646 fn default() -> Self {
1647 Self {
1648 autoscroll: Default::default(),
1649 include_gutter: true,
1650 }
1651 }
1652}
1653
1654struct RowHighlight {
1655 index: usize,
1656 range: Range<Anchor>,
1657 color: Hsla,
1658 options: RowHighlightOptions,
1659 type_id: TypeId,
1660}
1661
1662#[derive(Clone, Debug)]
1663struct AddSelectionsState {
1664 groups: Vec<AddSelectionsGroup>,
1665}
1666
1667#[derive(Clone, Debug)]
1668struct AddSelectionsGroup {
1669 above: bool,
1670 stack: Vec<usize>,
1671}
1672
1673#[derive(Clone)]
1674struct SelectNextState {
1675 query: AhoCorasick,
1676 wordwise: bool,
1677 done: bool,
1678}
1679
1680impl std::fmt::Debug for SelectNextState {
1681 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1682 f.debug_struct(std::any::type_name::<Self>())
1683 .field("wordwise", &self.wordwise)
1684 .field("done", &self.done)
1685 .finish()
1686 }
1687}
1688
1689#[derive(Debug)]
1690struct AutocloseRegion {
1691 selection_id: usize,
1692 range: Range<Anchor>,
1693 pair: BracketPair,
1694}
1695
1696#[derive(Debug)]
1697struct SnippetState {
1698 ranges: Vec<Vec<Range<Anchor>>>,
1699 active_index: usize,
1700 choices: Vec<Option<Vec<String>>>,
1701}
1702
1703#[doc(hidden)]
1704pub struct RenameState {
1705 pub range: Range<Anchor>,
1706 pub old_name: Arc<str>,
1707 pub editor: Entity<Editor>,
1708 block_id: CustomBlockId,
1709}
1710
1711struct InvalidationStack<T>(Vec<T>);
1712
1713struct RegisteredEditPredictionDelegate {
1714 provider: Arc<dyn EditPredictionDelegateHandle>,
1715 _subscription: Subscription,
1716}
1717
1718#[derive(Debug, PartialEq, Eq)]
1719pub struct ActiveDiagnosticGroup {
1720 pub active_range: Range<Anchor>,
1721 pub active_message: String,
1722 pub group_id: usize,
1723 pub blocks: HashSet<CustomBlockId>,
1724}
1725
1726#[derive(Debug, PartialEq, Eq)]
1727
1728pub(crate) enum ActiveDiagnostic {
1729 None,
1730 All,
1731 Group(ActiveDiagnosticGroup),
1732}
1733
1734#[derive(Serialize, Deserialize, Clone, Debug)]
1735pub struct ClipboardSelection {
1736 /// The number of bytes in this selection.
1737 pub len: usize,
1738 /// Whether this was a full-line selection.
1739 pub is_entire_line: bool,
1740 /// The indentation of the first line when this content was originally copied.
1741 pub first_line_indent: u32,
1742 #[serde(default)]
1743 pub file_path: Option<PathBuf>,
1744 #[serde(default)]
1745 pub line_range: Option<RangeInclusive<u32>>,
1746}
1747
1748impl ClipboardSelection {
1749 pub fn for_buffer(
1750 len: usize,
1751 is_entire_line: bool,
1752 range: Range<Point>,
1753 buffer: &MultiBufferSnapshot,
1754 project: Option<&Entity<Project>>,
1755 cx: &App,
1756 ) -> Self {
1757 let first_line_indent = buffer
1758 .indent_size_for_line(MultiBufferRow(range.start.row))
1759 .len;
1760
1761 let file_path = util::maybe!({
1762 let project = project?.read(cx);
1763 let file = buffer.file_at(range.start)?;
1764 let project_path = ProjectPath {
1765 worktree_id: file.worktree_id(cx),
1766 path: file.path().clone(),
1767 };
1768 project.absolute_path(&project_path, cx)
1769 });
1770
1771 let line_range = file_path.as_ref().and_then(|_| {
1772 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1773 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1774 if start_excerpt_id == end_excerpt_id {
1775 Some(start_point.row..=end_point.row)
1776 } else {
1777 None
1778 }
1779 });
1780
1781 Self {
1782 len,
1783 is_entire_line,
1784 first_line_indent,
1785 file_path,
1786 line_range,
1787 }
1788 }
1789}
1790
1791// selections, scroll behavior, was newest selection reversed
1792type SelectSyntaxNodeHistoryState = (
1793 Box<[Selection<Anchor>]>,
1794 SelectSyntaxNodeScrollBehavior,
1795 bool,
1796);
1797
1798#[derive(Default)]
1799struct SelectSyntaxNodeHistory {
1800 stack: Vec<SelectSyntaxNodeHistoryState>,
1801 // disable temporarily to allow changing selections without losing the stack
1802 pub disable_clearing: bool,
1803}
1804
1805impl SelectSyntaxNodeHistory {
1806 pub fn try_clear(&mut self) {
1807 if !self.disable_clearing {
1808 self.stack.clear();
1809 }
1810 }
1811
1812 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1813 self.stack.push(selection);
1814 }
1815
1816 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1817 self.stack.pop()
1818 }
1819}
1820
1821enum SelectSyntaxNodeScrollBehavior {
1822 CursorTop,
1823 FitSelection,
1824 CursorBottom,
1825}
1826
1827#[derive(Debug, Clone, Copy)]
1828pub(crate) struct NavigationData {
1829 cursor_anchor: Anchor,
1830 cursor_position: Point,
1831 scroll_anchor: ScrollAnchor,
1832 scroll_top_row: u32,
1833}
1834
1835#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1836pub enum GotoDefinitionKind {
1837 Symbol,
1838 Declaration,
1839 Type,
1840 Implementation,
1841}
1842
1843pub enum FormatTarget {
1844 Buffers(HashSet<Entity<Buffer>>),
1845 Ranges(Vec<Range<MultiBufferPoint>>),
1846}
1847
1848pub(crate) struct FocusedBlock {
1849 id: BlockId,
1850 focus_handle: WeakFocusHandle,
1851}
1852
1853#[derive(Clone, Debug)]
1854pub enum JumpData {
1855 MultiBufferRow {
1856 row: MultiBufferRow,
1857 line_offset_from_top: u32,
1858 },
1859 MultiBufferPoint {
1860 excerpt_id: ExcerptId,
1861 position: Point,
1862 anchor: text::Anchor,
1863 line_offset_from_top: u32,
1864 },
1865}
1866
1867pub enum MultibufferSelectionMode {
1868 First,
1869 All,
1870}
1871
1872#[derive(Clone, Copy, Debug, Default)]
1873pub struct RewrapOptions {
1874 pub override_language_settings: bool,
1875 pub preserve_existing_whitespace: bool,
1876}
1877
1878impl Editor {
1879 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1880 let buffer = cx.new(|cx| Buffer::local("", cx));
1881 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1882 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1883 }
1884
1885 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1886 let buffer = cx.new(|cx| Buffer::local("", cx));
1887 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1888 Self::new(EditorMode::full(), buffer, None, window, cx)
1889 }
1890
1891 pub fn auto_height(
1892 min_lines: usize,
1893 max_lines: usize,
1894 window: &mut Window,
1895 cx: &mut Context<Self>,
1896 ) -> Self {
1897 let buffer = cx.new(|cx| Buffer::local("", cx));
1898 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1899 Self::new(
1900 EditorMode::AutoHeight {
1901 min_lines,
1902 max_lines: Some(max_lines),
1903 },
1904 buffer,
1905 None,
1906 window,
1907 cx,
1908 )
1909 }
1910
1911 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1912 /// The editor grows as tall as needed to fit its content.
1913 pub fn auto_height_unbounded(
1914 min_lines: usize,
1915 window: &mut Window,
1916 cx: &mut Context<Self>,
1917 ) -> Self {
1918 let buffer = cx.new(|cx| Buffer::local("", cx));
1919 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1920 Self::new(
1921 EditorMode::AutoHeight {
1922 min_lines,
1923 max_lines: None,
1924 },
1925 buffer,
1926 None,
1927 window,
1928 cx,
1929 )
1930 }
1931
1932 pub fn for_buffer(
1933 buffer: Entity<Buffer>,
1934 project: Option<Entity<Project>>,
1935 window: &mut Window,
1936 cx: &mut Context<Self>,
1937 ) -> Self {
1938 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1939 Self::new(EditorMode::full(), buffer, project, window, cx)
1940 }
1941
1942 pub fn for_multibuffer(
1943 buffer: Entity<MultiBuffer>,
1944 project: Option<Entity<Project>>,
1945 window: &mut Window,
1946 cx: &mut Context<Self>,
1947 ) -> Self {
1948 Self::new(EditorMode::full(), buffer, project, window, cx)
1949 }
1950
1951 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1952 let mut clone = Self::new(
1953 self.mode.clone(),
1954 self.buffer.clone(),
1955 self.project.clone(),
1956 window,
1957 cx,
1958 );
1959 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1960 let snapshot = display_map.snapshot(cx);
1961 clone.display_map.update(cx, |display_map, cx| {
1962 display_map.set_state(&snapshot, cx);
1963 });
1964 snapshot
1965 });
1966 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1967 clone.folds_did_change(cx);
1968 clone.selections.clone_state(&self.selections);
1969 clone
1970 .scroll_manager
1971 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1972 clone.searchable = self.searchable;
1973 clone.read_only = self.read_only;
1974 clone
1975 }
1976
1977 pub fn new(
1978 mode: EditorMode,
1979 buffer: Entity<MultiBuffer>,
1980 project: Option<Entity<Project>>,
1981 window: &mut Window,
1982 cx: &mut Context<Self>,
1983 ) -> Self {
1984 Editor::new_internal(mode, buffer, project, None, window, cx)
1985 }
1986
1987 pub fn refresh_sticky_headers(
1988 &mut self,
1989 display_snapshot: &DisplaySnapshot,
1990 cx: &mut Context<Editor>,
1991 ) {
1992 if !self.mode.is_full() {
1993 return;
1994 }
1995 let multi_buffer = display_snapshot.buffer_snapshot();
1996 let scroll_anchor = self
1997 .scroll_manager
1998 .native_anchor(display_snapshot, cx)
1999 .anchor;
2000 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2001 return;
2002 };
2003 let buffer = buffer.clone();
2004
2005 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2006 let max_row = buffer.max_point().row;
2007 let start_row = buffer_visible_start.row.min(max_row);
2008 let end_row = (buffer_visible_start.row + 10).min(max_row);
2009
2010 let syntax = self.style(cx).syntax.clone();
2011 let background_task = cx.background_spawn(async move {
2012 buffer
2013 .outline_items_containing(
2014 Point::new(start_row, 0)..Point::new(end_row, 0),
2015 true,
2016 Some(syntax.as_ref()),
2017 )
2018 .into_iter()
2019 .map(|outline_item| OutlineItem {
2020 depth: outline_item.depth,
2021 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2022 source_range_for_text: Anchor::range_in_buffer(
2023 excerpt_id,
2024 outline_item.source_range_for_text,
2025 ),
2026 text: outline_item.text,
2027 highlight_ranges: outline_item.highlight_ranges,
2028 name_ranges: outline_item.name_ranges,
2029 body_range: outline_item
2030 .body_range
2031 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2032 annotation_range: outline_item
2033 .annotation_range
2034 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2035 })
2036 .collect()
2037 });
2038 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2039 let sticky_headers = background_task.await;
2040 this.update(cx, |this, cx| {
2041 this.sticky_headers = Some(sticky_headers);
2042 cx.notify();
2043 })
2044 .ok();
2045 });
2046 }
2047
2048 fn new_internal(
2049 mode: EditorMode,
2050 multi_buffer: Entity<MultiBuffer>,
2051 project: Option<Entity<Project>>,
2052 display_map: Option<Entity<DisplayMap>>,
2053 window: &mut Window,
2054 cx: &mut Context<Self>,
2055 ) -> Self {
2056 debug_assert!(
2057 display_map.is_none() || mode.is_minimap(),
2058 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2059 );
2060
2061 let full_mode = mode.is_full();
2062 let is_minimap = mode.is_minimap();
2063 let diagnostics_max_severity = if full_mode {
2064 EditorSettings::get_global(cx)
2065 .diagnostics_max_severity
2066 .unwrap_or(DiagnosticSeverity::Hint)
2067 } else {
2068 DiagnosticSeverity::Off
2069 };
2070 let style = window.text_style();
2071 let font_size = style.font_size.to_pixels(window.rem_size());
2072 let editor = cx.entity().downgrade();
2073 let fold_placeholder = FoldPlaceholder {
2074 constrain_width: false,
2075 render: Arc::new(move |fold_id, fold_range, cx| {
2076 let editor = editor.clone();
2077 FoldPlaceholder::fold_element(fold_id, cx)
2078 .cursor_pointer()
2079 .child("⋯")
2080 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2081 .on_click(move |_, _window, cx| {
2082 editor
2083 .update(cx, |editor, cx| {
2084 editor.unfold_ranges(
2085 &[fold_range.start..fold_range.end],
2086 true,
2087 false,
2088 cx,
2089 );
2090 cx.stop_propagation();
2091 })
2092 .ok();
2093 })
2094 .into_any()
2095 }),
2096 merge_adjacent: true,
2097 ..FoldPlaceholder::default()
2098 };
2099 let display_map = display_map.unwrap_or_else(|| {
2100 cx.new(|cx| {
2101 DisplayMap::new(
2102 multi_buffer.clone(),
2103 style.font(),
2104 font_size,
2105 None,
2106 FILE_HEADER_HEIGHT,
2107 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2108 fold_placeholder,
2109 diagnostics_max_severity,
2110 cx,
2111 )
2112 })
2113 });
2114
2115 let selections = SelectionsCollection::new();
2116
2117 let blink_manager = cx.new(|cx| {
2118 let mut blink_manager = BlinkManager::new(
2119 CURSOR_BLINK_INTERVAL,
2120 |cx| EditorSettings::get_global(cx).cursor_blink,
2121 cx,
2122 );
2123 if is_minimap {
2124 blink_manager.disable(cx);
2125 }
2126 blink_manager
2127 });
2128
2129 let soft_wrap_mode_override =
2130 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2131
2132 let mut project_subscriptions = Vec::new();
2133 if full_mode && let Some(project) = project.as_ref() {
2134 project_subscriptions.push(cx.subscribe_in(
2135 project,
2136 window,
2137 |editor, _, event, window, cx| match event {
2138 project::Event::RefreshCodeLens => {
2139 // we always query lens with actions, without storing them, always refreshing them
2140 }
2141 project::Event::RefreshInlayHints {
2142 server_id,
2143 request_id,
2144 } => {
2145 editor.refresh_inlay_hints(
2146 InlayHintRefreshReason::RefreshRequested {
2147 server_id: *server_id,
2148 request_id: *request_id,
2149 },
2150 cx,
2151 );
2152 }
2153 project::Event::RefreshSemanticTokens {
2154 server_id,
2155 request_id,
2156 } => {
2157 editor.refresh_semantic_tokens(
2158 None,
2159 Some(RefreshForServer {
2160 server_id: *server_id,
2161 request_id: *request_id,
2162 }),
2163 cx,
2164 );
2165 }
2166 project::Event::LanguageServerRemoved(_) => {
2167 editor.registered_buffers.clear();
2168 editor.register_visible_buffers(cx);
2169 editor.invalidate_semantic_tokens(None);
2170 editor.update_lsp_data(None, window, cx);
2171 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2172 if editor.tasks_update_task.is_none() {
2173 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2174 }
2175 }
2176 project::Event::LanguageServerAdded(..) => {
2177 if editor.tasks_update_task.is_none() {
2178 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2179 }
2180 }
2181 project::Event::SnippetEdit(id, snippet_edits) => {
2182 // todo(lw): Non singletons
2183 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2184 let snapshot = buffer.read(cx).snapshot();
2185 let focus_handle = editor.focus_handle(cx);
2186 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2187 for (range, snippet) in snippet_edits {
2188 let buffer_range =
2189 language::range_from_lsp(*range).to_offset(&snapshot);
2190 editor
2191 .insert_snippet(
2192 &[MultiBufferOffset(buffer_range.start)
2193 ..MultiBufferOffset(buffer_range.end)],
2194 snippet.clone(),
2195 window,
2196 cx,
2197 )
2198 .ok();
2199 }
2200 }
2201 }
2202 }
2203 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2204 let buffer_id = *buffer_id;
2205 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2206 editor.register_buffer(buffer_id, cx);
2207 editor.update_lsp_data(Some(buffer_id), window, cx);
2208 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2209 refresh_linked_ranges(editor, window, cx);
2210 editor.refresh_code_actions(window, cx);
2211 editor.refresh_document_highlights(cx);
2212 }
2213 }
2214
2215 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2216 let Some(workspace) = editor.workspace() else {
2217 return;
2218 };
2219 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2220 else {
2221 return;
2222 };
2223
2224 if active_editor.entity_id() == cx.entity_id() {
2225 let entity_id = cx.entity_id();
2226 workspace.update(cx, |this, cx| {
2227 this.panes_mut()
2228 .iter_mut()
2229 .filter(|pane| pane.entity_id() != entity_id)
2230 .for_each(|p| {
2231 p.update(cx, |pane, _| {
2232 pane.nav_history_mut().rename_item(
2233 entity_id,
2234 project_path.clone(),
2235 abs_path.clone().into(),
2236 );
2237 })
2238 });
2239 });
2240
2241 Self::open_transaction_for_hidden_buffers(
2242 workspace,
2243 transaction.clone(),
2244 "Rename".to_string(),
2245 window,
2246 cx,
2247 );
2248 }
2249 }
2250
2251 project::Event::WorkspaceEditApplied(transaction) => {
2252 let Some(workspace) = editor.workspace() else {
2253 return;
2254 };
2255 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2256 else {
2257 return;
2258 };
2259
2260 if active_editor.entity_id() == cx.entity_id() {
2261 Self::open_transaction_for_hidden_buffers(
2262 workspace,
2263 transaction.clone(),
2264 "LSP Edit".to_string(),
2265 window,
2266 cx,
2267 );
2268 }
2269 }
2270
2271 _ => {}
2272 },
2273 ));
2274 if let Some(task_inventory) = project
2275 .read(cx)
2276 .task_store()
2277 .read(cx)
2278 .task_inventory()
2279 .cloned()
2280 {
2281 project_subscriptions.push(cx.observe_in(
2282 &task_inventory,
2283 window,
2284 |editor, _, window, cx| {
2285 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2286 },
2287 ));
2288 };
2289
2290 project_subscriptions.push(cx.subscribe_in(
2291 &project.read(cx).breakpoint_store(),
2292 window,
2293 |editor, _, event, window, cx| match event {
2294 BreakpointStoreEvent::ClearDebugLines => {
2295 editor.clear_row_highlights::<ActiveDebugLine>();
2296 editor.refresh_inline_values(cx);
2297 }
2298 BreakpointStoreEvent::SetDebugLine => {
2299 if editor.go_to_active_debug_line(window, cx) {
2300 cx.stop_propagation();
2301 }
2302
2303 editor.refresh_inline_values(cx);
2304 }
2305 _ => {}
2306 },
2307 ));
2308 let git_store = project.read(cx).git_store().clone();
2309 let project = project.clone();
2310 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2311 if let GitStoreEvent::RepositoryAdded = event {
2312 this.load_diff_task = Some(
2313 update_uncommitted_diff_for_buffer(
2314 cx.entity(),
2315 &project,
2316 this.buffer.read(cx).all_buffers(),
2317 this.buffer.clone(),
2318 cx,
2319 )
2320 .shared(),
2321 );
2322 }
2323 }));
2324 }
2325
2326 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2327
2328 let inlay_hint_settings =
2329 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2330 let focus_handle = cx.focus_handle();
2331 if !is_minimap {
2332 cx.on_focus(&focus_handle, window, Self::handle_focus)
2333 .detach();
2334 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2335 .detach();
2336 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2337 .detach();
2338 cx.on_blur(&focus_handle, window, Self::handle_blur)
2339 .detach();
2340 cx.observe_pending_input(window, Self::observe_pending_input)
2341 .detach();
2342 }
2343
2344 let show_indent_guides =
2345 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2346 Some(false)
2347 } else {
2348 None
2349 };
2350
2351 let breakpoint_store = match (&mode, project.as_ref()) {
2352 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2353 _ => None,
2354 };
2355
2356 let mut code_action_providers = Vec::new();
2357 let mut load_uncommitted_diff = None;
2358 if let Some(project) = project.clone() {
2359 load_uncommitted_diff = Some(
2360 update_uncommitted_diff_for_buffer(
2361 cx.entity(),
2362 &project,
2363 multi_buffer.read(cx).all_buffers(),
2364 multi_buffer.clone(),
2365 cx,
2366 )
2367 .shared(),
2368 );
2369 code_action_providers.push(Rc::new(project) as Rc<_>);
2370 }
2371
2372 let mut editor = Self {
2373 focus_handle,
2374 show_cursor_when_unfocused: false,
2375 last_focused_descendant: None,
2376 buffer: multi_buffer.clone(),
2377 display_map: display_map.clone(),
2378 placeholder_display_map: None,
2379 selections,
2380 scroll_manager: ScrollManager::new(cx),
2381 columnar_selection_state: None,
2382 add_selections_state: None,
2383 select_next_state: None,
2384 select_prev_state: None,
2385 selection_history: SelectionHistory::default(),
2386 defer_selection_effects: false,
2387 deferred_selection_effects_state: None,
2388 autoclose_regions: Vec::new(),
2389 snippet_stack: InvalidationStack::default(),
2390 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2391 ime_transaction: None,
2392 active_diagnostics: ActiveDiagnostic::None,
2393 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2394 inline_diagnostics_update: Task::ready(()),
2395 inline_diagnostics: Vec::new(),
2396 soft_wrap_mode_override,
2397 diagnostics_max_severity,
2398 hard_wrap: None,
2399 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2400 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2401 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2402 project,
2403 blink_manager: blink_manager.clone(),
2404 show_local_selections: true,
2405 show_scrollbars: ScrollbarAxes {
2406 horizontal: full_mode,
2407 vertical: full_mode,
2408 },
2409 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2410 offset_content: !matches!(mode, EditorMode::SingleLine),
2411 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2412 show_gutter: full_mode,
2413 show_line_numbers: (!full_mode).then_some(false),
2414 use_relative_line_numbers: None,
2415 disable_expand_excerpt_buttons: !full_mode,
2416 delegate_expand_excerpts: false,
2417 delegate_stage_and_restore: false,
2418 delegate_open_excerpts: false,
2419 enable_lsp_data: true,
2420 enable_runnables: true,
2421 show_git_diff_gutter: None,
2422 show_code_actions: None,
2423 show_runnables: None,
2424 show_breakpoints: None,
2425 show_diff_review_button: false,
2426 show_wrap_guides: None,
2427 show_indent_guides,
2428 buffers_with_disabled_indent_guides: HashSet::default(),
2429 highlight_order: 0,
2430 highlighted_rows: HashMap::default(),
2431 background_highlights: HashMap::default(),
2432 gutter_highlights: HashMap::default(),
2433 scrollbar_marker_state: ScrollbarMarkerState::default(),
2434 active_indent_guides_state: ActiveIndentGuidesState::default(),
2435 nav_history: None,
2436 context_menu: RefCell::new(None),
2437 context_menu_options: None,
2438 mouse_context_menu: None,
2439 completion_tasks: Vec::new(),
2440 inline_blame_popover: None,
2441 inline_blame_popover_show_task: None,
2442 signature_help_state: SignatureHelpState::default(),
2443 auto_signature_help: None,
2444 find_all_references_task_sources: Vec::new(),
2445 next_completion_id: 0,
2446 next_inlay_id: 0,
2447 code_action_providers,
2448 available_code_actions: None,
2449 code_actions_task: None,
2450 quick_selection_highlight_task: None,
2451 debounced_selection_highlight_task: None,
2452 debounced_selection_highlight_complete: false,
2453 document_highlights_task: None,
2454 linked_editing_range_task: None,
2455 pending_rename: None,
2456 searchable: !is_minimap,
2457 cursor_shape: EditorSettings::get_global(cx)
2458 .cursor_shape
2459 .unwrap_or_default(),
2460 cursor_offset_on_selection: false,
2461 current_line_highlight: None,
2462 autoindent_mode: Some(AutoindentMode::EachLine),
2463 collapse_matches: false,
2464 workspace: None,
2465 input_enabled: !is_minimap,
2466 use_modal_editing: full_mode,
2467 read_only: is_minimap,
2468 use_autoclose: true,
2469 use_auto_surround: true,
2470 auto_replace_emoji_shortcode: false,
2471 jsx_tag_auto_close_enabled_in_any_buffer: false,
2472 leader_id: None,
2473 remote_id: None,
2474 hover_state: HoverState::default(),
2475 pending_mouse_down: None,
2476 prev_pressure_stage: None,
2477 hovered_link_state: None,
2478 edit_prediction_provider: None,
2479 active_edit_prediction: None,
2480 stale_edit_prediction_in_menu: None,
2481 edit_prediction_preview: EditPredictionPreview::Inactive {
2482 released_too_fast: false,
2483 },
2484 inline_diagnostics_enabled: full_mode,
2485 diagnostics_enabled: full_mode,
2486 word_completions_enabled: full_mode,
2487 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2488 gutter_hovered: false,
2489 pixel_position_of_newest_cursor: None,
2490 last_bounds: None,
2491 last_position_map: None,
2492 expect_bounds_change: None,
2493 gutter_dimensions: GutterDimensions::default(),
2494 style: None,
2495 show_cursor_names: false,
2496 hovered_cursors: HashMap::default(),
2497 next_editor_action_id: EditorActionId::default(),
2498 editor_actions: Rc::default(),
2499 edit_predictions_hidden_for_vim_mode: false,
2500 show_edit_predictions_override: None,
2501 show_completions_on_input_override: None,
2502 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2503 edit_prediction_settings: EditPredictionSettings::Disabled,
2504 edit_prediction_indent_conflict: false,
2505 edit_prediction_requires_modifier_in_indent_conflict: true,
2506 custom_context_menu: None,
2507 show_git_blame_gutter: false,
2508 show_git_blame_inline: false,
2509 show_selection_menu: None,
2510 show_git_blame_inline_delay_task: None,
2511 git_blame_inline_enabled: full_mode
2512 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2513 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2514 buffer_serialization: is_minimap.not().then(|| {
2515 BufferSerialization::new(
2516 ProjectSettings::get_global(cx)
2517 .session
2518 .restore_unsaved_buffers,
2519 )
2520 }),
2521 blame: None,
2522 blame_subscription: None,
2523 tasks: BTreeMap::default(),
2524
2525 breakpoint_store,
2526 gutter_breakpoint_indicator: (None, None),
2527 gutter_diff_review_indicator: (None, None),
2528 diff_review_drag_state: None,
2529 diff_review_overlays: Vec::new(),
2530 stored_review_comments: Vec::new(),
2531 next_review_comment_id: 0,
2532 hovered_diff_hunk_row: None,
2533 _subscriptions: (!is_minimap)
2534 .then(|| {
2535 vec![
2536 cx.observe(&multi_buffer, Self::on_buffer_changed),
2537 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2538 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2539 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2540 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2541 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2542 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2543 cx.observe_window_activation(window, |editor, window, cx| {
2544 let active = window.is_window_active();
2545 editor.blink_manager.update(cx, |blink_manager, cx| {
2546 if active {
2547 blink_manager.enable(cx);
2548 } else {
2549 blink_manager.disable(cx);
2550 }
2551 });
2552 if active {
2553 editor.show_mouse_cursor(cx);
2554 }
2555 }),
2556 ]
2557 })
2558 .unwrap_or_default(),
2559 tasks_update_task: None,
2560 pull_diagnostics_task: Task::ready(()),
2561 colors: None,
2562 refresh_colors_task: Task::ready(()),
2563 use_document_folding_ranges: false,
2564 refresh_folding_ranges_task: Task::ready(()),
2565 inlay_hints: None,
2566 next_color_inlay_id: 0,
2567 post_scroll_update: Task::ready(()),
2568 linked_edit_ranges: Default::default(),
2569 in_project_search: false,
2570 previous_search_ranges: None,
2571 breadcrumb_header: None,
2572 focused_block: None,
2573 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2574 addons: HashMap::default(),
2575 registered_buffers: HashMap::default(),
2576 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2577 selection_mark_mode: false,
2578 toggle_fold_multiple_buffers: Task::ready(()),
2579 serialize_selections: Task::ready(()),
2580 serialize_folds: Task::ready(()),
2581 text_style_refinement: None,
2582 load_diff_task: load_uncommitted_diff,
2583 temporary_diff_override: false,
2584 mouse_cursor_hidden: false,
2585 minimap: None,
2586 hide_mouse_mode: EditorSettings::get_global(cx)
2587 .hide_mouse
2588 .unwrap_or_default(),
2589 change_list: ChangeList::new(),
2590 mode,
2591 selection_drag_state: SelectionDragState::None,
2592 folding_newlines: Task::ready(()),
2593 lookup_key: None,
2594 select_next_is_case_sensitive: None,
2595 on_local_selections_changed: None,
2596 suppress_selection_callback: false,
2597 applicable_language_settings: HashMap::default(),
2598 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2599 accent_data: None,
2600 fetched_tree_sitter_chunks: HashMap::default(),
2601 number_deleted_lines: false,
2602 refresh_matching_bracket_highlights_task: Task::ready(()),
2603 refresh_document_symbols_task: Task::ready(()).shared(),
2604 lsp_document_symbols: HashMap::default(),
2605 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2606 outline_symbols_at_cursor: None,
2607 sticky_headers_task: Task::ready(()),
2608 sticky_headers: None,
2609 };
2610
2611 if is_minimap {
2612 return editor;
2613 }
2614
2615 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2616 editor.accent_data = editor.fetch_accent_data(cx);
2617
2618 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2619 editor
2620 ._subscriptions
2621 .push(cx.observe(breakpoints, |_, _, cx| {
2622 cx.notify();
2623 }));
2624 }
2625 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2626 editor._subscriptions.extend(project_subscriptions);
2627
2628 editor._subscriptions.push(cx.subscribe_in(
2629 &cx.entity(),
2630 window,
2631 |editor, _, e: &EditorEvent, window, cx| match e {
2632 EditorEvent::ScrollPositionChanged { local, .. } => {
2633 if *local {
2634 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2635 editor.inline_blame_popover.take();
2636 let snapshot = editor.snapshot(window, cx);
2637 let new_anchor = editor
2638 .scroll_manager
2639 .native_anchor(&snapshot.display_snapshot, cx);
2640 editor.update_restoration_data(cx, move |data| {
2641 data.scroll_position = (
2642 new_anchor.top_row(snapshot.buffer_snapshot()),
2643 new_anchor.offset,
2644 );
2645 });
2646
2647 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2648 cx.background_executor()
2649 .timer(Duration::from_millis(50))
2650 .await;
2651 editor
2652 .update_in(cx, |editor, window, cx| {
2653 editor.register_visible_buffers(cx);
2654 editor.colorize_brackets(false, cx);
2655 editor.refresh_inlay_hints(
2656 InlayHintRefreshReason::NewLinesShown,
2657 cx,
2658 );
2659 if !editor.buffer().read(cx).is_singleton() {
2660 editor.update_lsp_data(None, window, cx);
2661 }
2662 })
2663 .ok();
2664 });
2665 }
2666 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2667 }
2668 EditorEvent::Edited { .. } => {
2669 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2670 .map(|vim_mode| vim_mode.0)
2671 .unwrap_or(false);
2672 if !vim_mode {
2673 let display_map = editor.display_snapshot(cx);
2674 let selections = editor.selections.all_adjusted_display(&display_map);
2675 let pop_state = editor
2676 .change_list
2677 .last()
2678 .map(|previous| {
2679 previous.len() == selections.len()
2680 && previous.iter().enumerate().all(|(ix, p)| {
2681 p.to_display_point(&display_map).row()
2682 == selections[ix].head().row()
2683 })
2684 })
2685 .unwrap_or(false);
2686 let new_positions = selections
2687 .into_iter()
2688 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2689 .collect();
2690 editor
2691 .change_list
2692 .push_to_change_list(pop_state, new_positions);
2693 }
2694 }
2695 _ => (),
2696 },
2697 ));
2698
2699 if let Some(dap_store) = editor
2700 .project
2701 .as_ref()
2702 .map(|project| project.read(cx).dap_store())
2703 {
2704 let weak_editor = cx.weak_entity();
2705
2706 editor
2707 ._subscriptions
2708 .push(
2709 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2710 let session_entity = cx.entity();
2711 weak_editor
2712 .update(cx, |editor, cx| {
2713 editor._subscriptions.push(
2714 cx.subscribe(&session_entity, Self::on_debug_session_event),
2715 );
2716 })
2717 .ok();
2718 }),
2719 );
2720
2721 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2722 editor
2723 ._subscriptions
2724 .push(cx.subscribe(&session, Self::on_debug_session_event));
2725 }
2726 }
2727
2728 // skip adding the initial selection to selection history
2729 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2730 editor.end_selection(window, cx);
2731 editor.selection_history.mode = SelectionHistoryMode::Normal;
2732
2733 editor.scroll_manager.show_scrollbars(window, cx);
2734 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2735
2736 if full_mode {
2737 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2738 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2739
2740 if editor.git_blame_inline_enabled {
2741 editor.start_git_blame_inline(false, window, cx);
2742 }
2743
2744 editor.go_to_active_debug_line(window, cx);
2745
2746 editor.minimap =
2747 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2748 editor.colors = Some(LspColorData::new(cx));
2749 editor.use_document_folding_ranges = true;
2750 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2751
2752 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2753 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2754 }
2755 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2756 }
2757
2758 editor
2759 }
2760
2761 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2762 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2763 }
2764
2765 pub fn deploy_mouse_context_menu(
2766 &mut self,
2767 position: gpui::Point<Pixels>,
2768 context_menu: Entity<ContextMenu>,
2769 window: &mut Window,
2770 cx: &mut Context<Self>,
2771 ) {
2772 self.mouse_context_menu = Some(MouseContextMenu::new(
2773 self,
2774 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2775 context_menu,
2776 window,
2777 cx,
2778 ));
2779 }
2780
2781 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2782 self.mouse_context_menu
2783 .as_ref()
2784 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2785 }
2786
2787 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2788 if self
2789 .selections
2790 .pending_anchor()
2791 .is_some_and(|pending_selection| {
2792 let snapshot = self.buffer().read(cx).snapshot(cx);
2793 pending_selection.range().includes(range, &snapshot)
2794 })
2795 {
2796 return true;
2797 }
2798
2799 self.selections
2800 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2801 .into_iter()
2802 .any(|selection| {
2803 // This is needed to cover a corner case, if we just check for an existing
2804 // selection in the fold range, having a cursor at the start of the fold
2805 // marks it as selected. Non-empty selections don't cause this.
2806 let length = selection.end - selection.start;
2807 length > 0
2808 })
2809 }
2810
2811 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2812 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2813 }
2814
2815 fn key_context_internal(
2816 &self,
2817 has_active_edit_prediction: bool,
2818 window: &mut Window,
2819 cx: &mut App,
2820 ) -> KeyContext {
2821 let mut key_context = KeyContext::new_with_defaults();
2822 key_context.add("Editor");
2823 let mode = match self.mode {
2824 EditorMode::SingleLine => "single_line",
2825 EditorMode::AutoHeight { .. } => "auto_height",
2826 EditorMode::Minimap { .. } => "minimap",
2827 EditorMode::Full { .. } => "full",
2828 };
2829
2830 if EditorSettings::jupyter_enabled(cx) {
2831 key_context.add("jupyter");
2832 }
2833
2834 key_context.set("mode", mode);
2835 if self.pending_rename.is_some() {
2836 key_context.add("renaming");
2837 }
2838
2839 if let Some(snippet_stack) = self.snippet_stack.last() {
2840 key_context.add("in_snippet");
2841
2842 if snippet_stack.active_index > 0 {
2843 key_context.add("has_previous_tabstop");
2844 }
2845
2846 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2847 key_context.add("has_next_tabstop");
2848 }
2849 }
2850
2851 match self.context_menu.borrow().as_ref() {
2852 Some(CodeContextMenu::Completions(menu)) => {
2853 if menu.visible() {
2854 key_context.add("menu");
2855 key_context.add("showing_completions");
2856 }
2857 }
2858 Some(CodeContextMenu::CodeActions(menu)) => {
2859 if menu.visible() {
2860 key_context.add("menu");
2861 key_context.add("showing_code_actions")
2862 }
2863 }
2864 None => {}
2865 }
2866
2867 if self.signature_help_state.has_multiple_signatures() {
2868 key_context.add("showing_signature_help");
2869 }
2870
2871 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2872 if !self.focus_handle(cx).contains_focused(window, cx)
2873 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2874 {
2875 for addon in self.addons.values() {
2876 addon.extend_key_context(&mut key_context, cx)
2877 }
2878 }
2879
2880 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2881 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2882 Some(
2883 file.full_path(cx)
2884 .extension()?
2885 .to_string_lossy()
2886 .to_lowercase(),
2887 )
2888 }) {
2889 key_context.set("extension", extension);
2890 }
2891 } else {
2892 key_context.add("multibuffer");
2893 }
2894
2895 if has_active_edit_prediction {
2896 if self.edit_prediction_in_conflict() {
2897 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2898 } else {
2899 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2900 key_context.add("copilot_suggestion");
2901 }
2902 }
2903
2904 if self.selection_mark_mode {
2905 key_context.add("selection_mode");
2906 }
2907
2908 let disjoint = self.selections.disjoint_anchors();
2909 let snapshot = self.snapshot(window, cx);
2910 let snapshot = snapshot.buffer_snapshot();
2911 if self.mode == EditorMode::SingleLine
2912 && let [selection] = disjoint
2913 && selection.start == selection.end
2914 && selection.end.to_offset(snapshot) == snapshot.len()
2915 {
2916 key_context.add("end_of_input");
2917 }
2918
2919 if self.has_any_expanded_diff_hunks(cx) {
2920 key_context.add("diffs_expanded");
2921 }
2922
2923 key_context
2924 }
2925
2926 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2927 self.last_bounds.as_ref()
2928 }
2929
2930 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2931 if self.mouse_cursor_hidden {
2932 self.mouse_cursor_hidden = false;
2933 cx.notify();
2934 }
2935 }
2936
2937 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2938 let hide_mouse_cursor = match origin {
2939 HideMouseCursorOrigin::TypingAction => {
2940 matches!(
2941 self.hide_mouse_mode,
2942 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2943 )
2944 }
2945 HideMouseCursorOrigin::MovementAction => {
2946 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2947 }
2948 };
2949 if self.mouse_cursor_hidden != hide_mouse_cursor {
2950 self.mouse_cursor_hidden = hide_mouse_cursor;
2951 cx.notify();
2952 }
2953 }
2954
2955 pub fn edit_prediction_in_conflict(&self) -> bool {
2956 if !self.show_edit_predictions_in_menu() {
2957 return false;
2958 }
2959
2960 let showing_completions = self
2961 .context_menu
2962 .borrow()
2963 .as_ref()
2964 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2965
2966 showing_completions
2967 || self.edit_prediction_requires_modifier()
2968 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2969 // bindings to insert tab characters.
2970 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2971 }
2972
2973 pub fn accept_edit_prediction_keybind(
2974 &self,
2975 granularity: EditPredictionGranularity,
2976 window: &mut Window,
2977 cx: &mut App,
2978 ) -> AcceptEditPredictionBinding {
2979 let key_context = self.key_context_internal(true, window, cx);
2980 let in_conflict = self.edit_prediction_in_conflict();
2981
2982 let bindings =
2983 match granularity {
2984 EditPredictionGranularity::Word => window
2985 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2986 EditPredictionGranularity::Line => window
2987 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2988 EditPredictionGranularity::Full => {
2989 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2990 }
2991 };
2992
2993 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2994 !in_conflict
2995 || binding
2996 .keystrokes()
2997 .first()
2998 .is_some_and(|keystroke| keystroke.modifiers().modified())
2999 }))
3000 }
3001
3002 pub fn new_file(
3003 workspace: &mut Workspace,
3004 _: &workspace::NewFile,
3005 window: &mut Window,
3006 cx: &mut Context<Workspace>,
3007 ) {
3008 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3009 "Failed to create buffer",
3010 window,
3011 cx,
3012 |e, _, _| match e.error_code() {
3013 ErrorCode::RemoteUpgradeRequired => Some(format!(
3014 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3015 e.error_tag("required").unwrap_or("the latest version")
3016 )),
3017 _ => None,
3018 },
3019 );
3020 }
3021
3022 pub fn new_in_workspace(
3023 workspace: &mut Workspace,
3024 window: &mut Window,
3025 cx: &mut Context<Workspace>,
3026 ) -> Task<Result<Entity<Editor>>> {
3027 let project = workspace.project().clone();
3028 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3029
3030 cx.spawn_in(window, async move |workspace, cx| {
3031 let buffer = create.await?;
3032 workspace.update_in(cx, |workspace, window, cx| {
3033 let editor =
3034 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3035 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3036 editor
3037 })
3038 })
3039 }
3040
3041 fn new_file_vertical(
3042 workspace: &mut Workspace,
3043 _: &workspace::NewFileSplitVertical,
3044 window: &mut Window,
3045 cx: &mut Context<Workspace>,
3046 ) {
3047 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3048 }
3049
3050 fn new_file_horizontal(
3051 workspace: &mut Workspace,
3052 _: &workspace::NewFileSplitHorizontal,
3053 window: &mut Window,
3054 cx: &mut Context<Workspace>,
3055 ) {
3056 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3057 }
3058
3059 fn new_file_split(
3060 workspace: &mut Workspace,
3061 action: &workspace::NewFileSplit,
3062 window: &mut Window,
3063 cx: &mut Context<Workspace>,
3064 ) {
3065 Self::new_file_in_direction(workspace, action.0, window, cx)
3066 }
3067
3068 fn new_file_in_direction(
3069 workspace: &mut Workspace,
3070 direction: SplitDirection,
3071 window: &mut Window,
3072 cx: &mut Context<Workspace>,
3073 ) {
3074 let project = workspace.project().clone();
3075 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3076
3077 cx.spawn_in(window, async move |workspace, cx| {
3078 let buffer = create.await?;
3079 workspace.update_in(cx, move |workspace, window, cx| {
3080 workspace.split_item(
3081 direction,
3082 Box::new(
3083 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3084 ),
3085 window,
3086 cx,
3087 )
3088 })?;
3089 anyhow::Ok(())
3090 })
3091 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3092 match e.error_code() {
3093 ErrorCode::RemoteUpgradeRequired => Some(format!(
3094 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3095 e.error_tag("required").unwrap_or("the latest version")
3096 )),
3097 _ => None,
3098 }
3099 });
3100 }
3101
3102 pub fn leader_id(&self) -> Option<CollaboratorId> {
3103 self.leader_id
3104 }
3105
3106 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3107 &self.buffer
3108 }
3109
3110 pub fn project(&self) -> Option<&Entity<Project>> {
3111 self.project.as_ref()
3112 }
3113
3114 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3115 self.workspace.as_ref()?.0.upgrade()
3116 }
3117
3118 /// Detaches a task and shows an error notification in the workspace if available,
3119 /// otherwise just logs the error.
3120 pub fn detach_and_notify_err<R, E>(
3121 &self,
3122 task: Task<Result<R, E>>,
3123 window: &mut Window,
3124 cx: &mut App,
3125 ) where
3126 E: std::fmt::Debug + std::fmt::Display + 'static,
3127 R: 'static,
3128 {
3129 if let Some(workspace) = self.workspace() {
3130 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3131 } else {
3132 task.detach_and_log_err(cx);
3133 }
3134 }
3135
3136 /// Returns the workspace serialization ID if this editor should be serialized.
3137 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3138 self.workspace
3139 .as_ref()
3140 .filter(|_| self.should_serialize_buffer())
3141 .and_then(|workspace| workspace.1)
3142 }
3143
3144 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3145 self.buffer().read(cx).title(cx)
3146 }
3147
3148 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3149 let git_blame_gutter_max_author_length = self
3150 .render_git_blame_gutter(cx)
3151 .then(|| {
3152 if let Some(blame) = self.blame.as_ref() {
3153 let max_author_length =
3154 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3155 Some(max_author_length)
3156 } else {
3157 None
3158 }
3159 })
3160 .flatten();
3161
3162 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3163
3164 EditorSnapshot {
3165 mode: self.mode.clone(),
3166 show_gutter: self.show_gutter,
3167 offset_content: self.offset_content,
3168 show_line_numbers: self.show_line_numbers,
3169 number_deleted_lines: self.number_deleted_lines,
3170 show_git_diff_gutter: self.show_git_diff_gutter,
3171 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3172 show_code_actions: self.show_code_actions,
3173 show_runnables: self.show_runnables,
3174 show_breakpoints: self.show_breakpoints,
3175 git_blame_gutter_max_author_length,
3176 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3177 display_snapshot,
3178 placeholder_display_snapshot: self
3179 .placeholder_display_map
3180 .as_ref()
3181 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3182 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3183 is_focused: self.focus_handle.is_focused(window),
3184 current_line_highlight: self
3185 .current_line_highlight
3186 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3187 gutter_hovered: self.gutter_hovered,
3188 }
3189 }
3190
3191 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3192 self.buffer.read(cx).language_at(point, cx)
3193 }
3194
3195 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3196 self.buffer.read(cx).read(cx).file_at(point).cloned()
3197 }
3198
3199 pub fn active_excerpt(
3200 &self,
3201 cx: &App,
3202 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3203 self.buffer
3204 .read(cx)
3205 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3206 }
3207
3208 pub fn mode(&self) -> &EditorMode {
3209 &self.mode
3210 }
3211
3212 pub fn set_mode(&mut self, mode: EditorMode) {
3213 self.mode = mode;
3214 }
3215
3216 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3217 self.collaboration_hub.as_deref()
3218 }
3219
3220 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3221 self.collaboration_hub = Some(hub);
3222 }
3223
3224 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3225 self.in_project_search = in_project_search;
3226 }
3227
3228 pub fn set_custom_context_menu(
3229 &mut self,
3230 f: impl 'static
3231 + Fn(
3232 &mut Self,
3233 DisplayPoint,
3234 &mut Window,
3235 &mut Context<Self>,
3236 ) -> Option<Entity<ui::ContextMenu>>,
3237 ) {
3238 self.custom_context_menu = Some(Box::new(f))
3239 }
3240
3241 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3242 self.completion_provider = provider;
3243 }
3244
3245 #[cfg(any(test, feature = "test-support"))]
3246 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3247 self.completion_provider.clone()
3248 }
3249
3250 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3251 self.semantics_provider.clone()
3252 }
3253
3254 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3255 self.semantics_provider = provider;
3256 }
3257
3258 pub fn set_edit_prediction_provider<T>(
3259 &mut self,
3260 provider: Option<Entity<T>>,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 ) where
3264 T: EditPredictionDelegate,
3265 {
3266 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3267 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3268 if this.focus_handle.is_focused(window) {
3269 this.update_visible_edit_prediction(window, cx);
3270 }
3271 }),
3272 provider: Arc::new(provider),
3273 });
3274 self.update_edit_prediction_settings(cx);
3275 self.refresh_edit_prediction(false, false, window, cx);
3276 }
3277
3278 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3279 self.placeholder_display_map
3280 .as_ref()
3281 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3282 }
3283
3284 pub fn set_placeholder_text(
3285 &mut self,
3286 placeholder_text: &str,
3287 window: &mut Window,
3288 cx: &mut Context<Self>,
3289 ) {
3290 let multibuffer = cx
3291 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3292
3293 let style = window.text_style();
3294
3295 self.placeholder_display_map = Some(cx.new(|cx| {
3296 DisplayMap::new(
3297 multibuffer,
3298 style.font(),
3299 style.font_size.to_pixels(window.rem_size()),
3300 None,
3301 FILE_HEADER_HEIGHT,
3302 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3303 Default::default(),
3304 DiagnosticSeverity::Off,
3305 cx,
3306 )
3307 }));
3308 cx.notify();
3309 }
3310
3311 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3312 self.cursor_shape = cursor_shape;
3313
3314 // Disrupt blink for immediate user feedback that the cursor shape has changed
3315 self.blink_manager.update(cx, BlinkManager::show_cursor);
3316
3317 cx.notify();
3318 }
3319
3320 pub fn cursor_shape(&self) -> CursorShape {
3321 self.cursor_shape
3322 }
3323
3324 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3325 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3326 }
3327
3328 pub fn set_current_line_highlight(
3329 &mut self,
3330 current_line_highlight: Option<CurrentLineHighlight>,
3331 ) {
3332 self.current_line_highlight = current_line_highlight;
3333 }
3334
3335 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3336 self.collapse_matches = collapse_matches;
3337 }
3338
3339 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3340 if self.collapse_matches {
3341 return range.start..range.start;
3342 }
3343 range.clone()
3344 }
3345
3346 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3347 self.display_map.read(cx).clip_at_line_ends
3348 }
3349
3350 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3351 if self.display_map.read(cx).clip_at_line_ends != clip {
3352 self.display_map
3353 .update(cx, |map, _| map.clip_at_line_ends = clip);
3354 }
3355 }
3356
3357 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3358 self.input_enabled = input_enabled;
3359 }
3360
3361 pub fn set_edit_predictions_hidden_for_vim_mode(
3362 &mut self,
3363 hidden: bool,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 if hidden != self.edit_predictions_hidden_for_vim_mode {
3368 self.edit_predictions_hidden_for_vim_mode = hidden;
3369 if hidden {
3370 self.update_visible_edit_prediction(window, cx);
3371 } else {
3372 self.refresh_edit_prediction(true, false, window, cx);
3373 }
3374 }
3375 }
3376
3377 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3378 self.menu_edit_predictions_policy = value;
3379 }
3380
3381 pub fn set_autoindent(&mut self, autoindent: bool) {
3382 if autoindent {
3383 self.autoindent_mode = Some(AutoindentMode::EachLine);
3384 } else {
3385 self.autoindent_mode = None;
3386 }
3387 }
3388
3389 pub fn capability(&self, cx: &App) -> Capability {
3390 if self.read_only {
3391 Capability::ReadOnly
3392 } else {
3393 self.buffer.read(cx).capability()
3394 }
3395 }
3396
3397 pub fn read_only(&self, cx: &App) -> bool {
3398 self.read_only || self.buffer.read(cx).read_only()
3399 }
3400
3401 pub fn set_read_only(&mut self, read_only: bool) {
3402 self.read_only = read_only;
3403 }
3404
3405 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3406 self.use_autoclose = autoclose;
3407 }
3408
3409 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3410 self.use_auto_surround = auto_surround;
3411 }
3412
3413 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3414 self.auto_replace_emoji_shortcode = auto_replace;
3415 }
3416
3417 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3418 self.buffer_serialization = should_serialize.then(|| {
3419 BufferSerialization::new(
3420 ProjectSettings::get_global(cx)
3421 .session
3422 .restore_unsaved_buffers,
3423 )
3424 })
3425 }
3426
3427 fn should_serialize_buffer(&self) -> bool {
3428 self.buffer_serialization.is_some()
3429 }
3430
3431 pub fn toggle_edit_predictions(
3432 &mut self,
3433 _: &ToggleEditPrediction,
3434 window: &mut Window,
3435 cx: &mut Context<Self>,
3436 ) {
3437 if self.show_edit_predictions_override.is_some() {
3438 self.set_show_edit_predictions(None, window, cx);
3439 } else {
3440 let show_edit_predictions = !self.edit_predictions_enabled();
3441 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3442 }
3443 }
3444
3445 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3446 self.show_completions_on_input_override = show_completions_on_input;
3447 }
3448
3449 pub fn set_show_edit_predictions(
3450 &mut self,
3451 show_edit_predictions: Option<bool>,
3452 window: &mut Window,
3453 cx: &mut Context<Self>,
3454 ) {
3455 self.show_edit_predictions_override = show_edit_predictions;
3456 self.update_edit_prediction_settings(cx);
3457
3458 if let Some(false) = show_edit_predictions {
3459 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3460 } else {
3461 self.refresh_edit_prediction(false, true, window, cx);
3462 }
3463 }
3464
3465 fn edit_predictions_disabled_in_scope(
3466 &self,
3467 buffer: &Entity<Buffer>,
3468 buffer_position: language::Anchor,
3469 cx: &App,
3470 ) -> bool {
3471 let snapshot = buffer.read(cx).snapshot();
3472 let settings = snapshot.settings_at(buffer_position, cx);
3473
3474 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3475 return false;
3476 };
3477
3478 scope.override_name().is_some_and(|scope_name| {
3479 settings
3480 .edit_predictions_disabled_in
3481 .iter()
3482 .any(|s| s == scope_name)
3483 })
3484 }
3485
3486 pub fn set_use_modal_editing(&mut self, to: bool) {
3487 self.use_modal_editing = to;
3488 }
3489
3490 pub fn use_modal_editing(&self) -> bool {
3491 self.use_modal_editing
3492 }
3493
3494 fn selections_did_change(
3495 &mut self,
3496 local: bool,
3497 old_cursor_position: &Anchor,
3498 effects: SelectionEffects,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 window.invalidate_character_coordinates();
3503
3504 // Copy selections to primary selection buffer
3505 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3506 if local {
3507 let selections = self
3508 .selections
3509 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3510 let buffer_handle = self.buffer.read(cx).read(cx);
3511
3512 let mut text = String::new();
3513 for (index, selection) in selections.iter().enumerate() {
3514 let text_for_selection = buffer_handle
3515 .text_for_range(selection.start..selection.end)
3516 .collect::<String>();
3517
3518 text.push_str(&text_for_selection);
3519 if index != selections.len() - 1 {
3520 text.push('\n');
3521 }
3522 }
3523
3524 if !text.is_empty() {
3525 cx.write_to_primary(ClipboardItem::new_string(text));
3526 }
3527 }
3528
3529 let selection_anchors = self.selections.disjoint_anchors_arc();
3530
3531 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3532 self.buffer.update(cx, |buffer, cx| {
3533 buffer.set_active_selections(
3534 &selection_anchors,
3535 self.selections.line_mode(),
3536 self.cursor_shape,
3537 cx,
3538 )
3539 });
3540 }
3541 let display_map = self
3542 .display_map
3543 .update(cx, |display_map, cx| display_map.snapshot(cx));
3544 let buffer = display_map.buffer_snapshot();
3545 if self.selections.count() == 1 {
3546 self.add_selections_state = None;
3547 }
3548 self.select_next_state = None;
3549 self.select_prev_state = None;
3550 self.select_syntax_node_history.try_clear();
3551 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3552 self.snippet_stack.invalidate(&selection_anchors, buffer);
3553 self.take_rename(false, window, cx);
3554
3555 let newest_selection = self.selections.newest_anchor();
3556 let new_cursor_position = newest_selection.head();
3557 let selection_start = newest_selection.start;
3558
3559 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3560 self.push_to_nav_history(
3561 *old_cursor_position,
3562 Some(new_cursor_position.to_point(buffer)),
3563 false,
3564 effects.nav_history == Some(true),
3565 cx,
3566 );
3567 }
3568
3569 if local {
3570 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3571 self.register_buffer(buffer_id, cx);
3572 }
3573
3574 let mut context_menu = self.context_menu.borrow_mut();
3575 let completion_menu = match context_menu.as_ref() {
3576 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3577 Some(CodeContextMenu::CodeActions(_)) => {
3578 *context_menu = None;
3579 None
3580 }
3581 None => None,
3582 };
3583 let completion_position = completion_menu.map(|menu| menu.initial_position);
3584 drop(context_menu);
3585
3586 if effects.completions
3587 && let Some(completion_position) = completion_position
3588 {
3589 let start_offset = selection_start.to_offset(buffer);
3590 let position_matches = start_offset == completion_position.to_offset(buffer);
3591 let continue_showing = if let Some((snap, ..)) =
3592 buffer.point_to_buffer_offset(completion_position)
3593 && !snap.capability.editable()
3594 {
3595 false
3596 } else if position_matches {
3597 if self.snippet_stack.is_empty() {
3598 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3599 == Some(CharKind::Word)
3600 } else {
3601 // Snippet choices can be shown even when the cursor is in whitespace.
3602 // Dismissing the menu with actions like backspace is handled by
3603 // invalidation regions.
3604 true
3605 }
3606 } else {
3607 false
3608 };
3609
3610 if continue_showing {
3611 self.open_or_update_completions_menu(None, None, false, window, cx);
3612 } else {
3613 self.hide_context_menu(window, cx);
3614 }
3615 }
3616
3617 hide_hover(self, cx);
3618
3619 if old_cursor_position.to_display_point(&display_map).row()
3620 != new_cursor_position.to_display_point(&display_map).row()
3621 {
3622 self.available_code_actions.take();
3623 }
3624 self.refresh_code_actions(window, cx);
3625 self.refresh_document_highlights(cx);
3626 refresh_linked_ranges(self, window, cx);
3627
3628 self.refresh_selected_text_highlights(false, window, cx);
3629 self.refresh_matching_bracket_highlights(&display_map, cx);
3630 self.refresh_outline_symbols_at_cursor(cx);
3631 self.update_visible_edit_prediction(window, cx);
3632 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3633 self.inline_blame_popover.take();
3634 if self.git_blame_inline_enabled {
3635 self.start_inline_blame_timer(window, cx);
3636 }
3637 }
3638
3639 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3640
3641 if local && !self.suppress_selection_callback {
3642 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3643 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3644 callback(cursor_position, window, cx);
3645 }
3646 }
3647
3648 cx.emit(EditorEvent::SelectionsChanged { local });
3649
3650 let selections = &self.selections.disjoint_anchors_arc();
3651 if selections.len() == 1 {
3652 cx.emit(SearchEvent::ActiveMatchChanged)
3653 }
3654 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3655 let inmemory_selections = selections
3656 .iter()
3657 .map(|s| {
3658 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3659 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3660 })
3661 .collect();
3662 self.update_restoration_data(cx, |data| {
3663 data.selections = inmemory_selections;
3664 });
3665
3666 if WorkspaceSettings::get(None, cx).restore_on_startup
3667 != RestoreOnStartupBehavior::EmptyTab
3668 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3669 {
3670 let snapshot = self.buffer().read(cx).snapshot(cx);
3671 let selections = selections.clone();
3672 let background_executor = cx.background_executor().clone();
3673 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3674 self.serialize_selections = cx.background_spawn(async move {
3675 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3676 let db_selections = selections
3677 .iter()
3678 .map(|selection| {
3679 (
3680 selection.start.to_offset(&snapshot).0,
3681 selection.end.to_offset(&snapshot).0,
3682 )
3683 })
3684 .collect();
3685
3686 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3687 .await
3688 .with_context(|| {
3689 format!(
3690 "persisting editor selections for editor {editor_id}, \
3691 workspace {workspace_id:?}"
3692 )
3693 })
3694 .log_err();
3695 });
3696 }
3697 }
3698
3699 cx.notify();
3700 }
3701
3702 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3703 use text::ToOffset as _;
3704 use text::ToPoint as _;
3705
3706 if self.mode.is_minimap()
3707 || WorkspaceSettings::get(None, cx).restore_on_startup
3708 == RestoreOnStartupBehavior::EmptyTab
3709 {
3710 return;
3711 }
3712
3713 if !self.buffer().read(cx).is_singleton() {
3714 return;
3715 }
3716
3717 let display_snapshot = self
3718 .display_map
3719 .update(cx, |display_map, cx| display_map.snapshot(cx));
3720 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3721 return;
3722 };
3723 let inmemory_folds = display_snapshot
3724 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3725 .map(|fold| {
3726 fold.range.start.text_anchor.to_point(&snapshot)
3727 ..fold.range.end.text_anchor.to_point(&snapshot)
3728 })
3729 .collect();
3730 self.update_restoration_data(cx, |data| {
3731 data.folds = inmemory_folds;
3732 });
3733
3734 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3735 return;
3736 };
3737
3738 // Get file path for path-based fold storage (survives tab close)
3739 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3740 project::File::from_dyn(buffer.read(cx).file())
3741 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3742 }) else {
3743 return;
3744 };
3745
3746 let background_executor = cx.background_executor().clone();
3747 const FINGERPRINT_LEN: usize = 32;
3748 let db_folds = display_snapshot
3749 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3750 .map(|fold| {
3751 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3752 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3753
3754 // Extract fingerprints - content at fold boundaries for validation on restore
3755 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3756 // content that might change independently.
3757 // start_fp: first min(32, fold_len) bytes of fold content
3758 // end_fp: last min(32, fold_len) bytes of fold content
3759 // Clip to character boundaries to handle multibyte UTF-8 characters.
3760 let fold_len = end - start;
3761 let start_fp_end = snapshot
3762 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3763 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3764 let end_fp_start = snapshot
3765 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3766 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3767
3768 (start, end, start_fp, end_fp)
3769 })
3770 .collect::<Vec<_>>();
3771 self.serialize_folds = cx.background_spawn(async move {
3772 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3773 if db_folds.is_empty() {
3774 // No folds - delete any persisted folds for this file
3775 DB.delete_file_folds(workspace_id, file_path)
3776 .await
3777 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3778 .log_err();
3779 } else {
3780 DB.save_file_folds(workspace_id, file_path, db_folds)
3781 .await
3782 .with_context(|| {
3783 format!("persisting file folds for workspace {workspace_id:?}")
3784 })
3785 .log_err();
3786 }
3787 });
3788 }
3789
3790 pub fn sync_selections(
3791 &mut self,
3792 other: Entity<Editor>,
3793 cx: &mut Context<Self>,
3794 ) -> gpui::Subscription {
3795 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3796 if !other_selections.is_empty() {
3797 self.selections
3798 .change_with(&self.display_snapshot(cx), |selections| {
3799 selections.select_anchors(other_selections);
3800 });
3801 }
3802
3803 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3804 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3805 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3806 if other_selections.is_empty() {
3807 return;
3808 }
3809 let snapshot = this.display_snapshot(cx);
3810 this.selections.change_with(&snapshot, |selections| {
3811 selections.select_anchors(other_selections);
3812 });
3813 }
3814 });
3815
3816 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3817 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3818 let these_selections = this.selections.disjoint_anchors().to_vec();
3819 if these_selections.is_empty() {
3820 return;
3821 }
3822 other.update(cx, |other_editor, cx| {
3823 let snapshot = other_editor.display_snapshot(cx);
3824 other_editor
3825 .selections
3826 .change_with(&snapshot, |selections| {
3827 selections.select_anchors(these_selections);
3828 })
3829 });
3830 }
3831 });
3832
3833 Subscription::join(other_subscription, this_subscription)
3834 }
3835
3836 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3837 if self.buffer().read(cx).is_singleton() {
3838 return;
3839 }
3840 let snapshot = self.buffer.read(cx).snapshot(cx);
3841 let buffer_ids: HashSet<BufferId> = self
3842 .selections
3843 .disjoint_anchor_ranges()
3844 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3845 .collect();
3846 for buffer_id in buffer_ids {
3847 self.unfold_buffer(buffer_id, cx);
3848 }
3849 }
3850
3851 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3852 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3853 /// effects of selection change occur at the end of the transaction.
3854 pub fn change_selections<R>(
3855 &mut self,
3856 effects: SelectionEffects,
3857 window: &mut Window,
3858 cx: &mut Context<Self>,
3859 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3860 ) -> R {
3861 let snapshot = self.display_snapshot(cx);
3862 if let Some(state) = &mut self.deferred_selection_effects_state {
3863 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3864 state.effects.completions = effects.completions;
3865 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3866 let (changed, result) = self.selections.change_with(&snapshot, change);
3867 state.changed |= changed;
3868 return result;
3869 }
3870 let mut state = DeferredSelectionEffectsState {
3871 changed: false,
3872 effects,
3873 old_cursor_position: self.selections.newest_anchor().head(),
3874 history_entry: SelectionHistoryEntry {
3875 selections: self.selections.disjoint_anchors_arc(),
3876 select_next_state: self.select_next_state.clone(),
3877 select_prev_state: self.select_prev_state.clone(),
3878 add_selections_state: self.add_selections_state.clone(),
3879 },
3880 };
3881 let (changed, result) = self.selections.change_with(&snapshot, change);
3882 state.changed = state.changed || changed;
3883 if self.defer_selection_effects {
3884 self.deferred_selection_effects_state = Some(state);
3885 } else {
3886 self.apply_selection_effects(state, window, cx);
3887 }
3888 result
3889 }
3890
3891 /// Defers the effects of selection change, so that the effects of multiple calls to
3892 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3893 /// to selection history and the state of popovers based on selection position aren't
3894 /// erroneously updated.
3895 pub fn with_selection_effects_deferred<R>(
3896 &mut self,
3897 window: &mut Window,
3898 cx: &mut Context<Self>,
3899 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3900 ) -> R {
3901 let already_deferred = self.defer_selection_effects;
3902 self.defer_selection_effects = true;
3903 let result = update(self, window, cx);
3904 if !already_deferred {
3905 self.defer_selection_effects = false;
3906 if let Some(state) = self.deferred_selection_effects_state.take() {
3907 self.apply_selection_effects(state, window, cx);
3908 }
3909 }
3910 result
3911 }
3912
3913 fn apply_selection_effects(
3914 &mut self,
3915 state: DeferredSelectionEffectsState,
3916 window: &mut Window,
3917 cx: &mut Context<Self>,
3918 ) {
3919 if state.changed {
3920 self.selection_history.push(state.history_entry);
3921
3922 if let Some(autoscroll) = state.effects.scroll {
3923 self.request_autoscroll(autoscroll, cx);
3924 }
3925
3926 let old_cursor_position = &state.old_cursor_position;
3927
3928 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3929
3930 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3931 self.show_signature_help_auto(window, cx);
3932 }
3933 }
3934 }
3935
3936 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3937 where
3938 I: IntoIterator<Item = (Range<S>, T)>,
3939 S: ToOffset,
3940 T: Into<Arc<str>>,
3941 {
3942 if self.read_only(cx) {
3943 return;
3944 }
3945
3946 self.buffer
3947 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3948 }
3949
3950 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3951 where
3952 I: IntoIterator<Item = (Range<S>, T)>,
3953 S: ToOffset,
3954 T: Into<Arc<str>>,
3955 {
3956 if self.read_only(cx) {
3957 return;
3958 }
3959
3960 self.buffer.update(cx, |buffer, cx| {
3961 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3962 });
3963 }
3964
3965 pub fn edit_with_block_indent<I, S, T>(
3966 &mut self,
3967 edits: I,
3968 original_indent_columns: Vec<Option<u32>>,
3969 cx: &mut Context<Self>,
3970 ) where
3971 I: IntoIterator<Item = (Range<S>, T)>,
3972 S: ToOffset,
3973 T: Into<Arc<str>>,
3974 {
3975 if self.read_only(cx) {
3976 return;
3977 }
3978
3979 self.buffer.update(cx, |buffer, cx| {
3980 buffer.edit(
3981 edits,
3982 Some(AutoindentMode::Block {
3983 original_indent_columns,
3984 }),
3985 cx,
3986 )
3987 });
3988 }
3989
3990 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3991 self.hide_context_menu(window, cx);
3992
3993 match phase {
3994 SelectPhase::Begin {
3995 position,
3996 add,
3997 click_count,
3998 } => self.begin_selection(position, add, click_count, window, cx),
3999 SelectPhase::BeginColumnar {
4000 position,
4001 goal_column,
4002 reset,
4003 mode,
4004 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4005 SelectPhase::Extend {
4006 position,
4007 click_count,
4008 } => self.extend_selection(position, click_count, window, cx),
4009 SelectPhase::Update {
4010 position,
4011 goal_column,
4012 scroll_delta,
4013 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4014 SelectPhase::End => self.end_selection(window, cx),
4015 }
4016 }
4017
4018 fn extend_selection(
4019 &mut self,
4020 position: DisplayPoint,
4021 click_count: usize,
4022 window: &mut Window,
4023 cx: &mut Context<Self>,
4024 ) {
4025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4026 let tail = self
4027 .selections
4028 .newest::<MultiBufferOffset>(&display_map)
4029 .tail();
4030 let click_count = click_count.max(match self.selections.select_mode() {
4031 SelectMode::Character => 1,
4032 SelectMode::Word(_) => 2,
4033 SelectMode::Line(_) => 3,
4034 SelectMode::All => 4,
4035 });
4036 self.begin_selection(position, false, click_count, window, cx);
4037
4038 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4039
4040 let current_selection = match self.selections.select_mode() {
4041 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4042 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4043 };
4044
4045 let mut pending_selection = self
4046 .selections
4047 .pending_anchor()
4048 .cloned()
4049 .expect("extend_selection not called with pending selection");
4050
4051 if pending_selection
4052 .start
4053 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4054 == Ordering::Greater
4055 {
4056 pending_selection.start = current_selection.start;
4057 }
4058 if pending_selection
4059 .end
4060 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4061 == Ordering::Less
4062 {
4063 pending_selection.end = current_selection.end;
4064 pending_selection.reversed = true;
4065 }
4066
4067 let mut pending_mode = self.selections.pending_mode().unwrap();
4068 match &mut pending_mode {
4069 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4070 _ => {}
4071 }
4072
4073 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4074 SelectionEffects::scroll(Autoscroll::fit())
4075 } else {
4076 SelectionEffects::no_scroll()
4077 };
4078
4079 self.change_selections(effects, window, cx, |s| {
4080 s.set_pending(pending_selection.clone(), pending_mode);
4081 s.set_is_extending(true);
4082 });
4083 }
4084
4085 fn begin_selection(
4086 &mut self,
4087 position: DisplayPoint,
4088 add: bool,
4089 click_count: usize,
4090 window: &mut Window,
4091 cx: &mut Context<Self>,
4092 ) {
4093 if !self.focus_handle.is_focused(window) {
4094 self.last_focused_descendant = None;
4095 window.focus(&self.focus_handle, cx);
4096 }
4097
4098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4099 let buffer = display_map.buffer_snapshot();
4100 let position = display_map.clip_point(position, Bias::Left);
4101
4102 let start;
4103 let end;
4104 let mode;
4105 let mut auto_scroll;
4106 match click_count {
4107 1 => {
4108 start = buffer.anchor_before(position.to_point(&display_map));
4109 end = start;
4110 mode = SelectMode::Character;
4111 auto_scroll = true;
4112 }
4113 2 => {
4114 let position = display_map
4115 .clip_point(position, Bias::Left)
4116 .to_offset(&display_map, Bias::Left);
4117 let (range, _) = buffer.surrounding_word(position, None);
4118 start = buffer.anchor_before(range.start);
4119 end = buffer.anchor_before(range.end);
4120 mode = SelectMode::Word(start..end);
4121 auto_scroll = true;
4122 }
4123 3 => {
4124 let position = display_map
4125 .clip_point(position, Bias::Left)
4126 .to_point(&display_map);
4127 let line_start = display_map.prev_line_boundary(position).0;
4128 let next_line_start = buffer.clip_point(
4129 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4130 Bias::Left,
4131 );
4132 start = buffer.anchor_before(line_start);
4133 end = buffer.anchor_before(next_line_start);
4134 mode = SelectMode::Line(start..end);
4135 auto_scroll = true;
4136 }
4137 _ => {
4138 start = buffer.anchor_before(MultiBufferOffset(0));
4139 end = buffer.anchor_before(buffer.len());
4140 mode = SelectMode::All;
4141 auto_scroll = false;
4142 }
4143 }
4144 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4145
4146 let point_to_delete: Option<usize> = {
4147 let selected_points: Vec<Selection<Point>> =
4148 self.selections.disjoint_in_range(start..end, &display_map);
4149
4150 if !add || click_count > 1 {
4151 None
4152 } else if !selected_points.is_empty() {
4153 Some(selected_points[0].id)
4154 } else {
4155 let clicked_point_already_selected =
4156 self.selections.disjoint_anchors().iter().find(|selection| {
4157 selection.start.to_point(buffer) == start.to_point(buffer)
4158 || selection.end.to_point(buffer) == end.to_point(buffer)
4159 });
4160
4161 clicked_point_already_selected.map(|selection| selection.id)
4162 }
4163 };
4164
4165 let selections_count = self.selections.count();
4166 let effects = if auto_scroll {
4167 SelectionEffects::default()
4168 } else {
4169 SelectionEffects::no_scroll()
4170 };
4171
4172 self.change_selections(effects, window, cx, |s| {
4173 if let Some(point_to_delete) = point_to_delete {
4174 s.delete(point_to_delete);
4175
4176 if selections_count == 1 {
4177 s.set_pending_anchor_range(start..end, mode);
4178 }
4179 } else {
4180 if !add {
4181 s.clear_disjoint();
4182 }
4183
4184 s.set_pending_anchor_range(start..end, mode);
4185 }
4186 });
4187 }
4188
4189 fn begin_columnar_selection(
4190 &mut self,
4191 position: DisplayPoint,
4192 goal_column: u32,
4193 reset: bool,
4194 mode: ColumnarMode,
4195 window: &mut Window,
4196 cx: &mut Context<Self>,
4197 ) {
4198 if !self.focus_handle.is_focused(window) {
4199 self.last_focused_descendant = None;
4200 window.focus(&self.focus_handle, cx);
4201 }
4202
4203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4204
4205 if reset {
4206 let pointer_position = display_map
4207 .buffer_snapshot()
4208 .anchor_before(position.to_point(&display_map));
4209
4210 self.change_selections(
4211 SelectionEffects::scroll(Autoscroll::newest()),
4212 window,
4213 cx,
4214 |s| {
4215 s.clear_disjoint();
4216 s.set_pending_anchor_range(
4217 pointer_position..pointer_position,
4218 SelectMode::Character,
4219 );
4220 },
4221 );
4222 };
4223
4224 let tail = self.selections.newest::<Point>(&display_map).tail();
4225 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4226 self.columnar_selection_state = match mode {
4227 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4228 selection_tail: selection_anchor,
4229 display_point: if reset {
4230 if position.column() != goal_column {
4231 Some(DisplayPoint::new(position.row(), goal_column))
4232 } else {
4233 None
4234 }
4235 } else {
4236 None
4237 },
4238 }),
4239 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4240 selection_tail: selection_anchor,
4241 }),
4242 };
4243
4244 if !reset {
4245 self.select_columns(position, goal_column, &display_map, window, cx);
4246 }
4247 }
4248
4249 fn update_selection(
4250 &mut self,
4251 position: DisplayPoint,
4252 goal_column: u32,
4253 scroll_delta: gpui::Point<f32>,
4254 window: &mut Window,
4255 cx: &mut Context<Self>,
4256 ) {
4257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4258
4259 if self.columnar_selection_state.is_some() {
4260 self.select_columns(position, goal_column, &display_map, window, cx);
4261 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4262 let buffer = display_map.buffer_snapshot();
4263 let head;
4264 let tail;
4265 let mode = self.selections.pending_mode().unwrap();
4266 match &mode {
4267 SelectMode::Character => {
4268 head = position.to_point(&display_map);
4269 tail = pending.tail().to_point(buffer);
4270 }
4271 SelectMode::Word(original_range) => {
4272 let offset = display_map
4273 .clip_point(position, Bias::Left)
4274 .to_offset(&display_map, Bias::Left);
4275 let original_range = original_range.to_offset(buffer);
4276
4277 let head_offset = if buffer.is_inside_word(offset, None)
4278 || original_range.contains(&offset)
4279 {
4280 let (word_range, _) = buffer.surrounding_word(offset, None);
4281 if word_range.start < original_range.start {
4282 word_range.start
4283 } else {
4284 word_range.end
4285 }
4286 } else {
4287 offset
4288 };
4289
4290 head = head_offset.to_point(buffer);
4291 if head_offset <= original_range.start {
4292 tail = original_range.end.to_point(buffer);
4293 } else {
4294 tail = original_range.start.to_point(buffer);
4295 }
4296 }
4297 SelectMode::Line(original_range) => {
4298 let original_range = original_range.to_point(display_map.buffer_snapshot());
4299
4300 let position = display_map
4301 .clip_point(position, Bias::Left)
4302 .to_point(&display_map);
4303 let line_start = display_map.prev_line_boundary(position).0;
4304 let next_line_start = buffer.clip_point(
4305 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4306 Bias::Left,
4307 );
4308
4309 if line_start < original_range.start {
4310 head = line_start
4311 } else {
4312 head = next_line_start
4313 }
4314
4315 if head <= original_range.start {
4316 tail = original_range.end;
4317 } else {
4318 tail = original_range.start;
4319 }
4320 }
4321 SelectMode::All => {
4322 return;
4323 }
4324 };
4325
4326 if head < tail {
4327 pending.start = buffer.anchor_before(head);
4328 pending.end = buffer.anchor_before(tail);
4329 pending.reversed = true;
4330 } else {
4331 pending.start = buffer.anchor_before(tail);
4332 pending.end = buffer.anchor_before(head);
4333 pending.reversed = false;
4334 }
4335
4336 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4337 s.set_pending(pending.clone(), mode);
4338 });
4339 } else {
4340 log::error!("update_selection dispatched with no pending selection");
4341 return;
4342 }
4343
4344 self.apply_scroll_delta(scroll_delta, window, cx);
4345 cx.notify();
4346 }
4347
4348 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4349 self.columnar_selection_state.take();
4350 if let Some(pending_mode) = self.selections.pending_mode() {
4351 let selections = self
4352 .selections
4353 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4354 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4355 s.select(selections);
4356 s.clear_pending();
4357 if s.is_extending() {
4358 s.set_is_extending(false);
4359 } else {
4360 s.set_select_mode(pending_mode);
4361 }
4362 });
4363 }
4364 }
4365
4366 fn select_columns(
4367 &mut self,
4368 head: DisplayPoint,
4369 goal_column: u32,
4370 display_map: &DisplaySnapshot,
4371 window: &mut Window,
4372 cx: &mut Context<Self>,
4373 ) {
4374 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4375 return;
4376 };
4377
4378 let tail = match columnar_state {
4379 ColumnarSelectionState::FromMouse {
4380 selection_tail,
4381 display_point,
4382 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4383 ColumnarSelectionState::FromSelection { selection_tail } => {
4384 selection_tail.to_display_point(display_map)
4385 }
4386 };
4387
4388 let start_row = cmp::min(tail.row(), head.row());
4389 let end_row = cmp::max(tail.row(), head.row());
4390 let start_column = cmp::min(tail.column(), goal_column);
4391 let end_column = cmp::max(tail.column(), goal_column);
4392 let reversed = start_column < tail.column();
4393
4394 let selection_ranges = (start_row.0..=end_row.0)
4395 .map(DisplayRow)
4396 .filter_map(|row| {
4397 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4398 || start_column <= display_map.line_len(row))
4399 && !display_map.is_block_line(row)
4400 {
4401 let start = display_map
4402 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4403 .to_point(display_map);
4404 let end = display_map
4405 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4406 .to_point(display_map);
4407 if reversed {
4408 Some(end..start)
4409 } else {
4410 Some(start..end)
4411 }
4412 } else {
4413 None
4414 }
4415 })
4416 .collect::<Vec<_>>();
4417 if selection_ranges.is_empty() {
4418 return;
4419 }
4420
4421 let ranges = match columnar_state {
4422 ColumnarSelectionState::FromMouse { .. } => {
4423 let mut non_empty_ranges = selection_ranges
4424 .iter()
4425 .filter(|selection_range| selection_range.start != selection_range.end)
4426 .peekable();
4427 if non_empty_ranges.peek().is_some() {
4428 non_empty_ranges.cloned().collect()
4429 } else {
4430 selection_ranges
4431 }
4432 }
4433 _ => selection_ranges,
4434 };
4435
4436 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4437 s.select_ranges(ranges);
4438 });
4439 cx.notify();
4440 }
4441
4442 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4443 self.selections
4444 .all_adjusted(snapshot)
4445 .iter()
4446 .any(|selection| !selection.is_empty())
4447 }
4448
4449 pub fn has_pending_nonempty_selection(&self) -> bool {
4450 let pending_nonempty_selection = match self.selections.pending_anchor() {
4451 Some(Selection { start, end, .. }) => start != end,
4452 None => false,
4453 };
4454
4455 pending_nonempty_selection
4456 || (self.columnar_selection_state.is_some()
4457 && self.selections.disjoint_anchors().len() > 1)
4458 }
4459
4460 pub fn has_pending_selection(&self) -> bool {
4461 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4462 }
4463
4464 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4465 self.selection_mark_mode = false;
4466 self.selection_drag_state = SelectionDragState::None;
4467
4468 if self.dismiss_menus_and_popups(true, window, cx) {
4469 cx.notify();
4470 return;
4471 }
4472 if self.clear_expanded_diff_hunks(cx) {
4473 cx.notify();
4474 return;
4475 }
4476 if self.show_git_blame_gutter {
4477 self.show_git_blame_gutter = false;
4478 cx.notify();
4479 return;
4480 }
4481
4482 if self.mode.is_full()
4483 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4484 {
4485 cx.notify();
4486 return;
4487 }
4488
4489 cx.propagate();
4490 }
4491
4492 pub fn dismiss_menus_and_popups(
4493 &mut self,
4494 is_user_requested: bool,
4495 window: &mut Window,
4496 cx: &mut Context<Self>,
4497 ) -> bool {
4498 let mut dismissed = false;
4499
4500 dismissed |= self.take_rename(false, window, cx).is_some();
4501 dismissed |= self.hide_blame_popover(true, cx);
4502 dismissed |= hide_hover(self, cx);
4503 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4504 dismissed |= self.hide_context_menu(window, cx).is_some();
4505 dismissed |= self.mouse_context_menu.take().is_some();
4506 dismissed |= is_user_requested
4507 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4508 dismissed |= self.snippet_stack.pop().is_some();
4509 if self.diff_review_drag_state.is_some() {
4510 self.cancel_diff_review_drag(cx);
4511 dismissed = true;
4512 }
4513 if !self.diff_review_overlays.is_empty() {
4514 self.dismiss_all_diff_review_overlays(cx);
4515 dismissed = true;
4516 }
4517
4518 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4519 self.dismiss_diagnostics(cx);
4520 dismissed = true;
4521 }
4522
4523 dismissed
4524 }
4525
4526 fn linked_editing_ranges_for(
4527 &self,
4528 selection: Range<text::Anchor>,
4529 cx: &App,
4530 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4531 if self.linked_edit_ranges.is_empty() {
4532 return None;
4533 }
4534 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4535 selection.end.buffer_id.and_then(|end_buffer_id| {
4536 if selection.start.buffer_id != Some(end_buffer_id) {
4537 return None;
4538 }
4539 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4540 let snapshot = buffer.read(cx).snapshot();
4541 self.linked_edit_ranges
4542 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4543 .map(|ranges| (ranges, snapshot, buffer))
4544 })?;
4545 use text::ToOffset as TO;
4546 // find offset from the start of current range to current cursor position
4547 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4548
4549 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4550 let start_difference = start_offset - start_byte_offset;
4551 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4552 let end_difference = end_offset - start_byte_offset;
4553
4554 // Current range has associated linked ranges.
4555 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4556 for range in linked_ranges.iter() {
4557 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4558 let end_offset = start_offset + end_difference;
4559 let start_offset = start_offset + start_difference;
4560 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4561 continue;
4562 }
4563 if self.selections.disjoint_anchor_ranges().any(|s| {
4564 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4565 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4566 {
4567 return false;
4568 }
4569 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4570 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4571 }) {
4572 continue;
4573 }
4574 let start = buffer_snapshot.anchor_after(start_offset);
4575 let end = buffer_snapshot.anchor_after(end_offset);
4576 linked_edits
4577 .entry(buffer.clone())
4578 .or_default()
4579 .push(start..end);
4580 }
4581 Some(linked_edits)
4582 }
4583
4584 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4585 let text: Arc<str> = text.into();
4586
4587 if self.read_only(cx) {
4588 return;
4589 }
4590
4591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4592
4593 self.unfold_buffers_with_selections(cx);
4594
4595 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4596 let mut bracket_inserted = false;
4597 let mut edits = Vec::new();
4598 let mut linked_edits = LinkedEdits::new();
4599 let mut new_selections = Vec::with_capacity(selections.len());
4600 let mut new_autoclose_regions = Vec::new();
4601 let snapshot = self.buffer.read(cx).read(cx);
4602 let mut clear_linked_edit_ranges = false;
4603 let mut all_selections_read_only = true;
4604 let mut has_adjacent_edits = false;
4605 let mut in_adjacent_group = false;
4606
4607 let mut regions = self
4608 .selections_with_autoclose_regions(selections, &snapshot)
4609 .peekable();
4610
4611 while let Some((selection, autoclose_region)) = regions.next() {
4612 if snapshot
4613 .point_to_buffer_point(selection.head())
4614 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4615 {
4616 continue;
4617 }
4618 if snapshot
4619 .point_to_buffer_point(selection.tail())
4620 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4621 {
4622 // note, ideally we'd clip the tail to the closest writeable region towards the head
4623 continue;
4624 }
4625 all_selections_read_only = false;
4626
4627 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4628 // Determine if the inserted text matches the opening or closing
4629 // bracket of any of this language's bracket pairs.
4630 let mut bracket_pair = None;
4631 let mut is_bracket_pair_start = false;
4632 let mut is_bracket_pair_end = false;
4633 if !text.is_empty() {
4634 let mut bracket_pair_matching_end = None;
4635 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4636 // and they are removing the character that triggered IME popup.
4637 for (pair, enabled) in scope.brackets() {
4638 if !pair.close && !pair.surround {
4639 continue;
4640 }
4641
4642 if enabled && pair.start.ends_with(text.as_ref()) {
4643 let prefix_len = pair.start.len() - text.len();
4644 let preceding_text_matches_prefix = prefix_len == 0
4645 || (selection.start.column >= (prefix_len as u32)
4646 && snapshot.contains_str_at(
4647 Point::new(
4648 selection.start.row,
4649 selection.start.column - (prefix_len as u32),
4650 ),
4651 &pair.start[..prefix_len],
4652 ));
4653 if preceding_text_matches_prefix {
4654 bracket_pair = Some(pair.clone());
4655 is_bracket_pair_start = true;
4656 break;
4657 }
4658 }
4659 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4660 {
4661 // take first bracket pair matching end, but don't break in case a later bracket
4662 // pair matches start
4663 bracket_pair_matching_end = Some(pair.clone());
4664 }
4665 }
4666 if let Some(end) = bracket_pair_matching_end
4667 && bracket_pair.is_none()
4668 {
4669 bracket_pair = Some(end);
4670 is_bracket_pair_end = true;
4671 }
4672 }
4673
4674 if let Some(bracket_pair) = bracket_pair {
4675 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4676 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4677 let auto_surround =
4678 self.use_auto_surround && snapshot_settings.use_auto_surround;
4679 if selection.is_empty() {
4680 if is_bracket_pair_start {
4681 // If the inserted text is a suffix of an opening bracket and the
4682 // selection is preceded by the rest of the opening bracket, then
4683 // insert the closing bracket.
4684 let following_text_allows_autoclose = snapshot
4685 .chars_at(selection.start)
4686 .next()
4687 .is_none_or(|c| scope.should_autoclose_before(c));
4688
4689 let preceding_text_allows_autoclose = selection.start.column == 0
4690 || snapshot
4691 .reversed_chars_at(selection.start)
4692 .next()
4693 .is_none_or(|c| {
4694 bracket_pair.start != bracket_pair.end
4695 || !snapshot
4696 .char_classifier_at(selection.start)
4697 .is_word(c)
4698 });
4699
4700 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4701 && bracket_pair.start.len() == 1
4702 {
4703 let target = bracket_pair.start.chars().next().unwrap();
4704 let mut byte_offset = 0u32;
4705 let current_line_count = snapshot
4706 .reversed_chars_at(selection.start)
4707 .take_while(|&c| c != '\n')
4708 .filter(|c| {
4709 byte_offset += c.len_utf8() as u32;
4710 if *c != target {
4711 return false;
4712 }
4713
4714 let point = Point::new(
4715 selection.start.row,
4716 selection.start.column.saturating_sub(byte_offset),
4717 );
4718
4719 let is_enabled = snapshot
4720 .language_scope_at(point)
4721 .and_then(|scope| {
4722 scope
4723 .brackets()
4724 .find(|(pair, _)| {
4725 pair.start == bracket_pair.start
4726 })
4727 .map(|(_, enabled)| enabled)
4728 })
4729 .unwrap_or(true);
4730
4731 let is_delimiter = snapshot
4732 .language_scope_at(Point::new(
4733 point.row,
4734 point.column + 1,
4735 ))
4736 .and_then(|scope| {
4737 scope
4738 .brackets()
4739 .find(|(pair, _)| {
4740 pair.start == bracket_pair.start
4741 })
4742 .map(|(_, enabled)| !enabled)
4743 })
4744 .unwrap_or(false);
4745
4746 is_enabled && !is_delimiter
4747 })
4748 .count();
4749 current_line_count % 2 == 1
4750 } else {
4751 false
4752 };
4753
4754 if autoclose
4755 && bracket_pair.close
4756 && following_text_allows_autoclose
4757 && preceding_text_allows_autoclose
4758 && !is_closing_quote
4759 {
4760 let anchor = snapshot.anchor_before(selection.end);
4761 new_selections.push((selection.map(|_| anchor), text.len()));
4762 new_autoclose_regions.push((
4763 anchor,
4764 text.len(),
4765 selection.id,
4766 bracket_pair.clone(),
4767 ));
4768 edits.push((
4769 selection.range(),
4770 format!("{}{}", text, bracket_pair.end).into(),
4771 ));
4772 bracket_inserted = true;
4773 continue;
4774 }
4775 }
4776
4777 if let Some(region) = autoclose_region {
4778 // If the selection is followed by an auto-inserted closing bracket,
4779 // then don't insert that closing bracket again; just move the selection
4780 // past the closing bracket.
4781 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4782 && text.as_ref() == region.pair.end.as_str()
4783 && snapshot.contains_str_at(region.range.end, text.as_ref());
4784 if should_skip {
4785 let anchor = snapshot.anchor_after(selection.end);
4786 new_selections
4787 .push((selection.map(|_| anchor), region.pair.end.len()));
4788 continue;
4789 }
4790 }
4791
4792 let always_treat_brackets_as_autoclosed = snapshot
4793 .language_settings_at(selection.start, cx)
4794 .always_treat_brackets_as_autoclosed;
4795 if always_treat_brackets_as_autoclosed
4796 && is_bracket_pair_end
4797 && snapshot.contains_str_at(selection.end, text.as_ref())
4798 {
4799 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4800 // and the inserted text is a closing bracket and the selection is followed
4801 // by the closing bracket then move the selection past the closing bracket.
4802 let anchor = snapshot.anchor_after(selection.end);
4803 new_selections.push((selection.map(|_| anchor), text.len()));
4804 continue;
4805 }
4806 }
4807 // If an opening bracket is 1 character long and is typed while
4808 // text is selected, then surround that text with the bracket pair.
4809 else if auto_surround
4810 && bracket_pair.surround
4811 && is_bracket_pair_start
4812 && bracket_pair.start.chars().count() == 1
4813 {
4814 edits.push((selection.start..selection.start, text.clone()));
4815 edits.push((
4816 selection.end..selection.end,
4817 bracket_pair.end.as_str().into(),
4818 ));
4819 bracket_inserted = true;
4820 new_selections.push((
4821 Selection {
4822 id: selection.id,
4823 start: snapshot.anchor_after(selection.start),
4824 end: snapshot.anchor_before(selection.end),
4825 reversed: selection.reversed,
4826 goal: selection.goal,
4827 },
4828 0,
4829 ));
4830 continue;
4831 }
4832 }
4833 }
4834
4835 if self.auto_replace_emoji_shortcode
4836 && selection.is_empty()
4837 && text.as_ref().ends_with(':')
4838 && let Some(possible_emoji_short_code) =
4839 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4840 && !possible_emoji_short_code.is_empty()
4841 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4842 {
4843 let emoji_shortcode_start = Point::new(
4844 selection.start.row,
4845 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4846 );
4847
4848 // Remove shortcode from buffer
4849 edits.push((
4850 emoji_shortcode_start..selection.start,
4851 "".to_string().into(),
4852 ));
4853 new_selections.push((
4854 Selection {
4855 id: selection.id,
4856 start: snapshot.anchor_after(emoji_shortcode_start),
4857 end: snapshot.anchor_before(selection.start),
4858 reversed: selection.reversed,
4859 goal: selection.goal,
4860 },
4861 0,
4862 ));
4863
4864 // Insert emoji
4865 let selection_start_anchor = snapshot.anchor_after(selection.start);
4866 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4867 edits.push((selection.start..selection.end, emoji.to_string().into()));
4868
4869 continue;
4870 }
4871
4872 let next_is_adjacent = regions
4873 .peek()
4874 .is_some_and(|(next, _)| selection.end == next.start);
4875
4876 // If not handling any auto-close operation, then just replace the selected
4877 // text with the given input and move the selection to the end of the
4878 // newly inserted text.
4879 let anchor = if in_adjacent_group || next_is_adjacent {
4880 // After edits the right bias would shift those anchor to the next visible fragment
4881 // but we want to resolve to the previous one
4882 snapshot.anchor_before(selection.end)
4883 } else {
4884 snapshot.anchor_after(selection.end)
4885 };
4886
4887 if !self.linked_edit_ranges.is_empty() {
4888 let start_anchor = snapshot.anchor_before(selection.start);
4889
4890 let is_word_char = text.chars().next().is_none_or(|char| {
4891 let classifier = snapshot
4892 .char_classifier_at(start_anchor.to_offset(&snapshot))
4893 .scope_context(Some(CharScopeContext::LinkedEdit));
4894 classifier.is_word(char)
4895 });
4896
4897 if is_word_char {
4898 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
4899 linked_edits.push(&self, anchor_range, text.clone(), cx);
4900 } else {
4901 clear_linked_edit_ranges = true;
4902 }
4903 }
4904
4905 new_selections.push((selection.map(|_| anchor), 0));
4906 edits.push((selection.start..selection.end, text.clone()));
4907
4908 has_adjacent_edits |= next_is_adjacent;
4909 in_adjacent_group = next_is_adjacent;
4910 }
4911
4912 if all_selections_read_only {
4913 return;
4914 }
4915
4916 drop(regions);
4917 drop(snapshot);
4918
4919 self.transact(window, cx, |this, window, cx| {
4920 if clear_linked_edit_ranges {
4921 this.linked_edit_ranges.clear();
4922 }
4923 let initial_buffer_versions =
4924 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4925
4926 this.buffer.update(cx, |buffer, cx| {
4927 if has_adjacent_edits {
4928 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4929 } else {
4930 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4931 }
4932 });
4933 linked_edits.apply(cx);
4934 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4935 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4936 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4937 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4938 new_anchor_selections,
4939 &map,
4940 )
4941 .zip(new_selection_deltas)
4942 .map(|(selection, delta)| Selection {
4943 id: selection.id,
4944 start: selection.start + delta,
4945 end: selection.end + delta,
4946 reversed: selection.reversed,
4947 goal: SelectionGoal::None,
4948 })
4949 .collect::<Vec<_>>();
4950
4951 let mut i = 0;
4952 for (position, delta, selection_id, pair) in new_autoclose_regions {
4953 let position = position.to_offset(map.buffer_snapshot()) + delta;
4954 let start = map.buffer_snapshot().anchor_before(position);
4955 let end = map.buffer_snapshot().anchor_after(position);
4956 while let Some(existing_state) = this.autoclose_regions.get(i) {
4957 match existing_state
4958 .range
4959 .start
4960 .cmp(&start, map.buffer_snapshot())
4961 {
4962 Ordering::Less => i += 1,
4963 Ordering::Greater => break,
4964 Ordering::Equal => {
4965 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4966 Ordering::Less => i += 1,
4967 Ordering::Equal => break,
4968 Ordering::Greater => break,
4969 }
4970 }
4971 }
4972 }
4973 this.autoclose_regions.insert(
4974 i,
4975 AutocloseRegion {
4976 selection_id,
4977 range: start..end,
4978 pair,
4979 },
4980 );
4981 }
4982
4983 let had_active_edit_prediction = this.has_active_edit_prediction();
4984 this.change_selections(
4985 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4986 window,
4987 cx,
4988 |s| s.select(new_selections),
4989 );
4990
4991 if !bracket_inserted
4992 && let Some(on_type_format_task) =
4993 this.trigger_on_type_formatting(text.to_string(), window, cx)
4994 {
4995 on_type_format_task.detach_and_log_err(cx);
4996 }
4997
4998 let editor_settings = EditorSettings::get_global(cx);
4999 if bracket_inserted
5000 && (editor_settings.auto_signature_help
5001 || editor_settings.show_signature_help_after_edits)
5002 {
5003 this.show_signature_help(&ShowSignatureHelp, window, cx);
5004 }
5005
5006 let trigger_in_words =
5007 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5008 if this.hard_wrap.is_some() {
5009 let latest: Range<Point> = this.selections.newest(&map).range();
5010 if latest.is_empty()
5011 && this
5012 .buffer()
5013 .read(cx)
5014 .snapshot(cx)
5015 .line_len(MultiBufferRow(latest.start.row))
5016 == latest.start.column
5017 {
5018 this.rewrap_impl(
5019 RewrapOptions {
5020 override_language_settings: true,
5021 preserve_existing_whitespace: true,
5022 },
5023 cx,
5024 )
5025 }
5026 }
5027 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5028 refresh_linked_ranges(this, window, cx);
5029 this.refresh_edit_prediction(true, false, window, cx);
5030 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5031 });
5032 }
5033
5034 fn find_possible_emoji_shortcode_at_position(
5035 snapshot: &MultiBufferSnapshot,
5036 position: Point,
5037 ) -> Option<String> {
5038 let mut chars = Vec::new();
5039 let mut found_colon = false;
5040 for char in snapshot.reversed_chars_at(position).take(100) {
5041 // Found a possible emoji shortcode in the middle of the buffer
5042 if found_colon {
5043 if char.is_whitespace() {
5044 chars.reverse();
5045 return Some(chars.iter().collect());
5046 }
5047 // If the previous character is not a whitespace, we are in the middle of a word
5048 // and we only want to complete the shortcode if the word is made up of other emojis
5049 let mut containing_word = String::new();
5050 for ch in snapshot
5051 .reversed_chars_at(position)
5052 .skip(chars.len() + 1)
5053 .take(100)
5054 {
5055 if ch.is_whitespace() {
5056 break;
5057 }
5058 containing_word.push(ch);
5059 }
5060 let containing_word = containing_word.chars().rev().collect::<String>();
5061 if util::word_consists_of_emojis(containing_word.as_str()) {
5062 chars.reverse();
5063 return Some(chars.iter().collect());
5064 }
5065 }
5066
5067 if char.is_whitespace() || !char.is_ascii() {
5068 return None;
5069 }
5070 if char == ':' {
5071 found_colon = true;
5072 } else {
5073 chars.push(char);
5074 }
5075 }
5076 // Found a possible emoji shortcode at the beginning of the buffer
5077 chars.reverse();
5078 Some(chars.iter().collect())
5079 }
5080
5081 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5083 self.transact(window, cx, |this, window, cx| {
5084 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5085 let selections = this
5086 .selections
5087 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5088 let multi_buffer = this.buffer.read(cx);
5089 let buffer = multi_buffer.snapshot(cx);
5090 selections
5091 .iter()
5092 .map(|selection| {
5093 let start_point = selection.start.to_point(&buffer);
5094 let mut existing_indent =
5095 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5096 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5097 let start = selection.start;
5098 let end = selection.end;
5099 let selection_is_empty = start == end;
5100 let language_scope = buffer.language_scope_at(start);
5101 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5102 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5103 &buffer,
5104 start..end,
5105 language,
5106 )
5107 || NewlineConfig::insert_extra_newline_tree_sitter(
5108 &buffer,
5109 start..end,
5110 );
5111
5112 let mut newline_config = NewlineConfig::Newline {
5113 additional_indent: IndentSize::spaces(0),
5114 extra_line_additional_indent: if needs_extra_newline {
5115 Some(IndentSize::spaces(0))
5116 } else {
5117 None
5118 },
5119 prevent_auto_indent: false,
5120 };
5121
5122 let comment_delimiter = maybe!({
5123 if !selection_is_empty {
5124 return None;
5125 }
5126
5127 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5128 return None;
5129 }
5130
5131 return comment_delimiter_for_newline(
5132 &start_point,
5133 &buffer,
5134 language,
5135 );
5136 });
5137
5138 let doc_delimiter = maybe!({
5139 if !selection_is_empty {
5140 return None;
5141 }
5142
5143 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5144 return None;
5145 }
5146
5147 return documentation_delimiter_for_newline(
5148 &start_point,
5149 &buffer,
5150 language,
5151 &mut newline_config,
5152 );
5153 });
5154
5155 let list_delimiter = maybe!({
5156 if !selection_is_empty {
5157 return None;
5158 }
5159
5160 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5161 return None;
5162 }
5163
5164 return list_delimiter_for_newline(
5165 &start_point,
5166 &buffer,
5167 language,
5168 &mut newline_config,
5169 );
5170 });
5171
5172 (
5173 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5174 newline_config,
5175 )
5176 } else {
5177 (
5178 None,
5179 NewlineConfig::Newline {
5180 additional_indent: IndentSize::spaces(0),
5181 extra_line_additional_indent: None,
5182 prevent_auto_indent: false,
5183 },
5184 )
5185 };
5186
5187 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5188 NewlineConfig::ClearCurrentLine => {
5189 let row_start =
5190 buffer.point_to_offset(Point::new(start_point.row, 0));
5191 (row_start, String::new(), false)
5192 }
5193 NewlineConfig::UnindentCurrentLine { continuation } => {
5194 let row_start =
5195 buffer.point_to_offset(Point::new(start_point.row, 0));
5196 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5197 let tab_size_indent = IndentSize::spaces(tab_size.get());
5198 let reduced_indent =
5199 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5200 let mut new_text = String::new();
5201 new_text.extend(reduced_indent.chars());
5202 new_text.push_str(continuation);
5203 (row_start, new_text, true)
5204 }
5205 NewlineConfig::Newline {
5206 additional_indent,
5207 extra_line_additional_indent,
5208 prevent_auto_indent,
5209 } => {
5210 let capacity_for_delimiter =
5211 delimiter.as_deref().map(str::len).unwrap_or_default();
5212 let extra_line_len = extra_line_additional_indent
5213 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
5214 .unwrap_or(0);
5215 let mut new_text = String::with_capacity(
5216 1 + capacity_for_delimiter
5217 + existing_indent.len as usize
5218 + additional_indent.len as usize
5219 + extra_line_len,
5220 );
5221 new_text.push('\n');
5222 new_text.extend(existing_indent.chars());
5223 new_text.extend(additional_indent.chars());
5224 if let Some(delimiter) = &delimiter {
5225 new_text.push_str(delimiter);
5226 }
5227 if let Some(extra_indent) = extra_line_additional_indent {
5228 new_text.push('\n');
5229 new_text.extend(existing_indent.chars());
5230 new_text.extend(extra_indent.chars());
5231 }
5232 (start, new_text, *prevent_auto_indent)
5233 }
5234 };
5235
5236 let anchor = buffer.anchor_after(end);
5237 let new_selection = selection.map(|_| anchor);
5238 (
5239 ((edit_start..end, new_text), prevent_auto_indent),
5240 (newline_config.has_extra_line(), new_selection),
5241 )
5242 })
5243 .unzip()
5244 };
5245
5246 let mut auto_indent_edits = Vec::new();
5247 let mut edits = Vec::new();
5248 for (edit, prevent_auto_indent) in edits_with_flags {
5249 if prevent_auto_indent {
5250 edits.push(edit);
5251 } else {
5252 auto_indent_edits.push(edit);
5253 }
5254 }
5255 if !edits.is_empty() {
5256 this.edit(edits, cx);
5257 }
5258 if !auto_indent_edits.is_empty() {
5259 this.edit_with_autoindent(auto_indent_edits, cx);
5260 }
5261
5262 let buffer = this.buffer.read(cx).snapshot(cx);
5263 let new_selections = selection_info
5264 .into_iter()
5265 .map(|(extra_newline_inserted, new_selection)| {
5266 let mut cursor = new_selection.end.to_point(&buffer);
5267 if extra_newline_inserted {
5268 cursor.row -= 1;
5269 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5270 }
5271 new_selection.map(|_| cursor)
5272 })
5273 .collect();
5274
5275 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5276 this.refresh_edit_prediction(true, false, window, cx);
5277 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5278 task.detach_and_log_err(cx);
5279 }
5280 });
5281 }
5282
5283 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5285
5286 let buffer = self.buffer.read(cx);
5287 let snapshot = buffer.snapshot(cx);
5288
5289 let mut edits = Vec::new();
5290 let mut rows = Vec::new();
5291
5292 for (rows_inserted, selection) in self
5293 .selections
5294 .all_adjusted(&self.display_snapshot(cx))
5295 .into_iter()
5296 .enumerate()
5297 {
5298 let cursor = selection.head();
5299 let row = cursor.row;
5300
5301 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5302
5303 let newline = "\n".to_string();
5304 edits.push((start_of_line..start_of_line, newline));
5305
5306 rows.push(row + rows_inserted as u32);
5307 }
5308
5309 self.transact(window, cx, |editor, window, cx| {
5310 editor.edit(edits, cx);
5311
5312 editor.change_selections(Default::default(), window, cx, |s| {
5313 let mut index = 0;
5314 s.move_cursors_with(&mut |map, _, _| {
5315 let row = rows[index];
5316 index += 1;
5317
5318 let point = Point::new(row, 0);
5319 let boundary = map.next_line_boundary(point).1;
5320 let clipped = map.clip_point(boundary, Bias::Left);
5321
5322 (clipped, SelectionGoal::None)
5323 });
5324 });
5325
5326 let mut indent_edits = Vec::new();
5327 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5328 for row in rows {
5329 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5330 for (row, indent) in indents {
5331 if indent.len == 0 {
5332 continue;
5333 }
5334
5335 let text = match indent.kind {
5336 IndentKind::Space => " ".repeat(indent.len as usize),
5337 IndentKind::Tab => "\t".repeat(indent.len as usize),
5338 };
5339 let point = Point::new(row.0, 0);
5340 indent_edits.push((point..point, text));
5341 }
5342 }
5343 editor.edit(indent_edits, cx);
5344 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5345 format.detach_and_log_err(cx);
5346 }
5347 });
5348 }
5349
5350 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5352
5353 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5354 let mut rows = Vec::new();
5355 let mut rows_inserted = 0;
5356
5357 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5358 let cursor = selection.head();
5359 let row = cursor.row;
5360
5361 let point = Point::new(row, 0);
5362 let Some((buffer_handle, buffer_point, _)) =
5363 self.buffer.read(cx).point_to_buffer_point(point, cx)
5364 else {
5365 continue;
5366 };
5367
5368 buffer_edits
5369 .entry(buffer_handle.entity_id())
5370 .or_insert_with(|| (buffer_handle, Vec::new()))
5371 .1
5372 .push(buffer_point);
5373
5374 rows_inserted += 1;
5375 rows.push(row + rows_inserted);
5376 }
5377
5378 self.transact(window, cx, |editor, window, cx| {
5379 for (_, (buffer_handle, points)) in &buffer_edits {
5380 buffer_handle.update(cx, |buffer, cx| {
5381 let edits: Vec<_> = points
5382 .iter()
5383 .map(|point| {
5384 let target = Point::new(point.row + 1, 0);
5385 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5386 (start_of_line..start_of_line, "\n")
5387 })
5388 .collect();
5389 buffer.edit(edits, None, cx);
5390 });
5391 }
5392
5393 editor.change_selections(Default::default(), window, cx, |s| {
5394 let mut index = 0;
5395 s.move_cursors_with(&mut |map, _, _| {
5396 let row = rows[index];
5397 index += 1;
5398
5399 let point = Point::new(row, 0);
5400 let boundary = map.next_line_boundary(point).1;
5401 let clipped = map.clip_point(boundary, Bias::Left);
5402
5403 (clipped, SelectionGoal::None)
5404 });
5405 });
5406
5407 let mut indent_edits = Vec::new();
5408 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5409 for row in rows {
5410 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5411 for (row, indent) in indents {
5412 if indent.len == 0 {
5413 continue;
5414 }
5415
5416 let text = match indent.kind {
5417 IndentKind::Space => " ".repeat(indent.len as usize),
5418 IndentKind::Tab => "\t".repeat(indent.len as usize),
5419 };
5420 let point = Point::new(row.0, 0);
5421 indent_edits.push((point..point, text));
5422 }
5423 }
5424 editor.edit(indent_edits, cx);
5425 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5426 format.detach_and_log_err(cx);
5427 }
5428 });
5429 }
5430
5431 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5432 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5433 original_indent_columns: Vec::new(),
5434 });
5435 self.replace_selections(text, autoindent, window, cx, false);
5436 }
5437
5438 /// Replaces the editor's selections with the provided `text`, applying the
5439 /// given `autoindent_mode` (`None` will skip autoindentation).
5440 ///
5441 /// Early returns if the editor is in read-only mode, without applying any
5442 /// edits.
5443 fn replace_selections(
5444 &mut self,
5445 text: &str,
5446 autoindent_mode: Option<AutoindentMode>,
5447 window: &mut Window,
5448 cx: &mut Context<Self>,
5449 apply_linked_edits: bool,
5450 ) {
5451 if self.read_only(cx) {
5452 return;
5453 }
5454
5455 let text: Arc<str> = text.into();
5456 self.transact(window, cx, |this, window, cx| {
5457 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5458 let linked_edits = if apply_linked_edits {
5459 this.linked_edits_for_selections(text.clone(), cx)
5460 } else {
5461 LinkedEdits::new()
5462 };
5463
5464 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5465 let anchors = {
5466 let snapshot = buffer.read(cx);
5467 old_selections
5468 .iter()
5469 .map(|s| {
5470 let anchor = snapshot.anchor_after(s.head());
5471 s.map(|_| anchor)
5472 })
5473 .collect::<Vec<_>>()
5474 };
5475 buffer.edit(
5476 old_selections
5477 .iter()
5478 .map(|s| (s.start..s.end, text.clone())),
5479 autoindent_mode,
5480 cx,
5481 );
5482 anchors
5483 });
5484
5485 linked_edits.apply(cx);
5486
5487 this.change_selections(Default::default(), window, cx, |s| {
5488 s.select_anchors(selection_anchors);
5489 });
5490
5491 if apply_linked_edits {
5492 refresh_linked_ranges(this, window, cx);
5493 }
5494
5495 cx.notify();
5496 });
5497 }
5498
5499 /// Collects linked edits for the current selections, pairing each linked
5500 /// range with `text`.
5501 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5502 let mut linked_edits = LinkedEdits::new();
5503 if !self.linked_edit_ranges.is_empty() {
5504 for selection in self.selections.disjoint_anchors() {
5505 let start = selection.start.text_anchor;
5506 let end = selection.end.text_anchor;
5507 linked_edits.push(self, start..end, text.clone(), cx);
5508 }
5509 }
5510 linked_edits
5511 }
5512
5513 /// Deletes the content covered by the current selections and applies
5514 /// linked edits.
5515 pub fn delete_selections_with_linked_edits(
5516 &mut self,
5517 window: &mut Window,
5518 cx: &mut Context<Self>,
5519 ) {
5520 self.replace_selections("", None, window, cx, true);
5521 }
5522
5523 #[cfg(any(test, feature = "test-support"))]
5524 pub fn set_linked_edit_ranges_for_testing(
5525 &mut self,
5526 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5527 cx: &mut Context<Self>,
5528 ) -> Option<()> {
5529 let Some((buffer, _)) = self
5530 .buffer
5531 .read(cx)
5532 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5533 else {
5534 return None;
5535 };
5536 let buffer = buffer.read(cx);
5537 let buffer_id = buffer.remote_id();
5538 let mut linked_ranges = Vec::with_capacity(ranges.len());
5539 for (base_range, linked_ranges_points) in ranges {
5540 let base_anchor =
5541 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5542 let linked_anchors = linked_ranges_points
5543 .into_iter()
5544 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5545 .collect();
5546 linked_ranges.push((base_anchor, linked_anchors));
5547 }
5548 let mut map = HashMap::default();
5549 map.insert(buffer_id, linked_ranges);
5550 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5551 Some(())
5552 }
5553
5554 fn trigger_completion_on_input(
5555 &mut self,
5556 text: &str,
5557 trigger_in_words: bool,
5558 window: &mut Window,
5559 cx: &mut Context<Self>,
5560 ) {
5561 let completions_source = self
5562 .context_menu
5563 .borrow()
5564 .as_ref()
5565 .and_then(|menu| match menu {
5566 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5567 CodeContextMenu::CodeActions(_) => None,
5568 });
5569
5570 match completions_source {
5571 Some(CompletionsMenuSource::Words { .. }) => {
5572 self.open_or_update_completions_menu(
5573 Some(CompletionsMenuSource::Words {
5574 ignore_threshold: false,
5575 }),
5576 None,
5577 trigger_in_words,
5578 window,
5579 cx,
5580 );
5581 }
5582 _ => self.open_or_update_completions_menu(
5583 None,
5584 Some(text.to_owned()).filter(|x| !x.is_empty()),
5585 true,
5586 window,
5587 cx,
5588 ),
5589 }
5590 }
5591
5592 /// If any empty selections is touching the start of its innermost containing autoclose
5593 /// region, expand it to select the brackets.
5594 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5595 let selections = self
5596 .selections
5597 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5598 let buffer = self.buffer.read(cx).read(cx);
5599 let new_selections = self
5600 .selections_with_autoclose_regions(selections, &buffer)
5601 .map(|(mut selection, region)| {
5602 if !selection.is_empty() {
5603 return selection;
5604 }
5605
5606 if let Some(region) = region {
5607 let mut range = region.range.to_offset(&buffer);
5608 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5609 range.start -= region.pair.start.len();
5610 if buffer.contains_str_at(range.start, ®ion.pair.start)
5611 && buffer.contains_str_at(range.end, ®ion.pair.end)
5612 {
5613 range.end += region.pair.end.len();
5614 selection.start = range.start;
5615 selection.end = range.end;
5616
5617 return selection;
5618 }
5619 }
5620 }
5621
5622 let always_treat_brackets_as_autoclosed = buffer
5623 .language_settings_at(selection.start, cx)
5624 .always_treat_brackets_as_autoclosed;
5625
5626 if !always_treat_brackets_as_autoclosed {
5627 return selection;
5628 }
5629
5630 if let Some(scope) = buffer.language_scope_at(selection.start) {
5631 for (pair, enabled) in scope.brackets() {
5632 if !enabled || !pair.close {
5633 continue;
5634 }
5635
5636 if buffer.contains_str_at(selection.start, &pair.end) {
5637 let pair_start_len = pair.start.len();
5638 if buffer.contains_str_at(
5639 selection.start.saturating_sub_usize(pair_start_len),
5640 &pair.start,
5641 ) {
5642 selection.start -= pair_start_len;
5643 selection.end += pair.end.len();
5644
5645 return selection;
5646 }
5647 }
5648 }
5649 }
5650
5651 selection
5652 })
5653 .collect();
5654
5655 drop(buffer);
5656 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5657 selections.select(new_selections)
5658 });
5659 }
5660
5661 /// Iterate the given selections, and for each one, find the smallest surrounding
5662 /// autoclose region. This uses the ordering of the selections and the autoclose
5663 /// regions to avoid repeated comparisons.
5664 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5665 &'a self,
5666 selections: impl IntoIterator<Item = Selection<D>>,
5667 buffer: &'a MultiBufferSnapshot,
5668 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5669 let mut i = 0;
5670 let mut regions = self.autoclose_regions.as_slice();
5671 selections.into_iter().map(move |selection| {
5672 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5673
5674 let mut enclosing = None;
5675 while let Some(pair_state) = regions.get(i) {
5676 if pair_state.range.end.to_offset(buffer) < range.start {
5677 regions = ®ions[i + 1..];
5678 i = 0;
5679 } else if pair_state.range.start.to_offset(buffer) > range.end {
5680 break;
5681 } else {
5682 if pair_state.selection_id == selection.id {
5683 enclosing = Some(pair_state);
5684 }
5685 i += 1;
5686 }
5687 }
5688
5689 (selection, enclosing)
5690 })
5691 }
5692
5693 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5694 fn invalidate_autoclose_regions(
5695 &mut self,
5696 mut selections: &[Selection<Anchor>],
5697 buffer: &MultiBufferSnapshot,
5698 ) {
5699 self.autoclose_regions.retain(|state| {
5700 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5701 return false;
5702 }
5703
5704 let mut i = 0;
5705 while let Some(selection) = selections.get(i) {
5706 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5707 selections = &selections[1..];
5708 continue;
5709 }
5710 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5711 break;
5712 }
5713 if selection.id == state.selection_id {
5714 return true;
5715 } else {
5716 i += 1;
5717 }
5718 }
5719 false
5720 });
5721 }
5722
5723 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5724 let offset = position.to_offset(buffer);
5725 let (word_range, kind) =
5726 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5727 if offset > word_range.start && kind == Some(CharKind::Word) {
5728 Some(
5729 buffer
5730 .text_for_range(word_range.start..offset)
5731 .collect::<String>(),
5732 )
5733 } else {
5734 None
5735 }
5736 }
5737
5738 pub fn visible_excerpts(
5739 &self,
5740 lsp_related_only: bool,
5741 cx: &mut Context<Editor>,
5742 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5743 let project = self.project().cloned();
5744 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5745 let multi_buffer = self.buffer().read(cx);
5746 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5747 let multi_buffer_visible_start = self
5748 .scroll_manager
5749 .native_anchor(&display_snapshot, cx)
5750 .anchor
5751 .to_point(&multi_buffer_snapshot);
5752 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5753 multi_buffer_visible_start
5754 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5755 Bias::Left,
5756 );
5757 multi_buffer_snapshot
5758 .range_to_buffer_ranges(multi_buffer_visible_start..=multi_buffer_visible_end)
5759 .into_iter()
5760 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5761 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5762 if !lsp_related_only {
5763 return Some((
5764 excerpt_id,
5765 (
5766 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5767 buffer.version().clone(),
5768 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5769 ),
5770 ));
5771 }
5772
5773 let project = project.as_ref()?.read(cx);
5774 let buffer_file = project::File::from_dyn(buffer.file())?;
5775 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5776 let worktree_entry = buffer_worktree
5777 .read(cx)
5778 .entry_for_id(buffer_file.project_entry_id()?)?;
5779 if worktree_entry.is_ignored {
5780 None
5781 } else {
5782 Some((
5783 excerpt_id,
5784 (
5785 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5786 buffer.version().clone(),
5787 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5788 ),
5789 ))
5790 }
5791 })
5792 .collect()
5793 }
5794
5795 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5796 TextLayoutDetails {
5797 text_system: window.text_system().clone(),
5798 editor_style: self.style.clone().unwrap(),
5799 rem_size: window.rem_size(),
5800 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5801 visible_rows: self.visible_line_count(),
5802 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5803 }
5804 }
5805
5806 fn trigger_on_type_formatting(
5807 &self,
5808 input: String,
5809 window: &mut Window,
5810 cx: &mut Context<Self>,
5811 ) -> Option<Task<Result<()>>> {
5812 if input.chars().count() != 1 {
5813 return None;
5814 }
5815
5816 let project = self.project()?;
5817 let position = self.selections.newest_anchor().head();
5818 let (buffer, buffer_position) = self
5819 .buffer
5820 .read(cx)
5821 .text_anchor_for_position(position, cx)?;
5822
5823 let settings = language_settings::language_settings(
5824 buffer
5825 .read(cx)
5826 .language_at(buffer_position)
5827 .map(|l| l.name()),
5828 buffer.read(cx).file(),
5829 cx,
5830 );
5831 if !settings.use_on_type_format {
5832 return None;
5833 }
5834
5835 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5836 // hence we do LSP request & edit on host side only — add formats to host's history.
5837 let push_to_lsp_host_history = true;
5838 // If this is not the host, append its history with new edits.
5839 let push_to_client_history = project.read(cx).is_via_collab();
5840
5841 let on_type_formatting = project.update(cx, |project, cx| {
5842 project.on_type_format(
5843 buffer.clone(),
5844 buffer_position,
5845 input,
5846 push_to_lsp_host_history,
5847 cx,
5848 )
5849 });
5850 Some(cx.spawn_in(window, async move |editor, cx| {
5851 if let Some(transaction) = on_type_formatting.await? {
5852 if push_to_client_history {
5853 buffer.update(cx, |buffer, _| {
5854 buffer.push_transaction(transaction, Instant::now());
5855 buffer.finalize_last_transaction();
5856 });
5857 }
5858 editor.update(cx, |editor, cx| {
5859 editor.refresh_document_highlights(cx);
5860 })?;
5861 }
5862 Ok(())
5863 }))
5864 }
5865
5866 pub fn show_word_completions(
5867 &mut self,
5868 _: &ShowWordCompletions,
5869 window: &mut Window,
5870 cx: &mut Context<Self>,
5871 ) {
5872 self.open_or_update_completions_menu(
5873 Some(CompletionsMenuSource::Words {
5874 ignore_threshold: true,
5875 }),
5876 None,
5877 false,
5878 window,
5879 cx,
5880 );
5881 }
5882
5883 pub fn show_completions(
5884 &mut self,
5885 _: &ShowCompletions,
5886 window: &mut Window,
5887 cx: &mut Context<Self>,
5888 ) {
5889 self.open_or_update_completions_menu(None, None, false, window, cx);
5890 }
5891
5892 fn open_or_update_completions_menu(
5893 &mut self,
5894 requested_source: Option<CompletionsMenuSource>,
5895 trigger: Option<String>,
5896 trigger_in_words: bool,
5897 window: &mut Window,
5898 cx: &mut Context<Self>,
5899 ) {
5900 if self.pending_rename.is_some() {
5901 return;
5902 }
5903
5904 let completions_source = self
5905 .context_menu
5906 .borrow()
5907 .as_ref()
5908 .and_then(|menu| match menu {
5909 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5910 CodeContextMenu::CodeActions(_) => None,
5911 });
5912
5913 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5914
5915 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5916 // inserted and selected. To handle that case, the start of the selection is used so that
5917 // the menu starts with all choices.
5918 let position = self
5919 .selections
5920 .newest_anchor()
5921 .start
5922 .bias_right(&multibuffer_snapshot);
5923 if position.diff_base_anchor.is_some() {
5924 return;
5925 }
5926 let buffer_position = multibuffer_snapshot.anchor_before(position);
5927 let Some(buffer) = buffer_position
5928 .text_anchor
5929 .buffer_id
5930 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5931 else {
5932 return;
5933 };
5934 let buffer_snapshot = buffer.read(cx).snapshot();
5935
5936 let menu_is_open = matches!(
5937 self.context_menu.borrow().as_ref(),
5938 Some(CodeContextMenu::Completions(_))
5939 );
5940
5941 let language = buffer_snapshot
5942 .language_at(buffer_position.text_anchor)
5943 .map(|language| language.name());
5944
5945 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5946 let completion_settings = language_settings.completions.clone();
5947
5948 let show_completions_on_input = self
5949 .show_completions_on_input_override
5950 .unwrap_or(language_settings.show_completions_on_input);
5951 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5952 return;
5953 }
5954
5955 let query: Option<Arc<String>> =
5956 Self::completion_query(&multibuffer_snapshot, buffer_position)
5957 .map(|query| query.into());
5958
5959 drop(multibuffer_snapshot);
5960
5961 // Hide the current completions menu when query is empty. Without this, cached
5962 // completions from before the trigger char may be reused (#32774).
5963 if query.is_none() && menu_is_open {
5964 self.hide_context_menu(window, cx);
5965 }
5966
5967 let mut ignore_word_threshold = false;
5968 let provider = match requested_source {
5969 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5970 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5971 ignore_word_threshold = ignore_threshold;
5972 None
5973 }
5974 Some(CompletionsMenuSource::SnippetChoices)
5975 | Some(CompletionsMenuSource::SnippetsOnly) => {
5976 log::error!("bug: SnippetChoices requested_source is not handled");
5977 None
5978 }
5979 };
5980
5981 let sort_completions = provider
5982 .as_ref()
5983 .is_some_and(|provider| provider.sort_completions());
5984
5985 let filter_completions = provider
5986 .as_ref()
5987 .is_none_or(|provider| provider.filter_completions());
5988
5989 let was_snippets_only = matches!(
5990 completions_source,
5991 Some(CompletionsMenuSource::SnippetsOnly)
5992 );
5993
5994 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5995 if filter_completions {
5996 menu.filter(
5997 query.clone().unwrap_or_default(),
5998 buffer_position.text_anchor,
5999 &buffer,
6000 provider.clone(),
6001 window,
6002 cx,
6003 );
6004 }
6005 // When `is_incomplete` is false, no need to re-query completions when the current query
6006 // is a suffix of the initial query.
6007 let was_complete = !menu.is_incomplete;
6008 if was_complete && !was_snippets_only {
6009 // If the new query is a suffix of the old query (typing more characters) and
6010 // the previous result was complete, the existing completions can be filtered.
6011 //
6012 // Note that snippet completions are always complete.
6013 let query_matches = match (&menu.initial_query, &query) {
6014 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6015 (None, _) => true,
6016 _ => false,
6017 };
6018 if query_matches {
6019 let position_matches = if menu.initial_position == position {
6020 true
6021 } else {
6022 let snapshot = self.buffer.read(cx).read(cx);
6023 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6024 };
6025 if position_matches {
6026 return;
6027 }
6028 }
6029 }
6030 };
6031
6032 let Anchor {
6033 excerpt_id: buffer_excerpt_id,
6034 text_anchor: buffer_position,
6035 ..
6036 } = buffer_position;
6037
6038 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6039 buffer_snapshot.surrounding_word(buffer_position, None)
6040 {
6041 let word_to_exclude = buffer_snapshot
6042 .text_for_range(word_range.clone())
6043 .collect::<String>();
6044 (
6045 buffer_snapshot.anchor_before(word_range.start)
6046 ..buffer_snapshot.anchor_after(buffer_position),
6047 Some(word_to_exclude),
6048 )
6049 } else {
6050 (buffer_position..buffer_position, None)
6051 };
6052
6053 let show_completion_documentation = buffer_snapshot
6054 .settings_at(buffer_position, cx)
6055 .show_completion_documentation;
6056
6057 // The document can be large, so stay in reasonable bounds when searching for words,
6058 // otherwise completion pop-up might be slow to appear.
6059 const WORD_LOOKUP_ROWS: u32 = 5_000;
6060 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6061 let min_word_search = buffer_snapshot.clip_point(
6062 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6063 Bias::Left,
6064 );
6065 let max_word_search = buffer_snapshot.clip_point(
6066 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6067 Bias::Right,
6068 );
6069 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6070 ..buffer_snapshot.point_to_offset(max_word_search);
6071
6072 let skip_digits = query
6073 .as_ref()
6074 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6075
6076 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6077 trigger.as_ref().is_none_or(|trigger| {
6078 provider.is_completion_trigger(
6079 &buffer,
6080 position.text_anchor,
6081 trigger,
6082 trigger_in_words,
6083 cx,
6084 )
6085 })
6086 });
6087
6088 let provider_responses = if let Some(provider) = &provider
6089 && load_provider_completions
6090 {
6091 let trigger_character =
6092 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6093 let completion_context = CompletionContext {
6094 trigger_kind: match &trigger_character {
6095 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6096 None => CompletionTriggerKind::INVOKED,
6097 },
6098 trigger_character,
6099 };
6100
6101 provider.completions(
6102 buffer_excerpt_id,
6103 &buffer,
6104 buffer_position,
6105 completion_context,
6106 window,
6107 cx,
6108 )
6109 } else {
6110 Task::ready(Ok(Vec::new()))
6111 };
6112
6113 let load_word_completions = if !self.word_completions_enabled {
6114 false
6115 } else if requested_source
6116 == Some(CompletionsMenuSource::Words {
6117 ignore_threshold: true,
6118 })
6119 {
6120 true
6121 } else {
6122 load_provider_completions
6123 && completion_settings.words != WordsCompletionMode::Disabled
6124 && (ignore_word_threshold || {
6125 let words_min_length = completion_settings.words_min_length;
6126 // check whether word has at least `words_min_length` characters
6127 let query_chars = query.iter().flat_map(|q| q.chars());
6128 query_chars.take(words_min_length).count() == words_min_length
6129 })
6130 };
6131
6132 let mut words = if load_word_completions {
6133 cx.background_spawn({
6134 let buffer_snapshot = buffer_snapshot.clone();
6135 async move {
6136 buffer_snapshot.words_in_range(WordsQuery {
6137 fuzzy_contents: None,
6138 range: word_search_range,
6139 skip_digits,
6140 })
6141 }
6142 })
6143 } else {
6144 Task::ready(BTreeMap::default())
6145 };
6146
6147 let snippets = if let Some(provider) = &provider
6148 && provider.show_snippets()
6149 && let Some(project) = self.project()
6150 {
6151 let char_classifier = buffer_snapshot
6152 .char_classifier_at(buffer_position)
6153 .scope_context(Some(CharScopeContext::Completion));
6154 project.update(cx, |project, cx| {
6155 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6156 })
6157 } else {
6158 Task::ready(Ok(CompletionResponse {
6159 completions: Vec::new(),
6160 display_options: Default::default(),
6161 is_incomplete: false,
6162 }))
6163 };
6164
6165 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6166
6167 let id = post_inc(&mut self.next_completion_id);
6168 let task = cx.spawn_in(window, async move |editor, cx| {
6169 let Ok(()) = editor.update(cx, |this, _| {
6170 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6171 }) else {
6172 return;
6173 };
6174
6175 // TODO: Ideally completions from different sources would be selectively re-queried, so
6176 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6177 let mut completions = Vec::new();
6178 let mut is_incomplete = false;
6179 let mut display_options: Option<CompletionDisplayOptions> = None;
6180 if let Some(provider_responses) = provider_responses.await.log_err()
6181 && !provider_responses.is_empty()
6182 {
6183 for response in provider_responses {
6184 completions.extend(response.completions);
6185 is_incomplete = is_incomplete || response.is_incomplete;
6186 match display_options.as_mut() {
6187 None => {
6188 display_options = Some(response.display_options);
6189 }
6190 Some(options) => options.merge(&response.display_options),
6191 }
6192 }
6193 if completion_settings.words == WordsCompletionMode::Fallback {
6194 words = Task::ready(BTreeMap::default());
6195 }
6196 }
6197 let display_options = display_options.unwrap_or_default();
6198
6199 let mut words = words.await;
6200 if let Some(word_to_exclude) = &word_to_exclude {
6201 words.remove(word_to_exclude);
6202 }
6203 for lsp_completion in &completions {
6204 words.remove(&lsp_completion.new_text);
6205 }
6206 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6207 replace_range: word_replace_range.clone(),
6208 new_text: word.clone(),
6209 label: CodeLabel::plain(word, None),
6210 match_start: None,
6211 snippet_deduplication_key: None,
6212 icon_path: None,
6213 documentation: None,
6214 source: CompletionSource::BufferWord {
6215 word_range,
6216 resolved: false,
6217 },
6218 insert_text_mode: Some(InsertTextMode::AS_IS),
6219 confirm: None,
6220 }));
6221
6222 completions.extend(
6223 snippets
6224 .await
6225 .into_iter()
6226 .flat_map(|response| response.completions),
6227 );
6228
6229 let menu = if completions.is_empty() {
6230 None
6231 } else {
6232 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6233 let languages = editor
6234 .workspace
6235 .as_ref()
6236 .and_then(|(workspace, _)| workspace.upgrade())
6237 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6238 let menu = CompletionsMenu::new(
6239 id,
6240 requested_source.unwrap_or(if load_provider_completions {
6241 CompletionsMenuSource::Normal
6242 } else {
6243 CompletionsMenuSource::SnippetsOnly
6244 }),
6245 sort_completions,
6246 show_completion_documentation,
6247 position,
6248 query.clone(),
6249 is_incomplete,
6250 buffer.clone(),
6251 completions.into(),
6252 editor
6253 .context_menu()
6254 .borrow_mut()
6255 .as_ref()
6256 .map(|menu| menu.primary_scroll_handle()),
6257 display_options,
6258 snippet_sort_order,
6259 languages,
6260 language,
6261 cx,
6262 );
6263
6264 let query = if filter_completions { query } else { None };
6265 let matches_task = menu.do_async_filtering(
6266 query.unwrap_or_default(),
6267 buffer_position,
6268 &buffer,
6269 cx,
6270 );
6271 (menu, matches_task)
6272 }) else {
6273 return;
6274 };
6275
6276 let matches = matches_task.await;
6277
6278 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6279 // Newer menu already set, so exit.
6280 if let Some(CodeContextMenu::Completions(prev_menu)) =
6281 editor.context_menu.borrow().as_ref()
6282 && prev_menu.id > id
6283 {
6284 return;
6285 };
6286
6287 // Only valid to take prev_menu because either the new menu is immediately set
6288 // below, or the menu is hidden.
6289 if let Some(CodeContextMenu::Completions(prev_menu)) =
6290 editor.context_menu.borrow_mut().take()
6291 {
6292 let position_matches =
6293 if prev_menu.initial_position == menu.initial_position {
6294 true
6295 } else {
6296 let snapshot = editor.buffer.read(cx).read(cx);
6297 prev_menu.initial_position.to_offset(&snapshot)
6298 == menu.initial_position.to_offset(&snapshot)
6299 };
6300 if position_matches {
6301 // Preserve markdown cache before `set_filter_results` because it will
6302 // try to populate the documentation cache.
6303 menu.preserve_markdown_cache(prev_menu);
6304 }
6305 };
6306
6307 menu.set_filter_results(matches, provider, window, cx);
6308 }) else {
6309 return;
6310 };
6311
6312 menu.visible().then_some(menu)
6313 };
6314
6315 editor
6316 .update_in(cx, |editor, window, cx| {
6317 if editor.focus_handle.is_focused(window)
6318 && let Some(menu) = menu
6319 {
6320 *editor.context_menu.borrow_mut() =
6321 Some(CodeContextMenu::Completions(menu));
6322
6323 crate::hover_popover::hide_hover(editor, cx);
6324 if editor.show_edit_predictions_in_menu() {
6325 editor.update_visible_edit_prediction(window, cx);
6326 } else {
6327 editor
6328 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6329 }
6330
6331 cx.notify();
6332 return;
6333 }
6334
6335 if editor.completion_tasks.len() <= 1 {
6336 // If there are no more completion tasks and the last menu was empty, we should hide it.
6337 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6338 // If it was already hidden and we don't show edit predictions in the menu,
6339 // we should also show the edit prediction when available.
6340 if was_hidden && editor.show_edit_predictions_in_menu() {
6341 editor.update_visible_edit_prediction(window, cx);
6342 }
6343 }
6344 })
6345 .ok();
6346 });
6347
6348 self.completion_tasks.push((id, task));
6349 }
6350
6351 #[cfg(any(test, feature = "test-support"))]
6352 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6353 let menu = self.context_menu.borrow();
6354 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6355 let completions = menu.completions.borrow();
6356 Some(completions.to_vec())
6357 } else {
6358 None
6359 }
6360 }
6361
6362 pub fn with_completions_menu_matching_id<R>(
6363 &self,
6364 id: CompletionId,
6365 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6366 ) -> R {
6367 let mut context_menu = self.context_menu.borrow_mut();
6368 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6369 return f(None);
6370 };
6371 if completions_menu.id != id {
6372 return f(None);
6373 }
6374 f(Some(completions_menu))
6375 }
6376
6377 pub fn confirm_completion(
6378 &mut self,
6379 action: &ConfirmCompletion,
6380 window: &mut Window,
6381 cx: &mut Context<Self>,
6382 ) -> Option<Task<Result<()>>> {
6383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6384 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6385 }
6386
6387 pub fn confirm_completion_insert(
6388 &mut self,
6389 _: &ConfirmCompletionInsert,
6390 window: &mut Window,
6391 cx: &mut Context<Self>,
6392 ) -> Option<Task<Result<()>>> {
6393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6394 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6395 }
6396
6397 pub fn confirm_completion_replace(
6398 &mut self,
6399 _: &ConfirmCompletionReplace,
6400 window: &mut Window,
6401 cx: &mut Context<Self>,
6402 ) -> Option<Task<Result<()>>> {
6403 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6404 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6405 }
6406
6407 pub fn compose_completion(
6408 &mut self,
6409 action: &ComposeCompletion,
6410 window: &mut Window,
6411 cx: &mut Context<Self>,
6412 ) -> Option<Task<Result<()>>> {
6413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6414 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6415 }
6416
6417 fn do_completion(
6418 &mut self,
6419 item_ix: Option<usize>,
6420 intent: CompletionIntent,
6421 window: &mut Window,
6422 cx: &mut Context<Editor>,
6423 ) -> Option<Task<Result<()>>> {
6424 use language::ToOffset as _;
6425
6426 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6427 else {
6428 return None;
6429 };
6430
6431 let candidate_id = {
6432 let entries = completions_menu.entries.borrow();
6433 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6434 if self.show_edit_predictions_in_menu() {
6435 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6436 }
6437 mat.candidate_id
6438 };
6439
6440 let completion = completions_menu
6441 .completions
6442 .borrow()
6443 .get(candidate_id)?
6444 .clone();
6445 cx.stop_propagation();
6446
6447 let buffer_handle = completions_menu.buffer.clone();
6448
6449 let CompletionEdit {
6450 new_text,
6451 snippet,
6452 replace_range,
6453 } = process_completion_for_edit(
6454 &completion,
6455 intent,
6456 &buffer_handle,
6457 &completions_menu.initial_position.text_anchor,
6458 cx,
6459 );
6460
6461 let buffer = buffer_handle.read(cx);
6462 let snapshot = self.buffer.read(cx).snapshot(cx);
6463 let newest_anchor = self.selections.newest_anchor();
6464 let replace_range_multibuffer = {
6465 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6466 excerpt.map_range_from_buffer(replace_range.clone())
6467 };
6468 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6469 return None;
6470 }
6471
6472 let old_text = buffer
6473 .text_for_range(replace_range.clone())
6474 .collect::<String>();
6475 let lookbehind = newest_anchor
6476 .start
6477 .text_anchor
6478 .to_offset(buffer)
6479 .saturating_sub(replace_range.start.0);
6480 let lookahead = replace_range
6481 .end
6482 .0
6483 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6484 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6485 let suffix = &old_text[lookbehind.min(old_text.len())..];
6486
6487 let selections = self
6488 .selections
6489 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6490 let mut ranges = Vec::new();
6491 let mut linked_edits = LinkedEdits::new();
6492
6493 let text: Arc<str> = new_text.clone().into();
6494 for selection in &selections {
6495 let range = if selection.id == newest_anchor.id {
6496 replace_range_multibuffer.clone()
6497 } else {
6498 let mut range = selection.range();
6499
6500 // if prefix is present, don't duplicate it
6501 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6502 range.start = range.start.saturating_sub_usize(lookbehind);
6503
6504 // if suffix is also present, mimic the newest cursor and replace it
6505 if selection.id != newest_anchor.id
6506 && snapshot.contains_str_at(range.end, suffix)
6507 {
6508 range.end += lookahead;
6509 }
6510 }
6511 range
6512 };
6513
6514 ranges.push(range.clone());
6515
6516 if !self.linked_edit_ranges.is_empty() {
6517 let start_anchor = snapshot.anchor_before(range.start);
6518 let end_anchor = snapshot.anchor_after(range.end);
6519 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6520 linked_edits.push(&self, anchor_range, text.clone(), cx);
6521 }
6522 }
6523
6524 let common_prefix_len = old_text
6525 .chars()
6526 .zip(new_text.chars())
6527 .take_while(|(a, b)| a == b)
6528 .map(|(a, _)| a.len_utf8())
6529 .sum::<usize>();
6530
6531 cx.emit(EditorEvent::InputHandled {
6532 utf16_range_to_replace: None,
6533 text: new_text[common_prefix_len..].into(),
6534 });
6535
6536 self.transact(window, cx, |editor, window, cx| {
6537 if let Some(mut snippet) = snippet {
6538 snippet.text = new_text.to_string();
6539 editor
6540 .insert_snippet(&ranges, snippet, window, cx)
6541 .log_err();
6542 } else {
6543 editor.buffer.update(cx, |multi_buffer, cx| {
6544 let auto_indent = match completion.insert_text_mode {
6545 Some(InsertTextMode::AS_IS) => None,
6546 _ => editor.autoindent_mode.clone(),
6547 };
6548 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6549 multi_buffer.edit(edits, auto_indent, cx);
6550 });
6551 }
6552 linked_edits.apply(cx);
6553 editor.refresh_edit_prediction(true, false, window, cx);
6554 });
6555 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6556
6557 let show_new_completions_on_confirm = completion
6558 .confirm
6559 .as_ref()
6560 .is_some_and(|confirm| confirm(intent, window, cx));
6561 if show_new_completions_on_confirm {
6562 self.open_or_update_completions_menu(None, None, false, window, cx);
6563 }
6564
6565 let provider = self.completion_provider.as_ref()?;
6566
6567 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6568 let command = lsp_store.as_ref().and_then(|lsp_store| {
6569 let CompletionSource::Lsp {
6570 lsp_completion,
6571 server_id,
6572 ..
6573 } = &completion.source
6574 else {
6575 return None;
6576 };
6577 let lsp_command = lsp_completion.command.as_ref()?;
6578 let available_commands = lsp_store
6579 .read(cx)
6580 .lsp_server_capabilities
6581 .get(server_id)
6582 .and_then(|server_capabilities| {
6583 server_capabilities
6584 .execute_command_provider
6585 .as_ref()
6586 .map(|options| options.commands.as_slice())
6587 })?;
6588 if available_commands.contains(&lsp_command.command) {
6589 Some(CodeAction {
6590 server_id: *server_id,
6591 range: language::Anchor::MIN..language::Anchor::MIN,
6592 lsp_action: LspAction::Command(lsp_command.clone()),
6593 resolved: false,
6594 })
6595 } else {
6596 None
6597 }
6598 });
6599
6600 drop(completion);
6601 let apply_edits = provider.apply_additional_edits_for_completion(
6602 buffer_handle.clone(),
6603 completions_menu.completions.clone(),
6604 candidate_id,
6605 true,
6606 cx,
6607 );
6608
6609 let editor_settings = EditorSettings::get_global(cx);
6610 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6611 // After the code completion is finished, users often want to know what signatures are needed.
6612 // so we should automatically call signature_help
6613 self.show_signature_help(&ShowSignatureHelp, window, cx);
6614 }
6615
6616 Some(cx.spawn_in(window, async move |editor, cx| {
6617 apply_edits.await?;
6618
6619 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6620 let title = command.lsp_action.title().to_owned();
6621 let project_transaction = lsp_store
6622 .update(cx, |lsp_store, cx| {
6623 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6624 })
6625 .await
6626 .context("applying post-completion command")?;
6627 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6628 Self::open_project_transaction(
6629 &editor,
6630 workspace.downgrade(),
6631 project_transaction,
6632 title,
6633 cx,
6634 )
6635 .await?;
6636 }
6637 }
6638
6639 Ok(())
6640 }))
6641 }
6642
6643 pub fn toggle_code_actions(
6644 &mut self,
6645 action: &ToggleCodeActions,
6646 window: &mut Window,
6647 cx: &mut Context<Self>,
6648 ) {
6649 let quick_launch = action.quick_launch;
6650 let mut context_menu = self.context_menu.borrow_mut();
6651 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6652 if code_actions.deployed_from == action.deployed_from {
6653 // Toggle if we're selecting the same one
6654 *context_menu = None;
6655 cx.notify();
6656 return;
6657 } else {
6658 // Otherwise, clear it and start a new one
6659 *context_menu = None;
6660 cx.notify();
6661 }
6662 }
6663 drop(context_menu);
6664 let snapshot = self.snapshot(window, cx);
6665 let deployed_from = action.deployed_from.clone();
6666 let action = action.clone();
6667 self.completion_tasks.clear();
6668 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6669
6670 let multibuffer_point = match &action.deployed_from {
6671 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6672 DisplayPoint::new(*row, 0).to_point(&snapshot)
6673 }
6674 _ => self
6675 .selections
6676 .newest::<Point>(&snapshot.display_snapshot)
6677 .head(),
6678 };
6679 let Some((buffer, buffer_row)) = snapshot
6680 .buffer_snapshot()
6681 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6682 .and_then(|(buffer_snapshot, range)| {
6683 self.buffer()
6684 .read(cx)
6685 .buffer(buffer_snapshot.remote_id())
6686 .map(|buffer| (buffer, range.start.row))
6687 })
6688 else {
6689 return;
6690 };
6691 let buffer_id = buffer.read(cx).remote_id();
6692 let tasks = self
6693 .tasks
6694 .get(&(buffer_id, buffer_row))
6695 .map(|t| Arc::new(t.to_owned()));
6696
6697 if !self.focus_handle.is_focused(window) {
6698 return;
6699 }
6700 let project = self.project.clone();
6701
6702 let code_actions_task = match deployed_from {
6703 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6704 _ => self.code_actions(buffer_row, window, cx),
6705 };
6706
6707 let runnable_task = match deployed_from {
6708 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6709 _ => {
6710 let mut task_context_task = Task::ready(None);
6711 if let Some(tasks) = &tasks
6712 && let Some(project) = project
6713 {
6714 task_context_task =
6715 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6716 }
6717
6718 cx.spawn_in(window, {
6719 let buffer = buffer.clone();
6720 async move |editor, cx| {
6721 let task_context = task_context_task.await;
6722
6723 let resolved_tasks =
6724 tasks
6725 .zip(task_context.clone())
6726 .map(|(tasks, task_context)| ResolvedTasks {
6727 templates: tasks.resolve(&task_context).collect(),
6728 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6729 multibuffer_point.row,
6730 tasks.column,
6731 )),
6732 });
6733 let debug_scenarios = editor
6734 .update(cx, |editor, cx| {
6735 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6736 })?
6737 .await;
6738 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6739 }
6740 })
6741 }
6742 };
6743
6744 cx.spawn_in(window, async move |editor, cx| {
6745 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6746 let code_actions = code_actions_task.await;
6747 let spawn_straight_away = quick_launch
6748 && resolved_tasks
6749 .as_ref()
6750 .is_some_and(|tasks| tasks.templates.len() == 1)
6751 && code_actions
6752 .as_ref()
6753 .is_none_or(|actions| actions.is_empty())
6754 && debug_scenarios.is_empty();
6755
6756 editor.update_in(cx, |editor, window, cx| {
6757 crate::hover_popover::hide_hover(editor, cx);
6758 let actions = CodeActionContents::new(
6759 resolved_tasks,
6760 code_actions,
6761 debug_scenarios,
6762 task_context.unwrap_or_default(),
6763 );
6764
6765 // Don't show the menu if there are no actions available
6766 if actions.is_empty() {
6767 cx.notify();
6768 return Task::ready(Ok(()));
6769 }
6770
6771 *editor.context_menu.borrow_mut() =
6772 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6773 buffer,
6774 actions,
6775 selected_item: Default::default(),
6776 scroll_handle: UniformListScrollHandle::default(),
6777 deployed_from,
6778 }));
6779 cx.notify();
6780 if spawn_straight_away
6781 && let Some(task) = editor.confirm_code_action(
6782 &ConfirmCodeAction { item_ix: Some(0) },
6783 window,
6784 cx,
6785 )
6786 {
6787 return task;
6788 }
6789
6790 Task::ready(Ok(()))
6791 })
6792 })
6793 .detach_and_log_err(cx);
6794 }
6795
6796 fn debug_scenarios(
6797 &mut self,
6798 resolved_tasks: &Option<ResolvedTasks>,
6799 buffer: &Entity<Buffer>,
6800 cx: &mut App,
6801 ) -> Task<Vec<task::DebugScenario>> {
6802 maybe!({
6803 let project = self.project()?;
6804 let dap_store = project.read(cx).dap_store();
6805 let mut scenarios = vec![];
6806 let resolved_tasks = resolved_tasks.as_ref()?;
6807 let buffer = buffer.read(cx);
6808 let language = buffer.language()?;
6809 let file = buffer.file();
6810 let debug_adapter = language_settings(language.name().into(), file, cx)
6811 .debuggers
6812 .first()
6813 .map(SharedString::from)
6814 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6815
6816 dap_store.update(cx, |dap_store, cx| {
6817 for (_, task) in &resolved_tasks.templates {
6818 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6819 task.original_task().clone(),
6820 debug_adapter.clone().into(),
6821 task.display_label().to_owned().into(),
6822 cx,
6823 );
6824 scenarios.push(maybe_scenario);
6825 }
6826 });
6827 Some(cx.background_spawn(async move {
6828 futures::future::join_all(scenarios)
6829 .await
6830 .into_iter()
6831 .flatten()
6832 .collect::<Vec<_>>()
6833 }))
6834 })
6835 .unwrap_or_else(|| Task::ready(vec![]))
6836 }
6837
6838 fn code_actions(
6839 &mut self,
6840 buffer_row: u32,
6841 window: &mut Window,
6842 cx: &mut Context<Self>,
6843 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6844 let mut task = self.code_actions_task.take();
6845 cx.spawn_in(window, async move |editor, cx| {
6846 while let Some(prev_task) = task {
6847 prev_task.await.log_err();
6848 task = editor
6849 .update(cx, |this, _| this.code_actions_task.take())
6850 .ok()?;
6851 }
6852
6853 editor
6854 .update(cx, |editor, cx| {
6855 editor
6856 .available_code_actions
6857 .clone()
6858 .and_then(|(location, code_actions)| {
6859 let snapshot = location.buffer.read(cx).snapshot();
6860 let point_range = location.range.to_point(&snapshot);
6861 let point_range = point_range.start.row..=point_range.end.row;
6862 if point_range.contains(&buffer_row) {
6863 Some(code_actions)
6864 } else {
6865 None
6866 }
6867 })
6868 })
6869 .ok()
6870 .flatten()
6871 })
6872 }
6873
6874 pub fn confirm_code_action(
6875 &mut self,
6876 action: &ConfirmCodeAction,
6877 window: &mut Window,
6878 cx: &mut Context<Self>,
6879 ) -> Option<Task<Result<()>>> {
6880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6881
6882 let actions_menu =
6883 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6884 menu
6885 } else {
6886 return None;
6887 };
6888
6889 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6890 let action = actions_menu.actions.get(action_ix)?;
6891 let title = action.label();
6892 let buffer = actions_menu.buffer;
6893 let workspace = self.workspace()?;
6894
6895 match action {
6896 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6897 workspace.update(cx, |workspace, cx| {
6898 workspace.schedule_resolved_task(
6899 task_source_kind,
6900 resolved_task,
6901 false,
6902 window,
6903 cx,
6904 );
6905
6906 Some(Task::ready(Ok(())))
6907 })
6908 }
6909 CodeActionsItem::CodeAction {
6910 excerpt_id,
6911 action,
6912 provider,
6913 } => {
6914 let apply_code_action =
6915 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6916 let workspace = workspace.downgrade();
6917 Some(cx.spawn_in(window, async move |editor, cx| {
6918 let project_transaction = apply_code_action.await?;
6919 Self::open_project_transaction(
6920 &editor,
6921 workspace,
6922 project_transaction,
6923 title,
6924 cx,
6925 )
6926 .await
6927 }))
6928 }
6929 CodeActionsItem::DebugScenario(scenario) => {
6930 let context = actions_menu.actions.context.into();
6931
6932 workspace.update(cx, |workspace, cx| {
6933 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6934 workspace.start_debug_session(
6935 scenario,
6936 context,
6937 Some(buffer),
6938 None,
6939 window,
6940 cx,
6941 );
6942 });
6943 Some(Task::ready(Ok(())))
6944 }
6945 }
6946 }
6947
6948 fn open_transaction_for_hidden_buffers(
6949 workspace: Entity<Workspace>,
6950 transaction: ProjectTransaction,
6951 title: String,
6952 window: &mut Window,
6953 cx: &mut Context<Self>,
6954 ) {
6955 if transaction.0.is_empty() {
6956 return;
6957 }
6958
6959 let edited_buffers_already_open = {
6960 let other_editors: Vec<Entity<Editor>> = workspace
6961 .read(cx)
6962 .panes()
6963 .iter()
6964 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6965 .filter(|editor| editor.entity_id() != cx.entity_id())
6966 .collect();
6967
6968 transaction.0.keys().all(|buffer| {
6969 other_editors.iter().any(|editor| {
6970 let multi_buffer = editor.read(cx).buffer();
6971 multi_buffer.read(cx).is_singleton()
6972 && multi_buffer
6973 .read(cx)
6974 .as_singleton()
6975 .map_or(false, |singleton| {
6976 singleton.entity_id() == buffer.entity_id()
6977 })
6978 })
6979 })
6980 };
6981 if !edited_buffers_already_open {
6982 let workspace = workspace.downgrade();
6983 cx.defer_in(window, move |_, window, cx| {
6984 cx.spawn_in(window, async move |editor, cx| {
6985 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6986 .await
6987 .ok()
6988 })
6989 .detach();
6990 });
6991 }
6992 }
6993
6994 pub async fn open_project_transaction(
6995 editor: &WeakEntity<Editor>,
6996 workspace: WeakEntity<Workspace>,
6997 transaction: ProjectTransaction,
6998 title: String,
6999 cx: &mut AsyncWindowContext,
7000 ) -> Result<()> {
7001 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7002 cx.update(|_, cx| {
7003 entries.sort_unstable_by_key(|(buffer, _)| {
7004 buffer.read(cx).file().map(|f| f.path().clone())
7005 });
7006 })?;
7007 if entries.is_empty() {
7008 return Ok(());
7009 }
7010
7011 // If the project transaction's edits are all contained within this editor, then
7012 // avoid opening a new editor to display them.
7013
7014 if let [(buffer, transaction)] = &*entries {
7015 let excerpt = editor.update(cx, |editor, cx| {
7016 editor
7017 .buffer()
7018 .read(cx)
7019 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7020 })?;
7021 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7022 && excerpted_buffer == *buffer
7023 {
7024 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7025 let excerpt_range = excerpt_range.to_offset(buffer);
7026 buffer
7027 .edited_ranges_for_transaction::<usize>(transaction)
7028 .all(|range| {
7029 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7030 })
7031 });
7032
7033 if all_edits_within_excerpt {
7034 return Ok(());
7035 }
7036 }
7037 }
7038
7039 let mut ranges_to_highlight = Vec::new();
7040 let excerpt_buffer = cx.new(|cx| {
7041 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7042 for (buffer_handle, transaction) in &entries {
7043 let edited_ranges = buffer_handle
7044 .read(cx)
7045 .edited_ranges_for_transaction::<Point>(transaction)
7046 .collect::<Vec<_>>();
7047 let (ranges, _) = multibuffer.set_excerpts_for_path(
7048 PathKey::for_buffer(buffer_handle, cx),
7049 buffer_handle.clone(),
7050 edited_ranges,
7051 multibuffer_context_lines(cx),
7052 cx,
7053 );
7054
7055 ranges_to_highlight.extend(ranges);
7056 }
7057 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7058 multibuffer
7059 });
7060
7061 workspace.update_in(cx, |workspace, window, cx| {
7062 let project = workspace.project().clone();
7063 let editor =
7064 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7065 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7066 editor.update(cx, |editor, cx| {
7067 editor.highlight_background(
7068 HighlightKey::Editor,
7069 &ranges_to_highlight,
7070 |_, theme| theme.colors().editor_highlighted_line_background,
7071 cx,
7072 );
7073 });
7074 })?;
7075
7076 Ok(())
7077 }
7078
7079 pub fn clear_code_action_providers(&mut self) {
7080 self.code_action_providers.clear();
7081 self.available_code_actions.take();
7082 }
7083
7084 pub fn add_code_action_provider(
7085 &mut self,
7086 provider: Rc<dyn CodeActionProvider>,
7087 window: &mut Window,
7088 cx: &mut Context<Self>,
7089 ) {
7090 if self
7091 .code_action_providers
7092 .iter()
7093 .any(|existing_provider| existing_provider.id() == provider.id())
7094 {
7095 return;
7096 }
7097
7098 self.code_action_providers.push(provider);
7099 self.refresh_code_actions(window, cx);
7100 }
7101
7102 pub fn remove_code_action_provider(
7103 &mut self,
7104 id: Arc<str>,
7105 window: &mut Window,
7106 cx: &mut Context<Self>,
7107 ) {
7108 self.code_action_providers
7109 .retain(|provider| provider.id() != id);
7110 self.refresh_code_actions(window, cx);
7111 }
7112
7113 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7114 !self.code_action_providers.is_empty()
7115 && EditorSettings::get_global(cx).toolbar.code_actions
7116 }
7117
7118 pub fn has_available_code_actions(&self) -> bool {
7119 self.available_code_actions
7120 .as_ref()
7121 .is_some_and(|(_, actions)| !actions.is_empty())
7122 }
7123
7124 fn render_inline_code_actions(
7125 &self,
7126 icon_size: ui::IconSize,
7127 display_row: DisplayRow,
7128 is_active: bool,
7129 cx: &mut Context<Self>,
7130 ) -> AnyElement {
7131 let show_tooltip = !self.context_menu_visible();
7132 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7133 .icon_size(icon_size)
7134 .shape(ui::IconButtonShape::Square)
7135 .icon_color(ui::Color::Hidden)
7136 .toggle_state(is_active)
7137 .when(show_tooltip, |this| {
7138 this.tooltip({
7139 let focus_handle = self.focus_handle.clone();
7140 move |_window, cx| {
7141 Tooltip::for_action_in(
7142 "Toggle Code Actions",
7143 &ToggleCodeActions {
7144 deployed_from: None,
7145 quick_launch: false,
7146 },
7147 &focus_handle,
7148 cx,
7149 )
7150 }
7151 })
7152 })
7153 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7154 window.focus(&editor.focus_handle(cx), cx);
7155 editor.toggle_code_actions(
7156 &crate::actions::ToggleCodeActions {
7157 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7158 display_row,
7159 )),
7160 quick_launch: false,
7161 },
7162 window,
7163 cx,
7164 );
7165 }))
7166 .into_any_element()
7167 }
7168
7169 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7170 &self.context_menu
7171 }
7172
7173 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7174 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7175 cx.background_executor()
7176 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7177 .await;
7178
7179 let (start_buffer, start, _, end, newest_selection) = this
7180 .update(cx, |this, cx| {
7181 let newest_selection = this.selections.newest_anchor().clone();
7182 if newest_selection.head().diff_base_anchor.is_some() {
7183 return None;
7184 }
7185 let display_snapshot = this.display_snapshot(cx);
7186 let newest_selection_adjusted =
7187 this.selections.newest_adjusted(&display_snapshot);
7188 let buffer = this.buffer.read(cx);
7189
7190 let (start_buffer, start) =
7191 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7192 let (end_buffer, end) =
7193 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7194
7195 Some((start_buffer, start, end_buffer, end, newest_selection))
7196 })?
7197 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7198 .context(
7199 "Expected selection to lie in a single buffer when refreshing code actions",
7200 )?;
7201 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7202 let providers = this.code_action_providers.clone();
7203 let tasks = this
7204 .code_action_providers
7205 .iter()
7206 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7207 .collect::<Vec<_>>();
7208 (providers, tasks)
7209 })?;
7210
7211 let mut actions = Vec::new();
7212 for (provider, provider_actions) in
7213 providers.into_iter().zip(future::join_all(tasks).await)
7214 {
7215 if let Some(provider_actions) = provider_actions.log_err() {
7216 actions.extend(provider_actions.into_iter().map(|action| {
7217 AvailableCodeAction {
7218 excerpt_id: newest_selection.start.excerpt_id,
7219 action,
7220 provider: provider.clone(),
7221 }
7222 }));
7223 }
7224 }
7225
7226 this.update(cx, |this, cx| {
7227 this.available_code_actions = if actions.is_empty() {
7228 None
7229 } else {
7230 Some((
7231 Location {
7232 buffer: start_buffer,
7233 range: start..end,
7234 },
7235 actions.into(),
7236 ))
7237 };
7238 cx.notify();
7239 })
7240 }));
7241 }
7242
7243 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7244 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7245 self.show_git_blame_inline = false;
7246
7247 self.show_git_blame_inline_delay_task =
7248 Some(cx.spawn_in(window, async move |this, cx| {
7249 cx.background_executor().timer(delay).await;
7250
7251 this.update(cx, |this, cx| {
7252 this.show_git_blame_inline = true;
7253 cx.notify();
7254 })
7255 .log_err();
7256 }));
7257 }
7258 }
7259
7260 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7261 let snapshot = self.snapshot(window, cx);
7262 let cursor = self
7263 .selections
7264 .newest::<Point>(&snapshot.display_snapshot)
7265 .head();
7266 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7267 else {
7268 return;
7269 };
7270
7271 if self.blame.is_none() {
7272 self.start_git_blame(true, window, cx);
7273 }
7274 let Some(blame) = self.blame.as_ref() else {
7275 return;
7276 };
7277
7278 let row_info = RowInfo {
7279 buffer_id: Some(buffer.remote_id()),
7280 buffer_row: Some(point.row),
7281 ..Default::default()
7282 };
7283 let Some((buffer, blame_entry)) = blame
7284 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7285 .flatten()
7286 else {
7287 return;
7288 };
7289
7290 let anchor = self.selections.newest_anchor().head();
7291 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7292 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7293 self.show_blame_popover(
7294 buffer,
7295 &blame_entry,
7296 position + last_bounds.origin,
7297 true,
7298 cx,
7299 );
7300 };
7301 }
7302
7303 fn show_blame_popover(
7304 &mut self,
7305 buffer: BufferId,
7306 blame_entry: &BlameEntry,
7307 position: gpui::Point<Pixels>,
7308 ignore_timeout: bool,
7309 cx: &mut Context<Self>,
7310 ) {
7311 if let Some(state) = &mut self.inline_blame_popover {
7312 state.hide_task.take();
7313 } else {
7314 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7315 let blame_entry = blame_entry.clone();
7316 let show_task = cx.spawn(async move |editor, cx| {
7317 if !ignore_timeout {
7318 cx.background_executor()
7319 .timer(std::time::Duration::from_millis(blame_popover_delay))
7320 .await;
7321 }
7322 editor
7323 .update(cx, |editor, cx| {
7324 editor.inline_blame_popover_show_task.take();
7325 let Some(blame) = editor.blame.as_ref() else {
7326 return;
7327 };
7328 let blame = blame.read(cx);
7329 let details = blame.details_for_entry(buffer, &blame_entry);
7330 let markdown = cx.new(|cx| {
7331 Markdown::new(
7332 details
7333 .as_ref()
7334 .map(|message| message.message.clone())
7335 .unwrap_or_default(),
7336 None,
7337 None,
7338 cx,
7339 )
7340 });
7341 editor.inline_blame_popover = Some(InlineBlamePopover {
7342 position,
7343 hide_task: None,
7344 popover_bounds: None,
7345 popover_state: InlineBlamePopoverState {
7346 scroll_handle: ScrollHandle::new(),
7347 commit_message: details,
7348 markdown,
7349 },
7350 keyboard_grace: ignore_timeout,
7351 });
7352 cx.notify();
7353 })
7354 .ok();
7355 });
7356 self.inline_blame_popover_show_task = Some(show_task);
7357 }
7358 }
7359
7360 pub fn has_mouse_context_menu(&self) -> bool {
7361 self.mouse_context_menu.is_some()
7362 }
7363
7364 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7365 self.inline_blame_popover_show_task.take();
7366 if let Some(state) = &mut self.inline_blame_popover {
7367 let hide_task = cx.spawn(async move |editor, cx| {
7368 if !ignore_timeout {
7369 cx.background_executor()
7370 .timer(std::time::Duration::from_millis(100))
7371 .await;
7372 }
7373 editor
7374 .update(cx, |editor, cx| {
7375 editor.inline_blame_popover.take();
7376 cx.notify();
7377 })
7378 .ok();
7379 });
7380 state.hide_task = Some(hide_task);
7381 true
7382 } else {
7383 false
7384 }
7385 }
7386
7387 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7388 if self.pending_rename.is_some() {
7389 return None;
7390 }
7391
7392 let provider = self.semantics_provider.clone()?;
7393 let buffer = self.buffer.read(cx);
7394 let newest_selection = self.selections.newest_anchor().clone();
7395 let cursor_position = newest_selection.head();
7396 let (cursor_buffer, cursor_buffer_position) =
7397 buffer.text_anchor_for_position(cursor_position, cx)?;
7398 let (tail_buffer, tail_buffer_position) =
7399 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7400 if cursor_buffer != tail_buffer {
7401 return None;
7402 }
7403
7404 let snapshot = cursor_buffer.read(cx).snapshot();
7405 let word_ranges = cx.background_spawn(async move {
7406 // this might look odd to put on the background thread, but
7407 // `surrounding_word` can be quite expensive as it calls into
7408 // tree-sitter language scopes
7409 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7410 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7411 (start_word_range, end_word_range)
7412 });
7413
7414 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7415 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7416 let (start_word_range, end_word_range) = word_ranges.await;
7417 if start_word_range != end_word_range {
7418 this.update(cx, |this, cx| {
7419 this.document_highlights_task.take();
7420 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7421 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7422 })
7423 .ok();
7424 return;
7425 }
7426 cx.background_executor()
7427 .timer(Duration::from_millis(debounce))
7428 .await;
7429
7430 let highlights = if let Some(highlights) = cx.update(|cx| {
7431 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7432 }) {
7433 highlights.await.log_err()
7434 } else {
7435 None
7436 };
7437
7438 if let Some(highlights) = highlights {
7439 this.update(cx, |this, cx| {
7440 if this.pending_rename.is_some() {
7441 return;
7442 }
7443
7444 let buffer = this.buffer.read(cx);
7445 if buffer
7446 .text_anchor_for_position(cursor_position, cx)
7447 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7448 {
7449 return;
7450 }
7451
7452 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7453 let mut write_ranges = Vec::new();
7454 let mut read_ranges = Vec::new();
7455 for highlight in highlights {
7456 let buffer_id = cursor_buffer.read(cx).remote_id();
7457 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7458 {
7459 let start = highlight
7460 .range
7461 .start
7462 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7463 let end = highlight
7464 .range
7465 .end
7466 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7467 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7468 continue;
7469 }
7470
7471 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7472 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7473 write_ranges.push(range);
7474 } else {
7475 read_ranges.push(range);
7476 }
7477 }
7478 }
7479
7480 this.highlight_background(
7481 HighlightKey::DocumentHighlightRead,
7482 &read_ranges,
7483 |_, theme| theme.colors().editor_document_highlight_read_background,
7484 cx,
7485 );
7486 this.highlight_background(
7487 HighlightKey::DocumentHighlightWrite,
7488 &write_ranges,
7489 |_, theme| theme.colors().editor_document_highlight_write_background,
7490 cx,
7491 );
7492 cx.notify();
7493 })
7494 .log_err();
7495 }
7496 }));
7497 None
7498 }
7499
7500 fn prepare_highlight_query_from_selection(
7501 &mut self,
7502 window: &Window,
7503 cx: &mut Context<Editor>,
7504 ) -> Option<(String, Range<Anchor>)> {
7505 if matches!(self.mode, EditorMode::SingleLine) {
7506 return None;
7507 }
7508 if !EditorSettings::get_global(cx).selection_highlight {
7509 return None;
7510 }
7511 if self.selections.count() != 1 || self.selections.line_mode() {
7512 return None;
7513 }
7514 let snapshot = self.snapshot(window, cx);
7515 let selection = self.selections.newest::<Point>(&snapshot);
7516 // If the selection spans multiple rows OR it is empty
7517 if selection.start.row != selection.end.row
7518 || selection.start.column == selection.end.column
7519 {
7520 return None;
7521 }
7522 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7523 let query = snapshot
7524 .buffer_snapshot()
7525 .text_for_range(selection_anchor_range.clone())
7526 .collect::<String>();
7527 if query.trim().is_empty() {
7528 return None;
7529 }
7530 Some((query, selection_anchor_range))
7531 }
7532
7533 #[ztracing::instrument(skip_all)]
7534 fn update_selection_occurrence_highlights(
7535 &mut self,
7536 query_text: String,
7537 query_range: Range<Anchor>,
7538 multi_buffer_range_to_query: Range<Point>,
7539 use_debounce: bool,
7540 window: &mut Window,
7541 cx: &mut Context<Editor>,
7542 ) -> Task<()> {
7543 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7544 cx.spawn_in(window, async move |editor, cx| {
7545 if use_debounce {
7546 cx.background_executor()
7547 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7548 .await;
7549 }
7550 let match_task = cx.background_spawn(async move {
7551 let buffer_ranges = multi_buffer_snapshot
7552 .range_to_buffer_ranges(
7553 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7554 )
7555 .into_iter()
7556 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7557 let mut match_ranges = Vec::new();
7558 let Ok(regex) = project::search::SearchQuery::text(
7559 query_text.clone(),
7560 false,
7561 false,
7562 false,
7563 Default::default(),
7564 Default::default(),
7565 false,
7566 None,
7567 ) else {
7568 return Vec::default();
7569 };
7570 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7571 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7572 match_ranges.extend(
7573 regex
7574 .search(
7575 buffer_snapshot,
7576 Some(search_range.start.0..search_range.end.0),
7577 )
7578 .await
7579 .into_iter()
7580 .filter_map(|match_range| {
7581 let match_start = buffer_snapshot
7582 .anchor_after(search_range.start + match_range.start);
7583 let match_end = buffer_snapshot
7584 .anchor_before(search_range.start + match_range.end);
7585 let match_anchor_range =
7586 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7587 (match_anchor_range != query_range).then_some(match_anchor_range)
7588 }),
7589 );
7590 }
7591 match_ranges
7592 });
7593 let match_ranges = match_task.await;
7594 editor
7595 .update_in(cx, |editor, _, cx| {
7596 if use_debounce {
7597 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7598 editor.debounced_selection_highlight_complete = true;
7599 } else if editor.debounced_selection_highlight_complete {
7600 return;
7601 }
7602 if !match_ranges.is_empty() {
7603 editor.highlight_background(
7604 HighlightKey::SelectedTextHighlight,
7605 &match_ranges,
7606 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7607 cx,
7608 )
7609 }
7610 })
7611 .log_err();
7612 })
7613 }
7614
7615 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7616 struct NewlineFold;
7617 let type_id = std::any::TypeId::of::<NewlineFold>();
7618 if !self.mode.is_single_line() {
7619 return;
7620 }
7621 let snapshot = self.snapshot(window, cx);
7622 if snapshot.buffer_snapshot().max_point().row == 0 {
7623 return;
7624 }
7625 let task = cx.background_spawn(async move {
7626 let new_newlines = snapshot
7627 .buffer_chars_at(MultiBufferOffset(0))
7628 .filter_map(|(c, i)| {
7629 if c == '\n' {
7630 Some(
7631 snapshot.buffer_snapshot().anchor_after(i)
7632 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7633 )
7634 } else {
7635 None
7636 }
7637 })
7638 .collect::<Vec<_>>();
7639 let existing_newlines = snapshot
7640 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7641 .filter_map(|fold| {
7642 if fold.placeholder.type_tag == Some(type_id) {
7643 Some(fold.range.start..fold.range.end)
7644 } else {
7645 None
7646 }
7647 })
7648 .collect::<Vec<_>>();
7649
7650 (new_newlines, existing_newlines)
7651 });
7652 self.folding_newlines = cx.spawn(async move |this, cx| {
7653 let (new_newlines, existing_newlines) = task.await;
7654 if new_newlines == existing_newlines {
7655 return;
7656 }
7657 let placeholder = FoldPlaceholder {
7658 render: Arc::new(move |_, _, cx| {
7659 div()
7660 .bg(cx.theme().status().hint_background)
7661 .border_b_1()
7662 .size_full()
7663 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7664 .border_color(cx.theme().status().hint)
7665 .child("\\n")
7666 .into_any()
7667 }),
7668 constrain_width: false,
7669 merge_adjacent: false,
7670 type_tag: Some(type_id),
7671 collapsed_text: None,
7672 };
7673 let creases = new_newlines
7674 .into_iter()
7675 .map(|range| Crease::simple(range, placeholder.clone()))
7676 .collect();
7677 this.update(cx, |this, cx| {
7678 this.display_map.update(cx, |display_map, cx| {
7679 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7680 display_map.fold(creases, cx);
7681 });
7682 })
7683 .ok();
7684 });
7685 }
7686
7687 #[ztracing::instrument(skip_all)]
7688 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7689 if !self.mode.is_full() {
7690 return;
7691 }
7692 let cursor = self.selections.newest_anchor().head();
7693 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7694
7695 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7696 self.outline_symbols_at_cursor =
7697 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7698 cx.emit(EditorEvent::OutlineSymbolsChanged);
7699 cx.notify();
7700 } else {
7701 let syntax = cx.theme().syntax().clone();
7702 let background_task = cx.background_spawn(async move {
7703 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7704 });
7705 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7706 cx.spawn(async move |this, cx| {
7707 let symbols = background_task.await;
7708 this.update(cx, |this, cx| {
7709 this.outline_symbols_at_cursor = symbols;
7710 cx.emit(EditorEvent::OutlineSymbolsChanged);
7711 cx.notify();
7712 })
7713 .ok();
7714 });
7715 }
7716 }
7717
7718 #[ztracing::instrument(skip_all)]
7719 fn refresh_selected_text_highlights(
7720 &mut self,
7721 on_buffer_edit: bool,
7722 window: &mut Window,
7723 cx: &mut Context<Editor>,
7724 ) {
7725 let Some((query_text, query_range)) =
7726 self.prepare_highlight_query_from_selection(window, cx)
7727 else {
7728 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7729 self.quick_selection_highlight_task.take();
7730 self.debounced_selection_highlight_task.take();
7731 self.debounced_selection_highlight_complete = false;
7732 return;
7733 };
7734 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7735 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7736 let query_changed = self
7737 .quick_selection_highlight_task
7738 .as_ref()
7739 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7740 if query_changed {
7741 self.debounced_selection_highlight_complete = false;
7742 }
7743 if on_buffer_edit || query_changed {
7744 let multi_buffer_visible_start = self
7745 .scroll_manager
7746 .native_anchor(&display_snapshot, cx)
7747 .anchor
7748 .to_point(&multi_buffer_snapshot);
7749 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7750 multi_buffer_visible_start
7751 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7752 Bias::Left,
7753 );
7754 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7755 self.quick_selection_highlight_task = Some((
7756 query_range.clone(),
7757 self.update_selection_occurrence_highlights(
7758 query_text.clone(),
7759 query_range.clone(),
7760 multi_buffer_visible_range,
7761 false,
7762 window,
7763 cx,
7764 ),
7765 ));
7766 }
7767 if on_buffer_edit
7768 || self
7769 .debounced_selection_highlight_task
7770 .as_ref()
7771 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7772 {
7773 let multi_buffer_start = multi_buffer_snapshot
7774 .anchor_before(MultiBufferOffset(0))
7775 .to_point(&multi_buffer_snapshot);
7776 let multi_buffer_end = multi_buffer_snapshot
7777 .anchor_after(multi_buffer_snapshot.len())
7778 .to_point(&multi_buffer_snapshot);
7779 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7780 self.debounced_selection_highlight_task = Some((
7781 query_range.clone(),
7782 self.update_selection_occurrence_highlights(
7783 query_text,
7784 query_range,
7785 multi_buffer_full_range,
7786 true,
7787 window,
7788 cx,
7789 ),
7790 ));
7791 }
7792 }
7793
7794 pub fn refresh_edit_prediction(
7795 &mut self,
7796 debounce: bool,
7797 user_requested: bool,
7798 window: &mut Window,
7799 cx: &mut Context<Self>,
7800 ) -> Option<()> {
7801 let provider = self.edit_prediction_provider()?;
7802 let cursor = self.selections.newest_anchor().head();
7803 let (buffer, cursor_buffer_position) =
7804 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7805
7806 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7807 return None;
7808 }
7809
7810 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7811 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7812 return None;
7813 }
7814
7815 self.update_visible_edit_prediction(window, cx);
7816
7817 if !user_requested
7818 && (!self.should_show_edit_predictions()
7819 || !self.is_focused(window)
7820 || buffer.read(cx).is_empty())
7821 {
7822 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7823 return None;
7824 }
7825
7826 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7827 Some(())
7828 }
7829
7830 fn show_edit_predictions_in_menu(&self) -> bool {
7831 match self.edit_prediction_settings {
7832 EditPredictionSettings::Disabled => false,
7833 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7834 }
7835 }
7836
7837 pub fn edit_predictions_enabled(&self) -> bool {
7838 match self.edit_prediction_settings {
7839 EditPredictionSettings::Disabled => false,
7840 EditPredictionSettings::Enabled { .. } => true,
7841 }
7842 }
7843
7844 fn edit_prediction_requires_modifier(&self) -> bool {
7845 match self.edit_prediction_settings {
7846 EditPredictionSettings::Disabled => false,
7847 EditPredictionSettings::Enabled {
7848 preview_requires_modifier,
7849 ..
7850 } => preview_requires_modifier,
7851 }
7852 }
7853
7854 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7855 if self.edit_prediction_provider.is_none() {
7856 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7857 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7858 return;
7859 }
7860
7861 let selection = self.selections.newest_anchor();
7862 let cursor = selection.head();
7863
7864 if let Some((buffer, cursor_buffer_position)) =
7865 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7866 {
7867 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7868 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7869 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7870 return;
7871 }
7872 self.edit_prediction_settings =
7873 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7874 }
7875 }
7876
7877 fn edit_prediction_settings_at_position(
7878 &self,
7879 buffer: &Entity<Buffer>,
7880 buffer_position: language::Anchor,
7881 cx: &App,
7882 ) -> EditPredictionSettings {
7883 if !self.mode.is_full()
7884 || !self.show_edit_predictions_override.unwrap_or(true)
7885 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7886 {
7887 return EditPredictionSettings::Disabled;
7888 }
7889
7890 let buffer = buffer.read(cx);
7891
7892 let file = buffer.file();
7893
7894 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7895 return EditPredictionSettings::Disabled;
7896 };
7897
7898 let by_provider = matches!(
7899 self.menu_edit_predictions_policy,
7900 MenuEditPredictionsPolicy::ByProvider
7901 );
7902
7903 let show_in_menu = by_provider
7904 && self
7905 .edit_prediction_provider
7906 .as_ref()
7907 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7908
7909 let preview_requires_modifier =
7910 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7911
7912 EditPredictionSettings::Enabled {
7913 show_in_menu,
7914 preview_requires_modifier,
7915 }
7916 }
7917
7918 fn should_show_edit_predictions(&self) -> bool {
7919 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7920 }
7921
7922 pub fn edit_prediction_preview_is_active(&self) -> bool {
7923 matches!(
7924 self.edit_prediction_preview,
7925 EditPredictionPreview::Active { .. }
7926 )
7927 }
7928
7929 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7930 let cursor = self.selections.newest_anchor().head();
7931 if let Some((buffer, cursor_position)) =
7932 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7933 {
7934 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7935 } else {
7936 false
7937 }
7938 }
7939
7940 pub fn supports_minimap(&self, cx: &App) -> bool {
7941 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7942 }
7943
7944 fn edit_predictions_enabled_in_buffer(
7945 &self,
7946 buffer: &Entity<Buffer>,
7947 buffer_position: language::Anchor,
7948 cx: &App,
7949 ) -> bool {
7950 maybe!({
7951 if self.read_only(cx) {
7952 return Some(false);
7953 }
7954 let provider = self.edit_prediction_provider()?;
7955 if !provider.is_enabled(buffer, buffer_position, cx) {
7956 return Some(false);
7957 }
7958 let buffer = buffer.read(cx);
7959 let Some(file) = buffer.file() else {
7960 return Some(true);
7961 };
7962 let settings = all_language_settings(Some(file), cx);
7963 Some(settings.edit_predictions_enabled_for_file(file, cx))
7964 })
7965 .unwrap_or(false)
7966 }
7967
7968 pub fn show_edit_prediction(
7969 &mut self,
7970 _: &ShowEditPrediction,
7971 window: &mut Window,
7972 cx: &mut Context<Self>,
7973 ) {
7974 if !self.has_active_edit_prediction() {
7975 self.refresh_edit_prediction(false, true, window, cx);
7976 return;
7977 }
7978
7979 self.update_visible_edit_prediction(window, cx);
7980 }
7981
7982 pub fn display_cursor_names(
7983 &mut self,
7984 _: &DisplayCursorNames,
7985 window: &mut Window,
7986 cx: &mut Context<Self>,
7987 ) {
7988 self.show_cursor_names(window, cx);
7989 }
7990
7991 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7992 self.show_cursor_names = true;
7993 cx.notify();
7994 cx.spawn_in(window, async move |this, cx| {
7995 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7996 this.update(cx, |this, cx| {
7997 this.show_cursor_names = false;
7998 cx.notify()
7999 })
8000 .ok()
8001 })
8002 .detach();
8003 }
8004
8005 pub fn accept_partial_edit_prediction(
8006 &mut self,
8007 granularity: EditPredictionGranularity,
8008 window: &mut Window,
8009 cx: &mut Context<Self>,
8010 ) {
8011 if self.show_edit_predictions_in_menu() {
8012 self.hide_context_menu(window, cx);
8013 }
8014
8015 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8016 return;
8017 };
8018
8019 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8020 return;
8021 }
8022
8023 match &active_edit_prediction.completion {
8024 EditPrediction::MoveWithin { target, .. } => {
8025 let target = *target;
8026
8027 if matches!(granularity, EditPredictionGranularity::Full) {
8028 if let Some(position_map) = &self.last_position_map {
8029 let target_row = target.to_display_point(&position_map.snapshot).row();
8030 let is_visible = position_map.visible_row_range.contains(&target_row);
8031
8032 if is_visible || !self.edit_prediction_requires_modifier() {
8033 self.unfold_ranges(&[target..target], true, false, cx);
8034 self.change_selections(
8035 SelectionEffects::scroll(Autoscroll::newest()),
8036 window,
8037 cx,
8038 |selections| {
8039 selections.select_anchor_ranges([target..target]);
8040 },
8041 );
8042 self.clear_row_highlights::<EditPredictionPreview>();
8043 self.edit_prediction_preview
8044 .set_previous_scroll_position(None);
8045 } else {
8046 // Highlight and request scroll
8047 self.edit_prediction_preview
8048 .set_previous_scroll_position(Some(
8049 position_map.snapshot.scroll_anchor,
8050 ));
8051 self.highlight_rows::<EditPredictionPreview>(
8052 target..target,
8053 cx.theme().colors().editor_highlighted_line_background,
8054 RowHighlightOptions {
8055 autoscroll: true,
8056 ..Default::default()
8057 },
8058 cx,
8059 );
8060 self.request_autoscroll(Autoscroll::fit(), cx);
8061 }
8062 }
8063 } else {
8064 self.change_selections(
8065 SelectionEffects::scroll(Autoscroll::newest()),
8066 window,
8067 cx,
8068 |selections| {
8069 selections.select_anchor_ranges([target..target]);
8070 },
8071 );
8072 }
8073 }
8074 EditPrediction::MoveOutside { snapshot, target } => {
8075 if let Some(workspace) = self.workspace() {
8076 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8077 .detach_and_log_err(cx);
8078 }
8079 }
8080 EditPrediction::Edit {
8081 edits,
8082 cursor_position,
8083 ..
8084 } => {
8085 self.report_edit_prediction_event(
8086 active_edit_prediction.completion_id.clone(),
8087 true,
8088 cx,
8089 );
8090
8091 match granularity {
8092 EditPredictionGranularity::Full => {
8093 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8094
8095 // Compute fallback cursor position BEFORE applying the edit,
8096 // so the anchor tracks through the edit correctly
8097 let fallback_cursor_target = {
8098 let snapshot = self.buffer.read(cx).snapshot(cx);
8099 edits.last().unwrap().0.end.bias_right(&snapshot)
8100 };
8101
8102 self.buffer.update(cx, |buffer, cx| {
8103 buffer.edit(edits.iter().cloned(), None, cx)
8104 });
8105
8106 if let Some(provider) = self.edit_prediction_provider() {
8107 provider.accept(cx);
8108 }
8109
8110 // Resolve cursor position after the edit is applied
8111 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8112 // The anchor tracks through the edit, then we add the offset
8113 let snapshot = self.buffer.read(cx).snapshot(cx);
8114 let base_offset = anchor.to_offset(&snapshot).0;
8115 let target_offset =
8116 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8117 snapshot.anchor_after(target_offset)
8118 } else {
8119 fallback_cursor_target
8120 };
8121
8122 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8123 s.select_anchor_ranges([cursor_target..cursor_target]);
8124 });
8125
8126 let selections = self.selections.disjoint_anchors_arc();
8127 if let Some(transaction_id_now) =
8128 self.buffer.read(cx).last_transaction_id(cx)
8129 {
8130 if transaction_id_prev != Some(transaction_id_now) {
8131 self.selection_history
8132 .insert_transaction(transaction_id_now, selections);
8133 }
8134 }
8135
8136 self.update_visible_edit_prediction(window, cx);
8137 if self.active_edit_prediction.is_none() {
8138 self.refresh_edit_prediction(true, true, window, cx);
8139 }
8140 cx.notify();
8141 }
8142 _ => {
8143 let snapshot = self.buffer.read(cx).snapshot(cx);
8144 let cursor_offset = self
8145 .selections
8146 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8147 .head();
8148
8149 let insertion = edits.iter().find_map(|(range, text)| {
8150 let range = range.to_offset(&snapshot);
8151 if range.is_empty() && range.start == cursor_offset {
8152 Some(text)
8153 } else {
8154 None
8155 }
8156 });
8157
8158 if let Some(text) = insertion {
8159 let text_to_insert = match granularity {
8160 EditPredictionGranularity::Word => {
8161 let mut partial = text
8162 .chars()
8163 .by_ref()
8164 .take_while(|c| c.is_alphabetic())
8165 .collect::<String>();
8166 if partial.is_empty() {
8167 partial = text
8168 .chars()
8169 .by_ref()
8170 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8171 .collect::<String>();
8172 }
8173 partial
8174 }
8175 EditPredictionGranularity::Line => {
8176 if let Some(line) = text.split_inclusive('\n').next() {
8177 line.to_string()
8178 } else {
8179 text.to_string()
8180 }
8181 }
8182 EditPredictionGranularity::Full => unreachable!(),
8183 };
8184
8185 cx.emit(EditorEvent::InputHandled {
8186 utf16_range_to_replace: None,
8187 text: text_to_insert.clone().into(),
8188 });
8189
8190 self.replace_selections(&text_to_insert, None, window, cx, false);
8191 self.refresh_edit_prediction(true, true, window, cx);
8192 cx.notify();
8193 } else {
8194 self.accept_partial_edit_prediction(
8195 EditPredictionGranularity::Full,
8196 window,
8197 cx,
8198 );
8199 }
8200 }
8201 }
8202 }
8203 }
8204
8205 self.edit_prediction_requires_modifier_in_indent_conflict = false;
8206 }
8207
8208 pub fn accept_next_word_edit_prediction(
8209 &mut self,
8210 _: &AcceptNextWordEditPrediction,
8211 window: &mut Window,
8212 cx: &mut Context<Self>,
8213 ) {
8214 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8215 }
8216
8217 pub fn accept_next_line_edit_prediction(
8218 &mut self,
8219 _: &AcceptNextLineEditPrediction,
8220 window: &mut Window,
8221 cx: &mut Context<Self>,
8222 ) {
8223 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8224 }
8225
8226 pub fn accept_edit_prediction(
8227 &mut self,
8228 _: &AcceptEditPrediction,
8229 window: &mut Window,
8230 cx: &mut Context<Self>,
8231 ) {
8232 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8233 }
8234
8235 fn discard_edit_prediction(
8236 &mut self,
8237 reason: EditPredictionDiscardReason,
8238 cx: &mut Context<Self>,
8239 ) -> bool {
8240 if reason == EditPredictionDiscardReason::Rejected {
8241 let completion_id = self
8242 .active_edit_prediction
8243 .as_ref()
8244 .and_then(|active_completion| active_completion.completion_id.clone());
8245
8246 self.report_edit_prediction_event(completion_id, false, cx);
8247 }
8248
8249 if let Some(provider) = self.edit_prediction_provider() {
8250 provider.discard(reason, cx);
8251 }
8252
8253 self.take_active_edit_prediction(cx)
8254 }
8255
8256 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8257 let Some(provider) = self.edit_prediction_provider() else {
8258 return;
8259 };
8260
8261 let Some((_, buffer, _)) = self
8262 .buffer
8263 .read(cx)
8264 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8265 else {
8266 return;
8267 };
8268
8269 let extension = buffer
8270 .read(cx)
8271 .file()
8272 .and_then(|file| Some(file.path().extension()?.to_string()));
8273
8274 let event_type = match accepted {
8275 true => "Edit Prediction Accepted",
8276 false => "Edit Prediction Discarded",
8277 };
8278 telemetry::event!(
8279 event_type,
8280 provider = provider.name(),
8281 prediction_id = id,
8282 suggestion_accepted = accepted,
8283 file_extension = extension,
8284 );
8285 }
8286
8287 fn open_editor_at_anchor(
8288 snapshot: &language::BufferSnapshot,
8289 target: language::Anchor,
8290 workspace: &Entity<Workspace>,
8291 window: &mut Window,
8292 cx: &mut App,
8293 ) -> Task<Result<()>> {
8294 workspace.update(cx, |workspace, cx| {
8295 let path = snapshot.file().map(|file| file.full_path(cx));
8296 let Some(path) =
8297 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8298 else {
8299 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8300 };
8301 let target = text::ToPoint::to_point(&target, snapshot);
8302 let item = workspace.open_path(path, None, true, window, cx);
8303 window.spawn(cx, async move |cx| {
8304 let Some(editor) = item.await?.downcast::<Editor>() else {
8305 return Ok(());
8306 };
8307 editor
8308 .update_in(cx, |editor, window, cx| {
8309 editor.go_to_singleton_buffer_point(target, window, cx);
8310 })
8311 .ok();
8312 anyhow::Ok(())
8313 })
8314 })
8315 }
8316
8317 pub fn has_active_edit_prediction(&self) -> bool {
8318 self.active_edit_prediction.is_some()
8319 }
8320
8321 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
8322 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8323 return false;
8324 };
8325
8326 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8327 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8328 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
8329 true
8330 }
8331
8332 /// Returns true when we're displaying the edit prediction popover below the cursor
8333 /// like we are not previewing and the LSP autocomplete menu is visible
8334 /// or we are in `when_holding_modifier` mode.
8335 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8336 if self.edit_prediction_preview_is_active()
8337 || !self.show_edit_predictions_in_menu()
8338 || !self.edit_predictions_enabled()
8339 {
8340 return false;
8341 }
8342
8343 if self.has_visible_completions_menu() {
8344 return true;
8345 }
8346
8347 has_completion && self.edit_prediction_requires_modifier()
8348 }
8349
8350 fn handle_modifiers_changed(
8351 &mut self,
8352 modifiers: Modifiers,
8353 position_map: &PositionMap,
8354 window: &mut Window,
8355 cx: &mut Context<Self>,
8356 ) {
8357 // Ensure that the edit prediction preview is updated, even when not
8358 // enabled, if there's an active edit prediction preview.
8359 if self.show_edit_predictions_in_menu()
8360 || matches!(
8361 self.edit_prediction_preview,
8362 EditPredictionPreview::Active { .. }
8363 )
8364 {
8365 self.update_edit_prediction_preview(&modifiers, window, cx);
8366 }
8367
8368 self.update_selection_mode(&modifiers, position_map, window, cx);
8369
8370 let mouse_position = window.mouse_position();
8371 if !position_map.text_hitbox.is_hovered(window) {
8372 return;
8373 }
8374
8375 self.update_hovered_link(
8376 position_map.point_for_position(mouse_position),
8377 &position_map.snapshot,
8378 modifiers,
8379 window,
8380 cx,
8381 )
8382 }
8383
8384 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8385 match EditorSettings::get_global(cx).multi_cursor_modifier {
8386 MultiCursorModifier::Alt => modifiers.secondary(),
8387 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8388 }
8389 }
8390
8391 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8392 match EditorSettings::get_global(cx).multi_cursor_modifier {
8393 MultiCursorModifier::Alt => modifiers.alt,
8394 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8395 }
8396 }
8397
8398 fn columnar_selection_mode(
8399 modifiers: &Modifiers,
8400 cx: &mut Context<Self>,
8401 ) -> Option<ColumnarMode> {
8402 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8403 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8404 Some(ColumnarMode::FromMouse)
8405 } else if Self::is_alt_pressed(modifiers, cx) {
8406 Some(ColumnarMode::FromSelection)
8407 } else {
8408 None
8409 }
8410 } else {
8411 None
8412 }
8413 }
8414
8415 fn update_selection_mode(
8416 &mut self,
8417 modifiers: &Modifiers,
8418 position_map: &PositionMap,
8419 window: &mut Window,
8420 cx: &mut Context<Self>,
8421 ) {
8422 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8423 return;
8424 };
8425 if self.selections.pending_anchor().is_none() {
8426 return;
8427 }
8428
8429 let mouse_position = window.mouse_position();
8430 let point_for_position = position_map.point_for_position(mouse_position);
8431 let position = point_for_position.previous_valid;
8432
8433 self.select(
8434 SelectPhase::BeginColumnar {
8435 position,
8436 reset: false,
8437 mode,
8438 goal_column: point_for_position.exact_unclipped.column(),
8439 },
8440 window,
8441 cx,
8442 );
8443 }
8444
8445 fn update_edit_prediction_preview(
8446 &mut self,
8447 modifiers: &Modifiers,
8448 window: &mut Window,
8449 cx: &mut Context<Self>,
8450 ) {
8451 let mut modifiers_held = false;
8452
8453 // Check bindings for all granularities.
8454 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8455 let granularities = [
8456 EditPredictionGranularity::Full,
8457 EditPredictionGranularity::Line,
8458 EditPredictionGranularity::Word,
8459 ];
8460
8461 for granularity in granularities {
8462 if let Some(keystroke) = self
8463 .accept_edit_prediction_keybind(granularity, window, cx)
8464 .keystroke()
8465 {
8466 modifiers_held = modifiers_held
8467 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8468 }
8469 }
8470
8471 if modifiers_held {
8472 if matches!(
8473 self.edit_prediction_preview,
8474 EditPredictionPreview::Inactive { .. }
8475 ) {
8476 self.edit_prediction_preview = EditPredictionPreview::Active {
8477 previous_scroll_position: None,
8478 since: Instant::now(),
8479 };
8480
8481 self.update_visible_edit_prediction(window, cx);
8482 cx.notify();
8483 }
8484 } else if let EditPredictionPreview::Active {
8485 previous_scroll_position,
8486 since,
8487 } = self.edit_prediction_preview
8488 {
8489 if let (Some(previous_scroll_position), Some(position_map)) =
8490 (previous_scroll_position, self.last_position_map.as_ref())
8491 {
8492 self.set_scroll_position(
8493 previous_scroll_position
8494 .scroll_position(&position_map.snapshot.display_snapshot),
8495 window,
8496 cx,
8497 );
8498 }
8499
8500 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8501 released_too_fast: since.elapsed() < Duration::from_millis(200),
8502 };
8503 self.clear_row_highlights::<EditPredictionPreview>();
8504 self.update_visible_edit_prediction(window, cx);
8505 cx.notify();
8506 }
8507 }
8508
8509 fn update_visible_edit_prediction(
8510 &mut self,
8511 _window: &mut Window,
8512 cx: &mut Context<Self>,
8513 ) -> Option<()> {
8514 if self.ime_transaction.is_some() {
8515 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8516 return None;
8517 }
8518
8519 let selection = self.selections.newest_anchor();
8520 let cursor = selection.head();
8521 let multibuffer = self.buffer.read(cx).snapshot(cx);
8522
8523 // Check project-level disable_ai setting for the current buffer
8524 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8525 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8526 return None;
8527 }
8528 }
8529 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8530 let excerpt_id = cursor.excerpt_id;
8531
8532 let show_in_menu = self.show_edit_predictions_in_menu();
8533 let completions_menu_has_precedence = !show_in_menu
8534 && (self.context_menu.borrow().is_some()
8535 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8536
8537 if completions_menu_has_precedence
8538 || !offset_selection.is_empty()
8539 || self
8540 .active_edit_prediction
8541 .as_ref()
8542 .is_some_and(|completion| {
8543 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8544 return false;
8545 };
8546 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8547 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8548 !invalidation_range.contains(&offset_selection.head())
8549 })
8550 {
8551 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8552 return None;
8553 }
8554
8555 self.take_active_edit_prediction(cx);
8556 let Some(provider) = self.edit_prediction_provider() else {
8557 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8558 return None;
8559 };
8560
8561 let (buffer, cursor_buffer_position) =
8562 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8563
8564 self.edit_prediction_settings =
8565 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8566
8567 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8568
8569 if self.edit_prediction_indent_conflict {
8570 let cursor_point = cursor.to_point(&multibuffer);
8571 let mut suggested_indent = None;
8572 multibuffer.suggested_indents_callback(
8573 cursor_point.row..cursor_point.row + 1,
8574 &mut |_, indent| {
8575 suggested_indent = Some(indent);
8576 ControlFlow::Break(())
8577 },
8578 cx,
8579 );
8580
8581 if let Some(indent) = suggested_indent
8582 && indent.len == cursor_point.column
8583 {
8584 self.edit_prediction_indent_conflict = false;
8585 }
8586 }
8587
8588 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8589
8590 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8591 {
8592 edit_prediction_types::EditPrediction::Local {
8593 id,
8594 edits,
8595 cursor_position,
8596 edit_preview,
8597 } => (id, edits, cursor_position, edit_preview),
8598 edit_prediction_types::EditPrediction::Jump {
8599 id,
8600 snapshot,
8601 target,
8602 } => {
8603 if let Some(provider) = &self.edit_prediction_provider {
8604 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8605 }
8606 self.stale_edit_prediction_in_menu = None;
8607 self.active_edit_prediction = Some(EditPredictionState {
8608 inlay_ids: vec![],
8609 completion: EditPrediction::MoveOutside { snapshot, target },
8610 completion_id: id,
8611 invalidation_range: None,
8612 });
8613 cx.notify();
8614 return Some(());
8615 }
8616 };
8617
8618 let edits = edits
8619 .into_iter()
8620 .flat_map(|(range, new_text)| {
8621 Some((
8622 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8623 new_text,
8624 ))
8625 })
8626 .collect::<Vec<_>>();
8627 if edits.is_empty() {
8628 return None;
8629 }
8630
8631 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8632 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8633 Some((anchor, predicted.offset))
8634 });
8635
8636 let first_edit_start = edits.first().unwrap().0.start;
8637 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8638 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8639
8640 let last_edit_end = edits.last().unwrap().0.end;
8641 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8642 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8643
8644 let cursor_row = cursor.to_point(&multibuffer).row;
8645
8646 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8647
8648 let mut inlay_ids = Vec::new();
8649 let invalidation_row_range;
8650 let move_invalidation_row_range = if cursor_row < edit_start_row {
8651 Some(cursor_row..edit_end_row)
8652 } else if cursor_row > edit_end_row {
8653 Some(edit_start_row..cursor_row)
8654 } else {
8655 None
8656 };
8657 let supports_jump = self
8658 .edit_prediction_provider
8659 .as_ref()
8660 .map(|provider| provider.provider.supports_jump_to_edit())
8661 .unwrap_or(true);
8662
8663 let is_move = supports_jump
8664 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8665 let completion = if is_move {
8666 if let Some(provider) = &self.edit_prediction_provider {
8667 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8668 }
8669 invalidation_row_range =
8670 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8671 let target = first_edit_start;
8672 EditPrediction::MoveWithin { target, snapshot }
8673 } else {
8674 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8675 && !self.edit_predictions_hidden_for_vim_mode;
8676
8677 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8678 if provider.show_tab_accept_marker() {
8679 EditDisplayMode::TabAccept
8680 } else {
8681 EditDisplayMode::Inline
8682 }
8683 } else {
8684 EditDisplayMode::DiffPopover
8685 };
8686
8687 if show_completions_in_buffer {
8688 if let Some(provider) = &self.edit_prediction_provider {
8689 let suggestion_display_type = match display_mode {
8690 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8691 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8692 SuggestionDisplayType::GhostText
8693 }
8694 };
8695 provider.provider.did_show(suggestion_display_type, cx);
8696 }
8697 if edits
8698 .iter()
8699 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8700 {
8701 let mut inlays = Vec::new();
8702 for (range, new_text) in &edits {
8703 let inlay = Inlay::edit_prediction(
8704 post_inc(&mut self.next_inlay_id),
8705 range.start,
8706 new_text.as_ref(),
8707 );
8708 inlay_ids.push(inlay.id);
8709 inlays.push(inlay);
8710 }
8711
8712 self.splice_inlays(&[], inlays, cx);
8713 } else {
8714 let background_color = cx.theme().status().deleted_background;
8715 self.highlight_text(
8716 HighlightKey::EditPredictionHighlight,
8717 edits.iter().map(|(range, _)| range.clone()).collect(),
8718 HighlightStyle {
8719 background_color: Some(background_color),
8720 ..Default::default()
8721 },
8722 cx,
8723 );
8724 }
8725 }
8726
8727 invalidation_row_range = edit_start_row..edit_end_row;
8728
8729 EditPrediction::Edit {
8730 edits,
8731 cursor_position,
8732 edit_preview,
8733 display_mode,
8734 snapshot,
8735 }
8736 };
8737
8738 let invalidation_range = multibuffer
8739 .anchor_before(Point::new(invalidation_row_range.start, 0))
8740 ..multibuffer.anchor_after(Point::new(
8741 invalidation_row_range.end,
8742 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8743 ));
8744
8745 self.stale_edit_prediction_in_menu = None;
8746 self.active_edit_prediction = Some(EditPredictionState {
8747 inlay_ids,
8748 completion,
8749 completion_id,
8750 invalidation_range: Some(invalidation_range),
8751 });
8752
8753 cx.notify();
8754
8755 Some(())
8756 }
8757
8758 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8759 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8760 }
8761
8762 fn clear_tasks(&mut self) {
8763 self.tasks.clear()
8764 }
8765
8766 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8767 if self.tasks.insert(key, value).is_some() {
8768 // This case should hopefully be rare, but just in case...
8769 log::error!(
8770 "multiple different run targets found on a single line, only the last target will be rendered"
8771 )
8772 }
8773 }
8774
8775 /// Get all display points of breakpoints that will be rendered within editor
8776 ///
8777 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8778 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8779 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8780 fn active_breakpoints(
8781 &self,
8782 range: Range<DisplayRow>,
8783 window: &mut Window,
8784 cx: &mut Context<Self>,
8785 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8786 let mut breakpoint_display_points = HashMap::default();
8787
8788 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8789 return breakpoint_display_points;
8790 };
8791
8792 let snapshot = self.snapshot(window, cx);
8793
8794 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8795 let Some(project) = self.project() else {
8796 return breakpoint_display_points;
8797 };
8798
8799 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8800 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8801
8802 for (buffer_snapshot, range, excerpt_id) in
8803 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8804 {
8805 let Some(buffer) = project
8806 .read(cx)
8807 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8808 else {
8809 continue;
8810 };
8811 let breakpoints = breakpoint_store.read(cx).breakpoints(
8812 &buffer,
8813 Some(
8814 buffer_snapshot.anchor_before(range.start)
8815 ..buffer_snapshot.anchor_after(range.end),
8816 ),
8817 buffer_snapshot,
8818 cx,
8819 );
8820 for (breakpoint, state) in breakpoints {
8821 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8822 let position = multi_buffer_anchor
8823 .to_point(&multi_buffer_snapshot)
8824 .to_display_point(&snapshot);
8825
8826 breakpoint_display_points.insert(
8827 position.row(),
8828 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8829 );
8830 }
8831 }
8832
8833 breakpoint_display_points
8834 }
8835
8836 fn breakpoint_context_menu(
8837 &self,
8838 anchor: Anchor,
8839 window: &mut Window,
8840 cx: &mut Context<Self>,
8841 ) -> Entity<ui::ContextMenu> {
8842 let weak_editor = cx.weak_entity();
8843 let focus_handle = self.focus_handle(cx);
8844
8845 let row = self
8846 .buffer
8847 .read(cx)
8848 .snapshot(cx)
8849 .summary_for_anchor::<Point>(&anchor)
8850 .row;
8851
8852 let breakpoint = self
8853 .breakpoint_at_row(row, window, cx)
8854 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8855
8856 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8857 "Edit Log Breakpoint"
8858 } else {
8859 "Set Log Breakpoint"
8860 };
8861
8862 let condition_breakpoint_msg = if breakpoint
8863 .as_ref()
8864 .is_some_and(|bp| bp.1.condition.is_some())
8865 {
8866 "Edit Condition Breakpoint"
8867 } else {
8868 "Set Condition Breakpoint"
8869 };
8870
8871 let hit_condition_breakpoint_msg = if breakpoint
8872 .as_ref()
8873 .is_some_and(|bp| bp.1.hit_condition.is_some())
8874 {
8875 "Edit Hit Condition Breakpoint"
8876 } else {
8877 "Set Hit Condition Breakpoint"
8878 };
8879
8880 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8881 "Unset Breakpoint"
8882 } else {
8883 "Set Breakpoint"
8884 };
8885
8886 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8887
8888 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8889 BreakpointState::Enabled => Some("Disable"),
8890 BreakpointState::Disabled => Some("Enable"),
8891 });
8892
8893 let (anchor, breakpoint) =
8894 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8895
8896 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8897 menu.on_blur_subscription(Subscription::new(|| {}))
8898 .context(focus_handle)
8899 .when(run_to_cursor, |this| {
8900 let weak_editor = weak_editor.clone();
8901 this.entry("Run to Cursor", None, move |window, cx| {
8902 weak_editor
8903 .update(cx, |editor, cx| {
8904 editor.change_selections(
8905 SelectionEffects::no_scroll(),
8906 window,
8907 cx,
8908 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8909 );
8910 })
8911 .ok();
8912
8913 window.dispatch_action(Box::new(RunToCursor), cx);
8914 })
8915 .separator()
8916 })
8917 .when_some(toggle_state_msg, |this, msg| {
8918 this.entry(msg, None, {
8919 let weak_editor = weak_editor.clone();
8920 let breakpoint = breakpoint.clone();
8921 move |_window, cx| {
8922 weak_editor
8923 .update(cx, |this, cx| {
8924 this.edit_breakpoint_at_anchor(
8925 anchor,
8926 breakpoint.as_ref().clone(),
8927 BreakpointEditAction::InvertState,
8928 cx,
8929 );
8930 })
8931 .log_err();
8932 }
8933 })
8934 })
8935 .entry(set_breakpoint_msg, None, {
8936 let weak_editor = weak_editor.clone();
8937 let breakpoint = breakpoint.clone();
8938 move |_window, cx| {
8939 weak_editor
8940 .update(cx, |this, cx| {
8941 this.edit_breakpoint_at_anchor(
8942 anchor,
8943 breakpoint.as_ref().clone(),
8944 BreakpointEditAction::Toggle,
8945 cx,
8946 );
8947 })
8948 .log_err();
8949 }
8950 })
8951 .entry(log_breakpoint_msg, None, {
8952 let breakpoint = breakpoint.clone();
8953 let weak_editor = weak_editor.clone();
8954 move |window, cx| {
8955 weak_editor
8956 .update(cx, |this, cx| {
8957 this.add_edit_breakpoint_block(
8958 anchor,
8959 breakpoint.as_ref(),
8960 BreakpointPromptEditAction::Log,
8961 window,
8962 cx,
8963 );
8964 })
8965 .log_err();
8966 }
8967 })
8968 .entry(condition_breakpoint_msg, None, {
8969 let breakpoint = breakpoint.clone();
8970 let weak_editor = weak_editor.clone();
8971 move |window, cx| {
8972 weak_editor
8973 .update(cx, |this, cx| {
8974 this.add_edit_breakpoint_block(
8975 anchor,
8976 breakpoint.as_ref(),
8977 BreakpointPromptEditAction::Condition,
8978 window,
8979 cx,
8980 );
8981 })
8982 .log_err();
8983 }
8984 })
8985 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8986 weak_editor
8987 .update(cx, |this, cx| {
8988 this.add_edit_breakpoint_block(
8989 anchor,
8990 breakpoint.as_ref(),
8991 BreakpointPromptEditAction::HitCondition,
8992 window,
8993 cx,
8994 );
8995 })
8996 .log_err();
8997 })
8998 })
8999 }
9000
9001 fn render_breakpoint(
9002 &self,
9003 position: Anchor,
9004 row: DisplayRow,
9005 breakpoint: &Breakpoint,
9006 state: Option<BreakpointSessionState>,
9007 cx: &mut Context<Self>,
9008 ) -> IconButton {
9009 let is_rejected = state.is_some_and(|s| !s.verified);
9010 // Is it a breakpoint that shows up when hovering over gutter?
9011 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9012 (false, false),
9013 |PhantomBreakpointIndicator {
9014 is_active,
9015 display_row,
9016 collides_with_existing_breakpoint,
9017 }| {
9018 (
9019 is_active && display_row == row,
9020 collides_with_existing_breakpoint,
9021 )
9022 },
9023 );
9024
9025 let (color, icon) = {
9026 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9027 (false, false) => ui::IconName::DebugBreakpoint,
9028 (true, false) => ui::IconName::DebugLogBreakpoint,
9029 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9030 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9031 };
9032
9033 let theme_colors = cx.theme().colors();
9034
9035 let color = if is_phantom {
9036 if collides_with_existing {
9037 Color::Custom(
9038 theme_colors
9039 .debugger_accent
9040 .blend(theme_colors.text.opacity(0.6)),
9041 )
9042 } else {
9043 Color::Hint
9044 }
9045 } else if is_rejected {
9046 Color::Disabled
9047 } else {
9048 Color::Debugger
9049 };
9050
9051 (color, icon)
9052 };
9053
9054 let breakpoint = Arc::from(breakpoint.clone());
9055
9056 let alt_as_text = gpui::Keystroke {
9057 modifiers: Modifiers::secondary_key(),
9058 ..Default::default()
9059 };
9060 let primary_action_text = if breakpoint.is_disabled() {
9061 "Enable breakpoint"
9062 } else if is_phantom && !collides_with_existing {
9063 "Set breakpoint"
9064 } else {
9065 "Unset breakpoint"
9066 };
9067 let focus_handle = self.focus_handle.clone();
9068
9069 let meta = if is_rejected {
9070 SharedString::from("No executable code is associated with this line.")
9071 } else if collides_with_existing && !breakpoint.is_disabled() {
9072 SharedString::from(format!(
9073 "{alt_as_text}-click to disable,\nright-click for more options."
9074 ))
9075 } else {
9076 SharedString::from("Right-click for more options.")
9077 };
9078 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9079 .icon_size(IconSize::XSmall)
9080 .size(ui::ButtonSize::None)
9081 .when(is_rejected, |this| {
9082 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9083 })
9084 .icon_color(color)
9085 .style(ButtonStyle::Transparent)
9086 .on_click(cx.listener({
9087 move |editor, event: &ClickEvent, window, cx| {
9088 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9089 BreakpointEditAction::InvertState
9090 } else {
9091 BreakpointEditAction::Toggle
9092 };
9093
9094 window.focus(&editor.focus_handle(cx), cx);
9095 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9096 editor.edit_breakpoint_at_anchor(
9097 position,
9098 breakpoint.as_ref().clone(),
9099 edit_action,
9100 cx,
9101 );
9102 }
9103 }))
9104 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9105 editor.set_breakpoint_context_menu(
9106 row,
9107 Some(position),
9108 event.position(),
9109 window,
9110 cx,
9111 );
9112 }))
9113 .tooltip(move |_window, cx| {
9114 Tooltip::with_meta_in(
9115 primary_action_text,
9116 Some(&ToggleBreakpoint),
9117 meta.clone(),
9118 &focus_handle,
9119 cx,
9120 )
9121 })
9122 }
9123
9124 fn build_tasks_context(
9125 project: &Entity<Project>,
9126 buffer: &Entity<Buffer>,
9127 buffer_row: u32,
9128 tasks: &Arc<RunnableTasks>,
9129 cx: &mut Context<Self>,
9130 ) -> Task<Option<task::TaskContext>> {
9131 let position = Point::new(buffer_row, tasks.column);
9132 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9133 let location = Location {
9134 buffer: buffer.clone(),
9135 range: range_start..range_start,
9136 };
9137 // Fill in the environmental variables from the tree-sitter captures
9138 let mut captured_task_variables = TaskVariables::default();
9139 for (capture_name, value) in tasks.extra_variables.clone() {
9140 captured_task_variables.insert(
9141 task::VariableName::Custom(capture_name.into()),
9142 value.clone(),
9143 );
9144 }
9145 project.update(cx, |project, cx| {
9146 project.task_store().update(cx, |task_store, cx| {
9147 task_store.task_context_for_location(captured_task_variables, location, cx)
9148 })
9149 })
9150 }
9151
9152 pub fn spawn_nearest_task(
9153 &mut self,
9154 action: &SpawnNearestTask,
9155 window: &mut Window,
9156 cx: &mut Context<Self>,
9157 ) {
9158 let Some((workspace, _)) = self.workspace.clone() else {
9159 return;
9160 };
9161 let Some(project) = self.project.clone() else {
9162 return;
9163 };
9164
9165 // Try to find a closest, enclosing node using tree-sitter that has a task
9166 let Some((buffer, buffer_row, tasks)) = self
9167 .find_enclosing_node_task(cx)
9168 // Or find the task that's closest in row-distance.
9169 .or_else(|| self.find_closest_task(cx))
9170 else {
9171 return;
9172 };
9173
9174 let reveal_strategy = action.reveal;
9175 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
9176 cx.spawn_in(window, async move |_, cx| {
9177 let context = task_context.await?;
9178 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
9179
9180 let resolved = &mut resolved_task.resolved;
9181 resolved.reveal = reveal_strategy;
9182
9183 workspace
9184 .update_in(cx, |workspace, window, cx| {
9185 workspace.schedule_resolved_task(
9186 task_source_kind,
9187 resolved_task,
9188 false,
9189 window,
9190 cx,
9191 );
9192 })
9193 .ok()
9194 })
9195 .detach();
9196 }
9197
9198 fn find_closest_task(
9199 &mut self,
9200 cx: &mut Context<Self>,
9201 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9202 let cursor_row = self
9203 .selections
9204 .newest_adjusted(&self.display_snapshot(cx))
9205 .head()
9206 .row;
9207
9208 let ((buffer_id, row), tasks) = self
9209 .tasks
9210 .iter()
9211 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
9212
9213 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
9214 let tasks = Arc::new(tasks.to_owned());
9215 Some((buffer, *row, tasks))
9216 }
9217
9218 fn find_enclosing_node_task(
9219 &mut self,
9220 cx: &mut Context<Self>,
9221 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
9222 let snapshot = self.buffer.read(cx).snapshot(cx);
9223 let offset = self
9224 .selections
9225 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
9226 .head();
9227 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
9228 let offset = excerpt.map_offset_to_buffer(offset);
9229 let buffer_id = excerpt.buffer().remote_id();
9230
9231 let layer = excerpt.buffer().syntax_layer_at(offset)?;
9232 let mut cursor = layer.node().walk();
9233
9234 while cursor.goto_first_child_for_byte(offset.0).is_some() {
9235 if cursor.node().end_byte() == offset.0 {
9236 cursor.goto_next_sibling();
9237 }
9238 }
9239
9240 // Ascend to the smallest ancestor that contains the range and has a task.
9241 loop {
9242 let node = cursor.node();
9243 let node_range = node.byte_range();
9244 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
9245
9246 // Check if this node contains our offset
9247 if node_range.start <= offset.0 && node_range.end >= offset.0 {
9248 // If it contains offset, check for task
9249 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
9250 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
9251 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
9252 }
9253 }
9254
9255 if !cursor.goto_parent() {
9256 break;
9257 }
9258 }
9259 None
9260 }
9261
9262 fn render_run_indicator(
9263 &self,
9264 _style: &EditorStyle,
9265 is_active: bool,
9266 row: DisplayRow,
9267 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
9268 cx: &mut Context<Self>,
9269 ) -> IconButton {
9270 let color = Color::Muted;
9271 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
9272
9273 IconButton::new(
9274 ("run_indicator", row.0 as usize),
9275 ui::IconName::PlayOutlined,
9276 )
9277 .shape(ui::IconButtonShape::Square)
9278 .icon_size(IconSize::XSmall)
9279 .icon_color(color)
9280 .toggle_state(is_active)
9281 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
9282 let quick_launch = match e {
9283 ClickEvent::Keyboard(_) => true,
9284 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
9285 };
9286
9287 window.focus(&editor.focus_handle(cx), cx);
9288 editor.toggle_code_actions(
9289 &ToggleCodeActions {
9290 deployed_from: Some(CodeActionSource::RunMenu(row)),
9291 quick_launch,
9292 },
9293 window,
9294 cx,
9295 );
9296 }))
9297 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9298 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
9299 }))
9300 }
9301
9302 pub fn context_menu_visible(&self) -> bool {
9303 !self.edit_prediction_preview_is_active()
9304 && self
9305 .context_menu
9306 .borrow()
9307 .as_ref()
9308 .is_some_and(|menu| menu.visible())
9309 }
9310
9311 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9312 self.context_menu
9313 .borrow()
9314 .as_ref()
9315 .map(|menu| menu.origin())
9316 }
9317
9318 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9319 self.context_menu_options = Some(options);
9320 }
9321
9322 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9323 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9324
9325 fn render_edit_prediction_popover(
9326 &mut self,
9327 text_bounds: &Bounds<Pixels>,
9328 content_origin: gpui::Point<Pixels>,
9329 right_margin: Pixels,
9330 editor_snapshot: &EditorSnapshot,
9331 visible_row_range: Range<DisplayRow>,
9332 scroll_top: ScrollOffset,
9333 scroll_bottom: ScrollOffset,
9334 line_layouts: &[LineWithInvisibles],
9335 line_height: Pixels,
9336 scroll_position: gpui::Point<ScrollOffset>,
9337 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9338 newest_selection_head: Option<DisplayPoint>,
9339 editor_width: Pixels,
9340 style: &EditorStyle,
9341 window: &mut Window,
9342 cx: &mut App,
9343 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9344 if self.mode().is_minimap() {
9345 return None;
9346 }
9347 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9348
9349 if self.edit_prediction_visible_in_cursor_popover(true) {
9350 return None;
9351 }
9352
9353 match &active_edit_prediction.completion {
9354 EditPrediction::MoveWithin { target, .. } => {
9355 let target_display_point = target.to_display_point(editor_snapshot);
9356
9357 if self.edit_prediction_requires_modifier() {
9358 if !self.edit_prediction_preview_is_active() {
9359 return None;
9360 }
9361
9362 self.render_edit_prediction_modifier_jump_popover(
9363 text_bounds,
9364 content_origin,
9365 visible_row_range,
9366 line_layouts,
9367 line_height,
9368 scroll_pixel_position,
9369 newest_selection_head,
9370 target_display_point,
9371 window,
9372 cx,
9373 )
9374 } else {
9375 self.render_edit_prediction_eager_jump_popover(
9376 text_bounds,
9377 content_origin,
9378 editor_snapshot,
9379 visible_row_range,
9380 scroll_top,
9381 scroll_bottom,
9382 line_height,
9383 scroll_pixel_position,
9384 target_display_point,
9385 editor_width,
9386 window,
9387 cx,
9388 )
9389 }
9390 }
9391 EditPrediction::Edit {
9392 display_mode: EditDisplayMode::Inline,
9393 ..
9394 } => None,
9395 EditPrediction::Edit {
9396 display_mode: EditDisplayMode::TabAccept,
9397 edits,
9398 ..
9399 } => {
9400 let range = &edits.first()?.0;
9401 let target_display_point = range.end.to_display_point(editor_snapshot);
9402
9403 self.render_edit_prediction_end_of_line_popover(
9404 "Accept",
9405 editor_snapshot,
9406 visible_row_range,
9407 target_display_point,
9408 line_height,
9409 scroll_pixel_position,
9410 content_origin,
9411 editor_width,
9412 window,
9413 cx,
9414 )
9415 }
9416 EditPrediction::Edit {
9417 edits,
9418 edit_preview,
9419 display_mode: EditDisplayMode::DiffPopover,
9420 snapshot,
9421 ..
9422 } => self.render_edit_prediction_diff_popover(
9423 text_bounds,
9424 content_origin,
9425 right_margin,
9426 editor_snapshot,
9427 visible_row_range,
9428 line_layouts,
9429 line_height,
9430 scroll_position,
9431 scroll_pixel_position,
9432 newest_selection_head,
9433 editor_width,
9434 style,
9435 edits,
9436 edit_preview,
9437 snapshot,
9438 window,
9439 cx,
9440 ),
9441 EditPrediction::MoveOutside { snapshot, .. } => {
9442 let mut element = self
9443 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9444 .into_any();
9445
9446 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9447 let origin_x = text_bounds.size.width - size.width - px(30.);
9448 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9449 element.prepaint_at(origin, window, cx);
9450
9451 Some((element, origin))
9452 }
9453 }
9454 }
9455
9456 fn render_edit_prediction_modifier_jump_popover(
9457 &mut self,
9458 text_bounds: &Bounds<Pixels>,
9459 content_origin: gpui::Point<Pixels>,
9460 visible_row_range: Range<DisplayRow>,
9461 line_layouts: &[LineWithInvisibles],
9462 line_height: Pixels,
9463 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9464 newest_selection_head: Option<DisplayPoint>,
9465 target_display_point: DisplayPoint,
9466 window: &mut Window,
9467 cx: &mut App,
9468 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9469 let scrolled_content_origin =
9470 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9471
9472 const SCROLL_PADDING_Y: Pixels = px(12.);
9473
9474 if target_display_point.row() < visible_row_range.start {
9475 return self.render_edit_prediction_scroll_popover(
9476 &|_| SCROLL_PADDING_Y,
9477 IconName::ArrowUp,
9478 visible_row_range,
9479 line_layouts,
9480 newest_selection_head,
9481 scrolled_content_origin,
9482 window,
9483 cx,
9484 );
9485 } else if target_display_point.row() >= visible_row_range.end {
9486 return self.render_edit_prediction_scroll_popover(
9487 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9488 IconName::ArrowDown,
9489 visible_row_range,
9490 line_layouts,
9491 newest_selection_head,
9492 scrolled_content_origin,
9493 window,
9494 cx,
9495 );
9496 }
9497
9498 const POLE_WIDTH: Pixels = px(2.);
9499
9500 let line_layout =
9501 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9502 let target_column = target_display_point.column() as usize;
9503
9504 let target_x = line_layout.x_for_index(target_column);
9505 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9506 - scroll_pixel_position.y;
9507
9508 let flag_on_right = target_x < text_bounds.size.width / 2.;
9509
9510 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9511 border_color.l += 0.001;
9512
9513 let mut element = v_flex()
9514 .items_end()
9515 .when(flag_on_right, |el| el.items_start())
9516 .child(if flag_on_right {
9517 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9518 .rounded_bl(px(0.))
9519 .rounded_tl(px(0.))
9520 .border_l_2()
9521 .border_color(border_color)
9522 } else {
9523 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9524 .rounded_br(px(0.))
9525 .rounded_tr(px(0.))
9526 .border_r_2()
9527 .border_color(border_color)
9528 })
9529 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9530 .into_any();
9531
9532 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9533
9534 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9535 - point(
9536 if flag_on_right {
9537 POLE_WIDTH
9538 } else {
9539 size.width - POLE_WIDTH
9540 },
9541 size.height - line_height,
9542 );
9543
9544 origin.x = origin.x.max(content_origin.x);
9545
9546 element.prepaint_at(origin, window, cx);
9547
9548 Some((element, origin))
9549 }
9550
9551 fn render_edit_prediction_scroll_popover(
9552 &mut self,
9553 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9554 scroll_icon: IconName,
9555 visible_row_range: Range<DisplayRow>,
9556 line_layouts: &[LineWithInvisibles],
9557 newest_selection_head: Option<DisplayPoint>,
9558 scrolled_content_origin: gpui::Point<Pixels>,
9559 window: &mut Window,
9560 cx: &mut App,
9561 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9562 let mut element = self
9563 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9564 .into_any();
9565
9566 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9567
9568 let cursor = newest_selection_head?;
9569 let cursor_row_layout =
9570 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9571 let cursor_column = cursor.column() as usize;
9572
9573 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9574
9575 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9576
9577 element.prepaint_at(origin, window, cx);
9578 Some((element, origin))
9579 }
9580
9581 fn render_edit_prediction_eager_jump_popover(
9582 &mut self,
9583 text_bounds: &Bounds<Pixels>,
9584 content_origin: gpui::Point<Pixels>,
9585 editor_snapshot: &EditorSnapshot,
9586 visible_row_range: Range<DisplayRow>,
9587 scroll_top: ScrollOffset,
9588 scroll_bottom: ScrollOffset,
9589 line_height: Pixels,
9590 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9591 target_display_point: DisplayPoint,
9592 editor_width: Pixels,
9593 window: &mut Window,
9594 cx: &mut App,
9595 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9596 if target_display_point.row().as_f64() < scroll_top {
9597 let mut element = self
9598 .render_edit_prediction_line_popover(
9599 "Jump to Edit",
9600 Some(IconName::ArrowUp),
9601 window,
9602 cx,
9603 )
9604 .into_any();
9605
9606 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9607 let offset = point(
9608 (text_bounds.size.width - size.width) / 2.,
9609 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9610 );
9611
9612 let origin = text_bounds.origin + offset;
9613 element.prepaint_at(origin, window, cx);
9614 Some((element, origin))
9615 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9616 let mut element = self
9617 .render_edit_prediction_line_popover(
9618 "Jump to Edit",
9619 Some(IconName::ArrowDown),
9620 window,
9621 cx,
9622 )
9623 .into_any();
9624
9625 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9626 let offset = point(
9627 (text_bounds.size.width - size.width) / 2.,
9628 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9629 );
9630
9631 let origin = text_bounds.origin + offset;
9632 element.prepaint_at(origin, window, cx);
9633 Some((element, origin))
9634 } else {
9635 self.render_edit_prediction_end_of_line_popover(
9636 "Jump to Edit",
9637 editor_snapshot,
9638 visible_row_range,
9639 target_display_point,
9640 line_height,
9641 scroll_pixel_position,
9642 content_origin,
9643 editor_width,
9644 window,
9645 cx,
9646 )
9647 }
9648 }
9649
9650 fn render_edit_prediction_end_of_line_popover(
9651 self: &mut Editor,
9652 label: &'static str,
9653 editor_snapshot: &EditorSnapshot,
9654 visible_row_range: Range<DisplayRow>,
9655 target_display_point: DisplayPoint,
9656 line_height: Pixels,
9657 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9658 content_origin: gpui::Point<Pixels>,
9659 editor_width: Pixels,
9660 window: &mut Window,
9661 cx: &mut App,
9662 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9663 let target_line_end = DisplayPoint::new(
9664 target_display_point.row(),
9665 editor_snapshot.line_len(target_display_point.row()),
9666 );
9667
9668 let mut element = self
9669 .render_edit_prediction_line_popover(label, None, window, cx)
9670 .into_any();
9671
9672 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9673
9674 let line_origin =
9675 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9676
9677 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9678 let mut origin = start_point
9679 + line_origin
9680 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9681 origin.x = origin.x.max(content_origin.x);
9682
9683 let max_x = content_origin.x + editor_width - size.width;
9684
9685 if origin.x > max_x {
9686 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9687
9688 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9689 origin.y += offset;
9690 IconName::ArrowUp
9691 } else {
9692 origin.y -= offset;
9693 IconName::ArrowDown
9694 };
9695
9696 element = self
9697 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9698 .into_any();
9699
9700 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9701
9702 origin.x = content_origin.x + editor_width - size.width - px(2.);
9703 }
9704
9705 element.prepaint_at(origin, window, cx);
9706 Some((element, origin))
9707 }
9708
9709 fn render_edit_prediction_diff_popover(
9710 self: &Editor,
9711 text_bounds: &Bounds<Pixels>,
9712 content_origin: gpui::Point<Pixels>,
9713 right_margin: Pixels,
9714 editor_snapshot: &EditorSnapshot,
9715 visible_row_range: Range<DisplayRow>,
9716 line_layouts: &[LineWithInvisibles],
9717 line_height: Pixels,
9718 scroll_position: gpui::Point<ScrollOffset>,
9719 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9720 newest_selection_head: Option<DisplayPoint>,
9721 editor_width: Pixels,
9722 style: &EditorStyle,
9723 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9724 edit_preview: &Option<language::EditPreview>,
9725 snapshot: &language::BufferSnapshot,
9726 window: &mut Window,
9727 cx: &mut App,
9728 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9729 let edit_start = edits
9730 .first()
9731 .unwrap()
9732 .0
9733 .start
9734 .to_display_point(editor_snapshot);
9735 let edit_end = edits
9736 .last()
9737 .unwrap()
9738 .0
9739 .end
9740 .to_display_point(editor_snapshot);
9741
9742 let is_visible = visible_row_range.contains(&edit_start.row())
9743 || visible_row_range.contains(&edit_end.row());
9744 if !is_visible {
9745 return None;
9746 }
9747
9748 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9749 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9750 } else {
9751 // Fallback for providers without edit_preview
9752 crate::edit_prediction_fallback_text(edits, cx)
9753 };
9754
9755 let styled_text = highlighted_edits.to_styled_text(&style.text);
9756 let line_count = highlighted_edits.text.lines().count();
9757
9758 const BORDER_WIDTH: Pixels = px(1.);
9759
9760 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9761 let has_keybind = keybind.is_some();
9762
9763 let mut element = h_flex()
9764 .items_start()
9765 .child(
9766 h_flex()
9767 .bg(cx.theme().colors().editor_background)
9768 .border(BORDER_WIDTH)
9769 .shadow_xs()
9770 .border_color(cx.theme().colors().border)
9771 .rounded_l_lg()
9772 .when(line_count > 1, |el| el.rounded_br_lg())
9773 .pr_1()
9774 .child(styled_text),
9775 )
9776 .child(
9777 h_flex()
9778 .h(line_height + BORDER_WIDTH * 2.)
9779 .px_1p5()
9780 .gap_1()
9781 // Workaround: For some reason, there's a gap if we don't do this
9782 .ml(-BORDER_WIDTH)
9783 .shadow(vec![gpui::BoxShadow {
9784 color: gpui::black().opacity(0.05),
9785 offset: point(px(1.), px(1.)),
9786 blur_radius: px(2.),
9787 spread_radius: px(0.),
9788 }])
9789 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9790 .border(BORDER_WIDTH)
9791 .border_color(cx.theme().colors().border)
9792 .rounded_r_lg()
9793 .id("edit_prediction_diff_popover_keybind")
9794 .when(!has_keybind, |el| {
9795 let status_colors = cx.theme().status();
9796
9797 el.bg(status_colors.error_background)
9798 .border_color(status_colors.error.opacity(0.6))
9799 .child(Icon::new(IconName::Info).color(Color::Error))
9800 .cursor_default()
9801 .hoverable_tooltip(move |_window, cx| {
9802 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9803 })
9804 })
9805 .children(keybind),
9806 )
9807 .into_any();
9808
9809 let longest_row =
9810 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9811 let longest_line_width = if visible_row_range.contains(&longest_row) {
9812 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9813 } else {
9814 layout_line(
9815 longest_row,
9816 editor_snapshot,
9817 style,
9818 editor_width,
9819 |_| false,
9820 window,
9821 cx,
9822 )
9823 .width
9824 };
9825
9826 let viewport_bounds =
9827 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9828 right: -right_margin,
9829 ..Default::default()
9830 });
9831
9832 let x_after_longest = Pixels::from(
9833 ScrollPixelOffset::from(
9834 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9835 ) - scroll_pixel_position.x,
9836 );
9837
9838 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9839
9840 // Fully visible if it can be displayed within the window (allow overlapping other
9841 // panes). However, this is only allowed if the popover starts within text_bounds.
9842 let can_position_to_the_right = x_after_longest < text_bounds.right()
9843 && x_after_longest + element_bounds.width < viewport_bounds.right();
9844
9845 let mut origin = if can_position_to_the_right {
9846 point(
9847 x_after_longest,
9848 text_bounds.origin.y
9849 + Pixels::from(
9850 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9851 - scroll_pixel_position.y,
9852 ),
9853 )
9854 } else {
9855 let cursor_row = newest_selection_head.map(|head| head.row());
9856 let above_edit = edit_start
9857 .row()
9858 .0
9859 .checked_sub(line_count as u32)
9860 .map(DisplayRow);
9861 let below_edit = Some(edit_end.row() + 1);
9862 let above_cursor =
9863 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9864 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9865
9866 // Place the edit popover adjacent to the edit if there is a location
9867 // available that is onscreen and does not obscure the cursor. Otherwise,
9868 // place it adjacent to the cursor.
9869 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9870 .into_iter()
9871 .flatten()
9872 .find(|&start_row| {
9873 let end_row = start_row + line_count as u32;
9874 visible_row_range.contains(&start_row)
9875 && visible_row_range.contains(&end_row)
9876 && cursor_row
9877 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9878 })?;
9879
9880 content_origin
9881 + point(
9882 Pixels::from(-scroll_pixel_position.x),
9883 Pixels::from(
9884 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9885 ),
9886 )
9887 };
9888
9889 origin.x -= BORDER_WIDTH;
9890
9891 window.defer_draw(element, origin, 1);
9892
9893 // Do not return an element, since it will already be drawn due to defer_draw.
9894 None
9895 }
9896
9897 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9898 px(30.)
9899 }
9900
9901 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9902 if self.read_only(cx) {
9903 cx.theme().players().read_only()
9904 } else {
9905 self.style.as_ref().unwrap().local_player
9906 }
9907 }
9908
9909 fn render_edit_prediction_accept_keybind(
9910 &self,
9911 window: &mut Window,
9912 cx: &mut App,
9913 ) -> Option<AnyElement> {
9914 let accept_binding =
9915 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9916 let accept_keystroke = accept_binding.keystroke()?;
9917
9918 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9919
9920 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9921 Color::Accent
9922 } else {
9923 Color::Muted
9924 };
9925
9926 h_flex()
9927 .px_0p5()
9928 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9929 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9930 .text_size(TextSize::XSmall.rems(cx))
9931 .child(h_flex().children(ui::render_modifiers(
9932 accept_keystroke.modifiers(),
9933 PlatformStyle::platform(),
9934 Some(modifiers_color),
9935 Some(IconSize::XSmall.rems().into()),
9936 true,
9937 )))
9938 .when(is_platform_style_mac, |parent| {
9939 parent.child(accept_keystroke.key().to_string())
9940 })
9941 .when(!is_platform_style_mac, |parent| {
9942 parent.child(
9943 Key::new(
9944 util::capitalize(accept_keystroke.key()),
9945 Some(Color::Default),
9946 )
9947 .size(Some(IconSize::XSmall.rems().into())),
9948 )
9949 })
9950 .into_any()
9951 .into()
9952 }
9953
9954 fn render_edit_prediction_line_popover(
9955 &self,
9956 label: impl Into<SharedString>,
9957 icon: Option<IconName>,
9958 window: &mut Window,
9959 cx: &mut App,
9960 ) -> Stateful<Div> {
9961 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9962
9963 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9964 let has_keybind = keybind.is_some();
9965 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
9966
9967 h_flex()
9968 .id("ep-line-popover")
9969 .py_0p5()
9970 .pl_1()
9971 .pr(padding_right)
9972 .gap_1()
9973 .rounded_md()
9974 .border_1()
9975 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9976 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9977 .shadow_xs()
9978 .when(!has_keybind, |el| {
9979 let status_colors = cx.theme().status();
9980
9981 el.bg(status_colors.error_background)
9982 .border_color(status_colors.error.opacity(0.6))
9983 .pl_2()
9984 .child(Icon::new(icons.error).color(Color::Error))
9985 .cursor_default()
9986 .hoverable_tooltip(move |_window, cx| {
9987 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9988 })
9989 })
9990 .children(keybind)
9991 .child(
9992 Label::new(label)
9993 .size(LabelSize::Small)
9994 .when(!has_keybind, |el| {
9995 el.color(cx.theme().status().error.into()).strikethrough()
9996 }),
9997 )
9998 .when(!has_keybind, |el| {
9999 el.child(
10000 h_flex().ml_1().child(
10001 Icon::new(IconName::Info)
10002 .size(IconSize::Small)
10003 .color(cx.theme().status().error.into()),
10004 ),
10005 )
10006 })
10007 .when_some(icon, |element, icon| {
10008 element.child(
10009 div()
10010 .mt(px(1.5))
10011 .child(Icon::new(icon).size(IconSize::Small)),
10012 )
10013 })
10014 }
10015
10016 fn render_edit_prediction_jump_outside_popover(
10017 &self,
10018 snapshot: &BufferSnapshot,
10019 window: &mut Window,
10020 cx: &mut App,
10021 ) -> Stateful<Div> {
10022 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
10023 let has_keybind = keybind.is_some();
10024 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10025
10026 let file_name = snapshot
10027 .file()
10028 .map(|file| SharedString::new(file.file_name(cx)))
10029 .unwrap_or(SharedString::new_static("untitled"));
10030
10031 h_flex()
10032 .id("ep-jump-outside-popover")
10033 .py_1()
10034 .px_2()
10035 .gap_1()
10036 .rounded_md()
10037 .border_1()
10038 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10039 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10040 .shadow_xs()
10041 .when(!has_keybind, |el| {
10042 let status_colors = cx.theme().status();
10043
10044 el.bg(status_colors.error_background)
10045 .border_color(status_colors.error.opacity(0.6))
10046 .pl_2()
10047 .child(Icon::new(icons.error).color(Color::Error))
10048 .cursor_default()
10049 .hoverable_tooltip(move |_window, cx| {
10050 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10051 })
10052 })
10053 .children(keybind)
10054 .child(
10055 Label::new(file_name)
10056 .size(LabelSize::Small)
10057 .buffer_font(cx)
10058 .when(!has_keybind, |el| {
10059 el.color(cx.theme().status().error.into()).strikethrough()
10060 }),
10061 )
10062 .when(!has_keybind, |el| {
10063 el.child(
10064 h_flex().ml_1().child(
10065 Icon::new(IconName::Info)
10066 .size(IconSize::Small)
10067 .color(cx.theme().status().error.into()),
10068 ),
10069 )
10070 })
10071 .child(
10072 div()
10073 .mt(px(1.5))
10074 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10075 )
10076 }
10077
10078 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10079 let accent_color = cx.theme().colors().text_accent;
10080 let editor_bg_color = cx.theme().colors().editor_background;
10081 editor_bg_color.blend(accent_color.opacity(0.1))
10082 }
10083
10084 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10085 let accent_color = cx.theme().colors().text_accent;
10086 let editor_bg_color = cx.theme().colors().editor_background;
10087 editor_bg_color.blend(accent_color.opacity(0.6))
10088 }
10089 fn get_prediction_provider_icons(
10090 provider: &Option<RegisteredEditPredictionDelegate>,
10091 cx: &App,
10092 ) -> edit_prediction_types::EditPredictionIconSet {
10093 match provider {
10094 Some(provider) => provider.provider.icons(cx),
10095 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10096 }
10097 }
10098
10099 fn render_edit_prediction_cursor_popover(
10100 &self,
10101 min_width: Pixels,
10102 max_width: Pixels,
10103 cursor_point: Point,
10104 style: &EditorStyle,
10105 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
10106 _window: &Window,
10107 cx: &mut Context<Editor>,
10108 ) -> Option<AnyElement> {
10109 let provider = self.edit_prediction_provider.as_ref()?;
10110 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10111
10112 let is_refreshing = provider.provider.is_refreshing(cx);
10113
10114 fn pending_completion_container(icon: IconName) -> Div {
10115 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10116 }
10117
10118 let completion = match &self.active_edit_prediction {
10119 Some(prediction) => {
10120 if !self.has_visible_completions_menu() {
10121 const RADIUS: Pixels = px(6.);
10122 const BORDER_WIDTH: Pixels = px(1.);
10123
10124 return Some(
10125 h_flex()
10126 .elevation_2(cx)
10127 .border(BORDER_WIDTH)
10128 .border_color(cx.theme().colors().border)
10129 .when(accept_keystroke.is_none(), |el| {
10130 el.border_color(cx.theme().status().error)
10131 })
10132 .rounded(RADIUS)
10133 .rounded_tl(px(0.))
10134 .overflow_hidden()
10135 .child(div().px_1p5().child(match &prediction.completion {
10136 EditPrediction::MoveWithin { target, snapshot } => {
10137 use text::ToPoint as _;
10138 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10139 {
10140 Icon::new(icons.down)
10141 } else {
10142 Icon::new(icons.up)
10143 }
10144 }
10145 EditPrediction::MoveOutside { .. } => {
10146 // TODO [zeta2] custom icon for external jump?
10147 Icon::new(icons.base)
10148 }
10149 EditPrediction::Edit { .. } => Icon::new(icons.base),
10150 }))
10151 .child(
10152 h_flex()
10153 .gap_1()
10154 .py_1()
10155 .px_2()
10156 .rounded_r(RADIUS - BORDER_WIDTH)
10157 .border_l_1()
10158 .border_color(cx.theme().colors().border)
10159 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10160 .when(self.edit_prediction_preview.released_too_fast(), |el| {
10161 el.child(
10162 Label::new("Hold")
10163 .size(LabelSize::Small)
10164 .when(accept_keystroke.is_none(), |el| {
10165 el.strikethrough()
10166 })
10167 .line_height_style(LineHeightStyle::UiLabel),
10168 )
10169 })
10170 .id("edit_prediction_cursor_popover_keybind")
10171 .when(accept_keystroke.is_none(), |el| {
10172 let status_colors = cx.theme().status();
10173
10174 el.bg(status_colors.error_background)
10175 .border_color(status_colors.error.opacity(0.6))
10176 .child(Icon::new(IconName::Info).color(Color::Error))
10177 .cursor_default()
10178 .hoverable_tooltip(move |_window, cx| {
10179 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10180 .into()
10181 })
10182 })
10183 .when_some(
10184 accept_keystroke.as_ref(),
10185 |el, accept_keystroke| {
10186 el.child(h_flex().children(ui::render_modifiers(
10187 accept_keystroke.modifiers(),
10188 PlatformStyle::platform(),
10189 Some(Color::Default),
10190 Some(IconSize::XSmall.rems().into()),
10191 false,
10192 )))
10193 },
10194 ),
10195 )
10196 .into_any(),
10197 );
10198 }
10199
10200 self.render_edit_prediction_cursor_popover_preview(
10201 prediction,
10202 cursor_point,
10203 style,
10204 cx,
10205 )?
10206 }
10207
10208 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10209 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10210 stale_completion,
10211 cursor_point,
10212 style,
10213 cx,
10214 )?,
10215
10216 None => pending_completion_container(icons.base)
10217 .child(Label::new("...").size(LabelSize::Small)),
10218 },
10219
10220 None => pending_completion_container(icons.base)
10221 .child(Label::new("...").size(LabelSize::Small)),
10222 };
10223
10224 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10225 completion
10226 .with_animation(
10227 "loading-completion",
10228 Animation::new(Duration::from_secs(2))
10229 .repeat()
10230 .with_easing(pulsating_between(0.4, 0.8)),
10231 |label, delta| label.opacity(delta),
10232 )
10233 .into_any_element()
10234 } else {
10235 completion.into_any_element()
10236 };
10237
10238 let has_completion = self.active_edit_prediction.is_some();
10239
10240 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10241 Some(
10242 h_flex()
10243 .min_w(min_width)
10244 .max_w(max_width)
10245 .flex_1()
10246 .elevation_2(cx)
10247 .border_color(cx.theme().colors().border)
10248 .child(
10249 div()
10250 .flex_1()
10251 .py_1()
10252 .px_2()
10253 .overflow_hidden()
10254 .child(completion),
10255 )
10256 .when_some(accept_keystroke, |el, accept_keystroke| {
10257 if !accept_keystroke.modifiers().modified() {
10258 return el;
10259 }
10260
10261 el.child(
10262 h_flex()
10263 .h_full()
10264 .border_l_1()
10265 .rounded_r_lg()
10266 .border_color(cx.theme().colors().border)
10267 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10268 .gap_1()
10269 .py_1()
10270 .px_2()
10271 .child(
10272 h_flex()
10273 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10274 .when(is_platform_style_mac, |parent| parent.gap_1())
10275 .child(h_flex().children(ui::render_modifiers(
10276 accept_keystroke.modifiers(),
10277 PlatformStyle::platform(),
10278 Some(if !has_completion {
10279 Color::Muted
10280 } else {
10281 Color::Default
10282 }),
10283 None,
10284 false,
10285 ))),
10286 )
10287 .child(Label::new("Preview").into_any_element())
10288 .opacity(if has_completion { 1.0 } else { 0.4 }),
10289 )
10290 })
10291 .into_any(),
10292 )
10293 }
10294
10295 fn render_edit_prediction_cursor_popover_preview(
10296 &self,
10297 completion: &EditPredictionState,
10298 cursor_point: Point,
10299 style: &EditorStyle,
10300 cx: &mut Context<Editor>,
10301 ) -> Option<Div> {
10302 use text::ToPoint as _;
10303
10304 fn render_relative_row_jump(
10305 prefix: impl Into<String>,
10306 current_row: u32,
10307 target_row: u32,
10308 ) -> Div {
10309 let (row_diff, arrow) = if target_row < current_row {
10310 (current_row - target_row, IconName::ArrowUp)
10311 } else {
10312 (target_row - current_row, IconName::ArrowDown)
10313 };
10314
10315 h_flex()
10316 .child(
10317 Label::new(format!("{}{}", prefix.into(), row_diff))
10318 .color(Color::Muted)
10319 .size(LabelSize::Small),
10320 )
10321 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10322 }
10323
10324 let supports_jump = self
10325 .edit_prediction_provider
10326 .as_ref()
10327 .map(|provider| provider.provider.supports_jump_to_edit())
10328 .unwrap_or(true);
10329
10330 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10331
10332 match &completion.completion {
10333 EditPrediction::MoveWithin {
10334 target, snapshot, ..
10335 } => {
10336 if !supports_jump {
10337 return None;
10338 }
10339
10340 Some(
10341 h_flex()
10342 .px_2()
10343 .gap_2()
10344 .flex_1()
10345 .child(
10346 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10347 Icon::new(icons.down)
10348 } else {
10349 Icon::new(icons.up)
10350 },
10351 )
10352 .child(Label::new("Jump to Edit")),
10353 )
10354 }
10355 EditPrediction::MoveOutside { snapshot, .. } => {
10356 let file_name = snapshot
10357 .file()
10358 .map(|file| file.file_name(cx))
10359 .unwrap_or("untitled");
10360 Some(
10361 h_flex()
10362 .px_2()
10363 .gap_2()
10364 .flex_1()
10365 .child(Icon::new(icons.base))
10366 .child(Label::new(format!("Jump to {file_name}"))),
10367 )
10368 }
10369 EditPrediction::Edit {
10370 edits,
10371 edit_preview,
10372 snapshot,
10373 ..
10374 } => {
10375 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10376
10377 let (highlighted_edits, has_more_lines) =
10378 if let Some(edit_preview) = edit_preview.as_ref() {
10379 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10380 .first_line_preview()
10381 } else {
10382 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10383 };
10384
10385 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10386 .with_default_highlights(&style.text, highlighted_edits.highlights);
10387
10388 let preview = h_flex()
10389 .gap_1()
10390 .min_w_16()
10391 .child(styled_text)
10392 .when(has_more_lines, |parent| parent.child("…"));
10393
10394 let left = if supports_jump && first_edit_row != cursor_point.row {
10395 render_relative_row_jump("", cursor_point.row, first_edit_row)
10396 .into_any_element()
10397 } else {
10398 Icon::new(icons.base).into_any_element()
10399 };
10400
10401 Some(
10402 h_flex()
10403 .h_full()
10404 .flex_1()
10405 .gap_2()
10406 .pr_1()
10407 .overflow_x_hidden()
10408 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10409 .child(left)
10410 .child(preview),
10411 )
10412 }
10413 }
10414 }
10415
10416 pub fn render_context_menu(
10417 &mut self,
10418 max_height_in_lines: u32,
10419 window: &mut Window,
10420 cx: &mut Context<Editor>,
10421 ) -> Option<AnyElement> {
10422 let menu = self.context_menu.borrow();
10423 let menu = menu.as_ref()?;
10424 if !menu.visible() {
10425 return None;
10426 };
10427 self.style
10428 .as_ref()
10429 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10430 }
10431
10432 fn render_context_menu_aside(
10433 &mut self,
10434 max_size: Size<Pixels>,
10435 window: &mut Window,
10436 cx: &mut Context<Editor>,
10437 ) -> Option<AnyElement> {
10438 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10439 if menu.visible() {
10440 menu.render_aside(max_size, window, cx)
10441 } else {
10442 None
10443 }
10444 })
10445 }
10446
10447 fn hide_context_menu(
10448 &mut self,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) -> Option<CodeContextMenu> {
10452 cx.notify();
10453 self.completion_tasks.clear();
10454 let context_menu = self.context_menu.borrow_mut().take();
10455 self.stale_edit_prediction_in_menu.take();
10456 self.update_visible_edit_prediction(window, cx);
10457 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10458 && let Some(completion_provider) = &self.completion_provider
10459 {
10460 completion_provider.selection_changed(None, window, cx);
10461 }
10462 context_menu
10463 }
10464
10465 fn show_snippet_choices(
10466 &mut self,
10467 choices: &Vec<String>,
10468 selection: Range<Anchor>,
10469 cx: &mut Context<Self>,
10470 ) {
10471 let Some((_, buffer, _)) = self
10472 .buffer()
10473 .read(cx)
10474 .excerpt_containing(selection.start, cx)
10475 else {
10476 return;
10477 };
10478 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10479 else {
10480 return;
10481 };
10482 if buffer != end_buffer {
10483 log::error!("expected anchor range to have matching buffer IDs");
10484 return;
10485 }
10486
10487 let id = post_inc(&mut self.next_completion_id);
10488 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10489 let mut context_menu = self.context_menu.borrow_mut();
10490 let old_menu = context_menu.take();
10491 *context_menu = Some(CodeContextMenu::Completions(
10492 CompletionsMenu::new_snippet_choices(
10493 id,
10494 true,
10495 choices,
10496 selection,
10497 buffer,
10498 old_menu.map(|menu| menu.primary_scroll_handle()),
10499 snippet_sort_order,
10500 ),
10501 ));
10502 }
10503
10504 pub fn insert_snippet(
10505 &mut self,
10506 insertion_ranges: &[Range<MultiBufferOffset>],
10507 snippet: Snippet,
10508 window: &mut Window,
10509 cx: &mut Context<Self>,
10510 ) -> Result<()> {
10511 struct Tabstop<T> {
10512 is_end_tabstop: bool,
10513 ranges: Vec<Range<T>>,
10514 choices: Option<Vec<String>>,
10515 }
10516
10517 let tabstops = self.buffer.update(cx, |buffer, cx| {
10518 let snippet_text: Arc<str> = snippet.text.clone().into();
10519 let edits = insertion_ranges
10520 .iter()
10521 .cloned()
10522 .map(|range| (range, snippet_text.clone()));
10523 let autoindent_mode = AutoindentMode::Block {
10524 original_indent_columns: Vec::new(),
10525 };
10526 buffer.edit(edits, Some(autoindent_mode), cx);
10527
10528 let snapshot = &*buffer.read(cx);
10529 let snippet = &snippet;
10530 snippet
10531 .tabstops
10532 .iter()
10533 .map(|tabstop| {
10534 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10535 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10536 });
10537 let mut tabstop_ranges = tabstop
10538 .ranges
10539 .iter()
10540 .flat_map(|tabstop_range| {
10541 let mut delta = 0_isize;
10542 insertion_ranges.iter().map(move |insertion_range| {
10543 let insertion_start = insertion_range.start + delta;
10544 delta += snippet.text.len() as isize
10545 - (insertion_range.end - insertion_range.start) as isize;
10546
10547 let start =
10548 (insertion_start + tabstop_range.start).min(snapshot.len());
10549 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10550 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10551 })
10552 })
10553 .collect::<Vec<_>>();
10554 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10555
10556 Tabstop {
10557 is_end_tabstop,
10558 ranges: tabstop_ranges,
10559 choices: tabstop.choices.clone(),
10560 }
10561 })
10562 .collect::<Vec<_>>()
10563 });
10564 if let Some(tabstop) = tabstops.first() {
10565 self.change_selections(Default::default(), window, cx, |s| {
10566 // Reverse order so that the first range is the newest created selection.
10567 // Completions will use it and autoscroll will prioritize it.
10568 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10569 });
10570
10571 if let Some(choices) = &tabstop.choices
10572 && let Some(selection) = tabstop.ranges.first()
10573 {
10574 self.show_snippet_choices(choices, selection.clone(), cx)
10575 }
10576
10577 // If we're already at the last tabstop and it's at the end of the snippet,
10578 // we're done, we don't need to keep the state around.
10579 if !tabstop.is_end_tabstop {
10580 let choices = tabstops
10581 .iter()
10582 .map(|tabstop| tabstop.choices.clone())
10583 .collect();
10584
10585 let ranges = tabstops
10586 .into_iter()
10587 .map(|tabstop| tabstop.ranges)
10588 .collect::<Vec<_>>();
10589
10590 self.snippet_stack.push(SnippetState {
10591 active_index: 0,
10592 ranges,
10593 choices,
10594 });
10595 }
10596
10597 // Check whether the just-entered snippet ends with an auto-closable bracket.
10598 if self.autoclose_regions.is_empty() {
10599 let snapshot = self.buffer.read(cx).snapshot(cx);
10600 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10601 let selection_head = selection.head();
10602 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10603 continue;
10604 };
10605
10606 let mut bracket_pair = None;
10607 let max_lookup_length = scope
10608 .brackets()
10609 .map(|(pair, _)| {
10610 pair.start
10611 .as_str()
10612 .chars()
10613 .count()
10614 .max(pair.end.as_str().chars().count())
10615 })
10616 .max();
10617 if let Some(max_lookup_length) = max_lookup_length {
10618 let next_text = snapshot
10619 .chars_at(selection_head)
10620 .take(max_lookup_length)
10621 .collect::<String>();
10622 let prev_text = snapshot
10623 .reversed_chars_at(selection_head)
10624 .take(max_lookup_length)
10625 .collect::<String>();
10626
10627 for (pair, enabled) in scope.brackets() {
10628 if enabled
10629 && pair.close
10630 && prev_text.starts_with(pair.start.as_str())
10631 && next_text.starts_with(pair.end.as_str())
10632 {
10633 bracket_pair = Some(pair.clone());
10634 break;
10635 }
10636 }
10637 }
10638
10639 if let Some(pair) = bracket_pair {
10640 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10641 let autoclose_enabled =
10642 self.use_autoclose && snapshot_settings.use_autoclose;
10643 if autoclose_enabled {
10644 let start = snapshot.anchor_after(selection_head);
10645 let end = snapshot.anchor_after(selection_head);
10646 self.autoclose_regions.push(AutocloseRegion {
10647 selection_id: selection.id,
10648 range: start..end,
10649 pair,
10650 });
10651 }
10652 }
10653 }
10654 }
10655 }
10656 Ok(())
10657 }
10658
10659 pub fn move_to_next_snippet_tabstop(
10660 &mut self,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) -> bool {
10664 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10665 }
10666
10667 pub fn move_to_prev_snippet_tabstop(
10668 &mut self,
10669 window: &mut Window,
10670 cx: &mut Context<Self>,
10671 ) -> bool {
10672 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10673 }
10674
10675 pub fn move_to_snippet_tabstop(
10676 &mut self,
10677 bias: Bias,
10678 window: &mut Window,
10679 cx: &mut Context<Self>,
10680 ) -> bool {
10681 if let Some(mut snippet) = self.snippet_stack.pop() {
10682 match bias {
10683 Bias::Left => {
10684 if snippet.active_index > 0 {
10685 snippet.active_index -= 1;
10686 } else {
10687 self.snippet_stack.push(snippet);
10688 return false;
10689 }
10690 }
10691 Bias::Right => {
10692 if snippet.active_index + 1 < snippet.ranges.len() {
10693 snippet.active_index += 1;
10694 } else {
10695 self.snippet_stack.push(snippet);
10696 return false;
10697 }
10698 }
10699 }
10700 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10701 self.change_selections(Default::default(), window, cx, |s| {
10702 // Reverse order so that the first range is the newest created selection.
10703 // Completions will use it and autoscroll will prioritize it.
10704 s.select_ranges(current_ranges.iter().rev().cloned())
10705 });
10706
10707 if let Some(choices) = &snippet.choices[snippet.active_index]
10708 && let Some(selection) = current_ranges.first()
10709 {
10710 self.show_snippet_choices(choices, selection.clone(), cx);
10711 }
10712
10713 // If snippet state is not at the last tabstop, push it back on the stack
10714 if snippet.active_index + 1 < snippet.ranges.len() {
10715 self.snippet_stack.push(snippet);
10716 }
10717 return true;
10718 }
10719 }
10720
10721 false
10722 }
10723
10724 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10725 self.transact(window, cx, |this, window, cx| {
10726 this.select_all(&SelectAll, window, cx);
10727 this.insert("", window, cx);
10728 });
10729 }
10730
10731 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10732 if self.read_only(cx) {
10733 return;
10734 }
10735 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10736 self.transact(window, cx, |this, window, cx| {
10737 this.select_autoclose_pair(window, cx);
10738
10739 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10740
10741 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10742 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10743 for selection in &mut selections {
10744 if selection.is_empty() {
10745 let old_head = selection.head();
10746 let mut new_head =
10747 movement::left(&display_map, old_head.to_display_point(&display_map))
10748 .to_point(&display_map);
10749 if let Some((buffer, line_buffer_range)) = display_map
10750 .buffer_snapshot()
10751 .buffer_line_for_row(MultiBufferRow(old_head.row))
10752 {
10753 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10754 let indent_len = match indent_size.kind {
10755 IndentKind::Space => {
10756 buffer.settings_at(line_buffer_range.start, cx).tab_size
10757 }
10758 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10759 };
10760 if old_head.column <= indent_size.len && old_head.column > 0 {
10761 let indent_len = indent_len.get();
10762 new_head = cmp::min(
10763 new_head,
10764 MultiBufferPoint::new(
10765 old_head.row,
10766 ((old_head.column - 1) / indent_len) * indent_len,
10767 ),
10768 );
10769 }
10770 }
10771
10772 selection.set_head(new_head, SelectionGoal::None);
10773 }
10774 }
10775
10776 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10777 this.insert("", window, cx);
10778 linked_edits.apply_with_left_expansion(cx);
10779 this.refresh_edit_prediction(true, false, window, cx);
10780 refresh_linked_ranges(this, window, cx);
10781 });
10782 }
10783
10784 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10785 if self.read_only(cx) {
10786 return;
10787 }
10788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10789 self.transact(window, cx, |this, window, cx| {
10790 this.change_selections(Default::default(), window, cx, |s| {
10791 s.move_with(&mut |map, selection| {
10792 if selection.is_empty() {
10793 let cursor = movement::right(map, selection.head());
10794 selection.end = cursor;
10795 selection.reversed = true;
10796 selection.goal = SelectionGoal::None;
10797 }
10798 })
10799 });
10800 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10801 this.insert("", window, cx);
10802 linked_edits.apply(cx);
10803 this.refresh_edit_prediction(true, false, window, cx);
10804 refresh_linked_ranges(this, window, cx);
10805 });
10806 }
10807
10808 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10809 if self.mode.is_single_line() {
10810 cx.propagate();
10811 return;
10812 }
10813
10814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10815 if self.move_to_prev_snippet_tabstop(window, cx) {
10816 return;
10817 }
10818 self.outdent(&Outdent, window, cx);
10819 }
10820
10821 pub fn next_snippet_tabstop(
10822 &mut self,
10823 _: &NextSnippetTabstop,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) {
10827 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10828 cx.propagate();
10829 return;
10830 }
10831
10832 if self.move_to_next_snippet_tabstop(window, cx) {
10833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10834 return;
10835 }
10836 cx.propagate();
10837 }
10838
10839 pub fn previous_snippet_tabstop(
10840 &mut self,
10841 _: &PreviousSnippetTabstop,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10846 cx.propagate();
10847 return;
10848 }
10849
10850 if self.move_to_prev_snippet_tabstop(window, cx) {
10851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10852 return;
10853 }
10854 cx.propagate();
10855 }
10856
10857 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10858 if self.mode.is_single_line() {
10859 cx.propagate();
10860 return;
10861 }
10862
10863 if self.move_to_next_snippet_tabstop(window, cx) {
10864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10865 return;
10866 }
10867 if self.read_only(cx) {
10868 return;
10869 }
10870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10871 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10872 let buffer = self.buffer.read(cx);
10873 let snapshot = buffer.snapshot(cx);
10874 let rows_iter = selections.iter().map(|s| s.head().row);
10875 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10876
10877 let has_some_cursor_in_whitespace = selections
10878 .iter()
10879 .filter(|selection| selection.is_empty())
10880 .any(|selection| {
10881 let cursor = selection.head();
10882 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10883 cursor.column < current_indent.len
10884 });
10885
10886 let mut edits = Vec::new();
10887 let mut prev_edited_row = 0;
10888 let mut row_delta = 0;
10889 for selection in &mut selections {
10890 if selection.start.row != prev_edited_row {
10891 row_delta = 0;
10892 }
10893 prev_edited_row = selection.end.row;
10894
10895 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10896 if selection.is_empty() {
10897 let cursor = selection.head();
10898 let settings = buffer.language_settings_at(cursor, cx);
10899 if settings.indent_list_on_tab {
10900 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10901 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10902 row_delta = Self::indent_selection(
10903 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10904 );
10905 continue;
10906 }
10907 }
10908 }
10909 }
10910
10911 // If the selection is non-empty, then increase the indentation of the selected lines.
10912 if !selection.is_empty() {
10913 row_delta =
10914 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10915 continue;
10916 }
10917
10918 let cursor = selection.head();
10919 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10920 if let Some(suggested_indent) =
10921 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10922 {
10923 // Don't do anything if already at suggested indent
10924 // and there is any other cursor which is not
10925 if has_some_cursor_in_whitespace
10926 && cursor.column == current_indent.len
10927 && current_indent.len == suggested_indent.len
10928 {
10929 continue;
10930 }
10931
10932 // Adjust line and move cursor to suggested indent
10933 // if cursor is not at suggested indent
10934 if cursor.column < suggested_indent.len
10935 && cursor.column <= current_indent.len
10936 && current_indent.len <= suggested_indent.len
10937 {
10938 selection.start = Point::new(cursor.row, suggested_indent.len);
10939 selection.end = selection.start;
10940 if row_delta == 0 {
10941 edits.extend(Buffer::edit_for_indent_size_adjustment(
10942 cursor.row,
10943 current_indent,
10944 suggested_indent,
10945 ));
10946 row_delta = suggested_indent.len - current_indent.len;
10947 }
10948 continue;
10949 }
10950
10951 // If current indent is more than suggested indent
10952 // only move cursor to current indent and skip indent
10953 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10954 selection.start = Point::new(cursor.row, current_indent.len);
10955 selection.end = selection.start;
10956 continue;
10957 }
10958 }
10959
10960 // Otherwise, insert a hard or soft tab.
10961 let settings = buffer.language_settings_at(cursor, cx);
10962 let tab_size = if settings.hard_tabs {
10963 IndentSize::tab()
10964 } else {
10965 let tab_size = settings.tab_size.get();
10966 let indent_remainder = snapshot
10967 .text_for_range(Point::new(cursor.row, 0)..cursor)
10968 .flat_map(str::chars)
10969 .fold(row_delta % tab_size, |counter: u32, c| {
10970 if c == '\t' {
10971 0
10972 } else {
10973 (counter + 1) % tab_size
10974 }
10975 });
10976
10977 let chars_to_next_tab_stop = tab_size - indent_remainder;
10978 IndentSize::spaces(chars_to_next_tab_stop)
10979 };
10980 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10981 selection.end = selection.start;
10982 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10983 row_delta += tab_size.len;
10984 }
10985
10986 self.transact(window, cx, |this, window, cx| {
10987 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10988 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10989 this.refresh_edit_prediction(true, false, window, cx);
10990 });
10991 }
10992
10993 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10994 if self.read_only(cx) {
10995 return;
10996 }
10997 if self.mode.is_single_line() {
10998 cx.propagate();
10999 return;
11000 }
11001
11002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11003 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11004 let mut prev_edited_row = 0;
11005 let mut row_delta = 0;
11006 let mut edits = Vec::new();
11007 let buffer = self.buffer.read(cx);
11008 let snapshot = buffer.snapshot(cx);
11009 for selection in &mut selections {
11010 if selection.start.row != prev_edited_row {
11011 row_delta = 0;
11012 }
11013 prev_edited_row = selection.end.row;
11014
11015 row_delta =
11016 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11017 }
11018
11019 self.transact(window, cx, |this, window, cx| {
11020 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11021 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11022 });
11023 }
11024
11025 fn indent_selection(
11026 buffer: &MultiBuffer,
11027 snapshot: &MultiBufferSnapshot,
11028 selection: &mut Selection<Point>,
11029 edits: &mut Vec<(Range<Point>, String)>,
11030 delta_for_start_row: u32,
11031 cx: &App,
11032 ) -> u32 {
11033 let settings = buffer.language_settings_at(selection.start, cx);
11034 let tab_size = settings.tab_size.get();
11035 let indent_kind = if settings.hard_tabs {
11036 IndentKind::Tab
11037 } else {
11038 IndentKind::Space
11039 };
11040 let mut start_row = selection.start.row;
11041 let mut end_row = selection.end.row + 1;
11042
11043 // If a selection ends at the beginning of a line, don't indent
11044 // that last line.
11045 if selection.end.column == 0 && selection.end.row > selection.start.row {
11046 end_row -= 1;
11047 }
11048
11049 // Avoid re-indenting a row that has already been indented by a
11050 // previous selection, but still update this selection's column
11051 // to reflect that indentation.
11052 if delta_for_start_row > 0 {
11053 start_row += 1;
11054 selection.start.column += delta_for_start_row;
11055 if selection.end.row == selection.start.row {
11056 selection.end.column += delta_for_start_row;
11057 }
11058 }
11059
11060 let mut delta_for_end_row = 0;
11061 let has_multiple_rows = start_row + 1 != end_row;
11062 for row in start_row..end_row {
11063 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11064 let indent_delta = match (current_indent.kind, indent_kind) {
11065 (IndentKind::Space, IndentKind::Space) => {
11066 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11067 IndentSize::spaces(columns_to_next_tab_stop)
11068 }
11069 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11070 (_, IndentKind::Tab) => IndentSize::tab(),
11071 };
11072
11073 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11074 0
11075 } else {
11076 selection.start.column
11077 };
11078 let row_start = Point::new(row, start);
11079 edits.push((
11080 row_start..row_start,
11081 indent_delta.chars().collect::<String>(),
11082 ));
11083
11084 // Update this selection's endpoints to reflect the indentation.
11085 if row == selection.start.row {
11086 selection.start.column += indent_delta.len;
11087 }
11088 if row == selection.end.row {
11089 selection.end.column += indent_delta.len;
11090 delta_for_end_row = indent_delta.len;
11091 }
11092 }
11093
11094 if selection.start.row == selection.end.row {
11095 delta_for_start_row + delta_for_end_row
11096 } else {
11097 delta_for_end_row
11098 }
11099 }
11100
11101 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11102 if self.read_only(cx) {
11103 return;
11104 }
11105 if self.mode.is_single_line() {
11106 cx.propagate();
11107 return;
11108 }
11109
11110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11111 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11112 let selections = self.selections.all::<Point>(&display_map);
11113 let mut deletion_ranges = Vec::new();
11114 let mut last_outdent = None;
11115 {
11116 let buffer = self.buffer.read(cx);
11117 let snapshot = buffer.snapshot(cx);
11118 for selection in &selections {
11119 let settings = buffer.language_settings_at(selection.start, cx);
11120 let tab_size = settings.tab_size.get();
11121 let mut rows = selection.spanned_rows(false, &display_map);
11122
11123 // Avoid re-outdenting a row that has already been outdented by a
11124 // previous selection.
11125 if let Some(last_row) = last_outdent
11126 && last_row == rows.start
11127 {
11128 rows.start = rows.start.next_row();
11129 }
11130 let has_multiple_rows = rows.len() > 1;
11131 for row in rows.iter_rows() {
11132 let indent_size = snapshot.indent_size_for_line(row);
11133 if indent_size.len > 0 {
11134 let deletion_len = match indent_size.kind {
11135 IndentKind::Space => {
11136 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11137 if columns_to_prev_tab_stop == 0 {
11138 tab_size
11139 } else {
11140 columns_to_prev_tab_stop
11141 }
11142 }
11143 IndentKind::Tab => 1,
11144 };
11145 let start = if has_multiple_rows
11146 || deletion_len > selection.start.column
11147 || indent_size.len < selection.start.column
11148 {
11149 0
11150 } else {
11151 selection.start.column - deletion_len
11152 };
11153 deletion_ranges.push(
11154 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11155 );
11156 last_outdent = Some(row);
11157 }
11158 }
11159 }
11160 }
11161
11162 self.transact(window, cx, |this, window, cx| {
11163 this.buffer.update(cx, |buffer, cx| {
11164 let empty_str: Arc<str> = Arc::default();
11165 buffer.edit(
11166 deletion_ranges
11167 .into_iter()
11168 .map(|range| (range, empty_str.clone())),
11169 None,
11170 cx,
11171 );
11172 });
11173 let selections = this
11174 .selections
11175 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11176 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11177 });
11178 }
11179
11180 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11181 if self.read_only(cx) {
11182 return;
11183 }
11184 if self.mode.is_single_line() {
11185 cx.propagate();
11186 return;
11187 }
11188
11189 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11190 let selections = self
11191 .selections
11192 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11193 .into_iter()
11194 .map(|s| s.range());
11195
11196 self.transact(window, cx, |this, window, cx| {
11197 this.buffer.update(cx, |buffer, cx| {
11198 buffer.autoindent_ranges(selections, cx);
11199 });
11200 let selections = this
11201 .selections
11202 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11203 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11204 });
11205 }
11206
11207 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11210 let selections = self.selections.all::<Point>(&display_map);
11211
11212 let mut new_cursors = Vec::new();
11213 let mut edit_ranges = Vec::new();
11214 let mut selections = selections.iter().peekable();
11215 while let Some(selection) = selections.next() {
11216 let mut rows = selection.spanned_rows(false, &display_map);
11217
11218 // Accumulate contiguous regions of rows that we want to delete.
11219 while let Some(next_selection) = selections.peek() {
11220 let next_rows = next_selection.spanned_rows(false, &display_map);
11221 if next_rows.start <= rows.end {
11222 rows.end = next_rows.end;
11223 selections.next().unwrap();
11224 } else {
11225 break;
11226 }
11227 }
11228
11229 let buffer = display_map.buffer_snapshot();
11230 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11231 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11232 // If there's a line after the range, delete the \n from the end of the row range
11233 (
11234 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11235 rows.end,
11236 )
11237 } else {
11238 // If there isn't a line after the range, delete the \n from the line before the
11239 // start of the row range
11240 edit_start = edit_start.saturating_sub_usize(1);
11241 (buffer.len(), rows.start.previous_row())
11242 };
11243
11244 let text_layout_details = self.text_layout_details(window, cx);
11245 let x = display_map.x_for_display_point(
11246 selection.head().to_display_point(&display_map),
11247 &text_layout_details,
11248 );
11249 let row = Point::new(target_row.0, 0)
11250 .to_display_point(&display_map)
11251 .row();
11252 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11253
11254 new_cursors.push((
11255 selection.id,
11256 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11257 SelectionGoal::None,
11258 ));
11259 edit_ranges.push(edit_start..edit_end);
11260 }
11261
11262 self.transact(window, cx, |this, window, cx| {
11263 let buffer = this.buffer.update(cx, |buffer, cx| {
11264 let empty_str: Arc<str> = Arc::default();
11265 buffer.edit(
11266 edit_ranges
11267 .into_iter()
11268 .map(|range| (range, empty_str.clone())),
11269 None,
11270 cx,
11271 );
11272 buffer.snapshot(cx)
11273 });
11274 let new_selections = new_cursors
11275 .into_iter()
11276 .map(|(id, cursor, goal)| {
11277 let cursor = cursor.to_point(&buffer);
11278 Selection {
11279 id,
11280 start: cursor,
11281 end: cursor,
11282 reversed: false,
11283 goal,
11284 }
11285 })
11286 .collect();
11287
11288 this.change_selections(Default::default(), window, cx, |s| {
11289 s.select(new_selections);
11290 });
11291 });
11292 }
11293
11294 pub fn join_lines_impl(
11295 &mut self,
11296 insert_whitespace: bool,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 if self.read_only(cx) {
11301 return;
11302 }
11303 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11304 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11305 let start = MultiBufferRow(selection.start.row);
11306 // Treat single line selections as if they include the next line. Otherwise this action
11307 // would do nothing for single line selections individual cursors.
11308 let end = if selection.start.row == selection.end.row {
11309 MultiBufferRow(selection.start.row + 1)
11310 } else {
11311 MultiBufferRow(selection.end.row)
11312 };
11313
11314 if let Some(last_row_range) = row_ranges.last_mut()
11315 && start <= last_row_range.end
11316 {
11317 last_row_range.end = end;
11318 continue;
11319 }
11320 row_ranges.push(start..end);
11321 }
11322
11323 let snapshot = self.buffer.read(cx).snapshot(cx);
11324 let mut cursor_positions = Vec::new();
11325 for row_range in &row_ranges {
11326 let anchor = snapshot.anchor_before(Point::new(
11327 row_range.end.previous_row().0,
11328 snapshot.line_len(row_range.end.previous_row()),
11329 ));
11330 cursor_positions.push(anchor..anchor);
11331 }
11332
11333 self.transact(window, cx, |this, window, cx| {
11334 for row_range in row_ranges.into_iter().rev() {
11335 for row in row_range.iter_rows().rev() {
11336 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11337 let next_line_row = row.next_row();
11338 let indent = snapshot.indent_size_for_line(next_line_row);
11339 let mut join_start_column = indent.len;
11340
11341 if let Some(language_scope) =
11342 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11343 {
11344 let line_end =
11345 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11346 let line_text_after_indent = snapshot
11347 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11348 .collect::<String>();
11349
11350 if !line_text_after_indent.is_empty() {
11351 let block_prefix = language_scope
11352 .block_comment()
11353 .map(|c| c.prefix.as_ref())
11354 .filter(|p| !p.is_empty());
11355 let doc_prefix = language_scope
11356 .documentation_comment()
11357 .map(|c| c.prefix.as_ref())
11358 .filter(|p| !p.is_empty());
11359 let all_prefixes = language_scope
11360 .line_comment_prefixes()
11361 .iter()
11362 .map(|p| p.as_ref())
11363 .chain(block_prefix)
11364 .chain(doc_prefix)
11365 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11366
11367 let mut longest_prefix_len = None;
11368 for prefix in all_prefixes {
11369 let trimmed = prefix.trim_end();
11370 if line_text_after_indent.starts_with(trimmed) {
11371 let candidate_len =
11372 if line_text_after_indent.starts_with(prefix) {
11373 prefix.len()
11374 } else {
11375 trimmed.len()
11376 };
11377 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11378 longest_prefix_len = Some(candidate_len);
11379 }
11380 }
11381 }
11382
11383 if let Some(prefix_len) = longest_prefix_len {
11384 join_start_column =
11385 join_start_column.saturating_add(prefix_len as u32);
11386 }
11387 }
11388 }
11389
11390 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11391
11392 let replace = if snapshot.line_len(next_line_row) > join_start_column
11393 && insert_whitespace
11394 {
11395 " "
11396 } else {
11397 ""
11398 };
11399
11400 this.buffer.update(cx, |buffer, cx| {
11401 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11402 });
11403 }
11404 }
11405
11406 this.change_selections(Default::default(), window, cx, |s| {
11407 s.select_anchor_ranges(cursor_positions)
11408 });
11409 });
11410 }
11411
11412 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11414 self.join_lines_impl(true, window, cx);
11415 }
11416
11417 pub fn sort_lines_case_sensitive(
11418 &mut self,
11419 _: &SortLinesCaseSensitive,
11420 window: &mut Window,
11421 cx: &mut Context<Self>,
11422 ) {
11423 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11424 }
11425
11426 pub fn sort_lines_by_length(
11427 &mut self,
11428 _: &SortLinesByLength,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.manipulate_immutable_lines(window, cx, |lines| {
11433 lines.sort_by_key(|&line| line.chars().count())
11434 })
11435 }
11436
11437 pub fn sort_lines_case_insensitive(
11438 &mut self,
11439 _: &SortLinesCaseInsensitive,
11440 window: &mut Window,
11441 cx: &mut Context<Self>,
11442 ) {
11443 self.manipulate_immutable_lines(window, cx, |lines| {
11444 lines.sort_by_key(|line| line.to_lowercase())
11445 })
11446 }
11447
11448 pub fn unique_lines_case_insensitive(
11449 &mut self,
11450 _: &UniqueLinesCaseInsensitive,
11451 window: &mut Window,
11452 cx: &mut Context<Self>,
11453 ) {
11454 self.manipulate_immutable_lines(window, cx, |lines| {
11455 let mut seen = HashSet::default();
11456 lines.retain(|line| seen.insert(line.to_lowercase()));
11457 })
11458 }
11459
11460 pub fn unique_lines_case_sensitive(
11461 &mut self,
11462 _: &UniqueLinesCaseSensitive,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 self.manipulate_immutable_lines(window, cx, |lines| {
11467 let mut seen = HashSet::default();
11468 lines.retain(|line| seen.insert(*line));
11469 })
11470 }
11471
11472 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11473 let snapshot = self.buffer.read(cx).snapshot(cx);
11474 for selection in self.selections.disjoint_anchors_arc().iter() {
11475 if snapshot
11476 .language_at(selection.start)
11477 .and_then(|lang| lang.config().wrap_characters.as_ref())
11478 .is_some()
11479 {
11480 return true;
11481 }
11482 }
11483 false
11484 }
11485
11486 fn wrap_selections_in_tag(
11487 &mut self,
11488 _: &WrapSelectionsInTag,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11493
11494 let snapshot = self.buffer.read(cx).snapshot(cx);
11495
11496 let mut edits = Vec::new();
11497 let mut boundaries = Vec::new();
11498
11499 for selection in self
11500 .selections
11501 .all_adjusted(&self.display_snapshot(cx))
11502 .iter()
11503 {
11504 let Some(wrap_config) = snapshot
11505 .language_at(selection.start)
11506 .and_then(|lang| lang.config().wrap_characters.clone())
11507 else {
11508 continue;
11509 };
11510
11511 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11512 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11513
11514 let start_before = snapshot.anchor_before(selection.start);
11515 let end_after = snapshot.anchor_after(selection.end);
11516
11517 edits.push((start_before..start_before, open_tag));
11518 edits.push((end_after..end_after, close_tag));
11519
11520 boundaries.push((
11521 start_before,
11522 end_after,
11523 wrap_config.start_prefix.len(),
11524 wrap_config.end_suffix.len(),
11525 ));
11526 }
11527
11528 if edits.is_empty() {
11529 return;
11530 }
11531
11532 self.transact(window, cx, |this, window, cx| {
11533 let buffer = this.buffer.update(cx, |buffer, cx| {
11534 buffer.edit(edits, None, cx);
11535 buffer.snapshot(cx)
11536 });
11537
11538 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11539 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11540 boundaries.into_iter()
11541 {
11542 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11543 let close_offset = end_after
11544 .to_offset(&buffer)
11545 .saturating_sub_usize(end_suffix_len);
11546 new_selections.push(open_offset..open_offset);
11547 new_selections.push(close_offset..close_offset);
11548 }
11549
11550 this.change_selections(Default::default(), window, cx, |s| {
11551 s.select_ranges(new_selections);
11552 });
11553
11554 this.request_autoscroll(Autoscroll::fit(), cx);
11555 });
11556 }
11557
11558 pub fn toggle_read_only(
11559 &mut self,
11560 _: &workspace::ToggleReadOnlyFile,
11561 _: &mut Window,
11562 cx: &mut Context<Self>,
11563 ) {
11564 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11565 buffer.update(cx, |buffer, cx| {
11566 buffer.set_capability(
11567 match buffer.capability() {
11568 Capability::ReadWrite => Capability::Read,
11569 Capability::Read => Capability::ReadWrite,
11570 Capability::ReadOnly => Capability::ReadOnly,
11571 },
11572 cx,
11573 );
11574 })
11575 }
11576 }
11577
11578 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11579 let Some(project) = self.project.clone() else {
11580 return;
11581 };
11582 let task = self.reload(project, window, cx);
11583 self.detach_and_notify_err(task, window, cx);
11584 }
11585
11586 pub fn restore_file(
11587 &mut self,
11588 _: &::git::RestoreFile,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11593 let mut buffer_ids = HashSet::default();
11594 let snapshot = self.buffer().read(cx).snapshot(cx);
11595 for selection in self
11596 .selections
11597 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11598 {
11599 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11600 }
11601
11602 let buffer = self.buffer().read(cx);
11603 let ranges = buffer_ids
11604 .into_iter()
11605 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11606 .collect::<Vec<_>>();
11607
11608 self.restore_hunks_in_ranges(ranges, window, cx);
11609 }
11610
11611 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11613 let selections = self
11614 .selections
11615 .all(&self.display_snapshot(cx))
11616 .into_iter()
11617 .map(|s| s.range())
11618 .collect();
11619 self.restore_hunks_in_ranges(selections, window, cx);
11620 }
11621
11622 pub fn restore_hunks_in_ranges(
11623 &mut self,
11624 ranges: Vec<Range<Point>>,
11625 window: &mut Window,
11626 cx: &mut Context<Editor>,
11627 ) {
11628 if self.delegate_stage_and_restore {
11629 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11630 if !hunks.is_empty() {
11631 cx.emit(EditorEvent::RestoreRequested { hunks });
11632 }
11633 return;
11634 }
11635 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11636 self.transact(window, cx, |editor, window, cx| {
11637 editor.restore_diff_hunks(hunks, cx);
11638 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11639 selections.refresh()
11640 });
11641 });
11642 }
11643
11644 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11645 let mut revert_changes = HashMap::default();
11646 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11647 for (buffer_id, hunks) in &chunk_by {
11648 let hunks = hunks.collect::<Vec<_>>();
11649 for hunk in &hunks {
11650 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11651 }
11652 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11653 }
11654 if !revert_changes.is_empty() {
11655 self.buffer().update(cx, |multi_buffer, cx| {
11656 for (buffer_id, changes) in revert_changes {
11657 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11658 buffer.update(cx, |buffer, cx| {
11659 buffer.edit(
11660 changes
11661 .into_iter()
11662 .map(|(range, text)| (range, text.to_string())),
11663 None,
11664 cx,
11665 );
11666 });
11667 }
11668 }
11669 });
11670 }
11671 }
11672
11673 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11674 if let Some(status) = self
11675 .addons
11676 .iter()
11677 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11678 {
11679 return Some(status);
11680 }
11681 self.project
11682 .as_ref()?
11683 .read(cx)
11684 .status_for_buffer_id(buffer_id, cx)
11685 }
11686
11687 pub fn open_active_item_in_terminal(
11688 &mut self,
11689 _: &OpenInTerminal,
11690 window: &mut Window,
11691 cx: &mut Context<Self>,
11692 ) {
11693 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11694 let project_path = buffer.read(cx).project_path(cx)?;
11695 let project = self.project()?.read(cx);
11696 let entry = project.entry_for_path(&project_path, cx)?;
11697 let parent = match &entry.canonical_path {
11698 Some(canonical_path) => canonical_path.to_path_buf(),
11699 None => project.absolute_path(&project_path, cx)?,
11700 }
11701 .parent()?
11702 .to_path_buf();
11703 Some(parent)
11704 }) {
11705 window.dispatch_action(
11706 OpenTerminal {
11707 working_directory,
11708 local: false,
11709 }
11710 .boxed_clone(),
11711 cx,
11712 );
11713 }
11714 }
11715
11716 fn set_breakpoint_context_menu(
11717 &mut self,
11718 display_row: DisplayRow,
11719 position: Option<Anchor>,
11720 clicked_point: gpui::Point<Pixels>,
11721 window: &mut Window,
11722 cx: &mut Context<Self>,
11723 ) {
11724 let source = self
11725 .buffer
11726 .read(cx)
11727 .snapshot(cx)
11728 .anchor_before(Point::new(display_row.0, 0u32));
11729
11730 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11731
11732 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11733 self,
11734 source,
11735 clicked_point,
11736 context_menu,
11737 window,
11738 cx,
11739 );
11740 }
11741
11742 fn add_edit_breakpoint_block(
11743 &mut self,
11744 anchor: Anchor,
11745 breakpoint: &Breakpoint,
11746 edit_action: BreakpointPromptEditAction,
11747 window: &mut Window,
11748 cx: &mut Context<Self>,
11749 ) {
11750 let weak_editor = cx.weak_entity();
11751 let bp_prompt = cx.new(|cx| {
11752 BreakpointPromptEditor::new(
11753 weak_editor,
11754 anchor,
11755 breakpoint.clone(),
11756 edit_action,
11757 window,
11758 cx,
11759 )
11760 });
11761
11762 let height = bp_prompt.update(cx, |this, cx| {
11763 this.prompt
11764 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11765 });
11766 let cloned_prompt = bp_prompt.clone();
11767 let blocks = vec![BlockProperties {
11768 style: BlockStyle::Sticky,
11769 placement: BlockPlacement::Above(anchor),
11770 height: Some(height),
11771 render: Arc::new(move |cx| {
11772 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11773 cloned_prompt.clone().into_any_element()
11774 }),
11775 priority: 0,
11776 }];
11777
11778 let focus_handle = bp_prompt.focus_handle(cx);
11779 window.focus(&focus_handle, cx);
11780
11781 let block_ids = self.insert_blocks(blocks, None, cx);
11782 bp_prompt.update(cx, |prompt, _| {
11783 prompt.add_block_ids(block_ids);
11784 });
11785 }
11786
11787 pub(crate) fn breakpoint_at_row(
11788 &self,
11789 row: u32,
11790 window: &mut Window,
11791 cx: &mut Context<Self>,
11792 ) -> Option<(Anchor, Breakpoint)> {
11793 let snapshot = self.snapshot(window, cx);
11794 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11795
11796 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11797 }
11798
11799 pub(crate) fn breakpoint_at_anchor(
11800 &self,
11801 breakpoint_position: Anchor,
11802 snapshot: &EditorSnapshot,
11803 cx: &mut Context<Self>,
11804 ) -> Option<(Anchor, Breakpoint)> {
11805 let buffer = self
11806 .buffer
11807 .read(cx)
11808 .buffer_for_anchor(breakpoint_position, cx)?;
11809
11810 let enclosing_excerpt = breakpoint_position.excerpt_id;
11811 let buffer_snapshot = buffer.read(cx).snapshot();
11812
11813 let row = buffer_snapshot
11814 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11815 .row;
11816
11817 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11818 let anchor_end = snapshot
11819 .buffer_snapshot()
11820 .anchor_after(Point::new(row, line_len));
11821
11822 self.breakpoint_store
11823 .as_ref()?
11824 .read_with(cx, |breakpoint_store, cx| {
11825 breakpoint_store
11826 .breakpoints(
11827 &buffer,
11828 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11829 &buffer_snapshot,
11830 cx,
11831 )
11832 .next()
11833 .and_then(|(bp, _)| {
11834 let breakpoint_row = buffer_snapshot
11835 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11836 .row;
11837
11838 if breakpoint_row == row {
11839 snapshot
11840 .buffer_snapshot()
11841 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11842 .map(|position| (position, bp.bp.clone()))
11843 } else {
11844 None
11845 }
11846 })
11847 })
11848 }
11849
11850 pub fn edit_log_breakpoint(
11851 &mut self,
11852 _: &EditLogBreakpoint,
11853 window: &mut Window,
11854 cx: &mut Context<Self>,
11855 ) {
11856 if self.breakpoint_store.is_none() {
11857 return;
11858 }
11859
11860 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11861 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11862 message: None,
11863 state: BreakpointState::Enabled,
11864 condition: None,
11865 hit_condition: None,
11866 });
11867
11868 self.add_edit_breakpoint_block(
11869 anchor,
11870 &breakpoint,
11871 BreakpointPromptEditAction::Log,
11872 window,
11873 cx,
11874 );
11875 }
11876 }
11877
11878 fn breakpoints_at_cursors(
11879 &self,
11880 window: &mut Window,
11881 cx: &mut Context<Self>,
11882 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11883 let snapshot = self.snapshot(window, cx);
11884 let cursors = self
11885 .selections
11886 .disjoint_anchors_arc()
11887 .iter()
11888 .map(|selection| {
11889 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11890
11891 let breakpoint_position = self
11892 .breakpoint_at_row(cursor_position.row, window, cx)
11893 .map(|bp| bp.0)
11894 .unwrap_or_else(|| {
11895 snapshot
11896 .display_snapshot
11897 .buffer_snapshot()
11898 .anchor_after(Point::new(cursor_position.row, 0))
11899 });
11900
11901 let breakpoint = self
11902 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11903 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11904
11905 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11906 })
11907 // 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.
11908 .collect::<HashMap<Anchor, _>>();
11909
11910 cursors.into_iter().collect()
11911 }
11912
11913 pub fn enable_breakpoint(
11914 &mut self,
11915 _: &crate::actions::EnableBreakpoint,
11916 window: &mut Window,
11917 cx: &mut Context<Self>,
11918 ) {
11919 if self.breakpoint_store.is_none() {
11920 return;
11921 }
11922
11923 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11924 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11925 continue;
11926 };
11927 self.edit_breakpoint_at_anchor(
11928 anchor,
11929 breakpoint,
11930 BreakpointEditAction::InvertState,
11931 cx,
11932 );
11933 }
11934 }
11935
11936 pub fn disable_breakpoint(
11937 &mut self,
11938 _: &crate::actions::DisableBreakpoint,
11939 window: &mut Window,
11940 cx: &mut Context<Self>,
11941 ) {
11942 if self.breakpoint_store.is_none() {
11943 return;
11944 }
11945
11946 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11947 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11948 continue;
11949 };
11950 self.edit_breakpoint_at_anchor(
11951 anchor,
11952 breakpoint,
11953 BreakpointEditAction::InvertState,
11954 cx,
11955 );
11956 }
11957 }
11958
11959 pub fn toggle_breakpoint(
11960 &mut self,
11961 _: &crate::actions::ToggleBreakpoint,
11962 window: &mut Window,
11963 cx: &mut Context<Self>,
11964 ) {
11965 if self.breakpoint_store.is_none() {
11966 return;
11967 }
11968
11969 let snapshot = self.snapshot(window, cx);
11970 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11971 if self.gutter_breakpoint_indicator.0.is_some() {
11972 let display_row = anchor
11973 .to_point(snapshot.buffer_snapshot())
11974 .to_display_point(&snapshot.display_snapshot)
11975 .row();
11976 self.update_breakpoint_collision_on_toggle(
11977 display_row,
11978 &BreakpointEditAction::Toggle,
11979 );
11980 }
11981
11982 if let Some(breakpoint) = breakpoint {
11983 self.edit_breakpoint_at_anchor(
11984 anchor,
11985 breakpoint,
11986 BreakpointEditAction::Toggle,
11987 cx,
11988 );
11989 } else {
11990 self.edit_breakpoint_at_anchor(
11991 anchor,
11992 Breakpoint::new_standard(),
11993 BreakpointEditAction::Toggle,
11994 cx,
11995 );
11996 }
11997 }
11998 }
11999
12000 fn update_breakpoint_collision_on_toggle(
12001 &mut self,
12002 display_row: DisplayRow,
12003 edit_action: &BreakpointEditAction,
12004 ) {
12005 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12006 if breakpoint_indicator.display_row == display_row
12007 && matches!(edit_action, BreakpointEditAction::Toggle)
12008 {
12009 breakpoint_indicator.collides_with_existing_breakpoint =
12010 !breakpoint_indicator.collides_with_existing_breakpoint;
12011 }
12012 }
12013 }
12014
12015 pub fn edit_breakpoint_at_anchor(
12016 &mut self,
12017 breakpoint_position: Anchor,
12018 breakpoint: Breakpoint,
12019 edit_action: BreakpointEditAction,
12020 cx: &mut Context<Self>,
12021 ) {
12022 let Some(breakpoint_store) = &self.breakpoint_store else {
12023 return;
12024 };
12025
12026 let Some(buffer) = self
12027 .buffer
12028 .read(cx)
12029 .buffer_for_anchor(breakpoint_position, cx)
12030 else {
12031 return;
12032 };
12033
12034 breakpoint_store.update(cx, |breakpoint_store, cx| {
12035 breakpoint_store.toggle_breakpoint(
12036 buffer,
12037 BreakpointWithPosition {
12038 position: breakpoint_position.text_anchor,
12039 bp: breakpoint,
12040 },
12041 edit_action,
12042 cx,
12043 );
12044 });
12045
12046 cx.notify();
12047 }
12048
12049 #[cfg(any(test, feature = "test-support"))]
12050 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12051 self.breakpoint_store.clone()
12052 }
12053
12054 pub fn prepare_restore_change(
12055 &self,
12056 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12057 hunk: &MultiBufferDiffHunk,
12058 cx: &mut App,
12059 ) -> Option<()> {
12060 if hunk.is_created_file() {
12061 return None;
12062 }
12063 let buffer = self.buffer.read(cx);
12064 let diff = buffer.diff_for(hunk.buffer_id)?;
12065 let buffer = buffer.buffer(hunk.buffer_id)?;
12066 let buffer = buffer.read(cx);
12067 let original_text = diff
12068 .read(cx)
12069 .base_text(cx)
12070 .as_rope()
12071 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12072 let buffer_snapshot = buffer.snapshot();
12073 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12074 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12075 probe
12076 .0
12077 .start
12078 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12079 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12080 }) {
12081 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12082 Some(())
12083 } else {
12084 None
12085 }
12086 }
12087
12088 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12089 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12090 }
12091
12092 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12093 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12094 }
12095
12096 pub fn rotate_selections_forward(
12097 &mut self,
12098 _: &RotateSelectionsForward,
12099 window: &mut Window,
12100 cx: &mut Context<Self>,
12101 ) {
12102 self.rotate_selections(window, cx, false)
12103 }
12104
12105 pub fn rotate_selections_backward(
12106 &mut self,
12107 _: &RotateSelectionsBackward,
12108 window: &mut Window,
12109 cx: &mut Context<Self>,
12110 ) {
12111 self.rotate_selections(window, cx, true)
12112 }
12113
12114 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12116 let display_snapshot = self.display_snapshot(cx);
12117 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12118
12119 if selections.len() < 2 {
12120 return;
12121 }
12122
12123 let (edits, new_selections) = {
12124 let buffer = self.buffer.read(cx).read(cx);
12125 let has_selections = selections.iter().any(|s| !s.is_empty());
12126 if has_selections {
12127 let mut selected_texts: Vec<String> = selections
12128 .iter()
12129 .map(|selection| {
12130 buffer
12131 .text_for_range(selection.start..selection.end)
12132 .collect()
12133 })
12134 .collect();
12135
12136 if reverse {
12137 selected_texts.rotate_left(1);
12138 } else {
12139 selected_texts.rotate_right(1);
12140 }
12141
12142 let mut offset_delta: i64 = 0;
12143 let mut new_selections = Vec::new();
12144 let edits: Vec<_> = selections
12145 .iter()
12146 .zip(selected_texts.iter())
12147 .map(|(selection, new_text)| {
12148 let old_len = (selection.end.0 - selection.start.0) as i64;
12149 let new_len = new_text.len() as i64;
12150 let adjusted_start =
12151 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12152 let adjusted_end =
12153 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12154
12155 new_selections.push(Selection {
12156 id: selection.id,
12157 start: adjusted_start,
12158 end: adjusted_end,
12159 reversed: selection.reversed,
12160 goal: selection.goal,
12161 });
12162
12163 offset_delta += new_len - old_len;
12164 (selection.start..selection.end, new_text.clone())
12165 })
12166 .collect();
12167 (edits, new_selections)
12168 } else {
12169 let mut all_rows: Vec<u32> = selections
12170 .iter()
12171 .map(|selection| buffer.offset_to_point(selection.start).row)
12172 .collect();
12173 all_rows.sort_unstable();
12174 all_rows.dedup();
12175
12176 if all_rows.len() < 2 {
12177 return;
12178 }
12179
12180 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12181 .iter()
12182 .map(|&row| {
12183 let start = Point::new(row, 0);
12184 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12185 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12186 })
12187 .collect();
12188
12189 let mut line_texts: Vec<String> = line_ranges
12190 .iter()
12191 .map(|range| buffer.text_for_range(range.clone()).collect())
12192 .collect();
12193
12194 if reverse {
12195 line_texts.rotate_left(1);
12196 } else {
12197 line_texts.rotate_right(1);
12198 }
12199
12200 let edits = line_ranges
12201 .iter()
12202 .zip(line_texts.iter())
12203 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12204 .collect();
12205
12206 let num_rows = all_rows.len();
12207 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12208 .iter()
12209 .enumerate()
12210 .map(|(i, &row)| (row, i))
12211 .collect();
12212
12213 // Compute new line start offsets after rotation (handles CRLF)
12214 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12215 let first_line_start = line_ranges[0].start.0;
12216 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12217 for text in line_texts.iter().take(num_rows - 1) {
12218 let prev_start = *new_line_starts.last().unwrap();
12219 new_line_starts.push(prev_start + text.len() + newline_len);
12220 }
12221
12222 let new_selections = selections
12223 .iter()
12224 .map(|selection| {
12225 let point = buffer.offset_to_point(selection.start);
12226 let old_index = row_to_index[&point.row];
12227 let new_index = if reverse {
12228 (old_index + num_rows - 1) % num_rows
12229 } else {
12230 (old_index + 1) % num_rows
12231 };
12232 let new_offset =
12233 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12234 Selection {
12235 id: selection.id,
12236 start: new_offset,
12237 end: new_offset,
12238 reversed: selection.reversed,
12239 goal: selection.goal,
12240 }
12241 })
12242 .collect();
12243
12244 (edits, new_selections)
12245 }
12246 };
12247
12248 self.transact(window, cx, |this, window, cx| {
12249 this.buffer.update(cx, |buffer, cx| {
12250 buffer.edit(edits, None, cx);
12251 });
12252 this.change_selections(Default::default(), window, cx, |s| {
12253 s.select(new_selections);
12254 });
12255 });
12256 }
12257
12258 fn manipulate_lines<M>(
12259 &mut self,
12260 window: &mut Window,
12261 cx: &mut Context<Self>,
12262 mut manipulate: M,
12263 ) where
12264 M: FnMut(&str) -> LineManipulationResult,
12265 {
12266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12267
12268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12269 let buffer = self.buffer.read(cx).snapshot(cx);
12270
12271 let mut edits = Vec::new();
12272
12273 let selections = self.selections.all::<Point>(&display_map);
12274 let mut selections = selections.iter().peekable();
12275 let mut contiguous_row_selections = Vec::new();
12276 let mut new_selections = Vec::new();
12277 let mut added_lines = 0;
12278 let mut removed_lines = 0;
12279
12280 while let Some(selection) = selections.next() {
12281 let (start_row, end_row) = consume_contiguous_rows(
12282 &mut contiguous_row_selections,
12283 selection,
12284 &display_map,
12285 &mut selections,
12286 );
12287
12288 let start_point = Point::new(start_row.0, 0);
12289 let end_point = Point::new(
12290 end_row.previous_row().0,
12291 buffer.line_len(end_row.previous_row()),
12292 );
12293 let text = buffer
12294 .text_for_range(start_point..end_point)
12295 .collect::<String>();
12296
12297 let LineManipulationResult {
12298 new_text,
12299 line_count_before,
12300 line_count_after,
12301 } = manipulate(&text);
12302
12303 edits.push((start_point..end_point, new_text));
12304
12305 // Selections must change based on added and removed line count
12306 let start_row =
12307 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12308 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12309 new_selections.push(Selection {
12310 id: selection.id,
12311 start: start_row,
12312 end: end_row,
12313 goal: SelectionGoal::None,
12314 reversed: selection.reversed,
12315 });
12316
12317 if line_count_after > line_count_before {
12318 added_lines += line_count_after - line_count_before;
12319 } else if line_count_before > line_count_after {
12320 removed_lines += line_count_before - line_count_after;
12321 }
12322 }
12323
12324 self.transact(window, cx, |this, window, cx| {
12325 let buffer = this.buffer.update(cx, |buffer, cx| {
12326 buffer.edit(edits, None, cx);
12327 buffer.snapshot(cx)
12328 });
12329
12330 // Recalculate offsets on newly edited buffer
12331 let new_selections = new_selections
12332 .iter()
12333 .map(|s| {
12334 let start_point = Point::new(s.start.0, 0);
12335 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12336 Selection {
12337 id: s.id,
12338 start: buffer.point_to_offset(start_point),
12339 end: buffer.point_to_offset(end_point),
12340 goal: s.goal,
12341 reversed: s.reversed,
12342 }
12343 })
12344 .collect();
12345
12346 this.change_selections(Default::default(), window, cx, |s| {
12347 s.select(new_selections);
12348 });
12349
12350 this.request_autoscroll(Autoscroll::fit(), cx);
12351 });
12352 }
12353
12354 fn manipulate_immutable_lines<Fn>(
12355 &mut self,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 mut callback: Fn,
12359 ) where
12360 Fn: FnMut(&mut Vec<&str>),
12361 {
12362 self.manipulate_lines(window, cx, |text| {
12363 let mut lines: Vec<&str> = text.split('\n').collect();
12364 let line_count_before = lines.len();
12365
12366 callback(&mut lines);
12367
12368 LineManipulationResult {
12369 new_text: lines.join("\n"),
12370 line_count_before,
12371 line_count_after: lines.len(),
12372 }
12373 });
12374 }
12375
12376 fn manipulate_mutable_lines<Fn>(
12377 &mut self,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 mut callback: Fn,
12381 ) where
12382 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12383 {
12384 self.manipulate_lines(window, cx, |text| {
12385 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12386 let line_count_before = lines.len();
12387
12388 callback(&mut lines);
12389
12390 LineManipulationResult {
12391 new_text: lines.join("\n"),
12392 line_count_before,
12393 line_count_after: lines.len(),
12394 }
12395 });
12396 }
12397
12398 pub fn convert_indentation_to_spaces(
12399 &mut self,
12400 _: &ConvertIndentationToSpaces,
12401 window: &mut Window,
12402 cx: &mut Context<Self>,
12403 ) {
12404 let settings = self.buffer.read(cx).language_settings(cx);
12405 let tab_size = settings.tab_size.get() as usize;
12406
12407 self.manipulate_mutable_lines(window, cx, |lines| {
12408 // Allocates a reasonably sized scratch buffer once for the whole loop
12409 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12410 // Avoids recomputing spaces that could be inserted many times
12411 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12412 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12413 .collect();
12414
12415 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12416 let mut chars = line.as_ref().chars();
12417 let mut col = 0;
12418 let mut changed = false;
12419
12420 for ch in chars.by_ref() {
12421 match ch {
12422 ' ' => {
12423 reindented_line.push(' ');
12424 col += 1;
12425 }
12426 '\t' => {
12427 // \t are converted to spaces depending on the current column
12428 let spaces_len = tab_size - (col % tab_size);
12429 reindented_line.extend(&space_cache[spaces_len - 1]);
12430 col += spaces_len;
12431 changed = true;
12432 }
12433 _ => {
12434 // If we dont append before break, the character is consumed
12435 reindented_line.push(ch);
12436 break;
12437 }
12438 }
12439 }
12440
12441 if !changed {
12442 reindented_line.clear();
12443 continue;
12444 }
12445 // Append the rest of the line and replace old reference with new one
12446 reindented_line.extend(chars);
12447 *line = Cow::Owned(reindented_line.clone());
12448 reindented_line.clear();
12449 }
12450 });
12451 }
12452
12453 pub fn convert_indentation_to_tabs(
12454 &mut self,
12455 _: &ConvertIndentationToTabs,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) {
12459 let settings = self.buffer.read(cx).language_settings(cx);
12460 let tab_size = settings.tab_size.get() as usize;
12461
12462 self.manipulate_mutable_lines(window, cx, |lines| {
12463 // Allocates a reasonably sized buffer once for the whole loop
12464 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12465 // Avoids recomputing spaces that could be inserted many times
12466 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12467 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12468 .collect();
12469
12470 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12471 let mut chars = line.chars();
12472 let mut spaces_count = 0;
12473 let mut first_non_indent_char = None;
12474 let mut changed = false;
12475
12476 for ch in chars.by_ref() {
12477 match ch {
12478 ' ' => {
12479 // Keep track of spaces. Append \t when we reach tab_size
12480 spaces_count += 1;
12481 changed = true;
12482 if spaces_count == tab_size {
12483 reindented_line.push('\t');
12484 spaces_count = 0;
12485 }
12486 }
12487 '\t' => {
12488 reindented_line.push('\t');
12489 spaces_count = 0;
12490 }
12491 _ => {
12492 // Dont append it yet, we might have remaining spaces
12493 first_non_indent_char = Some(ch);
12494 break;
12495 }
12496 }
12497 }
12498
12499 if !changed {
12500 reindented_line.clear();
12501 continue;
12502 }
12503 // Remaining spaces that didn't make a full tab stop
12504 if spaces_count > 0 {
12505 reindented_line.extend(&space_cache[spaces_count - 1]);
12506 }
12507 // If we consume an extra character that was not indentation, add it back
12508 if let Some(extra_char) = first_non_indent_char {
12509 reindented_line.push(extra_char);
12510 }
12511 // Append the rest of the line and replace old reference with new one
12512 reindented_line.extend(chars);
12513 *line = Cow::Owned(reindented_line.clone());
12514 reindented_line.clear();
12515 }
12516 });
12517 }
12518
12519 pub fn convert_to_upper_case(
12520 &mut self,
12521 _: &ConvertToUpperCase,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 self.manipulate_text(window, cx, |text| text.to_uppercase())
12526 }
12527
12528 pub fn convert_to_lower_case(
12529 &mut self,
12530 _: &ConvertToLowerCase,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) {
12534 self.manipulate_text(window, cx, |text| text.to_lowercase())
12535 }
12536
12537 pub fn convert_to_title_case(
12538 &mut self,
12539 _: &ConvertToTitleCase,
12540 window: &mut Window,
12541 cx: &mut Context<Self>,
12542 ) {
12543 self.manipulate_text(window, cx, |text| {
12544 text.split('\n')
12545 .map(|line| line.to_case(Case::Title))
12546 .join("\n")
12547 })
12548 }
12549
12550 pub fn convert_to_snake_case(
12551 &mut self,
12552 _: &ConvertToSnakeCase,
12553 window: &mut Window,
12554 cx: &mut Context<Self>,
12555 ) {
12556 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12557 }
12558
12559 pub fn convert_to_kebab_case(
12560 &mut self,
12561 _: &ConvertToKebabCase,
12562 window: &mut Window,
12563 cx: &mut Context<Self>,
12564 ) {
12565 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12566 }
12567
12568 pub fn convert_to_upper_camel_case(
12569 &mut self,
12570 _: &ConvertToUpperCamelCase,
12571 window: &mut Window,
12572 cx: &mut Context<Self>,
12573 ) {
12574 self.manipulate_text(window, cx, |text| {
12575 text.split('\n')
12576 .map(|line| line.to_case(Case::UpperCamel))
12577 .join("\n")
12578 })
12579 }
12580
12581 pub fn convert_to_lower_camel_case(
12582 &mut self,
12583 _: &ConvertToLowerCamelCase,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12588 }
12589
12590 pub fn convert_to_opposite_case(
12591 &mut self,
12592 _: &ConvertToOppositeCase,
12593 window: &mut Window,
12594 cx: &mut Context<Self>,
12595 ) {
12596 self.manipulate_text(window, cx, |text| {
12597 text.chars()
12598 .fold(String::with_capacity(text.len()), |mut t, c| {
12599 if c.is_uppercase() {
12600 t.extend(c.to_lowercase());
12601 } else {
12602 t.extend(c.to_uppercase());
12603 }
12604 t
12605 })
12606 })
12607 }
12608
12609 pub fn convert_to_sentence_case(
12610 &mut self,
12611 _: &ConvertToSentenceCase,
12612 window: &mut Window,
12613 cx: &mut Context<Self>,
12614 ) {
12615 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12616 }
12617
12618 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12619 self.manipulate_text(window, cx, |text| {
12620 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12621 if has_upper_case_characters {
12622 text.to_lowercase()
12623 } else {
12624 text.to_uppercase()
12625 }
12626 })
12627 }
12628
12629 pub fn convert_to_rot13(
12630 &mut self,
12631 _: &ConvertToRot13,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 self.manipulate_text(window, cx, |text| {
12636 text.chars()
12637 .map(|c| match c {
12638 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12639 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12640 _ => c,
12641 })
12642 .collect()
12643 })
12644 }
12645
12646 pub fn convert_to_rot47(
12647 &mut self,
12648 _: &ConvertToRot47,
12649 window: &mut Window,
12650 cx: &mut Context<Self>,
12651 ) {
12652 self.manipulate_text(window, cx, |text| {
12653 text.chars()
12654 .map(|c| {
12655 let code_point = c as u32;
12656 if code_point >= 33 && code_point <= 126 {
12657 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12658 }
12659 c
12660 })
12661 .collect()
12662 })
12663 }
12664
12665 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12666 where
12667 Fn: FnMut(&str) -> String,
12668 {
12669 let buffer = self.buffer.read(cx).snapshot(cx);
12670
12671 let mut new_selections = Vec::new();
12672 let mut edits = Vec::new();
12673 let mut selection_adjustment = 0isize;
12674
12675 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12676 let selection_is_empty = selection.is_empty();
12677
12678 let (start, end) = if selection_is_empty {
12679 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12680 (word_range.start, word_range.end)
12681 } else {
12682 (
12683 buffer.point_to_offset(selection.start),
12684 buffer.point_to_offset(selection.end),
12685 )
12686 };
12687
12688 let text = buffer.text_for_range(start..end).collect::<String>();
12689 let old_length = text.len() as isize;
12690 let text = callback(&text);
12691
12692 new_selections.push(Selection {
12693 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12694 end: MultiBufferOffset(
12695 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12696 ),
12697 goal: SelectionGoal::None,
12698 id: selection.id,
12699 reversed: selection.reversed,
12700 });
12701
12702 selection_adjustment += old_length - text.len() as isize;
12703
12704 edits.push((start..end, text));
12705 }
12706
12707 self.transact(window, cx, |this, window, cx| {
12708 this.buffer.update(cx, |buffer, cx| {
12709 buffer.edit(edits, None, cx);
12710 });
12711
12712 this.change_selections(Default::default(), window, cx, |s| {
12713 s.select(new_selections);
12714 });
12715
12716 this.request_autoscroll(Autoscroll::fit(), cx);
12717 });
12718 }
12719
12720 pub fn move_selection_on_drop(
12721 &mut self,
12722 selection: &Selection<Anchor>,
12723 target: DisplayPoint,
12724 is_cut: bool,
12725 window: &mut Window,
12726 cx: &mut Context<Self>,
12727 ) {
12728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12729 let buffer = display_map.buffer_snapshot();
12730 let mut edits = Vec::new();
12731 let insert_point = display_map
12732 .clip_point(target, Bias::Left)
12733 .to_point(&display_map);
12734 let text = buffer
12735 .text_for_range(selection.start..selection.end)
12736 .collect::<String>();
12737 if is_cut {
12738 edits.push(((selection.start..selection.end), String::new()));
12739 }
12740 let insert_anchor = buffer.anchor_before(insert_point);
12741 edits.push(((insert_anchor..insert_anchor), text));
12742 let last_edit_start = insert_anchor.bias_left(buffer);
12743 let last_edit_end = insert_anchor.bias_right(buffer);
12744 self.transact(window, cx, |this, window, cx| {
12745 this.buffer.update(cx, |buffer, cx| {
12746 buffer.edit(edits, None, cx);
12747 });
12748 this.change_selections(Default::default(), window, cx, |s| {
12749 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12750 });
12751 });
12752 }
12753
12754 pub fn clear_selection_drag_state(&mut self) {
12755 self.selection_drag_state = SelectionDragState::None;
12756 }
12757
12758 pub fn duplicate(
12759 &mut self,
12760 upwards: bool,
12761 whole_lines: bool,
12762 window: &mut Window,
12763 cx: &mut Context<Self>,
12764 ) {
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12766
12767 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12768 let buffer = display_map.buffer_snapshot();
12769 let selections = self.selections.all::<Point>(&display_map);
12770
12771 let mut edits = Vec::new();
12772 let mut selections_iter = selections.iter().peekable();
12773 while let Some(selection) = selections_iter.next() {
12774 let mut rows = selection.spanned_rows(false, &display_map);
12775 // duplicate line-wise
12776 if whole_lines || selection.start == selection.end {
12777 // Avoid duplicating the same lines twice.
12778 while let Some(next_selection) = selections_iter.peek() {
12779 let next_rows = next_selection.spanned_rows(false, &display_map);
12780 if next_rows.start < rows.end {
12781 rows.end = next_rows.end;
12782 selections_iter.next().unwrap();
12783 } else {
12784 break;
12785 }
12786 }
12787
12788 // Copy the text from the selected row region and splice it either at the start
12789 // or end of the region.
12790 let start = Point::new(rows.start.0, 0);
12791 let end = Point::new(
12792 rows.end.previous_row().0,
12793 buffer.line_len(rows.end.previous_row()),
12794 );
12795
12796 let mut text = buffer.text_for_range(start..end).collect::<String>();
12797
12798 let insert_location = if upwards {
12799 // When duplicating upward, we need to insert before the current line.
12800 // If we're on the last line and it doesn't end with a newline,
12801 // we need to add a newline before the duplicated content.
12802 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12803 && buffer.max_point().column > 0
12804 && !text.ends_with('\n');
12805
12806 if needs_leading_newline {
12807 text.insert(0, '\n');
12808 end
12809 } else {
12810 text.push('\n');
12811 Point::new(rows.start.0, 0)
12812 }
12813 } else {
12814 text.push('\n');
12815 start
12816 };
12817 edits.push((insert_location..insert_location, text));
12818 } else {
12819 // duplicate character-wise
12820 let start = selection.start;
12821 let end = selection.end;
12822 let text = buffer.text_for_range(start..end).collect::<String>();
12823 edits.push((selection.end..selection.end, text));
12824 }
12825 }
12826
12827 self.transact(window, cx, |this, window, cx| {
12828 this.buffer.update(cx, |buffer, cx| {
12829 buffer.edit(edits, None, cx);
12830 });
12831
12832 // When duplicating upward with whole lines, move the cursor to the duplicated line
12833 if upwards && whole_lines {
12834 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12835
12836 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12837 let mut new_ranges = Vec::new();
12838 let selections = s.all::<Point>(&display_map);
12839 let mut selections_iter = selections.iter().peekable();
12840
12841 while let Some(first_selection) = selections_iter.next() {
12842 // Group contiguous selections together to find the total row span
12843 let mut group_selections = vec![first_selection];
12844 let mut rows = first_selection.spanned_rows(false, &display_map);
12845
12846 while let Some(next_selection) = selections_iter.peek() {
12847 let next_rows = next_selection.spanned_rows(false, &display_map);
12848 if next_rows.start < rows.end {
12849 rows.end = next_rows.end;
12850 group_selections.push(selections_iter.next().unwrap());
12851 } else {
12852 break;
12853 }
12854 }
12855
12856 let row_count = rows.end.0 - rows.start.0;
12857
12858 // Move all selections in this group up by the total number of duplicated rows
12859 for selection in group_selections {
12860 let new_start = Point::new(
12861 selection.start.row.saturating_sub(row_count),
12862 selection.start.column,
12863 );
12864
12865 let new_end = Point::new(
12866 selection.end.row.saturating_sub(row_count),
12867 selection.end.column,
12868 );
12869
12870 new_ranges.push(new_start..new_end);
12871 }
12872 }
12873
12874 s.select_ranges(new_ranges);
12875 });
12876 }
12877
12878 this.request_autoscroll(Autoscroll::fit(), cx);
12879 });
12880 }
12881
12882 pub fn duplicate_line_up(
12883 &mut self,
12884 _: &DuplicateLineUp,
12885 window: &mut Window,
12886 cx: &mut Context<Self>,
12887 ) {
12888 self.duplicate(true, true, window, cx);
12889 }
12890
12891 pub fn duplicate_line_down(
12892 &mut self,
12893 _: &DuplicateLineDown,
12894 window: &mut Window,
12895 cx: &mut Context<Self>,
12896 ) {
12897 self.duplicate(false, true, window, cx);
12898 }
12899
12900 pub fn duplicate_selection(
12901 &mut self,
12902 _: &DuplicateSelection,
12903 window: &mut Window,
12904 cx: &mut Context<Self>,
12905 ) {
12906 self.duplicate(false, false, window, cx);
12907 }
12908
12909 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12910 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12911 if self.mode.is_single_line() {
12912 cx.propagate();
12913 return;
12914 }
12915
12916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12917 let buffer = self.buffer.read(cx).snapshot(cx);
12918
12919 let mut edits = Vec::new();
12920 let mut unfold_ranges = Vec::new();
12921 let mut refold_creases = Vec::new();
12922
12923 let selections = self.selections.all::<Point>(&display_map);
12924 let mut selections = selections.iter().peekable();
12925 let mut contiguous_row_selections = Vec::new();
12926 let mut new_selections = Vec::new();
12927
12928 while let Some(selection) = selections.next() {
12929 // Find all the selections that span a contiguous row range
12930 let (start_row, end_row) = consume_contiguous_rows(
12931 &mut contiguous_row_selections,
12932 selection,
12933 &display_map,
12934 &mut selections,
12935 );
12936
12937 // Move the text spanned by the row range to be before the line preceding the row range
12938 if start_row.0 > 0 {
12939 let range_to_move = Point::new(
12940 start_row.previous_row().0,
12941 buffer.line_len(start_row.previous_row()),
12942 )
12943 ..Point::new(
12944 end_row.previous_row().0,
12945 buffer.line_len(end_row.previous_row()),
12946 );
12947 let insertion_point = display_map
12948 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12949 .0;
12950
12951 // Don't move lines across excerpts
12952 if buffer
12953 .excerpt_containing(insertion_point..range_to_move.end)
12954 .is_some()
12955 {
12956 let text = buffer
12957 .text_for_range(range_to_move.clone())
12958 .flat_map(|s| s.chars())
12959 .skip(1)
12960 .chain(['\n'])
12961 .collect::<String>();
12962
12963 edits.push((
12964 buffer.anchor_after(range_to_move.start)
12965 ..buffer.anchor_before(range_to_move.end),
12966 String::new(),
12967 ));
12968 let insertion_anchor = buffer.anchor_after(insertion_point);
12969 edits.push((insertion_anchor..insertion_anchor, text));
12970
12971 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12972
12973 // Move selections up
12974 new_selections.extend(contiguous_row_selections.drain(..).map(
12975 |mut selection| {
12976 selection.start.row -= row_delta;
12977 selection.end.row -= row_delta;
12978 selection
12979 },
12980 ));
12981
12982 // Move folds up
12983 unfold_ranges.push(range_to_move.clone());
12984 for fold in display_map.folds_in_range(
12985 buffer.anchor_before(range_to_move.start)
12986 ..buffer.anchor_after(range_to_move.end),
12987 ) {
12988 let mut start = fold.range.start.to_point(&buffer);
12989 let mut end = fold.range.end.to_point(&buffer);
12990 start.row -= row_delta;
12991 end.row -= row_delta;
12992 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12993 }
12994 }
12995 }
12996
12997 // If we didn't move line(s), preserve the existing selections
12998 new_selections.append(&mut contiguous_row_selections);
12999 }
13000
13001 self.transact(window, cx, |this, window, cx| {
13002 this.unfold_ranges(&unfold_ranges, true, true, cx);
13003 this.buffer.update(cx, |buffer, cx| {
13004 for (range, text) in edits {
13005 buffer.edit([(range, text)], None, cx);
13006 }
13007 });
13008 this.fold_creases(refold_creases, true, window, cx);
13009 this.change_selections(Default::default(), window, cx, |s| {
13010 s.select(new_selections);
13011 })
13012 });
13013 }
13014
13015 pub fn move_line_down(
13016 &mut self,
13017 _: &MoveLineDown,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13022 if self.mode.is_single_line() {
13023 cx.propagate();
13024 return;
13025 }
13026
13027 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13028 let buffer = self.buffer.read(cx).snapshot(cx);
13029
13030 let mut edits = Vec::new();
13031 let mut unfold_ranges = Vec::new();
13032 let mut refold_creases = Vec::new();
13033
13034 let selections = self.selections.all::<Point>(&display_map);
13035 let mut selections = selections.iter().peekable();
13036 let mut contiguous_row_selections = Vec::new();
13037 let mut new_selections = Vec::new();
13038
13039 while let Some(selection) = selections.next() {
13040 // Find all the selections that span a contiguous row range
13041 let (start_row, end_row) = consume_contiguous_rows(
13042 &mut contiguous_row_selections,
13043 selection,
13044 &display_map,
13045 &mut selections,
13046 );
13047
13048 // Move the text spanned by the row range to be after the last line of the row range
13049 if end_row.0 <= buffer.max_point().row {
13050 let range_to_move =
13051 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13052 let insertion_point = display_map
13053 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13054 .0;
13055
13056 // Don't move lines across excerpt boundaries
13057 if buffer
13058 .excerpt_containing(range_to_move.start..insertion_point)
13059 .is_some()
13060 {
13061 let mut text = String::from("\n");
13062 text.extend(buffer.text_for_range(range_to_move.clone()));
13063 text.pop(); // Drop trailing newline
13064 edits.push((
13065 buffer.anchor_after(range_to_move.start)
13066 ..buffer.anchor_before(range_to_move.end),
13067 String::new(),
13068 ));
13069 let insertion_anchor = buffer.anchor_after(insertion_point);
13070 edits.push((insertion_anchor..insertion_anchor, text));
13071
13072 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13073
13074 // Move selections down
13075 new_selections.extend(contiguous_row_selections.drain(..).map(
13076 |mut selection| {
13077 selection.start.row += row_delta;
13078 selection.end.row += row_delta;
13079 selection
13080 },
13081 ));
13082
13083 // Move folds down
13084 unfold_ranges.push(range_to_move.clone());
13085 for fold in display_map.folds_in_range(
13086 buffer.anchor_before(range_to_move.start)
13087 ..buffer.anchor_after(range_to_move.end),
13088 ) {
13089 let mut start = fold.range.start.to_point(&buffer);
13090 let mut end = fold.range.end.to_point(&buffer);
13091 start.row += row_delta;
13092 end.row += row_delta;
13093 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13094 }
13095 }
13096 }
13097
13098 // If we didn't move line(s), preserve the existing selections
13099 new_selections.append(&mut contiguous_row_selections);
13100 }
13101
13102 self.transact(window, cx, |this, window, cx| {
13103 this.unfold_ranges(&unfold_ranges, true, true, cx);
13104 this.buffer.update(cx, |buffer, cx| {
13105 for (range, text) in edits {
13106 buffer.edit([(range, text)], None, cx);
13107 }
13108 });
13109 this.fold_creases(refold_creases, true, window, cx);
13110 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13111 });
13112 }
13113
13114 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13116 let text_layout_details = &self.text_layout_details(window, cx);
13117 self.transact(window, cx, |this, window, cx| {
13118 let edits = this.change_selections(Default::default(), window, cx, |s| {
13119 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13120 s.move_with(&mut |display_map, selection| {
13121 if !selection.is_empty() {
13122 return;
13123 }
13124
13125 let mut head = selection.head();
13126 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13127 if head.column() == display_map.line_len(head.row()) {
13128 transpose_offset = display_map
13129 .buffer_snapshot()
13130 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13131 }
13132
13133 if transpose_offset == MultiBufferOffset(0) {
13134 return;
13135 }
13136
13137 *head.column_mut() += 1;
13138 head = display_map.clip_point(head, Bias::Right);
13139 let goal = SelectionGoal::HorizontalPosition(
13140 display_map
13141 .x_for_display_point(head, text_layout_details)
13142 .into(),
13143 );
13144 selection.collapse_to(head, goal);
13145
13146 let transpose_start = display_map
13147 .buffer_snapshot()
13148 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13149 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13150 let transpose_end = display_map
13151 .buffer_snapshot()
13152 .clip_offset(transpose_offset + 1usize, Bias::Right);
13153 if let Some(ch) = display_map
13154 .buffer_snapshot()
13155 .chars_at(transpose_start)
13156 .next()
13157 {
13158 edits.push((transpose_start..transpose_offset, String::new()));
13159 edits.push((transpose_end..transpose_end, ch.to_string()));
13160 }
13161 }
13162 });
13163 edits
13164 });
13165 this.buffer
13166 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13167 let selections = this
13168 .selections
13169 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13170 this.change_selections(Default::default(), window, cx, |s| {
13171 s.select(selections);
13172 });
13173 });
13174 }
13175
13176 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13178 if self.mode.is_single_line() {
13179 cx.propagate();
13180 return;
13181 }
13182
13183 self.rewrap_impl(RewrapOptions::default(), cx)
13184 }
13185
13186 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13187 let buffer = self.buffer.read(cx).snapshot(cx);
13188 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13189
13190 #[derive(Clone, Debug, PartialEq)]
13191 enum CommentFormat {
13192 /// single line comment, with prefix for line
13193 Line(String),
13194 /// single line within a block comment, with prefix for line
13195 BlockLine(String),
13196 /// a single line of a block comment that includes the initial delimiter
13197 BlockCommentWithStart(BlockCommentConfig),
13198 /// a single line of a block comment that includes the ending delimiter
13199 BlockCommentWithEnd(BlockCommentConfig),
13200 }
13201
13202 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13203 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13204 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
13205 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13206 .peekable();
13207
13208 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13209 row
13210 } else {
13211 return Vec::new();
13212 };
13213
13214 let language_settings = buffer.language_settings_at(selection.head(), cx);
13215 let language_scope = buffer.language_scope_at(selection.head());
13216
13217 let indent_and_prefix_for_row =
13218 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13219 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13220 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13221 &language_scope
13222 {
13223 let indent_end = Point::new(row, indent.len);
13224 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13225 let line_text_after_indent = buffer
13226 .text_for_range(indent_end..line_end)
13227 .collect::<String>();
13228
13229 let is_within_comment_override = buffer
13230 .language_scope_at(indent_end)
13231 .is_some_and(|scope| scope.override_name() == Some("comment"));
13232 let comment_delimiters = if is_within_comment_override {
13233 // we are within a comment syntax node, but we don't
13234 // yet know what kind of comment: block, doc or line
13235 match (
13236 language_scope.documentation_comment(),
13237 language_scope.block_comment(),
13238 ) {
13239 (Some(config), _) | (_, Some(config))
13240 if buffer.contains_str_at(indent_end, &config.start) =>
13241 {
13242 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13243 }
13244 (Some(config), _) | (_, Some(config))
13245 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13246 {
13247 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13248 }
13249 (Some(config), _) | (_, Some(config))
13250 if buffer.contains_str_at(indent_end, &config.prefix) =>
13251 {
13252 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13253 }
13254 (_, _) => language_scope
13255 .line_comment_prefixes()
13256 .iter()
13257 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13258 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13259 }
13260 } else {
13261 // we not in an overridden comment node, but we may
13262 // be within a non-overridden line comment node
13263 language_scope
13264 .line_comment_prefixes()
13265 .iter()
13266 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13267 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13268 };
13269
13270 let rewrap_prefix = language_scope
13271 .rewrap_prefixes()
13272 .iter()
13273 .find_map(|prefix_regex| {
13274 prefix_regex.find(&line_text_after_indent).map(|mat| {
13275 if mat.start() == 0 {
13276 Some(mat.as_str().to_string())
13277 } else {
13278 None
13279 }
13280 })
13281 })
13282 .flatten();
13283 (comment_delimiters, rewrap_prefix)
13284 } else {
13285 (None, None)
13286 };
13287 (indent, comment_prefix, rewrap_prefix)
13288 };
13289
13290 let mut ranges = Vec::new();
13291 let from_empty_selection = selection.is_empty();
13292
13293 let mut current_range_start = first_row;
13294 let mut prev_row = first_row;
13295 let (
13296 mut current_range_indent,
13297 mut current_range_comment_delimiters,
13298 mut current_range_rewrap_prefix,
13299 ) = indent_and_prefix_for_row(first_row);
13300
13301 for row in non_blank_rows_iter.skip(1) {
13302 let has_paragraph_break = row > prev_row + 1;
13303
13304 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13305 indent_and_prefix_for_row(row);
13306
13307 let has_indent_change = row_indent != current_range_indent;
13308 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13309
13310 let has_boundary_change = has_comment_change
13311 || row_rewrap_prefix.is_some()
13312 || (has_indent_change && current_range_comment_delimiters.is_some());
13313
13314 if has_paragraph_break || has_boundary_change {
13315 ranges.push((
13316 language_settings.clone(),
13317 Point::new(current_range_start, 0)
13318 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13319 current_range_indent,
13320 current_range_comment_delimiters.clone(),
13321 current_range_rewrap_prefix.clone(),
13322 from_empty_selection,
13323 ));
13324 current_range_start = row;
13325 current_range_indent = row_indent;
13326 current_range_comment_delimiters = row_comment_delimiters;
13327 current_range_rewrap_prefix = row_rewrap_prefix;
13328 }
13329 prev_row = row;
13330 }
13331
13332 ranges.push((
13333 language_settings.clone(),
13334 Point::new(current_range_start, 0)
13335 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13336 current_range_indent,
13337 current_range_comment_delimiters,
13338 current_range_rewrap_prefix,
13339 from_empty_selection,
13340 ));
13341
13342 ranges
13343 });
13344
13345 let mut edits = Vec::new();
13346 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13347
13348 for (
13349 language_settings,
13350 wrap_range,
13351 mut indent_size,
13352 comment_prefix,
13353 rewrap_prefix,
13354 from_empty_selection,
13355 ) in wrap_ranges
13356 {
13357 let mut start_row = wrap_range.start.row;
13358 let mut end_row = wrap_range.end.row;
13359
13360 // Skip selections that overlap with a range that has already been rewrapped.
13361 let selection_range = start_row..end_row;
13362 if rewrapped_row_ranges
13363 .iter()
13364 .any(|range| range.overlaps(&selection_range))
13365 {
13366 continue;
13367 }
13368
13369 let tab_size = language_settings.tab_size;
13370
13371 let (line_prefix, inside_comment) = match &comment_prefix {
13372 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13373 (Some(prefix.as_str()), true)
13374 }
13375 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13376 (Some(prefix.as_ref()), true)
13377 }
13378 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13379 start: _,
13380 end: _,
13381 prefix,
13382 tab_size,
13383 })) => {
13384 indent_size.len += tab_size;
13385 (Some(prefix.as_ref()), true)
13386 }
13387 None => (None, false),
13388 };
13389 let indent_prefix = indent_size.chars().collect::<String>();
13390 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13391
13392 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13393 RewrapBehavior::InComments => inside_comment,
13394 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13395 RewrapBehavior::Anywhere => true,
13396 };
13397
13398 let should_rewrap = options.override_language_settings
13399 || allow_rewrap_based_on_language
13400 || self.hard_wrap.is_some();
13401 if !should_rewrap {
13402 continue;
13403 }
13404
13405 if from_empty_selection {
13406 'expand_upwards: while start_row > 0 {
13407 let prev_row = start_row - 1;
13408 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13409 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13410 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13411 {
13412 start_row = prev_row;
13413 } else {
13414 break 'expand_upwards;
13415 }
13416 }
13417
13418 'expand_downwards: while end_row < buffer.max_point().row {
13419 let next_row = end_row + 1;
13420 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13421 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13422 && !buffer.is_line_blank(MultiBufferRow(next_row))
13423 {
13424 end_row = next_row;
13425 } else {
13426 break 'expand_downwards;
13427 }
13428 }
13429 }
13430
13431 let start = Point::new(start_row, 0);
13432 let start_offset = ToOffset::to_offset(&start, &buffer);
13433 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13434 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13435 let mut first_line_delimiter = None;
13436 let mut last_line_delimiter = None;
13437 let Some(lines_without_prefixes) = selection_text
13438 .lines()
13439 .enumerate()
13440 .map(|(ix, line)| {
13441 let line_trimmed = line.trim_start();
13442 if rewrap_prefix.is_some() && ix > 0 {
13443 Ok(line_trimmed)
13444 } else if let Some(
13445 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13446 start,
13447 prefix,
13448 end,
13449 tab_size,
13450 })
13451 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13452 start,
13453 prefix,
13454 end,
13455 tab_size,
13456 }),
13457 ) = &comment_prefix
13458 {
13459 let line_trimmed = line_trimmed
13460 .strip_prefix(start.as_ref())
13461 .map(|s| {
13462 let mut indent_size = indent_size;
13463 indent_size.len -= tab_size;
13464 let indent_prefix: String = indent_size.chars().collect();
13465 first_line_delimiter = Some((indent_prefix, start));
13466 s.trim_start()
13467 })
13468 .unwrap_or(line_trimmed);
13469 let line_trimmed = line_trimmed
13470 .strip_suffix(end.as_ref())
13471 .map(|s| {
13472 last_line_delimiter = Some(end);
13473 s.trim_end()
13474 })
13475 .unwrap_or(line_trimmed);
13476 let line_trimmed = line_trimmed
13477 .strip_prefix(prefix.as_ref())
13478 .unwrap_or(line_trimmed);
13479 Ok(line_trimmed)
13480 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13481 line_trimmed.strip_prefix(prefix).with_context(|| {
13482 format!("line did not start with prefix {prefix:?}: {line:?}")
13483 })
13484 } else {
13485 line_trimmed
13486 .strip_prefix(&line_prefix.trim_start())
13487 .with_context(|| {
13488 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13489 })
13490 }
13491 })
13492 .collect::<Result<Vec<_>, _>>()
13493 .log_err()
13494 else {
13495 continue;
13496 };
13497
13498 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13499 buffer
13500 .language_settings_at(Point::new(start_row, 0), cx)
13501 .preferred_line_length as usize
13502 });
13503
13504 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13505 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13506 } else {
13507 line_prefix.clone()
13508 };
13509
13510 let wrapped_text = {
13511 let mut wrapped_text = wrap_with_prefix(
13512 line_prefix,
13513 subsequent_lines_prefix,
13514 lines_without_prefixes.join("\n"),
13515 wrap_column,
13516 tab_size,
13517 options.preserve_existing_whitespace,
13518 );
13519
13520 if let Some((indent, delimiter)) = first_line_delimiter {
13521 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13522 }
13523 if let Some(last_line) = last_line_delimiter {
13524 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13525 }
13526
13527 wrapped_text
13528 };
13529
13530 // TODO: should always use char-based diff while still supporting cursor behavior that
13531 // matches vim.
13532 let mut diff_options = DiffOptions::default();
13533 if options.override_language_settings {
13534 diff_options.max_word_diff_len = 0;
13535 diff_options.max_word_diff_line_count = 0;
13536 } else {
13537 diff_options.max_word_diff_len = usize::MAX;
13538 diff_options.max_word_diff_line_count = usize::MAX;
13539 }
13540
13541 for (old_range, new_text) in
13542 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13543 {
13544 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13545 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13546 edits.push((edit_start..edit_end, new_text));
13547 }
13548
13549 rewrapped_row_ranges.push(start_row..=end_row);
13550 }
13551
13552 self.buffer
13553 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13554 }
13555
13556 pub fn cut_common(
13557 &mut self,
13558 cut_no_selection_line: bool,
13559 window: &mut Window,
13560 cx: &mut Context<Self>,
13561 ) -> ClipboardItem {
13562 let mut text = String::new();
13563 let buffer = self.buffer.read(cx).snapshot(cx);
13564 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13565 let mut clipboard_selections = Vec::with_capacity(selections.len());
13566 {
13567 let max_point = buffer.max_point();
13568 let mut is_first = true;
13569 let mut prev_selection_was_entire_line = false;
13570 for selection in &mut selections {
13571 let is_entire_line =
13572 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13573 if is_entire_line {
13574 selection.start = Point::new(selection.start.row, 0);
13575 if !selection.is_empty() && selection.end.column == 0 {
13576 selection.end = cmp::min(max_point, selection.end);
13577 } else {
13578 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13579 }
13580 selection.goal = SelectionGoal::None;
13581 }
13582 if is_first {
13583 is_first = false;
13584 } else if !prev_selection_was_entire_line {
13585 text += "\n";
13586 }
13587 prev_selection_was_entire_line = is_entire_line;
13588 let mut len = 0;
13589 for chunk in buffer.text_for_range(selection.start..selection.end) {
13590 text.push_str(chunk);
13591 len += chunk.len();
13592 }
13593
13594 clipboard_selections.push(ClipboardSelection::for_buffer(
13595 len,
13596 is_entire_line,
13597 selection.range(),
13598 &buffer,
13599 self.project.as_ref(),
13600 cx,
13601 ));
13602 }
13603 }
13604
13605 self.transact(window, cx, |this, window, cx| {
13606 this.change_selections(Default::default(), window, cx, |s| {
13607 s.select(selections);
13608 });
13609 this.insert("", window, cx);
13610 });
13611 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13612 }
13613
13614 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13616 let item = self.cut_common(true, window, cx);
13617 cx.write_to_clipboard(item);
13618 }
13619
13620 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13621 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13622 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13623 s.move_with(&mut |snapshot, sel| {
13624 if sel.is_empty() {
13625 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13626 }
13627 if sel.is_empty() {
13628 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13629 }
13630 });
13631 });
13632 let item = self.cut_common(false, window, cx);
13633 cx.set_global(KillRing(item))
13634 }
13635
13636 pub fn kill_ring_yank(
13637 &mut self,
13638 _: &KillRingYank,
13639 window: &mut Window,
13640 cx: &mut Context<Self>,
13641 ) {
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13643 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13644 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13645 (kill_ring.text().to_string(), kill_ring.metadata_json())
13646 } else {
13647 return;
13648 }
13649 } else {
13650 return;
13651 };
13652 self.do_paste(&text, metadata, false, window, cx);
13653 }
13654
13655 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13656 self.do_copy(true, cx);
13657 }
13658
13659 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13660 self.do_copy(false, cx);
13661 }
13662
13663 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13664 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13665 let buffer = self.buffer.read(cx).read(cx);
13666 let mut text = String::new();
13667 let mut clipboard_selections = Vec::with_capacity(selections.len());
13668
13669 let max_point = buffer.max_point();
13670 let mut is_first = true;
13671 for selection in &selections {
13672 let mut start = selection.start;
13673 let mut end = selection.end;
13674 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13675 let mut add_trailing_newline = false;
13676 if is_entire_line {
13677 start = Point::new(start.row, 0);
13678 let next_line_start = Point::new(end.row + 1, 0);
13679 if next_line_start <= max_point {
13680 end = next_line_start;
13681 } else {
13682 // We're on the last line without a trailing newline.
13683 // Copy to the end of the line and add a newline afterwards.
13684 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13685 add_trailing_newline = true;
13686 }
13687 }
13688
13689 let mut trimmed_selections = Vec::new();
13690 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13691 let row = MultiBufferRow(start.row);
13692 let first_indent = buffer.indent_size_for_line(row);
13693 if first_indent.len == 0 || start.column > first_indent.len {
13694 trimmed_selections.push(start..end);
13695 } else {
13696 trimmed_selections.push(
13697 Point::new(row.0, first_indent.len)
13698 ..Point::new(row.0, buffer.line_len(row)),
13699 );
13700 for row in start.row + 1..=end.row {
13701 let mut line_len = buffer.line_len(MultiBufferRow(row));
13702 if row == end.row {
13703 line_len = end.column;
13704 }
13705 if line_len == 0 {
13706 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13707 continue;
13708 }
13709 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13710 if row_indent_size.len >= first_indent.len {
13711 trimmed_selections
13712 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13713 } else {
13714 trimmed_selections.clear();
13715 trimmed_selections.push(start..end);
13716 break;
13717 }
13718 }
13719 }
13720 } else {
13721 trimmed_selections.push(start..end);
13722 }
13723
13724 let is_multiline_trim = trimmed_selections.len() > 1;
13725 let mut selection_len: usize = 0;
13726 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13727
13728 for trimmed_range in trimmed_selections {
13729 if is_first {
13730 is_first = false;
13731 } else if is_multiline_trim || !prev_selection_was_entire_line {
13732 text.push('\n');
13733 if is_multiline_trim {
13734 selection_len += 1;
13735 }
13736 }
13737 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13738 text.push_str(chunk);
13739 selection_len += chunk.len();
13740 }
13741 if add_trailing_newline {
13742 text.push('\n');
13743 selection_len += 1;
13744 }
13745 }
13746
13747 clipboard_selections.push(ClipboardSelection::for_buffer(
13748 selection_len,
13749 is_entire_line,
13750 start..end,
13751 &buffer,
13752 self.project.as_ref(),
13753 cx,
13754 ));
13755 }
13756
13757 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13758 text,
13759 clipboard_selections,
13760 ));
13761 }
13762
13763 pub fn do_paste(
13764 &mut self,
13765 text: &String,
13766 clipboard_selections: Option<Vec<ClipboardSelection>>,
13767 handle_entire_lines: bool,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 if self.read_only(cx) {
13772 return;
13773 }
13774
13775 let clipboard_text = Cow::Borrowed(text.as_str());
13776
13777 self.transact(window, cx, |this, window, cx| {
13778 let had_active_edit_prediction = this.has_active_edit_prediction();
13779 let display_map = this.display_snapshot(cx);
13780 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13781 let cursor_offset = this
13782 .selections
13783 .last::<MultiBufferOffset>(&display_map)
13784 .head();
13785
13786 if let Some(mut clipboard_selections) = clipboard_selections {
13787 let all_selections_were_entire_line =
13788 clipboard_selections.iter().all(|s| s.is_entire_line);
13789 let first_selection_indent_column =
13790 clipboard_selections.first().map(|s| s.first_line_indent);
13791 if clipboard_selections.len() != old_selections.len() {
13792 clipboard_selections.drain(..);
13793 }
13794 let mut auto_indent_on_paste = true;
13795
13796 this.buffer.update(cx, |buffer, cx| {
13797 let snapshot = buffer.read(cx);
13798 auto_indent_on_paste = snapshot
13799 .language_settings_at(cursor_offset, cx)
13800 .auto_indent_on_paste;
13801
13802 let mut start_offset = 0;
13803 let mut edits = Vec::new();
13804 let mut original_indent_columns = Vec::new();
13805 for (ix, selection) in old_selections.iter().enumerate() {
13806 let to_insert;
13807 let entire_line;
13808 let original_indent_column;
13809 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13810 let end_offset = start_offset + clipboard_selection.len;
13811 to_insert = &clipboard_text[start_offset..end_offset];
13812 entire_line = clipboard_selection.is_entire_line;
13813 start_offset = if entire_line {
13814 end_offset
13815 } else {
13816 end_offset + 1
13817 };
13818 original_indent_column = Some(clipboard_selection.first_line_indent);
13819 } else {
13820 to_insert = &*clipboard_text;
13821 entire_line = all_selections_were_entire_line;
13822 original_indent_column = first_selection_indent_column
13823 }
13824
13825 let (range, to_insert) =
13826 if selection.is_empty() && handle_entire_lines && entire_line {
13827 // If the corresponding selection was empty when this slice of the
13828 // clipboard text was written, then the entire line containing the
13829 // selection was copied. If this selection is also currently empty,
13830 // then paste the line before the current line of the buffer.
13831 let column = selection.start.to_point(&snapshot).column as usize;
13832 let line_start = selection.start - column;
13833 (line_start..line_start, Cow::Borrowed(to_insert))
13834 } else {
13835 let language = snapshot.language_at(selection.head());
13836 let range = selection.range();
13837 if let Some(language) = language
13838 && language.name() == "Markdown"
13839 {
13840 edit_for_markdown_paste(
13841 &snapshot,
13842 range,
13843 to_insert,
13844 url::Url::parse(to_insert).ok(),
13845 )
13846 } else {
13847 (range, Cow::Borrowed(to_insert))
13848 }
13849 };
13850
13851 edits.push((range, to_insert));
13852 original_indent_columns.push(original_indent_column);
13853 }
13854 drop(snapshot);
13855
13856 buffer.edit(
13857 edits,
13858 if auto_indent_on_paste {
13859 Some(AutoindentMode::Block {
13860 original_indent_columns,
13861 })
13862 } else {
13863 None
13864 },
13865 cx,
13866 );
13867 });
13868
13869 let selections = this
13870 .selections
13871 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13872 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13873 } else {
13874 let url = url::Url::parse(&clipboard_text).ok();
13875
13876 let auto_indent_mode = if !clipboard_text.is_empty() {
13877 Some(AutoindentMode::Block {
13878 original_indent_columns: Vec::new(),
13879 })
13880 } else {
13881 None
13882 };
13883
13884 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13885 let snapshot = buffer.snapshot(cx);
13886
13887 let anchors = old_selections
13888 .iter()
13889 .map(|s| {
13890 let anchor = snapshot.anchor_after(s.head());
13891 s.map(|_| anchor)
13892 })
13893 .collect::<Vec<_>>();
13894
13895 let mut edits = Vec::new();
13896
13897 // When pasting text without metadata (e.g. copied from an
13898 // external editor using multiple cursors) and the number of
13899 // lines matches the number of selections, distribute one
13900 // line per cursor instead of pasting the whole text at each.
13901 let lines: Vec<&str> = clipboard_text.split('\n').collect();
13902 let distribute_lines =
13903 old_selections.len() > 1 && lines.len() == old_selections.len();
13904
13905 for (ix, selection) in old_selections.iter().enumerate() {
13906 let language = snapshot.language_at(selection.head());
13907 let range = selection.range();
13908
13909 let text_for_cursor: &str = if distribute_lines {
13910 lines[ix]
13911 } else {
13912 &clipboard_text
13913 };
13914
13915 let (edit_range, edit_text) = if let Some(language) = language
13916 && language.name() == "Markdown"
13917 {
13918 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
13919 } else {
13920 (range, Cow::Borrowed(text_for_cursor))
13921 };
13922
13923 edits.push((edit_range, edit_text));
13924 }
13925
13926 drop(snapshot);
13927 buffer.edit(edits, auto_indent_mode, cx);
13928
13929 anchors
13930 });
13931
13932 this.change_selections(Default::default(), window, cx, |s| {
13933 s.select_anchors(selection_anchors);
13934 });
13935 }
13936
13937 // 🤔 | .. | show_in_menu |
13938 // | .. | true true
13939 // | had_edit_prediction | false true
13940
13941 let trigger_in_words =
13942 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13943
13944 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13945 });
13946 }
13947
13948 pub fn diff_clipboard_with_selection(
13949 &mut self,
13950 _: &DiffClipboardWithSelection,
13951 window: &mut Window,
13952 cx: &mut Context<Self>,
13953 ) {
13954 let selections = self
13955 .selections
13956 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13957
13958 if selections.is_empty() {
13959 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13960 return;
13961 };
13962
13963 let clipboard_text = match cx.read_from_clipboard() {
13964 Some(item) => match item.entries().first() {
13965 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13966 _ => None,
13967 },
13968 None => None,
13969 };
13970
13971 let Some(clipboard_text) = clipboard_text else {
13972 log::warn!("Clipboard doesn't contain text.");
13973 return;
13974 };
13975
13976 window.dispatch_action(
13977 Box::new(DiffClipboardWithSelectionData {
13978 clipboard_text,
13979 editor: cx.entity(),
13980 }),
13981 cx,
13982 );
13983 }
13984
13985 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13987 if let Some(item) = cx.read_from_clipboard() {
13988 let entries = item.entries();
13989
13990 match entries.first() {
13991 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13992 // of all the pasted entries.
13993 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13994 .do_paste(
13995 clipboard_string.text(),
13996 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13997 true,
13998 window,
13999 cx,
14000 ),
14001 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14002 }
14003 }
14004 }
14005
14006 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14007 if self.read_only(cx) {
14008 return;
14009 }
14010
14011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14012
14013 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14014 if let Some((selections, _)) =
14015 self.selection_history.transaction(transaction_id).cloned()
14016 {
14017 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14018 s.select_anchors(selections.to_vec());
14019 });
14020 } else {
14021 log::error!(
14022 "No entry in selection_history found for undo. \
14023 This may correspond to a bug where undo does not update the selection. \
14024 If this is occurring, please add details to \
14025 https://github.com/zed-industries/zed/issues/22692"
14026 );
14027 }
14028 self.request_autoscroll(Autoscroll::fit(), cx);
14029 self.unmark_text(window, cx);
14030 self.refresh_edit_prediction(true, false, window, cx);
14031 cx.emit(EditorEvent::Edited { transaction_id });
14032 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14033 }
14034 }
14035
14036 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14037 if self.read_only(cx) {
14038 return;
14039 }
14040
14041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14042
14043 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14044 if let Some((_, Some(selections))) =
14045 self.selection_history.transaction(transaction_id).cloned()
14046 {
14047 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14048 s.select_anchors(selections.to_vec());
14049 });
14050 } else {
14051 log::error!(
14052 "No entry in selection_history found for redo. \
14053 This may correspond to a bug where undo does not update the selection. \
14054 If this is occurring, please add details to \
14055 https://github.com/zed-industries/zed/issues/22692"
14056 );
14057 }
14058 self.request_autoscroll(Autoscroll::fit(), cx);
14059 self.unmark_text(window, cx);
14060 self.refresh_edit_prediction(true, false, window, cx);
14061 cx.emit(EditorEvent::Edited { transaction_id });
14062 }
14063 }
14064
14065 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14066 self.buffer
14067 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14068 }
14069
14070 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14071 self.buffer
14072 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14073 }
14074
14075 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14077 self.change_selections(Default::default(), window, cx, |s| {
14078 s.move_with(&mut |map, selection| {
14079 let cursor = if selection.is_empty() {
14080 movement::left(map, selection.start)
14081 } else {
14082 selection.start
14083 };
14084 selection.collapse_to(cursor, SelectionGoal::None);
14085 });
14086 })
14087 }
14088
14089 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 self.change_selections(Default::default(), window, cx, |s| {
14092 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14093 })
14094 }
14095
14096 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14098 self.change_selections(Default::default(), window, cx, |s| {
14099 s.move_with(&mut |map, selection| {
14100 let cursor = if selection.is_empty() {
14101 movement::right(map, selection.end)
14102 } else {
14103 selection.end
14104 };
14105 selection.collapse_to(cursor, SelectionGoal::None)
14106 });
14107 })
14108 }
14109
14110 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14112 self.change_selections(Default::default(), window, cx, |s| {
14113 s.move_heads_with(&mut |map, head, _| {
14114 (movement::right(map, head), SelectionGoal::None)
14115 });
14116 });
14117 }
14118
14119 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14120 if self.take_rename(true, window, cx).is_some() {
14121 return;
14122 }
14123
14124 if self.mode.is_single_line() {
14125 cx.propagate();
14126 return;
14127 }
14128
14129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14130
14131 let text_layout_details = &self.text_layout_details(window, cx);
14132 let selection_count = self.selections.count();
14133 let first_selection = self.selections.first_anchor();
14134
14135 self.change_selections(Default::default(), window, cx, |s| {
14136 s.move_with(&mut |map, selection| {
14137 if !selection.is_empty() {
14138 selection.goal = SelectionGoal::None;
14139 }
14140 let (cursor, goal) = movement::up(
14141 map,
14142 selection.start,
14143 selection.goal,
14144 false,
14145 text_layout_details,
14146 );
14147 selection.collapse_to(cursor, goal);
14148 });
14149 });
14150
14151 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14152 {
14153 cx.propagate();
14154 }
14155 }
14156
14157 pub fn move_up_by_lines(
14158 &mut self,
14159 action: &MoveUpByLines,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 if self.take_rename(true, window, cx).is_some() {
14164 return;
14165 }
14166
14167 if self.mode.is_single_line() {
14168 cx.propagate();
14169 return;
14170 }
14171
14172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14173
14174 let text_layout_details = &self.text_layout_details(window, cx);
14175
14176 self.change_selections(Default::default(), window, cx, |s| {
14177 s.move_with(&mut |map, selection| {
14178 if !selection.is_empty() {
14179 selection.goal = SelectionGoal::None;
14180 }
14181 let (cursor, goal) = movement::up_by_rows(
14182 map,
14183 selection.start,
14184 action.lines,
14185 selection.goal,
14186 false,
14187 text_layout_details,
14188 );
14189 selection.collapse_to(cursor, goal);
14190 });
14191 })
14192 }
14193
14194 pub fn move_down_by_lines(
14195 &mut self,
14196 action: &MoveDownByLines,
14197 window: &mut Window,
14198 cx: &mut Context<Self>,
14199 ) {
14200 if self.take_rename(true, window, cx).is_some() {
14201 return;
14202 }
14203
14204 if self.mode.is_single_line() {
14205 cx.propagate();
14206 return;
14207 }
14208
14209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14210
14211 let text_layout_details = &self.text_layout_details(window, cx);
14212
14213 self.change_selections(Default::default(), window, cx, |s| {
14214 s.move_with(&mut |map, selection| {
14215 if !selection.is_empty() {
14216 selection.goal = SelectionGoal::None;
14217 }
14218 let (cursor, goal) = movement::down_by_rows(
14219 map,
14220 selection.start,
14221 action.lines,
14222 selection.goal,
14223 false,
14224 text_layout_details,
14225 );
14226 selection.collapse_to(cursor, goal);
14227 });
14228 })
14229 }
14230
14231 pub fn select_down_by_lines(
14232 &mut self,
14233 action: &SelectDownByLines,
14234 window: &mut Window,
14235 cx: &mut Context<Self>,
14236 ) {
14237 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14238 let text_layout_details = &self.text_layout_details(window, cx);
14239 self.change_selections(Default::default(), window, cx, |s| {
14240 s.move_heads_with(&mut |map, head, goal| {
14241 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14242 })
14243 })
14244 }
14245
14246 pub fn select_up_by_lines(
14247 &mut self,
14248 action: &SelectUpByLines,
14249 window: &mut Window,
14250 cx: &mut Context<Self>,
14251 ) {
14252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14253 let text_layout_details = &self.text_layout_details(window, cx);
14254 self.change_selections(Default::default(), window, cx, |s| {
14255 s.move_heads_with(&mut |map, head, goal| {
14256 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14257 })
14258 })
14259 }
14260
14261 pub fn select_page_up(
14262 &mut self,
14263 _: &SelectPageUp,
14264 window: &mut Window,
14265 cx: &mut Context<Self>,
14266 ) {
14267 let Some(row_count) = self.visible_row_count() else {
14268 return;
14269 };
14270
14271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14272
14273 let text_layout_details = &self.text_layout_details(window, cx);
14274
14275 self.change_selections(Default::default(), window, cx, |s| {
14276 s.move_heads_with(&mut |map, head, goal| {
14277 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14278 })
14279 })
14280 }
14281
14282 pub fn move_page_up(
14283 &mut self,
14284 action: &MovePageUp,
14285 window: &mut Window,
14286 cx: &mut Context<Self>,
14287 ) {
14288 if self.take_rename(true, window, cx).is_some() {
14289 return;
14290 }
14291
14292 if self
14293 .context_menu
14294 .borrow_mut()
14295 .as_mut()
14296 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14297 .unwrap_or(false)
14298 {
14299 return;
14300 }
14301
14302 if matches!(self.mode, EditorMode::SingleLine) {
14303 cx.propagate();
14304 return;
14305 }
14306
14307 let Some(row_count) = self.visible_row_count() else {
14308 return;
14309 };
14310
14311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14312
14313 let effects = if action.center_cursor {
14314 SelectionEffects::scroll(Autoscroll::center())
14315 } else {
14316 SelectionEffects::default()
14317 };
14318
14319 let text_layout_details = &self.text_layout_details(window, cx);
14320
14321 self.change_selections(effects, window, cx, |s| {
14322 s.move_with(&mut |map, selection| {
14323 if !selection.is_empty() {
14324 selection.goal = SelectionGoal::None;
14325 }
14326 let (cursor, goal) = movement::up_by_rows(
14327 map,
14328 selection.end,
14329 row_count,
14330 selection.goal,
14331 false,
14332 text_layout_details,
14333 );
14334 selection.collapse_to(cursor, goal);
14335 });
14336 });
14337 }
14338
14339 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341 let text_layout_details = &self.text_layout_details(window, cx);
14342 self.change_selections(Default::default(), window, cx, |s| {
14343 s.move_heads_with(&mut |map, head, goal| {
14344 movement::up(map, head, goal, false, text_layout_details)
14345 })
14346 })
14347 }
14348
14349 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14350 self.take_rename(true, window, cx);
14351
14352 if self.mode.is_single_line() {
14353 cx.propagate();
14354 return;
14355 }
14356
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358
14359 let text_layout_details = &self.text_layout_details(window, cx);
14360 let selection_count = self.selections.count();
14361 let first_selection = self.selections.first_anchor();
14362
14363 self.change_selections(Default::default(), window, cx, |s| {
14364 s.move_with(&mut |map, selection| {
14365 if !selection.is_empty() {
14366 selection.goal = SelectionGoal::None;
14367 }
14368 let (cursor, goal) = movement::down(
14369 map,
14370 selection.end,
14371 selection.goal,
14372 false,
14373 text_layout_details,
14374 );
14375 selection.collapse_to(cursor, goal);
14376 });
14377 });
14378
14379 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14380 {
14381 cx.propagate();
14382 }
14383 }
14384
14385 pub fn select_page_down(
14386 &mut self,
14387 _: &SelectPageDown,
14388 window: &mut Window,
14389 cx: &mut Context<Self>,
14390 ) {
14391 let Some(row_count) = self.visible_row_count() else {
14392 return;
14393 };
14394
14395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14396
14397 let text_layout_details = &self.text_layout_details(window, cx);
14398
14399 self.change_selections(Default::default(), window, cx, |s| {
14400 s.move_heads_with(&mut |map, head, goal| {
14401 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14402 })
14403 })
14404 }
14405
14406 pub fn move_page_down(
14407 &mut self,
14408 action: &MovePageDown,
14409 window: &mut Window,
14410 cx: &mut Context<Self>,
14411 ) {
14412 if self.take_rename(true, window, cx).is_some() {
14413 return;
14414 }
14415
14416 if self
14417 .context_menu
14418 .borrow_mut()
14419 .as_mut()
14420 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14421 .unwrap_or(false)
14422 {
14423 return;
14424 }
14425
14426 if matches!(self.mode, EditorMode::SingleLine) {
14427 cx.propagate();
14428 return;
14429 }
14430
14431 let Some(row_count) = self.visible_row_count() else {
14432 return;
14433 };
14434
14435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14436
14437 let effects = if action.center_cursor {
14438 SelectionEffects::scroll(Autoscroll::center())
14439 } else {
14440 SelectionEffects::default()
14441 };
14442
14443 let text_layout_details = &self.text_layout_details(window, cx);
14444 self.change_selections(effects, window, cx, |s| {
14445 s.move_with(&mut |map, selection| {
14446 if !selection.is_empty() {
14447 selection.goal = SelectionGoal::None;
14448 }
14449 let (cursor, goal) = movement::down_by_rows(
14450 map,
14451 selection.end,
14452 row_count,
14453 selection.goal,
14454 false,
14455 text_layout_details,
14456 );
14457 selection.collapse_to(cursor, goal);
14458 });
14459 });
14460 }
14461
14462 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14464 let text_layout_details = &self.text_layout_details(window, cx);
14465 self.change_selections(Default::default(), window, cx, |s| {
14466 s.move_heads_with(&mut |map, head, goal| {
14467 movement::down(map, head, goal, false, text_layout_details)
14468 })
14469 });
14470 }
14471
14472 pub fn context_menu_first(
14473 &mut self,
14474 _: &ContextMenuFirst,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) {
14478 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14479 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14480 }
14481 }
14482
14483 pub fn context_menu_prev(
14484 &mut self,
14485 _: &ContextMenuPrevious,
14486 window: &mut Window,
14487 cx: &mut Context<Self>,
14488 ) {
14489 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14490 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14491 }
14492 }
14493
14494 pub fn context_menu_next(
14495 &mut self,
14496 _: &ContextMenuNext,
14497 window: &mut Window,
14498 cx: &mut Context<Self>,
14499 ) {
14500 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14501 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14502 }
14503 }
14504
14505 pub fn context_menu_last(
14506 &mut self,
14507 _: &ContextMenuLast,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) {
14511 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14512 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14513 }
14514 }
14515
14516 pub fn signature_help_prev(
14517 &mut self,
14518 _: &SignatureHelpPrevious,
14519 _: &mut Window,
14520 cx: &mut Context<Self>,
14521 ) {
14522 if let Some(popover) = self.signature_help_state.popover_mut() {
14523 if popover.current_signature == 0 {
14524 popover.current_signature = popover.signatures.len() - 1;
14525 } else {
14526 popover.current_signature -= 1;
14527 }
14528 cx.notify();
14529 }
14530 }
14531
14532 pub fn signature_help_next(
14533 &mut self,
14534 _: &SignatureHelpNext,
14535 _: &mut Window,
14536 cx: &mut Context<Self>,
14537 ) {
14538 if let Some(popover) = self.signature_help_state.popover_mut() {
14539 if popover.current_signature + 1 == popover.signatures.len() {
14540 popover.current_signature = 0;
14541 } else {
14542 popover.current_signature += 1;
14543 }
14544 cx.notify();
14545 }
14546 }
14547
14548 pub fn move_to_previous_word_start(
14549 &mut self,
14550 _: &MoveToPreviousWordStart,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) {
14554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14555 self.change_selections(Default::default(), window, cx, |s| {
14556 s.move_cursors_with(&mut |map, head, _| {
14557 (
14558 movement::previous_word_start(map, head),
14559 SelectionGoal::None,
14560 )
14561 });
14562 })
14563 }
14564
14565 pub fn move_to_previous_subword_start(
14566 &mut self,
14567 _: &MoveToPreviousSubwordStart,
14568 window: &mut Window,
14569 cx: &mut Context<Self>,
14570 ) {
14571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14572 self.change_selections(Default::default(), window, cx, |s| {
14573 s.move_cursors_with(&mut |map, head, _| {
14574 (
14575 movement::previous_subword_start(map, head),
14576 SelectionGoal::None,
14577 )
14578 });
14579 })
14580 }
14581
14582 pub fn select_to_previous_word_start(
14583 &mut self,
14584 _: &SelectToPreviousWordStart,
14585 window: &mut Window,
14586 cx: &mut Context<Self>,
14587 ) {
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14589 self.change_selections(Default::default(), window, cx, |s| {
14590 s.move_heads_with(&mut |map, head, _| {
14591 (
14592 movement::previous_word_start(map, head),
14593 SelectionGoal::None,
14594 )
14595 });
14596 })
14597 }
14598
14599 pub fn select_to_previous_subword_start(
14600 &mut self,
14601 _: &SelectToPreviousSubwordStart,
14602 window: &mut Window,
14603 cx: &mut Context<Self>,
14604 ) {
14605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14606 self.change_selections(Default::default(), window, cx, |s| {
14607 s.move_heads_with(&mut |map, head, _| {
14608 (
14609 movement::previous_subword_start(map, head),
14610 SelectionGoal::None,
14611 )
14612 });
14613 })
14614 }
14615
14616 pub fn delete_to_previous_word_start(
14617 &mut self,
14618 action: &DeleteToPreviousWordStart,
14619 window: &mut Window,
14620 cx: &mut Context<Self>,
14621 ) {
14622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14623 self.transact(window, cx, |this, window, cx| {
14624 this.select_autoclose_pair(window, cx);
14625 this.change_selections(Default::default(), window, cx, |s| {
14626 s.move_with(&mut |map, selection| {
14627 if selection.is_empty() {
14628 let mut cursor = if action.ignore_newlines {
14629 movement::previous_word_start(map, selection.head())
14630 } else {
14631 movement::previous_word_start_or_newline(map, selection.head())
14632 };
14633 cursor = movement::adjust_greedy_deletion(
14634 map,
14635 selection.head(),
14636 cursor,
14637 action.ignore_brackets,
14638 );
14639 selection.set_head(cursor, SelectionGoal::None);
14640 }
14641 });
14642 });
14643 this.insert("", window, cx);
14644 });
14645 }
14646
14647 pub fn delete_to_previous_subword_start(
14648 &mut self,
14649 action: &DeleteToPreviousSubwordStart,
14650 window: &mut Window,
14651 cx: &mut Context<Self>,
14652 ) {
14653 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14654 self.transact(window, cx, |this, window, cx| {
14655 this.select_autoclose_pair(window, cx);
14656 this.change_selections(Default::default(), window, cx, |s| {
14657 s.move_with(&mut |map, selection| {
14658 if selection.is_empty() {
14659 let mut cursor = if action.ignore_newlines {
14660 movement::previous_subword_start(map, selection.head())
14661 } else {
14662 movement::previous_subword_start_or_newline(map, selection.head())
14663 };
14664 cursor = movement::adjust_greedy_deletion(
14665 map,
14666 selection.head(),
14667 cursor,
14668 action.ignore_brackets,
14669 );
14670 selection.set_head(cursor, SelectionGoal::None);
14671 }
14672 });
14673 });
14674 this.insert("", window, cx);
14675 });
14676 }
14677
14678 pub fn move_to_next_word_end(
14679 &mut self,
14680 _: &MoveToNextWordEnd,
14681 window: &mut Window,
14682 cx: &mut Context<Self>,
14683 ) {
14684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14685 self.change_selections(Default::default(), window, cx, |s| {
14686 s.move_cursors_with(&mut |map, head, _| {
14687 (movement::next_word_end(map, head), SelectionGoal::None)
14688 });
14689 })
14690 }
14691
14692 pub fn move_to_next_subword_end(
14693 &mut self,
14694 _: &MoveToNextSubwordEnd,
14695 window: &mut Window,
14696 cx: &mut Context<Self>,
14697 ) {
14698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14699 self.change_selections(Default::default(), window, cx, |s| {
14700 s.move_cursors_with(&mut |map, head, _| {
14701 (movement::next_subword_end(map, head), SelectionGoal::None)
14702 });
14703 })
14704 }
14705
14706 pub fn select_to_next_word_end(
14707 &mut self,
14708 _: &SelectToNextWordEnd,
14709 window: &mut Window,
14710 cx: &mut Context<Self>,
14711 ) {
14712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14713 self.change_selections(Default::default(), window, cx, |s| {
14714 s.move_heads_with(&mut |map, head, _| {
14715 (movement::next_word_end(map, head), SelectionGoal::None)
14716 });
14717 })
14718 }
14719
14720 pub fn select_to_next_subword_end(
14721 &mut self,
14722 _: &SelectToNextSubwordEnd,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) {
14726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14727 self.change_selections(Default::default(), window, cx, |s| {
14728 s.move_heads_with(&mut |map, head, _| {
14729 (movement::next_subword_end(map, head), SelectionGoal::None)
14730 });
14731 })
14732 }
14733
14734 pub fn delete_to_next_word_end(
14735 &mut self,
14736 action: &DeleteToNextWordEnd,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) {
14740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14741 self.transact(window, cx, |this, window, cx| {
14742 this.change_selections(Default::default(), window, cx, |s| {
14743 s.move_with(&mut |map, selection| {
14744 if selection.is_empty() {
14745 let mut cursor = if action.ignore_newlines {
14746 movement::next_word_end(map, selection.head())
14747 } else {
14748 movement::next_word_end_or_newline(map, selection.head())
14749 };
14750 cursor = movement::adjust_greedy_deletion(
14751 map,
14752 selection.head(),
14753 cursor,
14754 action.ignore_brackets,
14755 );
14756 selection.set_head(cursor, SelectionGoal::None);
14757 }
14758 });
14759 });
14760 this.insert("", window, cx);
14761 });
14762 }
14763
14764 pub fn delete_to_next_subword_end(
14765 &mut self,
14766 action: &DeleteToNextSubwordEnd,
14767 window: &mut Window,
14768 cx: &mut Context<Self>,
14769 ) {
14770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14771 self.transact(window, cx, |this, window, cx| {
14772 this.change_selections(Default::default(), window, cx, |s| {
14773 s.move_with(&mut |map, selection| {
14774 if selection.is_empty() {
14775 let mut cursor = if action.ignore_newlines {
14776 movement::next_subword_end(map, selection.head())
14777 } else {
14778 movement::next_subword_end_or_newline(map, selection.head())
14779 };
14780 cursor = movement::adjust_greedy_deletion(
14781 map,
14782 selection.head(),
14783 cursor,
14784 action.ignore_brackets,
14785 );
14786 selection.set_head(cursor, SelectionGoal::None);
14787 }
14788 });
14789 });
14790 this.insert("", window, cx);
14791 });
14792 }
14793
14794 pub fn move_to_beginning_of_line(
14795 &mut self,
14796 action: &MoveToBeginningOfLine,
14797 window: &mut Window,
14798 cx: &mut Context<Self>,
14799 ) {
14800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14801 self.change_selections(Default::default(), window, cx, |s| {
14802 s.move_cursors_with(&mut |map, head, _| {
14803 (
14804 movement::indented_line_beginning(
14805 map,
14806 head,
14807 action.stop_at_soft_wraps,
14808 action.stop_at_indent,
14809 ),
14810 SelectionGoal::None,
14811 )
14812 });
14813 })
14814 }
14815
14816 pub fn select_to_beginning_of_line(
14817 &mut self,
14818 action: &SelectToBeginningOfLine,
14819 window: &mut Window,
14820 cx: &mut Context<Self>,
14821 ) {
14822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14823 self.change_selections(Default::default(), window, cx, |s| {
14824 s.move_heads_with(&mut |map, head, _| {
14825 (
14826 movement::indented_line_beginning(
14827 map,
14828 head,
14829 action.stop_at_soft_wraps,
14830 action.stop_at_indent,
14831 ),
14832 SelectionGoal::None,
14833 )
14834 });
14835 });
14836 }
14837
14838 pub fn delete_to_beginning_of_line(
14839 &mut self,
14840 action: &DeleteToBeginningOfLine,
14841 window: &mut Window,
14842 cx: &mut Context<Self>,
14843 ) {
14844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14845 self.transact(window, cx, |this, window, cx| {
14846 this.change_selections(Default::default(), window, cx, |s| {
14847 s.move_with(&mut |_, selection| {
14848 selection.reversed = true;
14849 });
14850 });
14851
14852 this.select_to_beginning_of_line(
14853 &SelectToBeginningOfLine {
14854 stop_at_soft_wraps: false,
14855 stop_at_indent: action.stop_at_indent,
14856 },
14857 window,
14858 cx,
14859 );
14860 this.backspace(&Backspace, window, cx);
14861 });
14862 }
14863
14864 pub fn move_to_end_of_line(
14865 &mut self,
14866 action: &MoveToEndOfLine,
14867 window: &mut Window,
14868 cx: &mut Context<Self>,
14869 ) {
14870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14871 self.change_selections(Default::default(), window, cx, |s| {
14872 s.move_cursors_with(&mut |map, head, _| {
14873 (
14874 movement::line_end(map, head, action.stop_at_soft_wraps),
14875 SelectionGoal::None,
14876 )
14877 });
14878 })
14879 }
14880
14881 pub fn select_to_end_of_line(
14882 &mut self,
14883 action: &SelectToEndOfLine,
14884 window: &mut Window,
14885 cx: &mut Context<Self>,
14886 ) {
14887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14888 self.change_selections(Default::default(), window, cx, |s| {
14889 s.move_heads_with(&mut |map, head, _| {
14890 (
14891 movement::line_end(map, head, action.stop_at_soft_wraps),
14892 SelectionGoal::None,
14893 )
14894 });
14895 })
14896 }
14897
14898 pub fn delete_to_end_of_line(
14899 &mut self,
14900 _: &DeleteToEndOfLine,
14901 window: &mut Window,
14902 cx: &mut Context<Self>,
14903 ) {
14904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14905 self.transact(window, cx, |this, window, cx| {
14906 this.select_to_end_of_line(
14907 &SelectToEndOfLine {
14908 stop_at_soft_wraps: false,
14909 },
14910 window,
14911 cx,
14912 );
14913 this.delete(&Delete, window, cx);
14914 });
14915 }
14916
14917 pub fn cut_to_end_of_line(
14918 &mut self,
14919 action: &CutToEndOfLine,
14920 window: &mut Window,
14921 cx: &mut Context<Self>,
14922 ) {
14923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14924 self.transact(window, cx, |this, window, cx| {
14925 this.select_to_end_of_line(
14926 &SelectToEndOfLine {
14927 stop_at_soft_wraps: false,
14928 },
14929 window,
14930 cx,
14931 );
14932 if !action.stop_at_newlines {
14933 this.change_selections(Default::default(), window, cx, |s| {
14934 s.move_with(&mut |_, sel| {
14935 if sel.is_empty() {
14936 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14937 }
14938 });
14939 });
14940 }
14941 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14942 let item = this.cut_common(false, window, cx);
14943 cx.write_to_clipboard(item);
14944 });
14945 }
14946
14947 pub fn move_to_start_of_paragraph(
14948 &mut self,
14949 _: &MoveToStartOfParagraph,
14950 window: &mut Window,
14951 cx: &mut Context<Self>,
14952 ) {
14953 if matches!(self.mode, EditorMode::SingleLine) {
14954 cx.propagate();
14955 return;
14956 }
14957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14958 self.change_selections(Default::default(), window, cx, |s| {
14959 s.move_with(&mut |map, selection| {
14960 selection.collapse_to(
14961 movement::start_of_paragraph(map, selection.head(), 1),
14962 SelectionGoal::None,
14963 )
14964 });
14965 })
14966 }
14967
14968 pub fn move_to_end_of_paragraph(
14969 &mut self,
14970 _: &MoveToEndOfParagraph,
14971 window: &mut Window,
14972 cx: &mut Context<Self>,
14973 ) {
14974 if matches!(self.mode, EditorMode::SingleLine) {
14975 cx.propagate();
14976 return;
14977 }
14978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14979 self.change_selections(Default::default(), window, cx, |s| {
14980 s.move_with(&mut |map, selection| {
14981 selection.collapse_to(
14982 movement::end_of_paragraph(map, selection.head(), 1),
14983 SelectionGoal::None,
14984 )
14985 });
14986 })
14987 }
14988
14989 pub fn select_to_start_of_paragraph(
14990 &mut self,
14991 _: &SelectToStartOfParagraph,
14992 window: &mut Window,
14993 cx: &mut Context<Self>,
14994 ) {
14995 if matches!(self.mode, EditorMode::SingleLine) {
14996 cx.propagate();
14997 return;
14998 }
14999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15000 self.change_selections(Default::default(), window, cx, |s| {
15001 s.move_heads_with(&mut |map, head, _| {
15002 (
15003 movement::start_of_paragraph(map, head, 1),
15004 SelectionGoal::None,
15005 )
15006 });
15007 })
15008 }
15009
15010 pub fn select_to_end_of_paragraph(
15011 &mut self,
15012 _: &SelectToEndOfParagraph,
15013 window: &mut Window,
15014 cx: &mut Context<Self>,
15015 ) {
15016 if matches!(self.mode, EditorMode::SingleLine) {
15017 cx.propagate();
15018 return;
15019 }
15020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15021 self.change_selections(Default::default(), window, cx, |s| {
15022 s.move_heads_with(&mut |map, head, _| {
15023 (
15024 movement::end_of_paragraph(map, head, 1),
15025 SelectionGoal::None,
15026 )
15027 });
15028 })
15029 }
15030
15031 pub fn move_to_start_of_excerpt(
15032 &mut self,
15033 _: &MoveToStartOfExcerpt,
15034 window: &mut Window,
15035 cx: &mut Context<Self>,
15036 ) {
15037 if matches!(self.mode, EditorMode::SingleLine) {
15038 cx.propagate();
15039 return;
15040 }
15041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15042 self.change_selections(Default::default(), window, cx, |s| {
15043 s.move_with(&mut |map, selection| {
15044 selection.collapse_to(
15045 movement::start_of_excerpt(
15046 map,
15047 selection.head(),
15048 workspace::searchable::Direction::Prev,
15049 ),
15050 SelectionGoal::None,
15051 )
15052 });
15053 })
15054 }
15055
15056 pub fn move_to_start_of_next_excerpt(
15057 &mut self,
15058 _: &MoveToStartOfNextExcerpt,
15059 window: &mut Window,
15060 cx: &mut Context<Self>,
15061 ) {
15062 if matches!(self.mode, EditorMode::SingleLine) {
15063 cx.propagate();
15064 return;
15065 }
15066
15067 self.change_selections(Default::default(), window, cx, |s| {
15068 s.move_with(&mut |map, selection| {
15069 selection.collapse_to(
15070 movement::start_of_excerpt(
15071 map,
15072 selection.head(),
15073 workspace::searchable::Direction::Next,
15074 ),
15075 SelectionGoal::None,
15076 )
15077 });
15078 })
15079 }
15080
15081 pub fn move_to_end_of_excerpt(
15082 &mut self,
15083 _: &MoveToEndOfExcerpt,
15084 window: &mut Window,
15085 cx: &mut Context<Self>,
15086 ) {
15087 if matches!(self.mode, EditorMode::SingleLine) {
15088 cx.propagate();
15089 return;
15090 }
15091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15092 self.change_selections(Default::default(), window, cx, |s| {
15093 s.move_with(&mut |map, selection| {
15094 selection.collapse_to(
15095 movement::end_of_excerpt(
15096 map,
15097 selection.head(),
15098 workspace::searchable::Direction::Next,
15099 ),
15100 SelectionGoal::None,
15101 )
15102 });
15103 })
15104 }
15105
15106 pub fn move_to_end_of_previous_excerpt(
15107 &mut self,
15108 _: &MoveToEndOfPreviousExcerpt,
15109 window: &mut Window,
15110 cx: &mut Context<Self>,
15111 ) {
15112 if matches!(self.mode, EditorMode::SingleLine) {
15113 cx.propagate();
15114 return;
15115 }
15116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15117 self.change_selections(Default::default(), window, cx, |s| {
15118 s.move_with(&mut |map, selection| {
15119 selection.collapse_to(
15120 movement::end_of_excerpt(
15121 map,
15122 selection.head(),
15123 workspace::searchable::Direction::Prev,
15124 ),
15125 SelectionGoal::None,
15126 )
15127 });
15128 })
15129 }
15130
15131 pub fn select_to_start_of_excerpt(
15132 &mut self,
15133 _: &SelectToStartOfExcerpt,
15134 window: &mut Window,
15135 cx: &mut Context<Self>,
15136 ) {
15137 if matches!(self.mode, EditorMode::SingleLine) {
15138 cx.propagate();
15139 return;
15140 }
15141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15142 self.change_selections(Default::default(), window, cx, |s| {
15143 s.move_heads_with(&mut |map, head, _| {
15144 (
15145 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15146 SelectionGoal::None,
15147 )
15148 });
15149 })
15150 }
15151
15152 pub fn select_to_start_of_next_excerpt(
15153 &mut self,
15154 _: &SelectToStartOfNextExcerpt,
15155 window: &mut Window,
15156 cx: &mut Context<Self>,
15157 ) {
15158 if matches!(self.mode, EditorMode::SingleLine) {
15159 cx.propagate();
15160 return;
15161 }
15162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15163 self.change_selections(Default::default(), window, cx, |s| {
15164 s.move_heads_with(&mut |map, head, _| {
15165 (
15166 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15167 SelectionGoal::None,
15168 )
15169 });
15170 })
15171 }
15172
15173 pub fn select_to_end_of_excerpt(
15174 &mut self,
15175 _: &SelectToEndOfExcerpt,
15176 window: &mut Window,
15177 cx: &mut Context<Self>,
15178 ) {
15179 if matches!(self.mode, EditorMode::SingleLine) {
15180 cx.propagate();
15181 return;
15182 }
15183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15184 self.change_selections(Default::default(), window, cx, |s| {
15185 s.move_heads_with(&mut |map, head, _| {
15186 (
15187 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15188 SelectionGoal::None,
15189 )
15190 });
15191 })
15192 }
15193
15194 pub fn select_to_end_of_previous_excerpt(
15195 &mut self,
15196 _: &SelectToEndOfPreviousExcerpt,
15197 window: &mut Window,
15198 cx: &mut Context<Self>,
15199 ) {
15200 if matches!(self.mode, EditorMode::SingleLine) {
15201 cx.propagate();
15202 return;
15203 }
15204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15205 self.change_selections(Default::default(), window, cx, |s| {
15206 s.move_heads_with(&mut |map, head, _| {
15207 (
15208 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15209 SelectionGoal::None,
15210 )
15211 });
15212 })
15213 }
15214
15215 pub fn move_to_beginning(
15216 &mut self,
15217 _: &MoveToBeginning,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) {
15221 if matches!(self.mode, EditorMode::SingleLine) {
15222 cx.propagate();
15223 return;
15224 }
15225 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15226 self.change_selections(Default::default(), window, cx, |s| {
15227 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15228 });
15229 }
15230
15231 pub fn select_to_beginning(
15232 &mut self,
15233 _: &SelectToBeginning,
15234 window: &mut Window,
15235 cx: &mut Context<Self>,
15236 ) {
15237 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15238 selection.set_head(Point::zero(), SelectionGoal::None);
15239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15240 self.change_selections(Default::default(), window, cx, |s| {
15241 s.select(vec![selection]);
15242 });
15243 }
15244
15245 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15246 if matches!(self.mode, EditorMode::SingleLine) {
15247 cx.propagate();
15248 return;
15249 }
15250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15251 let cursor = self.buffer.read(cx).read(cx).len();
15252 self.change_selections(Default::default(), window, cx, |s| {
15253 s.select_ranges(vec![cursor..cursor])
15254 });
15255 }
15256
15257 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15258 self.nav_history = nav_history;
15259 }
15260
15261 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15262 self.nav_history.as_ref()
15263 }
15264
15265 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15266 self.push_to_nav_history(
15267 self.selections.newest_anchor().head(),
15268 None,
15269 false,
15270 true,
15271 cx,
15272 );
15273 }
15274
15275 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15276 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15277 let buffer = self.buffer.read(cx).read(cx);
15278 let cursor_position = cursor_anchor.to_point(&buffer);
15279 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15280 let scroll_top_row = scroll_anchor.top_row(&buffer);
15281 drop(buffer);
15282
15283 NavigationData {
15284 cursor_anchor,
15285 cursor_position,
15286 scroll_anchor,
15287 scroll_top_row,
15288 }
15289 }
15290
15291 fn navigation_entry(
15292 &self,
15293 cursor_anchor: Anchor,
15294 cx: &mut Context<Self>,
15295 ) -> Option<NavigationEntry> {
15296 let Some(history) = self.nav_history.clone() else {
15297 return None;
15298 };
15299 let data = self.navigation_data(cursor_anchor, cx);
15300 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15301 }
15302
15303 fn push_to_nav_history(
15304 &mut self,
15305 cursor_anchor: Anchor,
15306 new_position: Option<Point>,
15307 is_deactivate: bool,
15308 always: bool,
15309 cx: &mut Context<Self>,
15310 ) {
15311 let data = self.navigation_data(cursor_anchor, cx);
15312 if let Some(nav_history) = self.nav_history.as_mut() {
15313 if let Some(new_position) = new_position {
15314 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15315 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15316 return;
15317 }
15318 }
15319
15320 nav_history.push(Some(data), cx);
15321 cx.emit(EditorEvent::PushedToNavHistory {
15322 anchor: cursor_anchor,
15323 is_deactivate,
15324 })
15325 }
15326 }
15327
15328 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15330 let buffer = self.buffer.read(cx).snapshot(cx);
15331 let mut selection = self
15332 .selections
15333 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15334 selection.set_head(buffer.len(), SelectionGoal::None);
15335 self.change_selections(Default::default(), window, cx, |s| {
15336 s.select(vec![selection]);
15337 });
15338 }
15339
15340 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15342 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15343 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
15344 });
15345 }
15346
15347 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15349 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15350 let mut selections = self.selections.all::<Point>(&display_map);
15351 let max_point = display_map.buffer_snapshot().max_point();
15352 for selection in &mut selections {
15353 let rows = selection.spanned_rows(true, &display_map);
15354 selection.start = Point::new(rows.start.0, 0);
15355 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15356 selection.reversed = false;
15357 }
15358 self.change_selections(Default::default(), window, cx, |s| {
15359 s.select(selections);
15360 });
15361 }
15362
15363 pub fn split_selection_into_lines(
15364 &mut self,
15365 action: &SplitSelectionIntoLines,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 let selections = self
15370 .selections
15371 .all::<Point>(&self.display_snapshot(cx))
15372 .into_iter()
15373 .map(|selection| selection.start..selection.end)
15374 .collect::<Vec<_>>();
15375 self.unfold_ranges(&selections, true, true, cx);
15376
15377 let mut new_selection_ranges = Vec::new();
15378 {
15379 let buffer = self.buffer.read(cx).read(cx);
15380 for selection in selections {
15381 for row in selection.start.row..selection.end.row {
15382 let line_start = Point::new(row, 0);
15383 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15384
15385 if action.keep_selections {
15386 // Keep the selection range for each line
15387 let selection_start = if row == selection.start.row {
15388 selection.start
15389 } else {
15390 line_start
15391 };
15392 new_selection_ranges.push(selection_start..line_end);
15393 } else {
15394 // Collapse to cursor at end of line
15395 new_selection_ranges.push(line_end..line_end);
15396 }
15397 }
15398
15399 let is_multiline_selection = selection.start.row != selection.end.row;
15400 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15401 // so this action feels more ergonomic when paired with other selection operations
15402 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15403 if !should_skip_last {
15404 if action.keep_selections {
15405 if is_multiline_selection {
15406 let line_start = Point::new(selection.end.row, 0);
15407 new_selection_ranges.push(line_start..selection.end);
15408 } else {
15409 new_selection_ranges.push(selection.start..selection.end);
15410 }
15411 } else {
15412 new_selection_ranges.push(selection.end..selection.end);
15413 }
15414 }
15415 }
15416 }
15417 self.change_selections(Default::default(), window, cx, |s| {
15418 s.select_ranges(new_selection_ranges);
15419 });
15420 }
15421
15422 pub fn add_selection_above(
15423 &mut self,
15424 action: &AddSelectionAbove,
15425 window: &mut Window,
15426 cx: &mut Context<Self>,
15427 ) {
15428 self.add_selection(true, action.skip_soft_wrap, window, cx);
15429 }
15430
15431 pub fn add_selection_below(
15432 &mut self,
15433 action: &AddSelectionBelow,
15434 window: &mut Window,
15435 cx: &mut Context<Self>,
15436 ) {
15437 self.add_selection(false, action.skip_soft_wrap, window, cx);
15438 }
15439
15440 fn add_selection(
15441 &mut self,
15442 above: bool,
15443 skip_soft_wrap: bool,
15444 window: &mut Window,
15445 cx: &mut Context<Self>,
15446 ) {
15447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15448
15449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15450 let all_selections = self.selections.all::<Point>(&display_map);
15451 let text_layout_details = self.text_layout_details(window, cx);
15452
15453 let (mut columnar_selections, new_selections_to_columnarize) = {
15454 if let Some(state) = self.add_selections_state.as_ref() {
15455 let columnar_selection_ids: HashSet<_> = state
15456 .groups
15457 .iter()
15458 .flat_map(|group| group.stack.iter())
15459 .copied()
15460 .collect();
15461
15462 all_selections
15463 .into_iter()
15464 .partition(|s| columnar_selection_ids.contains(&s.id))
15465 } else {
15466 (Vec::new(), all_selections)
15467 }
15468 };
15469
15470 let mut state = self
15471 .add_selections_state
15472 .take()
15473 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15474
15475 for selection in new_selections_to_columnarize {
15476 let range = selection.display_range(&display_map).sorted();
15477 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15478 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15479 let positions = start_x.min(end_x)..start_x.max(end_x);
15480 let mut stack = Vec::new();
15481 for row in range.start.row().0..=range.end.row().0 {
15482 if let Some(selection) = self.selections.build_columnar_selection(
15483 &display_map,
15484 DisplayRow(row),
15485 &positions,
15486 selection.reversed,
15487 &text_layout_details,
15488 ) {
15489 stack.push(selection.id);
15490 columnar_selections.push(selection);
15491 }
15492 }
15493 if !stack.is_empty() {
15494 if above {
15495 stack.reverse();
15496 }
15497 state.groups.push(AddSelectionsGroup { above, stack });
15498 }
15499 }
15500
15501 let mut final_selections = Vec::new();
15502 let end_row = if above {
15503 DisplayRow(0)
15504 } else {
15505 display_map.max_point().row()
15506 };
15507
15508 // When `skip_soft_wrap` is true, we use buffer columns instead of pixel
15509 // positions to place new selections, so we need to keep track of the
15510 // column range of the oldest selection in each group, because
15511 // intermediate selections may have been clamped to shorter lines.
15512 // selections may have been clamped to shorter lines.
15513 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15514 let mut map = HashMap::default();
15515 for group in state.groups.iter() {
15516 if let Some(oldest_id) = group.stack.first() {
15517 if let Some(oldest_selection) =
15518 columnar_selections.iter().find(|s| s.id == *oldest_id)
15519 {
15520 let start_col = oldest_selection.start.column;
15521 let end_col = oldest_selection.end.column;
15522 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15523 for id in &group.stack {
15524 map.insert(*id, goal_columns.clone());
15525 }
15526 }
15527 }
15528 }
15529 map
15530 } else {
15531 HashMap::default()
15532 };
15533
15534 let mut last_added_item_per_group = HashMap::default();
15535 for group in state.groups.iter_mut() {
15536 if let Some(last_id) = group.stack.last() {
15537 last_added_item_per_group.insert(*last_id, group);
15538 }
15539 }
15540
15541 for selection in columnar_selections {
15542 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15543 if above == group.above {
15544 let range = selection.display_range(&display_map).sorted();
15545 debug_assert_eq!(range.start.row(), range.end.row());
15546 let row = range.start.row();
15547 let positions =
15548 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15549 Pixels::from(start)..Pixels::from(end)
15550 } else {
15551 let start_x =
15552 display_map.x_for_display_point(range.start, &text_layout_details);
15553 let end_x =
15554 display_map.x_for_display_point(range.end, &text_layout_details);
15555 start_x.min(end_x)..start_x.max(end_x)
15556 };
15557
15558 let maybe_new_selection = if skip_soft_wrap {
15559 let goal_columns = goal_columns_by_selection_id
15560 .remove(&selection.id)
15561 .unwrap_or_else(|| {
15562 let start_col = selection.start.column;
15563 let end_col = selection.end.column;
15564 start_col.min(end_col)..start_col.max(end_col)
15565 });
15566 self.selections.find_next_columnar_selection_by_buffer_row(
15567 &display_map,
15568 row,
15569 end_row,
15570 above,
15571 &goal_columns,
15572 selection.reversed,
15573 &text_layout_details,
15574 )
15575 } else {
15576 self.selections.find_next_columnar_selection_by_display_row(
15577 &display_map,
15578 row,
15579 end_row,
15580 above,
15581 &positions,
15582 selection.reversed,
15583 &text_layout_details,
15584 )
15585 };
15586
15587 if let Some(new_selection) = maybe_new_selection {
15588 group.stack.push(new_selection.id);
15589 if above {
15590 final_selections.push(new_selection);
15591 final_selections.push(selection);
15592 } else {
15593 final_selections.push(selection);
15594 final_selections.push(new_selection);
15595 }
15596 } else {
15597 final_selections.push(selection);
15598 }
15599 } else {
15600 group.stack.pop();
15601 }
15602 } else {
15603 final_selections.push(selection);
15604 }
15605 }
15606
15607 self.change_selections(Default::default(), window, cx, |s| {
15608 s.select(final_selections);
15609 });
15610
15611 let final_selection_ids: HashSet<_> = self
15612 .selections
15613 .all::<Point>(&display_map)
15614 .iter()
15615 .map(|s| s.id)
15616 .collect();
15617 state.groups.retain_mut(|group| {
15618 // selections might get merged above so we remove invalid items from stacks
15619 group.stack.retain(|id| final_selection_ids.contains(id));
15620
15621 // single selection in stack can be treated as initial state
15622 group.stack.len() > 1
15623 });
15624
15625 if !state.groups.is_empty() {
15626 self.add_selections_state = Some(state);
15627 }
15628 }
15629
15630 pub fn insert_snippet_at_selections(
15631 &mut self,
15632 action: &InsertSnippet,
15633 window: &mut Window,
15634 cx: &mut Context<Self>,
15635 ) {
15636 self.try_insert_snippet_at_selections(action, window, cx)
15637 .log_err();
15638 }
15639
15640 fn try_insert_snippet_at_selections(
15641 &mut self,
15642 action: &InsertSnippet,
15643 window: &mut Window,
15644 cx: &mut Context<Self>,
15645 ) -> Result<()> {
15646 let insertion_ranges = self
15647 .selections
15648 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15649 .into_iter()
15650 .map(|selection| selection.range())
15651 .collect_vec();
15652
15653 let snippet = if let Some(snippet_body) = &action.snippet {
15654 if action.language.is_none() && action.name.is_none() {
15655 Snippet::parse(snippet_body)?
15656 } else {
15657 bail!("`snippet` is mutually exclusive with `language` and `name`")
15658 }
15659 } else if let Some(name) = &action.name {
15660 let project = self.project().context("no project")?;
15661 let snippet_store = project.read(cx).snippets().read(cx);
15662 let snippet = snippet_store
15663 .snippets_for(action.language.clone(), cx)
15664 .into_iter()
15665 .find(|snippet| snippet.name == *name)
15666 .context("snippet not found")?;
15667 Snippet::parse(&snippet.body)?
15668 } else {
15669 // todo(andrew): open modal to select snippet
15670 bail!("`name` or `snippet` is required")
15671 };
15672
15673 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15674 }
15675
15676 fn select_match_ranges(
15677 &mut self,
15678 range: Range<MultiBufferOffset>,
15679 reversed: bool,
15680 replace_newest: bool,
15681 auto_scroll: Option<Autoscroll>,
15682 window: &mut Window,
15683 cx: &mut Context<Editor>,
15684 ) {
15685 self.unfold_ranges(
15686 std::slice::from_ref(&range),
15687 false,
15688 auto_scroll.is_some(),
15689 cx,
15690 );
15691 let effects = if let Some(scroll) = auto_scroll {
15692 SelectionEffects::scroll(scroll)
15693 } else {
15694 SelectionEffects::no_scroll()
15695 };
15696 self.change_selections(effects, window, cx, |s| {
15697 if replace_newest {
15698 s.delete(s.newest_anchor().id);
15699 }
15700 if reversed {
15701 s.insert_range(range.end..range.start);
15702 } else {
15703 s.insert_range(range);
15704 }
15705 });
15706 }
15707
15708 pub fn select_next_match_internal(
15709 &mut self,
15710 display_map: &DisplaySnapshot,
15711 replace_newest: bool,
15712 autoscroll: Option<Autoscroll>,
15713 window: &mut Window,
15714 cx: &mut Context<Self>,
15715 ) -> Result<()> {
15716 let buffer = display_map.buffer_snapshot();
15717 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15718 if let Some(mut select_next_state) = self.select_next_state.take() {
15719 let query = &select_next_state.query;
15720 if !select_next_state.done {
15721 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15722 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15723 let mut next_selected_range = None;
15724
15725 let bytes_after_last_selection =
15726 buffer.bytes_in_range(last_selection.end..buffer.len());
15727 let bytes_before_first_selection =
15728 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15729 let query_matches = query
15730 .stream_find_iter(bytes_after_last_selection)
15731 .map(|result| (last_selection.end, result))
15732 .chain(
15733 query
15734 .stream_find_iter(bytes_before_first_selection)
15735 .map(|result| (MultiBufferOffset(0), result)),
15736 );
15737
15738 for (start_offset, query_match) in query_matches {
15739 let query_match = query_match.unwrap(); // can only fail due to I/O
15740 let offset_range =
15741 start_offset + query_match.start()..start_offset + query_match.end();
15742
15743 if !select_next_state.wordwise
15744 || (!buffer.is_inside_word(offset_range.start, None)
15745 && !buffer.is_inside_word(offset_range.end, None))
15746 {
15747 let idx = selections
15748 .partition_point(|selection| selection.end <= offset_range.start);
15749 let overlaps = selections
15750 .get(idx)
15751 .map_or(false, |selection| selection.start < offset_range.end);
15752
15753 if !overlaps {
15754 next_selected_range = Some(offset_range);
15755 break;
15756 }
15757 }
15758 }
15759
15760 if let Some(next_selected_range) = next_selected_range {
15761 self.select_match_ranges(
15762 next_selected_range,
15763 last_selection.reversed,
15764 replace_newest,
15765 autoscroll,
15766 window,
15767 cx,
15768 );
15769 } else {
15770 select_next_state.done = true;
15771 }
15772 }
15773
15774 self.select_next_state = Some(select_next_state);
15775 } else {
15776 let mut only_carets = true;
15777 let mut same_text_selected = true;
15778 let mut selected_text = None;
15779
15780 let mut selections_iter = selections.iter().peekable();
15781 while let Some(selection) = selections_iter.next() {
15782 if selection.start != selection.end {
15783 only_carets = false;
15784 }
15785
15786 if same_text_selected {
15787 if selected_text.is_none() {
15788 selected_text =
15789 Some(buffer.text_for_range(selection.range()).collect::<String>());
15790 }
15791
15792 if let Some(next_selection) = selections_iter.peek() {
15793 if next_selection.len() == selection.len() {
15794 let next_selected_text = buffer
15795 .text_for_range(next_selection.range())
15796 .collect::<String>();
15797 if Some(next_selected_text) != selected_text {
15798 same_text_selected = false;
15799 selected_text = None;
15800 }
15801 } else {
15802 same_text_selected = false;
15803 selected_text = None;
15804 }
15805 }
15806 }
15807 }
15808
15809 if only_carets {
15810 for selection in &mut selections {
15811 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15812 selection.start = word_range.start;
15813 selection.end = word_range.end;
15814 selection.goal = SelectionGoal::None;
15815 selection.reversed = false;
15816 self.select_match_ranges(
15817 selection.start..selection.end,
15818 selection.reversed,
15819 replace_newest,
15820 autoscroll,
15821 window,
15822 cx,
15823 );
15824 }
15825
15826 if selections.len() == 1 {
15827 let selection = selections
15828 .last()
15829 .expect("ensured that there's only one selection");
15830 let query = buffer
15831 .text_for_range(selection.start..selection.end)
15832 .collect::<String>();
15833 let is_empty = query.is_empty();
15834 let select_state = SelectNextState {
15835 query: self.build_query(&[query], cx)?,
15836 wordwise: true,
15837 done: is_empty,
15838 };
15839 self.select_next_state = Some(select_state);
15840 } else {
15841 self.select_next_state = None;
15842 }
15843 } else if let Some(selected_text) = selected_text {
15844 self.select_next_state = Some(SelectNextState {
15845 query: self.build_query(&[selected_text], cx)?,
15846 wordwise: false,
15847 done: false,
15848 });
15849 self.select_next_match_internal(
15850 display_map,
15851 replace_newest,
15852 autoscroll,
15853 window,
15854 cx,
15855 )?;
15856 }
15857 }
15858 Ok(())
15859 }
15860
15861 pub fn select_all_matches(
15862 &mut self,
15863 _action: &SelectAllMatches,
15864 window: &mut Window,
15865 cx: &mut Context<Self>,
15866 ) -> Result<()> {
15867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15868
15869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15870
15871 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15872 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15873 else {
15874 return Ok(());
15875 };
15876
15877 let mut new_selections = Vec::new();
15878
15879 let reversed = self
15880 .selections
15881 .oldest::<MultiBufferOffset>(&display_map)
15882 .reversed;
15883 let buffer = display_map.buffer_snapshot();
15884 let query_matches = select_next_state
15885 .query
15886 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15887
15888 for query_match in query_matches.into_iter() {
15889 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15890 let offset_range = if reversed {
15891 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15892 } else {
15893 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15894 };
15895
15896 if !select_next_state.wordwise
15897 || (!buffer.is_inside_word(offset_range.start, None)
15898 && !buffer.is_inside_word(offset_range.end, None))
15899 {
15900 new_selections.push(offset_range.start..offset_range.end);
15901 }
15902 }
15903
15904 select_next_state.done = true;
15905
15906 if new_selections.is_empty() {
15907 log::error!("bug: new_selections is empty in select_all_matches");
15908 return Ok(());
15909 }
15910
15911 self.unfold_ranges(&new_selections, false, false, cx);
15912 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15913 selections.select_ranges(new_selections)
15914 });
15915
15916 Ok(())
15917 }
15918
15919 pub fn select_next(
15920 &mut self,
15921 action: &SelectNext,
15922 window: &mut Window,
15923 cx: &mut Context<Self>,
15924 ) -> Result<()> {
15925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15927 self.select_next_match_internal(
15928 &display_map,
15929 action.replace_newest,
15930 Some(Autoscroll::newest()),
15931 window,
15932 cx,
15933 )
15934 }
15935
15936 pub fn select_previous(
15937 &mut self,
15938 action: &SelectPrevious,
15939 window: &mut Window,
15940 cx: &mut Context<Self>,
15941 ) -> Result<()> {
15942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15944 let buffer = display_map.buffer_snapshot();
15945 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15946 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15947 let query = &select_prev_state.query;
15948 if !select_prev_state.done {
15949 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15950 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15951 let mut next_selected_range = None;
15952 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15953 let bytes_before_last_selection =
15954 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15955 let bytes_after_first_selection =
15956 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15957 let query_matches = query
15958 .stream_find_iter(bytes_before_last_selection)
15959 .map(|result| (last_selection.start, result))
15960 .chain(
15961 query
15962 .stream_find_iter(bytes_after_first_selection)
15963 .map(|result| (buffer.len(), result)),
15964 );
15965 for (end_offset, query_match) in query_matches {
15966 let query_match = query_match.unwrap(); // can only fail due to I/O
15967 let offset_range =
15968 end_offset - query_match.end()..end_offset - query_match.start();
15969
15970 if !select_prev_state.wordwise
15971 || (!buffer.is_inside_word(offset_range.start, None)
15972 && !buffer.is_inside_word(offset_range.end, None))
15973 {
15974 next_selected_range = Some(offset_range);
15975 break;
15976 }
15977 }
15978
15979 if let Some(next_selected_range) = next_selected_range {
15980 self.select_match_ranges(
15981 next_selected_range,
15982 last_selection.reversed,
15983 action.replace_newest,
15984 Some(Autoscroll::newest()),
15985 window,
15986 cx,
15987 );
15988 } else {
15989 select_prev_state.done = true;
15990 }
15991 }
15992
15993 self.select_prev_state = Some(select_prev_state);
15994 } else {
15995 let mut only_carets = true;
15996 let mut same_text_selected = true;
15997 let mut selected_text = None;
15998
15999 let mut selections_iter = selections.iter().peekable();
16000 while let Some(selection) = selections_iter.next() {
16001 if selection.start != selection.end {
16002 only_carets = false;
16003 }
16004
16005 if same_text_selected {
16006 if selected_text.is_none() {
16007 selected_text =
16008 Some(buffer.text_for_range(selection.range()).collect::<String>());
16009 }
16010
16011 if let Some(next_selection) = selections_iter.peek() {
16012 if next_selection.len() == selection.len() {
16013 let next_selected_text = buffer
16014 .text_for_range(next_selection.range())
16015 .collect::<String>();
16016 if Some(next_selected_text) != selected_text {
16017 same_text_selected = false;
16018 selected_text = None;
16019 }
16020 } else {
16021 same_text_selected = false;
16022 selected_text = None;
16023 }
16024 }
16025 }
16026 }
16027
16028 if only_carets {
16029 for selection in &mut selections {
16030 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16031 selection.start = word_range.start;
16032 selection.end = word_range.end;
16033 selection.goal = SelectionGoal::None;
16034 selection.reversed = false;
16035 self.select_match_ranges(
16036 selection.start..selection.end,
16037 selection.reversed,
16038 action.replace_newest,
16039 Some(Autoscroll::newest()),
16040 window,
16041 cx,
16042 );
16043 }
16044 if selections.len() == 1 {
16045 let selection = selections
16046 .last()
16047 .expect("ensured that there's only one selection");
16048 let query = buffer
16049 .text_for_range(selection.start..selection.end)
16050 .collect::<String>();
16051 let is_empty = query.is_empty();
16052 let select_state = SelectNextState {
16053 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16054 wordwise: true,
16055 done: is_empty,
16056 };
16057 self.select_prev_state = Some(select_state);
16058 } else {
16059 self.select_prev_state = None;
16060 }
16061 } else if let Some(selected_text) = selected_text {
16062 self.select_prev_state = Some(SelectNextState {
16063 query: self
16064 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16065 wordwise: false,
16066 done: false,
16067 });
16068 self.select_previous(action, window, cx)?;
16069 }
16070 }
16071 Ok(())
16072 }
16073
16074 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16075 /// setting the case sensitivity based on the global
16076 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16077 /// editor's settings.
16078 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16079 where
16080 I: IntoIterator<Item = P>,
16081 P: AsRef<[u8]>,
16082 {
16083 let case_sensitive = self
16084 .select_next_is_case_sensitive
16085 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16086
16087 let mut builder = AhoCorasickBuilder::new();
16088 builder.ascii_case_insensitive(!case_sensitive);
16089 builder.build(patterns)
16090 }
16091
16092 pub fn find_next_match(
16093 &mut self,
16094 _: &FindNextMatch,
16095 window: &mut Window,
16096 cx: &mut Context<Self>,
16097 ) -> Result<()> {
16098 let selections = self.selections.disjoint_anchors_arc();
16099 match selections.first() {
16100 Some(first) if selections.len() >= 2 => {
16101 self.change_selections(Default::default(), window, cx, |s| {
16102 s.select_ranges([first.range()]);
16103 });
16104 }
16105 _ => self.select_next(
16106 &SelectNext {
16107 replace_newest: true,
16108 },
16109 window,
16110 cx,
16111 )?,
16112 }
16113 Ok(())
16114 }
16115
16116 pub fn find_previous_match(
16117 &mut self,
16118 _: &FindPreviousMatch,
16119 window: &mut Window,
16120 cx: &mut Context<Self>,
16121 ) -> Result<()> {
16122 let selections = self.selections.disjoint_anchors_arc();
16123 match selections.last() {
16124 Some(last) if selections.len() >= 2 => {
16125 self.change_selections(Default::default(), window, cx, |s| {
16126 s.select_ranges([last.range()]);
16127 });
16128 }
16129 _ => self.select_previous(
16130 &SelectPrevious {
16131 replace_newest: true,
16132 },
16133 window,
16134 cx,
16135 )?,
16136 }
16137 Ok(())
16138 }
16139
16140 pub fn toggle_comments(
16141 &mut self,
16142 action: &ToggleComments,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) {
16146 if self.read_only(cx) {
16147 return;
16148 }
16149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16150 let text_layout_details = &self.text_layout_details(window, cx);
16151 self.transact(window, cx, |this, window, cx| {
16152 let mut selections = this
16153 .selections
16154 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16155 let mut edits = Vec::new();
16156 let mut selection_edit_ranges = Vec::new();
16157 let mut last_toggled_row = None;
16158 let snapshot = this.buffer.read(cx).read(cx);
16159 let empty_str: Arc<str> = Arc::default();
16160 let mut suffixes_inserted = Vec::new();
16161 let ignore_indent = action.ignore_indent;
16162
16163 fn comment_prefix_range(
16164 snapshot: &MultiBufferSnapshot,
16165 row: MultiBufferRow,
16166 comment_prefix: &str,
16167 comment_prefix_whitespace: &str,
16168 ignore_indent: bool,
16169 ) -> Range<Point> {
16170 let indent_size = if ignore_indent {
16171 0
16172 } else {
16173 snapshot.indent_size_for_line(row).len
16174 };
16175
16176 let start = Point::new(row.0, indent_size);
16177
16178 let mut line_bytes = snapshot
16179 .bytes_in_range(start..snapshot.max_point())
16180 .flatten()
16181 .copied();
16182
16183 // If this line currently begins with the line comment prefix, then record
16184 // the range containing the prefix.
16185 if line_bytes
16186 .by_ref()
16187 .take(comment_prefix.len())
16188 .eq(comment_prefix.bytes())
16189 {
16190 // Include any whitespace that matches the comment prefix.
16191 let matching_whitespace_len = line_bytes
16192 .zip(comment_prefix_whitespace.bytes())
16193 .take_while(|(a, b)| a == b)
16194 .count() as u32;
16195 let end = Point::new(
16196 start.row,
16197 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16198 );
16199 start..end
16200 } else {
16201 start..start
16202 }
16203 }
16204
16205 fn comment_suffix_range(
16206 snapshot: &MultiBufferSnapshot,
16207 row: MultiBufferRow,
16208 comment_suffix: &str,
16209 comment_suffix_has_leading_space: bool,
16210 ) -> Range<Point> {
16211 let end = Point::new(row.0, snapshot.line_len(row));
16212 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16213
16214 let mut line_end_bytes = snapshot
16215 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16216 .flatten()
16217 .copied();
16218
16219 let leading_space_len = if suffix_start_column > 0
16220 && line_end_bytes.next() == Some(b' ')
16221 && comment_suffix_has_leading_space
16222 {
16223 1
16224 } else {
16225 0
16226 };
16227
16228 // If this line currently begins with the line comment prefix, then record
16229 // the range containing the prefix.
16230 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16231 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16232 start..end
16233 } else {
16234 end..end
16235 }
16236 }
16237
16238 // TODO: Handle selections that cross excerpts
16239 for selection in &mut selections {
16240 let start_column = snapshot
16241 .indent_size_for_line(MultiBufferRow(selection.start.row))
16242 .len;
16243 let language = if let Some(language) =
16244 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16245 {
16246 language
16247 } else {
16248 continue;
16249 };
16250
16251 selection_edit_ranges.clear();
16252
16253 // If multiple selections contain a given row, avoid processing that
16254 // row more than once.
16255 let mut start_row = MultiBufferRow(selection.start.row);
16256 if last_toggled_row == Some(start_row) {
16257 start_row = start_row.next_row();
16258 }
16259 let end_row =
16260 if selection.end.row > selection.start.row && selection.end.column == 0 {
16261 MultiBufferRow(selection.end.row - 1)
16262 } else {
16263 MultiBufferRow(selection.end.row)
16264 };
16265 last_toggled_row = Some(end_row);
16266
16267 if start_row > end_row {
16268 continue;
16269 }
16270
16271 // If the language has line comments, toggle those.
16272 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16273
16274 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16275 if ignore_indent {
16276 full_comment_prefixes = full_comment_prefixes
16277 .into_iter()
16278 .map(|s| Arc::from(s.trim_end()))
16279 .collect();
16280 }
16281
16282 if !full_comment_prefixes.is_empty() {
16283 let first_prefix = full_comment_prefixes
16284 .first()
16285 .expect("prefixes is non-empty");
16286 let prefix_trimmed_lengths = full_comment_prefixes
16287 .iter()
16288 .map(|p| p.trim_end_matches(' ').len())
16289 .collect::<SmallVec<[usize; 4]>>();
16290
16291 let mut all_selection_lines_are_comments = true;
16292
16293 for row in start_row.0..=end_row.0 {
16294 let row = MultiBufferRow(row);
16295 if start_row < end_row && snapshot.is_line_blank(row) {
16296 continue;
16297 }
16298
16299 let prefix_range = full_comment_prefixes
16300 .iter()
16301 .zip(prefix_trimmed_lengths.iter().copied())
16302 .map(|(prefix, trimmed_prefix_len)| {
16303 comment_prefix_range(
16304 snapshot.deref(),
16305 row,
16306 &prefix[..trimmed_prefix_len],
16307 &prefix[trimmed_prefix_len..],
16308 ignore_indent,
16309 )
16310 })
16311 .max_by_key(|range| range.end.column - range.start.column)
16312 .expect("prefixes is non-empty");
16313
16314 if prefix_range.is_empty() {
16315 all_selection_lines_are_comments = false;
16316 }
16317
16318 selection_edit_ranges.push(prefix_range);
16319 }
16320
16321 if all_selection_lines_are_comments {
16322 edits.extend(
16323 selection_edit_ranges
16324 .iter()
16325 .cloned()
16326 .map(|range| (range, empty_str.clone())),
16327 );
16328 } else {
16329 let min_column = selection_edit_ranges
16330 .iter()
16331 .map(|range| range.start.column)
16332 .min()
16333 .unwrap_or(0);
16334 edits.extend(selection_edit_ranges.iter().map(|range| {
16335 let position = Point::new(range.start.row, min_column);
16336 (position..position, first_prefix.clone())
16337 }));
16338 }
16339 } else if let Some(BlockCommentConfig {
16340 start: full_comment_prefix,
16341 end: comment_suffix,
16342 ..
16343 }) = language.block_comment()
16344 {
16345 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16346 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16347 let prefix_range = comment_prefix_range(
16348 snapshot.deref(),
16349 start_row,
16350 comment_prefix,
16351 comment_prefix_whitespace,
16352 ignore_indent,
16353 );
16354 let suffix_range = comment_suffix_range(
16355 snapshot.deref(),
16356 end_row,
16357 comment_suffix.trim_start_matches(' '),
16358 comment_suffix.starts_with(' '),
16359 );
16360
16361 if prefix_range.is_empty() || suffix_range.is_empty() {
16362 edits.push((
16363 prefix_range.start..prefix_range.start,
16364 full_comment_prefix.clone(),
16365 ));
16366 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16367 suffixes_inserted.push((end_row, comment_suffix.len()));
16368 } else {
16369 edits.push((prefix_range, empty_str.clone()));
16370 edits.push((suffix_range, empty_str.clone()));
16371 }
16372 } else {
16373 continue;
16374 }
16375 }
16376
16377 drop(snapshot);
16378 this.buffer.update(cx, |buffer, cx| {
16379 buffer.edit(edits, None, cx);
16380 });
16381
16382 // Adjust selections so that they end before any comment suffixes that
16383 // were inserted.
16384 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16385 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16386 let snapshot = this.buffer.read(cx).read(cx);
16387 for selection in &mut selections {
16388 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16389 match row.cmp(&MultiBufferRow(selection.end.row)) {
16390 Ordering::Less => {
16391 suffixes_inserted.next();
16392 continue;
16393 }
16394 Ordering::Greater => break,
16395 Ordering::Equal => {
16396 if selection.end.column == snapshot.line_len(row) {
16397 if selection.is_empty() {
16398 selection.start.column -= suffix_len as u32;
16399 }
16400 selection.end.column -= suffix_len as u32;
16401 }
16402 break;
16403 }
16404 }
16405 }
16406 }
16407
16408 drop(snapshot);
16409 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16410
16411 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16412 let selections_on_single_row = selections.windows(2).all(|selections| {
16413 selections[0].start.row == selections[1].start.row
16414 && selections[0].end.row == selections[1].end.row
16415 && selections[0].start.row == selections[0].end.row
16416 });
16417 let selections_selecting = selections
16418 .iter()
16419 .any(|selection| selection.start != selection.end);
16420 let advance_downwards = action.advance_downwards
16421 && selections_on_single_row
16422 && !selections_selecting
16423 && !matches!(this.mode, EditorMode::SingleLine);
16424
16425 if advance_downwards {
16426 let snapshot = this.buffer.read(cx).snapshot(cx);
16427
16428 this.change_selections(Default::default(), window, cx, |s| {
16429 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16430 let mut point = display_point.to_point(display_snapshot);
16431 point.row += 1;
16432 point = snapshot.clip_point(point, Bias::Left);
16433 let display_point = point.to_display_point(display_snapshot);
16434 let goal = SelectionGoal::HorizontalPosition(
16435 display_snapshot
16436 .x_for_display_point(display_point, text_layout_details)
16437 .into(),
16438 );
16439 (display_point, goal)
16440 })
16441 });
16442 }
16443 });
16444 }
16445
16446 pub fn select_enclosing_symbol(
16447 &mut self,
16448 _: &SelectEnclosingSymbol,
16449 window: &mut Window,
16450 cx: &mut Context<Self>,
16451 ) {
16452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16453
16454 let buffer = self.buffer.read(cx).snapshot(cx);
16455 let old_selections = self
16456 .selections
16457 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16458 .into_boxed_slice();
16459
16460 fn update_selection(
16461 selection: &Selection<MultiBufferOffset>,
16462 buffer_snap: &MultiBufferSnapshot,
16463 ) -> Option<Selection<MultiBufferOffset>> {
16464 let cursor = selection.head();
16465 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16466 for symbol in symbols.iter().rev() {
16467 let start = symbol.range.start.to_offset(buffer_snap);
16468 let end = symbol.range.end.to_offset(buffer_snap);
16469 let new_range = start..end;
16470 if start < selection.start || end > selection.end {
16471 return Some(Selection {
16472 id: selection.id,
16473 start: new_range.start,
16474 end: new_range.end,
16475 goal: SelectionGoal::None,
16476 reversed: selection.reversed,
16477 });
16478 }
16479 }
16480 None
16481 }
16482
16483 let mut selected_larger_symbol = false;
16484 let new_selections = old_selections
16485 .iter()
16486 .map(|selection| match update_selection(selection, &buffer) {
16487 Some(new_selection) => {
16488 if new_selection.range() != selection.range() {
16489 selected_larger_symbol = true;
16490 }
16491 new_selection
16492 }
16493 None => selection.clone(),
16494 })
16495 .collect::<Vec<_>>();
16496
16497 if selected_larger_symbol {
16498 self.change_selections(Default::default(), window, cx, |s| {
16499 s.select(new_selections);
16500 });
16501 }
16502 }
16503
16504 pub fn select_larger_syntax_node(
16505 &mut self,
16506 _: &SelectLargerSyntaxNode,
16507 window: &mut Window,
16508 cx: &mut Context<Self>,
16509 ) {
16510 let Some(visible_row_count) = self.visible_row_count() else {
16511 return;
16512 };
16513 let old_selections: Box<[_]> = self
16514 .selections
16515 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16516 .into();
16517 if old_selections.is_empty() {
16518 return;
16519 }
16520
16521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16522
16523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16524 let buffer = self.buffer.read(cx).snapshot(cx);
16525
16526 let mut selected_larger_node = false;
16527 let mut new_selections = old_selections
16528 .iter()
16529 .map(|selection| {
16530 let old_range = selection.start..selection.end;
16531
16532 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16533 // manually select word at selection
16534 if ["string_content", "inline"].contains(&node.kind()) {
16535 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16536 // ignore if word is already selected
16537 if !word_range.is_empty() && old_range != word_range {
16538 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16539 // only select word if start and end point belongs to same word
16540 if word_range == last_word_range {
16541 selected_larger_node = true;
16542 return Selection {
16543 id: selection.id,
16544 start: word_range.start,
16545 end: word_range.end,
16546 goal: SelectionGoal::None,
16547 reversed: selection.reversed,
16548 };
16549 }
16550 }
16551 }
16552 }
16553
16554 let mut new_range = old_range.clone();
16555 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16556 new_range = range;
16557 if !node.is_named() {
16558 continue;
16559 }
16560 if !display_map.intersects_fold(new_range.start)
16561 && !display_map.intersects_fold(new_range.end)
16562 {
16563 break;
16564 }
16565 }
16566
16567 selected_larger_node |= new_range != old_range;
16568 Selection {
16569 id: selection.id,
16570 start: new_range.start,
16571 end: new_range.end,
16572 goal: SelectionGoal::None,
16573 reversed: selection.reversed,
16574 }
16575 })
16576 .collect::<Vec<_>>();
16577
16578 if !selected_larger_node {
16579 return; // don't put this call in the history
16580 }
16581
16582 // scroll based on transformation done to the last selection created by the user
16583 let (last_old, last_new) = old_selections
16584 .last()
16585 .zip(new_selections.last().cloned())
16586 .expect("old_selections isn't empty");
16587
16588 // revert selection
16589 let is_selection_reversed = {
16590 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16591 new_selections.last_mut().expect("checked above").reversed =
16592 should_newest_selection_be_reversed;
16593 should_newest_selection_be_reversed
16594 };
16595
16596 if selected_larger_node {
16597 self.select_syntax_node_history.disable_clearing = true;
16598 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16599 s.select(new_selections.clone());
16600 });
16601 self.select_syntax_node_history.disable_clearing = false;
16602 }
16603
16604 let start_row = last_new.start.to_display_point(&display_map).row().0;
16605 let end_row = last_new.end.to_display_point(&display_map).row().0;
16606 let selection_height = end_row - start_row + 1;
16607 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16608
16609 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16610 let scroll_behavior = if fits_on_the_screen {
16611 self.request_autoscroll(Autoscroll::fit(), cx);
16612 SelectSyntaxNodeScrollBehavior::FitSelection
16613 } else if is_selection_reversed {
16614 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16615 SelectSyntaxNodeScrollBehavior::CursorTop
16616 } else {
16617 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16618 SelectSyntaxNodeScrollBehavior::CursorBottom
16619 };
16620
16621 let old_selections: Box<[Selection<Anchor>]> = old_selections
16622 .iter()
16623 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16624 .collect();
16625 self.select_syntax_node_history.push((
16626 old_selections,
16627 scroll_behavior,
16628 is_selection_reversed,
16629 ));
16630 }
16631
16632 pub fn select_smaller_syntax_node(
16633 &mut self,
16634 _: &SelectSmallerSyntaxNode,
16635 window: &mut Window,
16636 cx: &mut Context<Self>,
16637 ) {
16638 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16639
16640 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16641 self.select_syntax_node_history.pop()
16642 {
16643 if let Some(selection) = selections.last_mut() {
16644 selection.reversed = is_selection_reversed;
16645 }
16646
16647 let snapshot = self.buffer.read(cx).snapshot(cx);
16648 let selections: Vec<Selection<MultiBufferOffset>> = selections
16649 .iter()
16650 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16651 .collect();
16652
16653 self.select_syntax_node_history.disable_clearing = true;
16654 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16655 s.select(selections);
16656 });
16657 self.select_syntax_node_history.disable_clearing = false;
16658
16659 match scroll_behavior {
16660 SelectSyntaxNodeScrollBehavior::CursorTop => {
16661 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16662 }
16663 SelectSyntaxNodeScrollBehavior::FitSelection => {
16664 self.request_autoscroll(Autoscroll::fit(), cx);
16665 }
16666 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16667 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16668 }
16669 }
16670 }
16671 }
16672
16673 pub fn unwrap_syntax_node(
16674 &mut self,
16675 _: &UnwrapSyntaxNode,
16676 window: &mut Window,
16677 cx: &mut Context<Self>,
16678 ) {
16679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16680
16681 let buffer = self.buffer.read(cx).snapshot(cx);
16682 let selections = self
16683 .selections
16684 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16685 .into_iter()
16686 // subtracting the offset requires sorting
16687 .sorted_by_key(|i| i.start);
16688
16689 let full_edits = selections
16690 .into_iter()
16691 .filter_map(|selection| {
16692 let child = if selection.is_empty()
16693 && let Some((_, ancestor_range)) =
16694 buffer.syntax_ancestor(selection.start..selection.end)
16695 {
16696 ancestor_range
16697 } else {
16698 selection.range()
16699 };
16700
16701 let mut parent = child.clone();
16702 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16703 parent = ancestor_range;
16704 if parent.start < child.start || parent.end > child.end {
16705 break;
16706 }
16707 }
16708
16709 if parent == child {
16710 return None;
16711 }
16712 let text = buffer.text_for_range(child).collect::<String>();
16713 Some((selection.id, parent, text))
16714 })
16715 .collect::<Vec<_>>();
16716 if full_edits.is_empty() {
16717 return;
16718 }
16719
16720 self.transact(window, cx, |this, window, cx| {
16721 this.buffer.update(cx, |buffer, cx| {
16722 buffer.edit(
16723 full_edits
16724 .iter()
16725 .map(|(_, p, t)| (p.clone(), t.clone()))
16726 .collect::<Vec<_>>(),
16727 None,
16728 cx,
16729 );
16730 });
16731 this.change_selections(Default::default(), window, cx, |s| {
16732 let mut offset = 0;
16733 let mut selections = vec![];
16734 for (id, parent, text) in full_edits {
16735 let start = parent.start - offset;
16736 offset += (parent.end - parent.start) - text.len();
16737 selections.push(Selection {
16738 id,
16739 start,
16740 end: start + text.len(),
16741 reversed: false,
16742 goal: Default::default(),
16743 });
16744 }
16745 s.select(selections);
16746 });
16747 });
16748 }
16749
16750 pub fn select_next_syntax_node(
16751 &mut self,
16752 _: &SelectNextSyntaxNode,
16753 window: &mut Window,
16754 cx: &mut Context<Self>,
16755 ) {
16756 let old_selections: Box<[_]> = self
16757 .selections
16758 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16759 .into();
16760 if old_selections.is_empty() {
16761 return;
16762 }
16763
16764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16765
16766 let buffer = self.buffer.read(cx).snapshot(cx);
16767 let mut selected_sibling = false;
16768
16769 let new_selections = old_selections
16770 .iter()
16771 .map(|selection| {
16772 let old_range = selection.start..selection.end;
16773
16774 let old_range =
16775 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16776 let excerpt = buffer.excerpt_containing(old_range.clone());
16777
16778 if let Some(mut excerpt) = excerpt
16779 && let Some(node) = excerpt
16780 .buffer()
16781 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16782 {
16783 let new_range = excerpt.map_range_from_buffer(
16784 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16785 );
16786 selected_sibling = true;
16787 Selection {
16788 id: selection.id,
16789 start: new_range.start,
16790 end: new_range.end,
16791 goal: SelectionGoal::None,
16792 reversed: selection.reversed,
16793 }
16794 } else {
16795 selection.clone()
16796 }
16797 })
16798 .collect::<Vec<_>>();
16799
16800 if selected_sibling {
16801 self.change_selections(
16802 SelectionEffects::scroll(Autoscroll::fit()),
16803 window,
16804 cx,
16805 |s| {
16806 s.select(new_selections);
16807 },
16808 );
16809 }
16810 }
16811
16812 pub fn select_prev_syntax_node(
16813 &mut self,
16814 _: &SelectPreviousSyntaxNode,
16815 window: &mut Window,
16816 cx: &mut Context<Self>,
16817 ) {
16818 let old_selections: Box<[_]> = self
16819 .selections
16820 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16821 .into();
16822 if old_selections.is_empty() {
16823 return;
16824 }
16825
16826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16827
16828 let buffer = self.buffer.read(cx).snapshot(cx);
16829 let mut selected_sibling = false;
16830
16831 let new_selections = old_selections
16832 .iter()
16833 .map(|selection| {
16834 let old_range = selection.start..selection.end;
16835 let old_range =
16836 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16837 let excerpt = buffer.excerpt_containing(old_range.clone());
16838
16839 if let Some(mut excerpt) = excerpt
16840 && let Some(node) = excerpt
16841 .buffer()
16842 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16843 {
16844 let new_range = excerpt.map_range_from_buffer(
16845 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16846 );
16847 selected_sibling = true;
16848 Selection {
16849 id: selection.id,
16850 start: new_range.start,
16851 end: new_range.end,
16852 goal: SelectionGoal::None,
16853 reversed: selection.reversed,
16854 }
16855 } else {
16856 selection.clone()
16857 }
16858 })
16859 .collect::<Vec<_>>();
16860
16861 if selected_sibling {
16862 self.change_selections(
16863 SelectionEffects::scroll(Autoscroll::fit()),
16864 window,
16865 cx,
16866 |s| {
16867 s.select(new_selections);
16868 },
16869 );
16870 }
16871 }
16872
16873 pub fn move_to_start_of_larger_syntax_node(
16874 &mut self,
16875 _: &MoveToStartOfLargerSyntaxNode,
16876 window: &mut Window,
16877 cx: &mut Context<Self>,
16878 ) {
16879 self.move_cursors_to_syntax_nodes(window, cx, false);
16880 }
16881
16882 pub fn move_to_end_of_larger_syntax_node(
16883 &mut self,
16884 _: &MoveToEndOfLargerSyntaxNode,
16885 window: &mut Window,
16886 cx: &mut Context<Self>,
16887 ) {
16888 self.move_cursors_to_syntax_nodes(window, cx, true);
16889 }
16890
16891 fn find_syntax_node_boundary(
16892 &self,
16893 selection_pos: MultiBufferOffset,
16894 move_to_end: bool,
16895 display_map: &DisplaySnapshot,
16896 buffer: &MultiBufferSnapshot,
16897 ) -> MultiBufferOffset {
16898 let old_range = selection_pos..selection_pos;
16899 let mut new_pos = selection_pos;
16900 let mut search_range = old_range;
16901 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
16902 search_range = range.clone();
16903 if !node.is_named()
16904 || display_map.intersects_fold(range.start)
16905 || display_map.intersects_fold(range.end)
16906 // If cursor is already at the end of the syntax node, continue searching
16907 || (move_to_end && range.end == selection_pos)
16908 // If cursor is already at the start of the syntax node, continue searching
16909 || (!move_to_end && range.start == selection_pos)
16910 {
16911 continue;
16912 }
16913
16914 // If we found a string_content node, find the largest parent that is still string_content
16915 // Enables us to skip to the end of strings without taking multiple steps inside the string
16916 let (_, final_range) = if node.kind() == "string_content" {
16917 let mut current_node = node;
16918 let mut current_range = range;
16919 while let Some((parent, parent_range)) =
16920 buffer.syntax_ancestor(current_range.clone())
16921 {
16922 if parent.kind() == "string_content" {
16923 current_node = parent;
16924 current_range = parent_range;
16925 } else {
16926 break;
16927 }
16928 }
16929
16930 (current_node, current_range)
16931 } else {
16932 (node, range)
16933 };
16934
16935 new_pos = if move_to_end {
16936 final_range.end
16937 } else {
16938 final_range.start
16939 };
16940
16941 break;
16942 }
16943
16944 new_pos
16945 }
16946
16947 fn move_cursors_to_syntax_nodes(
16948 &mut self,
16949 window: &mut Window,
16950 cx: &mut Context<Self>,
16951 move_to_end: bool,
16952 ) -> bool {
16953 let old_selections: Box<[_]> = self
16954 .selections
16955 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16956 .into();
16957 if old_selections.is_empty() {
16958 return false;
16959 }
16960
16961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16962
16963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16964 let buffer = self.buffer.read(cx).snapshot(cx);
16965
16966 let mut any_cursor_moved = false;
16967 let new_selections = old_selections
16968 .iter()
16969 .map(|selection| {
16970 if !selection.is_empty() {
16971 return selection.clone();
16972 }
16973
16974 let selection_pos = selection.head();
16975 let new_pos = self.find_syntax_node_boundary(
16976 selection_pos,
16977 move_to_end,
16978 &display_map,
16979 &buffer,
16980 );
16981
16982 any_cursor_moved |= new_pos != selection_pos;
16983
16984 Selection {
16985 id: selection.id,
16986 start: new_pos,
16987 end: new_pos,
16988 goal: SelectionGoal::None,
16989 reversed: false,
16990 }
16991 })
16992 .collect::<Vec<_>>();
16993
16994 self.change_selections(Default::default(), window, cx, |s| {
16995 s.select(new_selections);
16996 });
16997 self.request_autoscroll(Autoscroll::newest(), cx);
16998
16999 any_cursor_moved
17000 }
17001
17002 pub fn select_to_start_of_larger_syntax_node(
17003 &mut self,
17004 _: &SelectToStartOfLargerSyntaxNode,
17005 window: &mut Window,
17006 cx: &mut Context<Self>,
17007 ) {
17008 self.select_to_syntax_nodes(window, cx, false);
17009 }
17010
17011 pub fn select_to_end_of_larger_syntax_node(
17012 &mut self,
17013 _: &SelectToEndOfLargerSyntaxNode,
17014 window: &mut Window,
17015 cx: &mut Context<Self>,
17016 ) {
17017 self.select_to_syntax_nodes(window, cx, true);
17018 }
17019
17020 fn select_to_syntax_nodes(
17021 &mut self,
17022 window: &mut Window,
17023 cx: &mut Context<Self>,
17024 move_to_end: bool,
17025 ) {
17026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17027
17028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17029 let buffer = self.buffer.read(cx).snapshot(cx);
17030 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17031
17032 let new_selections = old_selections
17033 .iter()
17034 .map(|selection| {
17035 let new_pos = self.find_syntax_node_boundary(
17036 selection.head(),
17037 move_to_end,
17038 &display_map,
17039 &buffer,
17040 );
17041
17042 let mut new_selection = selection.clone();
17043 new_selection.set_head(new_pos, SelectionGoal::None);
17044 new_selection
17045 })
17046 .collect::<Vec<_>>();
17047
17048 self.change_selections(Default::default(), window, cx, |s| {
17049 s.select(new_selections);
17050 });
17051 }
17052
17053 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
17054 if !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables {
17055 self.clear_tasks();
17056 return Task::ready(());
17057 }
17058 let project = self.project().map(Entity::downgrade);
17059 let task_sources = self.lsp_task_sources(cx);
17060 let multi_buffer = self.buffer.downgrade();
17061 cx.spawn_in(window, async move |editor, cx| {
17062 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
17063 let Some(project) = project.and_then(|p| p.upgrade()) else {
17064 return;
17065 };
17066 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
17067 this.display_map.update(cx, |map, cx| map.snapshot(cx))
17068 }) else {
17069 return;
17070 };
17071
17072 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
17073 if hide_runnables {
17074 return;
17075 }
17076 let new_rows =
17077 cx.background_spawn({
17078 let snapshot = display_snapshot.clone();
17079 async move {
17080 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
17081 }
17082 })
17083 .await;
17084 let Ok(lsp_tasks) =
17085 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
17086 else {
17087 return;
17088 };
17089 let lsp_tasks = lsp_tasks.await;
17090
17091 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
17092 lsp_tasks
17093 .into_iter()
17094 .flat_map(|(kind, tasks)| {
17095 tasks.into_iter().filter_map(move |(location, task)| {
17096 Some((kind.clone(), location?, task))
17097 })
17098 })
17099 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
17100 let buffer = location.target.buffer;
17101 let buffer_snapshot = buffer.read(cx).snapshot();
17102 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
17103 |(excerpt_id, snapshot, _)| {
17104 if snapshot.remote_id() == buffer_snapshot.remote_id() {
17105 display_snapshot
17106 .buffer_snapshot()
17107 .anchor_in_excerpt(excerpt_id, location.target.range.start)
17108 } else {
17109 None
17110 }
17111 },
17112 );
17113 if let Some(offset) = offset {
17114 let task_buffer_range =
17115 location.target.range.to_point(&buffer_snapshot);
17116 let context_buffer_range =
17117 task_buffer_range.to_offset(&buffer_snapshot);
17118 let context_range = BufferOffset(context_buffer_range.start)
17119 ..BufferOffset(context_buffer_range.end);
17120
17121 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
17122 .or_insert_with(|| RunnableTasks {
17123 templates: Vec::new(),
17124 offset,
17125 column: task_buffer_range.start.column,
17126 extra_variables: HashMap::default(),
17127 context_range,
17128 })
17129 .templates
17130 .push((kind, task.original_task().clone()));
17131 }
17132
17133 acc
17134 })
17135 }) else {
17136 return;
17137 };
17138
17139 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
17140 buffer.language_settings(cx).tasks.prefer_lsp
17141 }) else {
17142 return;
17143 };
17144
17145 let rows = Self::runnable_rows(
17146 project,
17147 display_snapshot,
17148 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
17149 new_rows,
17150 cx.clone(),
17151 )
17152 .await;
17153 editor
17154 .update(cx, |editor, _| {
17155 editor.clear_tasks();
17156 for (key, mut value) in rows {
17157 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
17158 value.templates.extend(lsp_tasks.templates);
17159 }
17160
17161 editor.insert_tasks(key, value);
17162 }
17163 for (key, value) in lsp_tasks_by_rows {
17164 editor.insert_tasks(key, value);
17165 }
17166 })
17167 .ok();
17168 })
17169 }
17170 fn fetch_runnable_ranges(
17171 snapshot: &DisplaySnapshot,
17172 range: Range<Anchor>,
17173 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
17174 snapshot.buffer_snapshot().runnable_ranges(range).collect()
17175 }
17176
17177 fn runnable_rows(
17178 project: Entity<Project>,
17179 snapshot: DisplaySnapshot,
17180 prefer_lsp: bool,
17181 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
17182 cx: AsyncWindowContext,
17183 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
17184 cx.spawn(async move |cx| {
17185 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
17186 for (run_range, mut runnable) in runnable_ranges {
17187 let Some(tasks) = cx
17188 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
17189 .ok()
17190 else {
17191 continue;
17192 };
17193 let mut tasks = tasks.await;
17194
17195 if prefer_lsp {
17196 tasks.retain(|(task_kind, _)| {
17197 !matches!(task_kind, TaskSourceKind::Language { .. })
17198 });
17199 }
17200 if tasks.is_empty() {
17201 continue;
17202 }
17203
17204 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
17205 let Some(row) = snapshot
17206 .buffer_snapshot()
17207 .buffer_line_for_row(MultiBufferRow(point.row))
17208 .map(|(_, range)| range.start.row)
17209 else {
17210 continue;
17211 };
17212
17213 let context_range =
17214 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
17215 runnable_rows.push((
17216 (runnable.buffer_id, row),
17217 RunnableTasks {
17218 templates: tasks,
17219 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
17220 context_range,
17221 column: point.column,
17222 extra_variables: runnable.extra_captures,
17223 },
17224 ));
17225 }
17226 runnable_rows
17227 })
17228 }
17229
17230 fn templates_with_tags(
17231 project: &Entity<Project>,
17232 runnable: &mut Runnable,
17233 cx: &mut App,
17234 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
17235 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
17236 let (worktree_id, file) = project
17237 .buffer_for_id(runnable.buffer, cx)
17238 .and_then(|buffer| buffer.read(cx).file())
17239 .map(|file| (file.worktree_id(cx), file.clone()))
17240 .unzip();
17241
17242 (
17243 project.task_store().read(cx).task_inventory().cloned(),
17244 worktree_id,
17245 file,
17246 )
17247 });
17248
17249 let tags = mem::take(&mut runnable.tags);
17250 let language = runnable.language.clone();
17251 cx.spawn(async move |cx| {
17252 let mut templates_with_tags = Vec::new();
17253 if let Some(inventory) = inventory {
17254 for RunnableTag(tag) in tags {
17255 let new_tasks = inventory.update(cx, |inventory, cx| {
17256 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
17257 });
17258 templates_with_tags.extend(new_tasks.await.into_iter().filter(
17259 move |(_, template)| {
17260 template.tags.iter().any(|source_tag| source_tag == &tag)
17261 },
17262 ));
17263 }
17264 }
17265 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
17266
17267 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
17268 // Strongest source wins; if we have worktree tag binding, prefer that to
17269 // global and language bindings;
17270 // if we have a global binding, prefer that to language binding.
17271 let first_mismatch = templates_with_tags
17272 .iter()
17273 .position(|(tag_source, _)| tag_source != leading_tag_source);
17274 if let Some(index) = first_mismatch {
17275 templates_with_tags.truncate(index);
17276 }
17277 }
17278
17279 templates_with_tags
17280 })
17281 }
17282
17283 pub fn move_to_enclosing_bracket(
17284 &mut self,
17285 _: &MoveToEnclosingBracket,
17286 window: &mut Window,
17287 cx: &mut Context<Self>,
17288 ) {
17289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17290 self.change_selections(Default::default(), window, cx, |s| {
17291 s.move_offsets_with(&mut |snapshot, selection| {
17292 let Some(enclosing_bracket_ranges) =
17293 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17294 else {
17295 return;
17296 };
17297
17298 let mut best_length = usize::MAX;
17299 let mut best_inside = false;
17300 let mut best_in_bracket_range = false;
17301 let mut best_destination = None;
17302 for (open, close) in enclosing_bracket_ranges {
17303 let close = close.to_inclusive();
17304 let length = *close.end() - open.start;
17305 let inside = selection.start >= open.end && selection.end <= *close.start();
17306 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17307 || close.contains(&selection.head());
17308
17309 // If best is next to a bracket and current isn't, skip
17310 if !in_bracket_range && best_in_bracket_range {
17311 continue;
17312 }
17313
17314 // Prefer smaller lengths unless best is inside and current isn't
17315 if length > best_length && (best_inside || !inside) {
17316 continue;
17317 }
17318
17319 best_length = length;
17320 best_inside = inside;
17321 best_in_bracket_range = in_bracket_range;
17322 best_destination = Some(
17323 if close.contains(&selection.start) && close.contains(&selection.end) {
17324 if inside { open.end } else { open.start }
17325 } else if inside {
17326 *close.start()
17327 } else {
17328 *close.end()
17329 },
17330 );
17331 }
17332
17333 if let Some(destination) = best_destination {
17334 selection.collapse_to(destination, SelectionGoal::None);
17335 }
17336 })
17337 });
17338 }
17339
17340 pub fn undo_selection(
17341 &mut self,
17342 _: &UndoSelection,
17343 window: &mut Window,
17344 cx: &mut Context<Self>,
17345 ) {
17346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17347 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17348 self.selection_history.mode = SelectionHistoryMode::Undoing;
17349 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17350 this.end_selection(window, cx);
17351 this.change_selections(
17352 SelectionEffects::scroll(Autoscroll::newest()),
17353 window,
17354 cx,
17355 |s| s.select_anchors(entry.selections.to_vec()),
17356 );
17357 });
17358 self.selection_history.mode = SelectionHistoryMode::Normal;
17359
17360 self.select_next_state = entry.select_next_state;
17361 self.select_prev_state = entry.select_prev_state;
17362 self.add_selections_state = entry.add_selections_state;
17363 }
17364 }
17365
17366 pub fn redo_selection(
17367 &mut self,
17368 _: &RedoSelection,
17369 window: &mut Window,
17370 cx: &mut Context<Self>,
17371 ) {
17372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17373 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17374 self.selection_history.mode = SelectionHistoryMode::Redoing;
17375 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17376 this.end_selection(window, cx);
17377 this.change_selections(
17378 SelectionEffects::scroll(Autoscroll::newest()),
17379 window,
17380 cx,
17381 |s| s.select_anchors(entry.selections.to_vec()),
17382 );
17383 });
17384 self.selection_history.mode = SelectionHistoryMode::Normal;
17385
17386 self.select_next_state = entry.select_next_state;
17387 self.select_prev_state = entry.select_prev_state;
17388 self.add_selections_state = entry.add_selections_state;
17389 }
17390 }
17391
17392 pub fn expand_excerpts(
17393 &mut self,
17394 action: &ExpandExcerpts,
17395 _: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) {
17398 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17399 }
17400
17401 pub fn expand_excerpts_down(
17402 &mut self,
17403 action: &ExpandExcerptsDown,
17404 _: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) {
17407 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17408 }
17409
17410 pub fn expand_excerpts_up(
17411 &mut self,
17412 action: &ExpandExcerptsUp,
17413 _: &mut Window,
17414 cx: &mut Context<Self>,
17415 ) {
17416 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17417 }
17418
17419 pub fn expand_excerpts_for_direction(
17420 &mut self,
17421 lines: u32,
17422 direction: ExpandExcerptDirection,
17423 cx: &mut Context<Self>,
17424 ) {
17425 let selections = self.selections.disjoint_anchors_arc();
17426
17427 let lines = if lines == 0 {
17428 EditorSettings::get_global(cx).expand_excerpt_lines
17429 } else {
17430 lines
17431 };
17432
17433 let snapshot = self.buffer.read(cx).snapshot(cx);
17434 let excerpt_ids = selections
17435 .iter()
17436 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17437 .unique()
17438 .sorted()
17439 .collect::<Vec<_>>();
17440
17441 if self.delegate_expand_excerpts {
17442 cx.emit(EditorEvent::ExpandExcerptsRequested {
17443 excerpt_ids,
17444 lines,
17445 direction,
17446 });
17447 return;
17448 }
17449
17450 self.buffer.update(cx, |buffer, cx| {
17451 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17452 })
17453 }
17454
17455 pub fn expand_excerpt(
17456 &mut self,
17457 excerpt: ExcerptId,
17458 direction: ExpandExcerptDirection,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17463
17464 if self.delegate_expand_excerpts {
17465 cx.emit(EditorEvent::ExpandExcerptsRequested {
17466 excerpt_ids: vec![excerpt],
17467 lines: lines_to_expand,
17468 direction,
17469 });
17470 return;
17471 }
17472
17473 let current_scroll_position = self.scroll_position(cx);
17474 let mut scroll = None;
17475
17476 if direction == ExpandExcerptDirection::Down {
17477 let multi_buffer = self.buffer.read(cx);
17478 let snapshot = multi_buffer.snapshot(cx);
17479 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17480 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17481 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17482 {
17483 let buffer_snapshot = buffer.read(cx).snapshot();
17484 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17485 let last_row = buffer_snapshot.max_point().row;
17486 let lines_below = last_row.saturating_sub(excerpt_end_row);
17487 if lines_below >= lines_to_expand {
17488 scroll = Some(
17489 current_scroll_position
17490 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17491 );
17492 }
17493 }
17494 }
17495 if direction == ExpandExcerptDirection::Up
17496 && self
17497 .buffer
17498 .read(cx)
17499 .snapshot(cx)
17500 .excerpt_before(excerpt)
17501 .is_none()
17502 {
17503 scroll = Some(current_scroll_position);
17504 }
17505
17506 self.buffer.update(cx, |buffer, cx| {
17507 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17508 });
17509
17510 if let Some(new_scroll_position) = scroll {
17511 self.set_scroll_position(new_scroll_position, window, cx);
17512 }
17513 }
17514
17515 pub fn go_to_singleton_buffer_point(
17516 &mut self,
17517 point: Point,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) {
17521 self.go_to_singleton_buffer_range(point..point, window, cx);
17522 }
17523
17524 pub fn go_to_singleton_buffer_range(
17525 &mut self,
17526 range: Range<Point>,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) {
17530 let multibuffer = self.buffer().read(cx);
17531 let Some(buffer) = multibuffer.as_singleton() else {
17532 return;
17533 };
17534 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17535 return;
17536 };
17537 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17538 return;
17539 };
17540 self.change_selections(
17541 SelectionEffects::default().nav_history(true),
17542 window,
17543 cx,
17544 |s| s.select_anchor_ranges([start..end]),
17545 );
17546 }
17547
17548 pub fn go_to_diagnostic(
17549 &mut self,
17550 action: &GoToDiagnostic,
17551 window: &mut Window,
17552 cx: &mut Context<Self>,
17553 ) {
17554 if !self.diagnostics_enabled() {
17555 return;
17556 }
17557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17558 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17559 }
17560
17561 pub fn go_to_prev_diagnostic(
17562 &mut self,
17563 action: &GoToPreviousDiagnostic,
17564 window: &mut Window,
17565 cx: &mut Context<Self>,
17566 ) {
17567 if !self.diagnostics_enabled() {
17568 return;
17569 }
17570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17571 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17572 }
17573
17574 pub fn go_to_diagnostic_impl(
17575 &mut self,
17576 direction: Direction,
17577 severity: GoToDiagnosticSeverityFilter,
17578 window: &mut Window,
17579 cx: &mut Context<Self>,
17580 ) {
17581 let buffer = self.buffer.read(cx).snapshot(cx);
17582 let selection = self
17583 .selections
17584 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17585
17586 let mut active_group_id = None;
17587 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17588 && active_group.active_range.start.to_offset(&buffer) == selection.start
17589 {
17590 active_group_id = Some(active_group.group_id);
17591 }
17592
17593 fn filtered<'a>(
17594 severity: GoToDiagnosticSeverityFilter,
17595 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17596 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17597 diagnostics
17598 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17599 .filter(|entry| entry.range.start != entry.range.end)
17600 .filter(|entry| !entry.diagnostic.is_unnecessary)
17601 }
17602
17603 let before = filtered(
17604 severity,
17605 buffer
17606 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17607 .filter(|entry| entry.range.start <= selection.start),
17608 );
17609 let after = filtered(
17610 severity,
17611 buffer
17612 .diagnostics_in_range(selection.start..buffer.len())
17613 .filter(|entry| entry.range.start >= selection.start),
17614 );
17615
17616 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17617 if direction == Direction::Prev {
17618 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17619 {
17620 for diagnostic in prev_diagnostics.into_iter().rev() {
17621 if diagnostic.range.start != selection.start
17622 || active_group_id
17623 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17624 {
17625 found = Some(diagnostic);
17626 break 'outer;
17627 }
17628 }
17629 }
17630 } else {
17631 for diagnostic in after.chain(before) {
17632 if diagnostic.range.start != selection.start
17633 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17634 {
17635 found = Some(diagnostic);
17636 break;
17637 }
17638 }
17639 }
17640 let Some(next_diagnostic) = found else {
17641 return;
17642 };
17643
17644 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17645 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17646 return;
17647 };
17648 let snapshot = self.snapshot(window, cx);
17649 if snapshot.intersects_fold(next_diagnostic.range.start) {
17650 self.unfold_ranges(
17651 std::slice::from_ref(&next_diagnostic.range),
17652 true,
17653 false,
17654 cx,
17655 );
17656 }
17657 self.change_selections(Default::default(), window, cx, |s| {
17658 s.select_ranges(vec![
17659 next_diagnostic.range.start..next_diagnostic.range.start,
17660 ])
17661 });
17662 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17663 self.refresh_edit_prediction(false, true, window, cx);
17664 }
17665
17666 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17668 let snapshot = self.snapshot(window, cx);
17669 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17670 self.go_to_hunk_before_or_after_position(
17671 &snapshot,
17672 selection.head(),
17673 Direction::Next,
17674 window,
17675 cx,
17676 );
17677 }
17678
17679 pub fn go_to_hunk_before_or_after_position(
17680 &mut self,
17681 snapshot: &EditorSnapshot,
17682 position: Point,
17683 direction: Direction,
17684 window: &mut Window,
17685 cx: &mut Context<Editor>,
17686 ) {
17687 let row = if direction == Direction::Next {
17688 self.hunk_after_position(snapshot, position)
17689 .map(|hunk| hunk.row_range.start)
17690 } else {
17691 self.hunk_before_position(snapshot, position)
17692 };
17693
17694 if let Some(row) = row {
17695 let destination = Point::new(row.0, 0);
17696 let autoscroll = Autoscroll::center();
17697
17698 self.unfold_ranges(&[destination..destination], false, false, cx);
17699 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17700 s.select_ranges([destination..destination]);
17701 });
17702 }
17703 }
17704
17705 fn hunk_after_position(
17706 &mut self,
17707 snapshot: &EditorSnapshot,
17708 position: Point,
17709 ) -> Option<MultiBufferDiffHunk> {
17710 snapshot
17711 .buffer_snapshot()
17712 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17713 .find(|hunk| hunk.row_range.start.0 > position.row)
17714 .or_else(|| {
17715 snapshot
17716 .buffer_snapshot()
17717 .diff_hunks_in_range(Point::zero()..position)
17718 .find(|hunk| hunk.row_range.end.0 < position.row)
17719 })
17720 }
17721
17722 fn go_to_prev_hunk(
17723 &mut self,
17724 _: &GoToPreviousHunk,
17725 window: &mut Window,
17726 cx: &mut Context<Self>,
17727 ) {
17728 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17729 let snapshot = self.snapshot(window, cx);
17730 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17731 self.go_to_hunk_before_or_after_position(
17732 &snapshot,
17733 selection.head(),
17734 Direction::Prev,
17735 window,
17736 cx,
17737 );
17738 }
17739
17740 fn hunk_before_position(
17741 &mut self,
17742 snapshot: &EditorSnapshot,
17743 position: Point,
17744 ) -> Option<MultiBufferRow> {
17745 snapshot
17746 .buffer_snapshot()
17747 .diff_hunk_before(position)
17748 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17749 }
17750
17751 fn go_to_next_change(
17752 &mut self,
17753 _: &GoToNextChange,
17754 window: &mut Window,
17755 cx: &mut Context<Self>,
17756 ) {
17757 if let Some(selections) = self
17758 .change_list
17759 .next_change(1, Direction::Next)
17760 .map(|s| s.to_vec())
17761 {
17762 self.change_selections(Default::default(), window, cx, |s| {
17763 let map = s.display_snapshot();
17764 s.select_display_ranges(selections.iter().map(|a| {
17765 let point = a.to_display_point(&map);
17766 point..point
17767 }))
17768 })
17769 }
17770 }
17771
17772 fn go_to_previous_change(
17773 &mut self,
17774 _: &GoToPreviousChange,
17775 window: &mut Window,
17776 cx: &mut Context<Self>,
17777 ) {
17778 if let Some(selections) = self
17779 .change_list
17780 .next_change(1, Direction::Prev)
17781 .map(|s| s.to_vec())
17782 {
17783 self.change_selections(Default::default(), window, cx, |s| {
17784 let map = s.display_snapshot();
17785 s.select_display_ranges(selections.iter().map(|a| {
17786 let point = a.to_display_point(&map);
17787 point..point
17788 }))
17789 })
17790 }
17791 }
17792
17793 pub fn go_to_next_document_highlight(
17794 &mut self,
17795 _: &GoToNextDocumentHighlight,
17796 window: &mut Window,
17797 cx: &mut Context<Self>,
17798 ) {
17799 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17800 }
17801
17802 pub fn go_to_prev_document_highlight(
17803 &mut self,
17804 _: &GoToPreviousDocumentHighlight,
17805 window: &mut Window,
17806 cx: &mut Context<Self>,
17807 ) {
17808 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17809 }
17810
17811 pub fn go_to_document_highlight_before_or_after_position(
17812 &mut self,
17813 direction: Direction,
17814 window: &mut Window,
17815 cx: &mut Context<Editor>,
17816 ) {
17817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17818 let snapshot = self.snapshot(window, cx);
17819 let buffer = &snapshot.buffer_snapshot();
17820 let position = self
17821 .selections
17822 .newest::<Point>(&snapshot.display_snapshot)
17823 .head();
17824 let anchor_position = buffer.anchor_after(position);
17825
17826 // Get all document highlights (both read and write)
17827 let mut all_highlights = Vec::new();
17828
17829 if let Some((_, read_highlights)) = self
17830 .background_highlights
17831 .get(&HighlightKey::DocumentHighlightRead)
17832 {
17833 all_highlights.extend(read_highlights.iter());
17834 }
17835
17836 if let Some((_, write_highlights)) = self
17837 .background_highlights
17838 .get(&HighlightKey::DocumentHighlightWrite)
17839 {
17840 all_highlights.extend(write_highlights.iter());
17841 }
17842
17843 if all_highlights.is_empty() {
17844 return;
17845 }
17846
17847 // Sort highlights by position
17848 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17849
17850 let target_highlight = match direction {
17851 Direction::Next => {
17852 // Find the first highlight after the current position
17853 all_highlights
17854 .iter()
17855 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17856 }
17857 Direction::Prev => {
17858 // Find the last highlight before the current position
17859 all_highlights
17860 .iter()
17861 .rev()
17862 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17863 }
17864 };
17865
17866 if let Some(highlight) = target_highlight {
17867 let destination = highlight.start.to_point(buffer);
17868 let autoscroll = Autoscroll::center();
17869
17870 self.unfold_ranges(&[destination..destination], false, false, cx);
17871 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17872 s.select_ranges([destination..destination]);
17873 });
17874 }
17875 }
17876
17877 fn go_to_line<T: 'static>(
17878 &mut self,
17879 position: Anchor,
17880 highlight_color: Option<Hsla>,
17881 window: &mut Window,
17882 cx: &mut Context<Self>,
17883 ) {
17884 let snapshot = self.snapshot(window, cx).display_snapshot;
17885 let position = position.to_point(&snapshot.buffer_snapshot());
17886 let start = snapshot
17887 .buffer_snapshot()
17888 .clip_point(Point::new(position.row, 0), Bias::Left);
17889 let end = start + Point::new(1, 0);
17890 let start = snapshot.buffer_snapshot().anchor_before(start);
17891 let end = snapshot.buffer_snapshot().anchor_before(end);
17892
17893 self.highlight_rows::<T>(
17894 start..end,
17895 highlight_color
17896 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17897 Default::default(),
17898 cx,
17899 );
17900
17901 if self.buffer.read(cx).is_singleton() {
17902 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17903 }
17904 }
17905
17906 pub fn go_to_definition(
17907 &mut self,
17908 _: &GoToDefinition,
17909 window: &mut Window,
17910 cx: &mut Context<Self>,
17911 ) -> Task<Result<Navigated>> {
17912 let definition =
17913 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17914 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17915 cx.spawn_in(window, async move |editor, cx| {
17916 if definition.await? == Navigated::Yes {
17917 return Ok(Navigated::Yes);
17918 }
17919 match fallback_strategy {
17920 GoToDefinitionFallback::None => Ok(Navigated::No),
17921 GoToDefinitionFallback::FindAllReferences => {
17922 match editor.update_in(cx, |editor, window, cx| {
17923 editor.find_all_references(&FindAllReferences::default(), window, cx)
17924 })? {
17925 Some(references) => references.await,
17926 None => Ok(Navigated::No),
17927 }
17928 }
17929 }
17930 })
17931 }
17932
17933 pub fn go_to_declaration(
17934 &mut self,
17935 _: &GoToDeclaration,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) -> Task<Result<Navigated>> {
17939 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17940 }
17941
17942 pub fn go_to_declaration_split(
17943 &mut self,
17944 _: &GoToDeclaration,
17945 window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) -> Task<Result<Navigated>> {
17948 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17949 }
17950
17951 pub fn go_to_implementation(
17952 &mut self,
17953 _: &GoToImplementation,
17954 window: &mut Window,
17955 cx: &mut Context<Self>,
17956 ) -> Task<Result<Navigated>> {
17957 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17958 }
17959
17960 pub fn go_to_implementation_split(
17961 &mut self,
17962 _: &GoToImplementationSplit,
17963 window: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) -> Task<Result<Navigated>> {
17966 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17967 }
17968
17969 pub fn go_to_type_definition(
17970 &mut self,
17971 _: &GoToTypeDefinition,
17972 window: &mut Window,
17973 cx: &mut Context<Self>,
17974 ) -> Task<Result<Navigated>> {
17975 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17976 }
17977
17978 pub fn go_to_definition_split(
17979 &mut self,
17980 _: &GoToDefinitionSplit,
17981 window: &mut Window,
17982 cx: &mut Context<Self>,
17983 ) -> Task<Result<Navigated>> {
17984 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17985 }
17986
17987 pub fn go_to_type_definition_split(
17988 &mut self,
17989 _: &GoToTypeDefinitionSplit,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) -> Task<Result<Navigated>> {
17993 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17994 }
17995
17996 fn go_to_definition_of_kind(
17997 &mut self,
17998 kind: GotoDefinitionKind,
17999 split: bool,
18000 window: &mut Window,
18001 cx: &mut Context<Self>,
18002 ) -> Task<Result<Navigated>> {
18003 let Some(provider) = self.semantics_provider.clone() else {
18004 return Task::ready(Ok(Navigated::No));
18005 };
18006 let head = self
18007 .selections
18008 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18009 .head();
18010 let buffer = self.buffer.read(cx);
18011 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18012 return Task::ready(Ok(Navigated::No));
18013 };
18014 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18015 return Task::ready(Ok(Navigated::No));
18016 };
18017
18018 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18019
18020 cx.spawn_in(window, async move |editor, cx| {
18021 let Some(definitions) = definitions.await? else {
18022 return Ok(Navigated::No);
18023 };
18024 let navigated = editor
18025 .update_in(cx, |editor, window, cx| {
18026 editor.navigate_to_hover_links(
18027 Some(kind),
18028 definitions
18029 .into_iter()
18030 .filter(|location| {
18031 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18032 })
18033 .map(HoverLink::Text)
18034 .collect::<Vec<_>>(),
18035 nav_entry,
18036 split,
18037 window,
18038 cx,
18039 )
18040 })?
18041 .await?;
18042 anyhow::Ok(navigated)
18043 })
18044 }
18045
18046 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18047 let selection = self.selections.newest_anchor();
18048 let head = selection.head();
18049 let tail = selection.tail();
18050
18051 let Some((buffer, start_position)) =
18052 self.buffer.read(cx).text_anchor_for_position(head, cx)
18053 else {
18054 return;
18055 };
18056
18057 let end_position = if head != tail {
18058 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18059 return;
18060 };
18061 Some(pos)
18062 } else {
18063 None
18064 };
18065
18066 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18067 let url = if let Some(end_pos) = end_position {
18068 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18069 } else {
18070 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18071 };
18072
18073 if let Some(url) = url {
18074 cx.update(|window, cx| {
18075 if parse_zed_link(&url, cx).is_some() {
18076 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18077 } else {
18078 cx.open_url(&url);
18079 }
18080 })?;
18081 }
18082
18083 anyhow::Ok(())
18084 });
18085
18086 url_finder.detach();
18087 }
18088
18089 pub fn open_selected_filename(
18090 &mut self,
18091 _: &OpenSelectedFilename,
18092 window: &mut Window,
18093 cx: &mut Context<Self>,
18094 ) {
18095 let Some(workspace) = self.workspace() else {
18096 return;
18097 };
18098
18099 let position = self.selections.newest_anchor().head();
18100
18101 let Some((buffer, buffer_position)) =
18102 self.buffer.read(cx).text_anchor_for_position(position, cx)
18103 else {
18104 return;
18105 };
18106
18107 let project = self.project.clone();
18108
18109 cx.spawn_in(window, async move |_, cx| {
18110 let result = find_file(&buffer, project, buffer_position, cx).await;
18111
18112 if let Some((_, path)) = result {
18113 workspace
18114 .update_in(cx, |workspace, window, cx| {
18115 workspace.open_resolved_path(path, window, cx)
18116 })?
18117 .await?;
18118 }
18119 anyhow::Ok(())
18120 })
18121 .detach();
18122 }
18123
18124 pub(crate) fn navigate_to_hover_links(
18125 &mut self,
18126 kind: Option<GotoDefinitionKind>,
18127 definitions: Vec<HoverLink>,
18128 origin: Option<NavigationEntry>,
18129 split: bool,
18130 window: &mut Window,
18131 cx: &mut Context<Editor>,
18132 ) -> Task<Result<Navigated>> {
18133 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18134 let mut first_url_or_file = None;
18135 let definitions: Vec<_> = definitions
18136 .into_iter()
18137 .filter_map(|def| match def {
18138 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18139 HoverLink::InlayHint(lsp_location, server_id) => {
18140 let computation =
18141 self.compute_target_location(lsp_location, server_id, window, cx);
18142 Some(cx.background_spawn(computation))
18143 }
18144 HoverLink::Url(url) => {
18145 first_url_or_file = Some(Either::Left(url));
18146 None
18147 }
18148 HoverLink::File(path) => {
18149 first_url_or_file = Some(Either::Right(path));
18150 None
18151 }
18152 })
18153 .collect();
18154
18155 let workspace = self.workspace();
18156
18157 cx.spawn_in(window, async move |editor, cx| {
18158 let locations: Vec<Location> = future::join_all(definitions)
18159 .await
18160 .into_iter()
18161 .filter_map(|location| location.transpose())
18162 .collect::<Result<_>>()
18163 .context("location tasks")?;
18164 let mut locations = cx.update(|_, cx| {
18165 locations
18166 .into_iter()
18167 .map(|location| {
18168 let buffer = location.buffer.read(cx);
18169 (location.buffer, location.range.to_point(buffer))
18170 })
18171 .into_group_map()
18172 })?;
18173 let mut num_locations = 0;
18174 for ranges in locations.values_mut() {
18175 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18176 ranges.dedup();
18177 num_locations += ranges.len();
18178 }
18179
18180 if num_locations > 1 {
18181 let tab_kind = match kind {
18182 Some(GotoDefinitionKind::Implementation) => "Implementations",
18183 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18184 Some(GotoDefinitionKind::Declaration) => "Declarations",
18185 Some(GotoDefinitionKind::Type) => "Types",
18186 };
18187 let title = editor
18188 .update_in(cx, |_, _, cx| {
18189 let target = locations
18190 .iter()
18191 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18192 .map(|(buffer, location)| {
18193 buffer
18194 .read(cx)
18195 .text_for_range(location.clone())
18196 .collect::<String>()
18197 })
18198 .filter(|text| !text.contains('\n'))
18199 .unique()
18200 .take(3)
18201 .join(", ");
18202 if target.is_empty() {
18203 tab_kind.to_owned()
18204 } else {
18205 format!("{tab_kind} for {target}")
18206 }
18207 })
18208 .context("buffer title")?;
18209
18210 let Some(workspace) = workspace else {
18211 return Ok(Navigated::No);
18212 };
18213
18214 let opened = workspace
18215 .update_in(cx, |workspace, window, cx| {
18216 let allow_preview = PreviewTabsSettings::get_global(cx)
18217 .enable_preview_multibuffer_from_code_navigation;
18218 if let Some((target_editor, target_pane)) =
18219 Self::open_locations_in_multibuffer(
18220 workspace,
18221 locations,
18222 title,
18223 split,
18224 allow_preview,
18225 MultibufferSelectionMode::First,
18226 window,
18227 cx,
18228 )
18229 {
18230 // We create our own nav history instead of using
18231 // `target_editor.nav_history` because `nav_history`
18232 // seems to be populated asynchronously when an item
18233 // is added to a pane
18234 let mut nav_history = target_pane
18235 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18236 target_editor.update(cx, |editor, cx| {
18237 let nav_data = editor
18238 .navigation_data(editor.selections.newest_anchor().head(), cx);
18239 let target =
18240 Some(nav_history.navigation_entry(Some(
18241 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18242 )));
18243 nav_history.push_tag(origin, target);
18244 })
18245 }
18246 })
18247 .is_ok();
18248
18249 anyhow::Ok(Navigated::from_bool(opened))
18250 } else if num_locations == 0 {
18251 // If there is one url or file, open it directly
18252 match first_url_or_file {
18253 Some(Either::Left(url)) => {
18254 cx.update(|window, cx| {
18255 if parse_zed_link(&url, cx).is_some() {
18256 window
18257 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18258 } else {
18259 cx.open_url(&url);
18260 }
18261 })?;
18262 Ok(Navigated::Yes)
18263 }
18264 Some(Either::Right(path)) => {
18265 // TODO(andrew): respect preview tab settings
18266 // `enable_keep_preview_on_code_navigation` and
18267 // `enable_preview_file_from_code_navigation`
18268 let Some(workspace) = workspace else {
18269 return Ok(Navigated::No);
18270 };
18271 workspace
18272 .update_in(cx, |workspace, window, cx| {
18273 workspace.open_resolved_path(path, window, cx)
18274 })?
18275 .await?;
18276 Ok(Navigated::Yes)
18277 }
18278 None => Ok(Navigated::No),
18279 }
18280 } else {
18281 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18282 let target_range = target_ranges.first().unwrap().clone();
18283
18284 editor.update_in(cx, |editor, window, cx| {
18285 let range = editor.range_for_match(&target_range);
18286 let range = collapse_multiline_range(range);
18287
18288 if !split
18289 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18290 {
18291 editor.go_to_singleton_buffer_range(range, window, cx);
18292
18293 let target =
18294 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18295 if let Some(mut nav_history) = editor.nav_history.clone() {
18296 nav_history.push_tag(origin, target);
18297 }
18298 } else {
18299 let Some(workspace) = workspace else {
18300 return Navigated::No;
18301 };
18302 let pane = workspace.read(cx).active_pane().clone();
18303 window.defer(cx, move |window, cx| {
18304 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18305 workspace.update(cx, |workspace, cx| {
18306 let pane = if split {
18307 workspace.adjacent_pane(window, cx)
18308 } else {
18309 workspace.active_pane().clone()
18310 };
18311
18312 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18313 let keep_old_preview = preview_tabs_settings
18314 .enable_keep_preview_on_code_navigation;
18315 let allow_new_preview = preview_tabs_settings
18316 .enable_preview_file_from_code_navigation;
18317
18318 let editor = workspace.open_project_item(
18319 pane.clone(),
18320 target_buffer.clone(),
18321 true,
18322 true,
18323 keep_old_preview,
18324 allow_new_preview,
18325 window,
18326 cx,
18327 );
18328 (editor, pane)
18329 });
18330 // We create our own nav history instead of using
18331 // `target_editor.nav_history` because `nav_history`
18332 // seems to be populated asynchronously when an item
18333 // is added to a pane
18334 let mut nav_history = target_pane
18335 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18336 target_editor.update(cx, |target_editor, cx| {
18337 // When selecting a definition in a different buffer, disable the nav history
18338 // to avoid creating a history entry at the previous cursor location.
18339 pane.update(cx, |pane, _| pane.disable_history());
18340 target_editor.go_to_singleton_buffer_range(range, window, cx);
18341
18342 let nav_data = target_editor.navigation_data(
18343 target_editor.selections.newest_anchor().head(),
18344 cx,
18345 );
18346 let target =
18347 Some(nav_history.navigation_entry(Some(
18348 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18349 )));
18350 nav_history.push_tag(origin, target);
18351 pane.update(cx, |pane, _| pane.enable_history());
18352 });
18353 });
18354 }
18355 Navigated::Yes
18356 })
18357 }
18358 })
18359 }
18360
18361 fn compute_target_location(
18362 &self,
18363 lsp_location: lsp::Location,
18364 server_id: LanguageServerId,
18365 window: &mut Window,
18366 cx: &mut Context<Self>,
18367 ) -> Task<anyhow::Result<Option<Location>>> {
18368 let Some(project) = self.project.clone() else {
18369 return Task::ready(Ok(None));
18370 };
18371
18372 cx.spawn_in(window, async move |editor, cx| {
18373 let location_task = editor.update(cx, |_, cx| {
18374 project.update(cx, |project, cx| {
18375 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18376 })
18377 })?;
18378 let location = Some({
18379 let target_buffer_handle = location_task.await.context("open local buffer")?;
18380 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18381 let target_start = target_buffer
18382 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18383 let target_end = target_buffer
18384 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18385 target_buffer.anchor_after(target_start)
18386 ..target_buffer.anchor_before(target_end)
18387 });
18388 Location {
18389 buffer: target_buffer_handle,
18390 range,
18391 }
18392 });
18393 Ok(location)
18394 })
18395 }
18396
18397 fn go_to_next_reference(
18398 &mut self,
18399 _: &GoToNextReference,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) {
18403 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18404 if let Some(task) = task {
18405 task.detach();
18406 };
18407 }
18408
18409 fn go_to_prev_reference(
18410 &mut self,
18411 _: &GoToPreviousReference,
18412 window: &mut Window,
18413 cx: &mut Context<Self>,
18414 ) {
18415 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18416 if let Some(task) = task {
18417 task.detach();
18418 };
18419 }
18420
18421 pub fn go_to_reference_before_or_after_position(
18422 &mut self,
18423 direction: Direction,
18424 count: usize,
18425 window: &mut Window,
18426 cx: &mut Context<Self>,
18427 ) -> Option<Task<Result<()>>> {
18428 let selection = self.selections.newest_anchor();
18429 let head = selection.head();
18430
18431 let multi_buffer = self.buffer.read(cx);
18432
18433 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18434 let workspace = self.workspace()?;
18435 let project = workspace.read(cx).project().clone();
18436 let references =
18437 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18438 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18439 let Some(locations) = references.await? else {
18440 return Ok(());
18441 };
18442
18443 if locations.is_empty() {
18444 // totally normal - the cursor may be on something which is not
18445 // a symbol (e.g. a keyword)
18446 log::info!("no references found under cursor");
18447 return Ok(());
18448 }
18449
18450 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18451
18452 let (locations, current_location_index) =
18453 multi_buffer.update(cx, |multi_buffer, cx| {
18454 let mut locations = locations
18455 .into_iter()
18456 .filter_map(|loc| {
18457 let start = multi_buffer.buffer_anchor_to_anchor(
18458 &loc.buffer,
18459 loc.range.start,
18460 cx,
18461 )?;
18462 let end = multi_buffer.buffer_anchor_to_anchor(
18463 &loc.buffer,
18464 loc.range.end,
18465 cx,
18466 )?;
18467 Some(start..end)
18468 })
18469 .collect::<Vec<_>>();
18470
18471 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18472 // There is an O(n) implementation, but given this list will be
18473 // small (usually <100 items), the extra O(log(n)) factor isn't
18474 // worth the (surprisingly large amount of) extra complexity.
18475 locations
18476 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18477
18478 let head_offset = head.to_offset(&multi_buffer_snapshot);
18479
18480 let current_location_index = locations.iter().position(|loc| {
18481 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18482 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18483 });
18484
18485 (locations, current_location_index)
18486 });
18487
18488 let Some(current_location_index) = current_location_index else {
18489 // This indicates something has gone wrong, because we already
18490 // handle the "no references" case above
18491 log::error!(
18492 "failed to find current reference under cursor. Total references: {}",
18493 locations.len()
18494 );
18495 return Ok(());
18496 };
18497
18498 let destination_location_index = match direction {
18499 Direction::Next => (current_location_index + count) % locations.len(),
18500 Direction::Prev => {
18501 (current_location_index + locations.len() - count % locations.len())
18502 % locations.len()
18503 }
18504 };
18505
18506 // TODO(cameron): is this needed?
18507 // the thinking is to avoid "jumping to the current location" (avoid
18508 // polluting "jumplist" in vim terms)
18509 if current_location_index == destination_location_index {
18510 return Ok(());
18511 }
18512
18513 let Range { start, end } = locations[destination_location_index];
18514
18515 editor.update_in(cx, |editor, window, cx| {
18516 let effects = SelectionEffects::default();
18517
18518 editor.unfold_ranges(&[start..end], false, false, cx);
18519 editor.change_selections(effects, window, cx, |s| {
18520 s.select_ranges([start..start]);
18521 });
18522 })?;
18523
18524 Ok(())
18525 }))
18526 }
18527
18528 pub fn find_all_references(
18529 &mut self,
18530 action: &FindAllReferences,
18531 window: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) -> Option<Task<Result<Navigated>>> {
18534 let always_open_multibuffer = action.always_open_multibuffer;
18535 let selection = self.selections.newest_anchor();
18536 let multi_buffer = self.buffer.read(cx);
18537 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18538 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18539 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18540 let head = selection_offset.head();
18541
18542 let head_anchor = multi_buffer_snapshot.anchor_at(
18543 head,
18544 if head < selection_offset.tail() {
18545 Bias::Right
18546 } else {
18547 Bias::Left
18548 },
18549 );
18550
18551 match self
18552 .find_all_references_task_sources
18553 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18554 {
18555 Ok(_) => {
18556 log::info!(
18557 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18558 );
18559 return None;
18560 }
18561 Err(i) => {
18562 self.find_all_references_task_sources.insert(i, head_anchor);
18563 }
18564 }
18565
18566 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18567 let workspace = self.workspace()?;
18568 let project = workspace.read(cx).project().clone();
18569 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18570 Some(cx.spawn_in(window, async move |editor, cx| {
18571 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18572 if let Ok(i) = editor
18573 .find_all_references_task_sources
18574 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18575 {
18576 editor.find_all_references_task_sources.remove(i);
18577 }
18578 });
18579
18580 let Some(locations) = references.await? else {
18581 return anyhow::Ok(Navigated::No);
18582 };
18583 let mut locations = cx.update(|_, cx| {
18584 locations
18585 .into_iter()
18586 .map(|location| {
18587 let buffer = location.buffer.read(cx);
18588 (location.buffer, location.range.to_point(buffer))
18589 })
18590 // if special-casing the single-match case, remove ranges
18591 // that intersect current selection
18592 .filter(|(location_buffer, location)| {
18593 if always_open_multibuffer || &buffer != location_buffer {
18594 return true;
18595 }
18596
18597 !location.contains_inclusive(&selection_point.range())
18598 })
18599 .into_group_map()
18600 })?;
18601 if locations.is_empty() {
18602 return anyhow::Ok(Navigated::No);
18603 }
18604 for ranges in locations.values_mut() {
18605 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18606 ranges.dedup();
18607 }
18608 let mut num_locations = 0;
18609 for ranges in locations.values_mut() {
18610 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18611 ranges.dedup();
18612 num_locations += ranges.len();
18613 }
18614
18615 if num_locations == 1 && !always_open_multibuffer {
18616 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18617 let target_range = target_ranges.first().unwrap().clone();
18618
18619 return editor.update_in(cx, |editor, window, cx| {
18620 let range = target_range.to_point(target_buffer.read(cx));
18621 let range = editor.range_for_match(&range);
18622 let range = range.start..range.start;
18623
18624 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18625 editor.go_to_singleton_buffer_range(range, window, cx);
18626 } else {
18627 let pane = workspace.read(cx).active_pane().clone();
18628 window.defer(cx, move |window, cx| {
18629 let target_editor: Entity<Self> =
18630 workspace.update(cx, |workspace, cx| {
18631 let pane = workspace.active_pane().clone();
18632
18633 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18634 let keep_old_preview = preview_tabs_settings
18635 .enable_keep_preview_on_code_navigation;
18636 let allow_new_preview = preview_tabs_settings
18637 .enable_preview_file_from_code_navigation;
18638
18639 workspace.open_project_item(
18640 pane,
18641 target_buffer.clone(),
18642 true,
18643 true,
18644 keep_old_preview,
18645 allow_new_preview,
18646 window,
18647 cx,
18648 )
18649 });
18650 target_editor.update(cx, |target_editor, cx| {
18651 // When selecting a definition in a different buffer, disable the nav history
18652 // to avoid creating a history entry at the previous cursor location.
18653 pane.update(cx, |pane, _| pane.disable_history());
18654 target_editor.go_to_singleton_buffer_range(range, window, cx);
18655 pane.update(cx, |pane, _| pane.enable_history());
18656 });
18657 });
18658 }
18659 Navigated::No
18660 });
18661 }
18662
18663 workspace.update_in(cx, |workspace, window, cx| {
18664 let target = locations
18665 .iter()
18666 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18667 .map(|(buffer, location)| {
18668 buffer
18669 .read(cx)
18670 .text_for_range(location.clone())
18671 .collect::<String>()
18672 })
18673 .filter(|text| !text.contains('\n'))
18674 .unique()
18675 .take(3)
18676 .join(", ");
18677 let title = if target.is_empty() {
18678 "References".to_owned()
18679 } else {
18680 format!("References to {target}")
18681 };
18682 let allow_preview = PreviewTabsSettings::get_global(cx)
18683 .enable_preview_multibuffer_from_code_navigation;
18684 Self::open_locations_in_multibuffer(
18685 workspace,
18686 locations,
18687 title,
18688 false,
18689 allow_preview,
18690 MultibufferSelectionMode::First,
18691 window,
18692 cx,
18693 );
18694 Navigated::Yes
18695 })
18696 }))
18697 }
18698
18699 /// Opens a multibuffer with the given project locations in it.
18700 pub fn open_locations_in_multibuffer(
18701 workspace: &mut Workspace,
18702 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18703 title: String,
18704 split: bool,
18705 allow_preview: bool,
18706 multibuffer_selection_mode: MultibufferSelectionMode,
18707 window: &mut Window,
18708 cx: &mut Context<Workspace>,
18709 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18710 if locations.is_empty() {
18711 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18712 return None;
18713 }
18714
18715 let capability = workspace.project().read(cx).capability();
18716 let mut ranges = <Vec<Range<Anchor>>>::new();
18717
18718 // a key to find existing multibuffer editors with the same set of locations
18719 // to prevent us from opening more and more multibuffer tabs for searches and the like
18720 let mut key = (title.clone(), vec![]);
18721 let excerpt_buffer = cx.new(|cx| {
18722 let key = &mut key.1;
18723 let mut multibuffer = MultiBuffer::new(capability);
18724 for (buffer, mut ranges_for_buffer) in locations {
18725 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18726 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18727 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18728 PathKey::for_buffer(&buffer, cx),
18729 buffer.clone(),
18730 ranges_for_buffer,
18731 multibuffer_context_lines(cx),
18732 cx,
18733 );
18734 ranges.extend(new_ranges)
18735 }
18736
18737 multibuffer.with_title(title)
18738 });
18739 let existing = workspace.active_pane().update(cx, |pane, cx| {
18740 pane.items()
18741 .filter_map(|item| item.downcast::<Editor>())
18742 .find(|editor| {
18743 editor
18744 .read(cx)
18745 .lookup_key
18746 .as_ref()
18747 .and_then(|it| {
18748 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18749 })
18750 .is_some_and(|it| *it == key)
18751 })
18752 });
18753 let was_existing = existing.is_some();
18754 let editor = existing.unwrap_or_else(|| {
18755 cx.new(|cx| {
18756 let mut editor = Editor::for_multibuffer(
18757 excerpt_buffer,
18758 Some(workspace.project().clone()),
18759 window,
18760 cx,
18761 );
18762 editor.lookup_key = Some(Box::new(key));
18763 editor
18764 })
18765 });
18766 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18767 MultibufferSelectionMode::First => {
18768 if let Some(first_range) = ranges.first() {
18769 editor.change_selections(
18770 SelectionEffects::no_scroll(),
18771 window,
18772 cx,
18773 |selections| {
18774 selections.clear_disjoint();
18775 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18776 },
18777 );
18778 }
18779 editor.highlight_background(
18780 HighlightKey::Editor,
18781 &ranges,
18782 |_, theme| theme.colors().editor_highlighted_line_background,
18783 cx,
18784 );
18785 }
18786 MultibufferSelectionMode::All => {
18787 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18788 selections.clear_disjoint();
18789 selections.select_anchor_ranges(ranges);
18790 });
18791 }
18792 });
18793
18794 let item = Box::new(editor.clone());
18795
18796 let pane = if split {
18797 workspace.adjacent_pane(window, cx)
18798 } else {
18799 workspace.active_pane().clone()
18800 };
18801 let activate_pane = split;
18802
18803 let mut destination_index = None;
18804 pane.update(cx, |pane, cx| {
18805 if allow_preview && !was_existing {
18806 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18807 }
18808 if was_existing && !allow_preview {
18809 pane.unpreview_item_if_preview(item.item_id());
18810 }
18811 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18812 });
18813
18814 Some((editor, pane))
18815 }
18816
18817 pub fn rename(
18818 &mut self,
18819 _: &Rename,
18820 window: &mut Window,
18821 cx: &mut Context<Self>,
18822 ) -> Option<Task<Result<()>>> {
18823 use language::ToOffset as _;
18824
18825 let provider = self.semantics_provider.clone()?;
18826 let selection = self.selections.newest_anchor().clone();
18827 let (cursor_buffer, cursor_buffer_position) = self
18828 .buffer
18829 .read(cx)
18830 .text_anchor_for_position(selection.head(), cx)?;
18831 let (tail_buffer, cursor_buffer_position_end) = self
18832 .buffer
18833 .read(cx)
18834 .text_anchor_for_position(selection.tail(), cx)?;
18835 if tail_buffer != cursor_buffer {
18836 return None;
18837 }
18838
18839 let snapshot = cursor_buffer.read(cx).snapshot();
18840 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18841 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18842 let prepare_rename = provider
18843 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18844 .unwrap_or_else(|| Task::ready(Ok(None)));
18845 drop(snapshot);
18846
18847 Some(cx.spawn_in(window, async move |this, cx| {
18848 let rename_range = if let Some(range) = prepare_rename.await? {
18849 Some(range)
18850 } else {
18851 this.update(cx, |this, cx| {
18852 let buffer = this.buffer.read(cx).snapshot(cx);
18853 let mut buffer_highlights = this
18854 .document_highlights_for_position(selection.head(), &buffer)
18855 .filter(|highlight| {
18856 highlight.start.excerpt_id == selection.head().excerpt_id
18857 && highlight.end.excerpt_id == selection.head().excerpt_id
18858 });
18859 buffer_highlights
18860 .next()
18861 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18862 })?
18863 };
18864 if let Some(rename_range) = rename_range {
18865 this.update_in(cx, |this, window, cx| {
18866 let snapshot = cursor_buffer.read(cx).snapshot();
18867 let rename_buffer_range = rename_range.to_offset(&snapshot);
18868 let cursor_offset_in_rename_range =
18869 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18870 let cursor_offset_in_rename_range_end =
18871 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18872
18873 this.take_rename(false, window, cx);
18874 let buffer = this.buffer.read(cx).read(cx);
18875 let cursor_offset = selection.head().to_offset(&buffer);
18876 let rename_start =
18877 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18878 let rename_end = rename_start + rename_buffer_range.len();
18879 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18880 let mut old_highlight_id = None;
18881 let old_name: Arc<str> = buffer
18882 .chunks(rename_start..rename_end, true)
18883 .map(|chunk| {
18884 if old_highlight_id.is_none() {
18885 old_highlight_id = chunk.syntax_highlight_id;
18886 }
18887 chunk.text
18888 })
18889 .collect::<String>()
18890 .into();
18891
18892 drop(buffer);
18893
18894 // Position the selection in the rename editor so that it matches the current selection.
18895 this.show_local_selections = false;
18896 let rename_editor = cx.new(|cx| {
18897 let mut editor = Editor::single_line(window, cx);
18898 editor.buffer.update(cx, |buffer, cx| {
18899 buffer.edit(
18900 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18901 None,
18902 cx,
18903 )
18904 });
18905 let cursor_offset_in_rename_range =
18906 MultiBufferOffset(cursor_offset_in_rename_range);
18907 let cursor_offset_in_rename_range_end =
18908 MultiBufferOffset(cursor_offset_in_rename_range_end);
18909 let rename_selection_range = match cursor_offset_in_rename_range
18910 .cmp(&cursor_offset_in_rename_range_end)
18911 {
18912 Ordering::Equal => {
18913 editor.select_all(&SelectAll, window, cx);
18914 return editor;
18915 }
18916 Ordering::Less => {
18917 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18918 }
18919 Ordering::Greater => {
18920 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18921 }
18922 };
18923 if rename_selection_range.end.0 > old_name.len() {
18924 editor.select_all(&SelectAll, window, cx);
18925 } else {
18926 editor.change_selections(Default::default(), window, cx, |s| {
18927 s.select_ranges([rename_selection_range]);
18928 });
18929 }
18930 editor
18931 });
18932 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18933 if e == &EditorEvent::Focused {
18934 cx.emit(EditorEvent::FocusedIn)
18935 }
18936 })
18937 .detach();
18938
18939 let write_highlights =
18940 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
18941 let read_highlights =
18942 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
18943 let ranges = write_highlights
18944 .iter()
18945 .flat_map(|(_, ranges)| ranges.iter())
18946 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18947 .cloned()
18948 .collect();
18949
18950 this.highlight_text(
18951 HighlightKey::Rename,
18952 ranges,
18953 HighlightStyle {
18954 fade_out: Some(0.6),
18955 ..Default::default()
18956 },
18957 cx,
18958 );
18959 let rename_focus_handle = rename_editor.focus_handle(cx);
18960 window.focus(&rename_focus_handle, cx);
18961 let block_id = this.insert_blocks(
18962 [BlockProperties {
18963 style: BlockStyle::Flex,
18964 placement: BlockPlacement::Below(range.start),
18965 height: Some(1),
18966 render: Arc::new({
18967 let rename_editor = rename_editor.clone();
18968 move |cx: &mut BlockContext| {
18969 let mut text_style = cx.editor_style.text.clone();
18970 if let Some(highlight_style) = old_highlight_id
18971 .and_then(|h| h.style(&cx.editor_style.syntax))
18972 {
18973 text_style = text_style.highlight(highlight_style);
18974 }
18975 div()
18976 .block_mouse_except_scroll()
18977 .pl(cx.anchor_x)
18978 .child(EditorElement::new(
18979 &rename_editor,
18980 EditorStyle {
18981 background: cx.theme().system().transparent,
18982 local_player: cx.editor_style.local_player,
18983 text: text_style,
18984 scrollbar_width: cx.editor_style.scrollbar_width,
18985 syntax: cx.editor_style.syntax.clone(),
18986 status: cx.editor_style.status.clone(),
18987 inlay_hints_style: HighlightStyle {
18988 font_weight: Some(FontWeight::BOLD),
18989 ..make_inlay_hints_style(cx.app)
18990 },
18991 edit_prediction_styles: make_suggestion_styles(
18992 cx.app,
18993 ),
18994 ..EditorStyle::default()
18995 },
18996 ))
18997 .into_any_element()
18998 }
18999 }),
19000 priority: 0,
19001 }],
19002 Some(Autoscroll::fit()),
19003 cx,
19004 )[0];
19005 this.pending_rename = Some(RenameState {
19006 range,
19007 old_name,
19008 editor: rename_editor,
19009 block_id,
19010 });
19011 })?;
19012 }
19013
19014 Ok(())
19015 }))
19016 }
19017
19018 pub fn confirm_rename(
19019 &mut self,
19020 _: &ConfirmRename,
19021 window: &mut Window,
19022 cx: &mut Context<Self>,
19023 ) -> Option<Task<Result<()>>> {
19024 let rename = self.take_rename(false, window, cx)?;
19025 let workspace = self.workspace()?.downgrade();
19026 let (buffer, start) = self
19027 .buffer
19028 .read(cx)
19029 .text_anchor_for_position(rename.range.start, cx)?;
19030 let (end_buffer, _) = self
19031 .buffer
19032 .read(cx)
19033 .text_anchor_for_position(rename.range.end, cx)?;
19034 if buffer != end_buffer {
19035 return None;
19036 }
19037
19038 let old_name = rename.old_name;
19039 let new_name = rename.editor.read(cx).text(cx);
19040
19041 let rename = self.semantics_provider.as_ref()?.perform_rename(
19042 &buffer,
19043 start,
19044 new_name.clone(),
19045 cx,
19046 )?;
19047
19048 Some(cx.spawn_in(window, async move |editor, cx| {
19049 let project_transaction = rename.await?;
19050 Self::open_project_transaction(
19051 &editor,
19052 workspace,
19053 project_transaction,
19054 format!("Rename: {} → {}", old_name, new_name),
19055 cx,
19056 )
19057 .await?;
19058
19059 editor.update(cx, |editor, cx| {
19060 editor.refresh_document_highlights(cx);
19061 })?;
19062 Ok(())
19063 }))
19064 }
19065
19066 fn take_rename(
19067 &mut self,
19068 moving_cursor: bool,
19069 window: &mut Window,
19070 cx: &mut Context<Self>,
19071 ) -> Option<RenameState> {
19072 let rename = self.pending_rename.take()?;
19073 if rename.editor.focus_handle(cx).is_focused(window) {
19074 window.focus(&self.focus_handle, cx);
19075 }
19076
19077 self.remove_blocks(
19078 [rename.block_id].into_iter().collect(),
19079 Some(Autoscroll::fit()),
19080 cx,
19081 );
19082 self.clear_highlights(HighlightKey::Rename, cx);
19083 self.show_local_selections = true;
19084
19085 if moving_cursor {
19086 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19087 editor
19088 .selections
19089 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19090 .head()
19091 });
19092
19093 // Update the selection to match the position of the selection inside
19094 // the rename editor.
19095 let snapshot = self.buffer.read(cx).read(cx);
19096 let rename_range = rename.range.to_offset(&snapshot);
19097 let cursor_in_editor = snapshot
19098 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19099 .min(rename_range.end);
19100 drop(snapshot);
19101
19102 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19103 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19104 });
19105 } else {
19106 self.refresh_document_highlights(cx);
19107 }
19108
19109 Some(rename)
19110 }
19111
19112 pub fn pending_rename(&self) -> Option<&RenameState> {
19113 self.pending_rename.as_ref()
19114 }
19115
19116 fn format(
19117 &mut self,
19118 _: &Format,
19119 window: &mut Window,
19120 cx: &mut Context<Self>,
19121 ) -> Option<Task<Result<()>>> {
19122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19123
19124 let project = match &self.project {
19125 Some(project) => project.clone(),
19126 None => return None,
19127 };
19128
19129 Some(self.perform_format(
19130 project,
19131 FormatTrigger::Manual,
19132 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19133 window,
19134 cx,
19135 ))
19136 }
19137
19138 fn format_selections(
19139 &mut self,
19140 _: &FormatSelections,
19141 window: &mut Window,
19142 cx: &mut Context<Self>,
19143 ) -> Option<Task<Result<()>>> {
19144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19145
19146 let project = match &self.project {
19147 Some(project) => project.clone(),
19148 None => return None,
19149 };
19150
19151 let ranges = self
19152 .selections
19153 .all_adjusted(&self.display_snapshot(cx))
19154 .into_iter()
19155 .map(|selection| selection.range())
19156 .collect_vec();
19157
19158 Some(self.perform_format(
19159 project,
19160 FormatTrigger::Manual,
19161 FormatTarget::Ranges(ranges),
19162 window,
19163 cx,
19164 ))
19165 }
19166
19167 fn perform_format(
19168 &mut self,
19169 project: Entity<Project>,
19170 trigger: FormatTrigger,
19171 target: FormatTarget,
19172 window: &mut Window,
19173 cx: &mut Context<Self>,
19174 ) -> Task<Result<()>> {
19175 let buffer = self.buffer.clone();
19176 let (buffers, target) = match target {
19177 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19178 FormatTarget::Ranges(selection_ranges) => {
19179 let multi_buffer = buffer.read(cx);
19180 let snapshot = multi_buffer.read(cx);
19181 let mut buffers = HashSet::default();
19182 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19183 BTreeMap::new();
19184 for selection_range in selection_ranges {
19185 for (buffer, buffer_range, _) in
19186 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19187 {
19188 let buffer_id = buffer.remote_id();
19189 let start = buffer.anchor_before(buffer_range.start);
19190 let end = buffer.anchor_after(buffer_range.end);
19191 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19192 buffer_id_to_ranges
19193 .entry(buffer_id)
19194 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19195 .or_insert_with(|| vec![start..end]);
19196 }
19197 }
19198 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19199 }
19200 };
19201
19202 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19203 let selections_prev = transaction_id_prev
19204 .and_then(|transaction_id_prev| {
19205 // default to selections as they were after the last edit, if we have them,
19206 // instead of how they are now.
19207 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19208 // will take you back to where you made the last edit, instead of staying where you scrolled
19209 self.selection_history
19210 .transaction(transaction_id_prev)
19211 .map(|t| t.0.clone())
19212 })
19213 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19214
19215 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19216 let format = project.update(cx, |project, cx| {
19217 project.format(buffers, target, true, trigger, cx)
19218 });
19219
19220 cx.spawn_in(window, async move |editor, cx| {
19221 let transaction = futures::select_biased! {
19222 transaction = format.log_err().fuse() => transaction,
19223 () = timeout => {
19224 log::warn!("timed out waiting for formatting");
19225 None
19226 }
19227 };
19228
19229 buffer.update(cx, |buffer, cx| {
19230 if let Some(transaction) = transaction
19231 && !buffer.is_singleton()
19232 {
19233 buffer.push_transaction(&transaction.0, cx);
19234 }
19235 cx.notify();
19236 });
19237
19238 if let Some(transaction_id_now) =
19239 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19240 {
19241 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19242 if has_new_transaction {
19243 editor
19244 .update(cx, |editor, _| {
19245 editor
19246 .selection_history
19247 .insert_transaction(transaction_id_now, selections_prev);
19248 })
19249 .ok();
19250 }
19251 }
19252
19253 Ok(())
19254 })
19255 }
19256
19257 fn organize_imports(
19258 &mut self,
19259 _: &OrganizeImports,
19260 window: &mut Window,
19261 cx: &mut Context<Self>,
19262 ) -> Option<Task<Result<()>>> {
19263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19264 let project = match &self.project {
19265 Some(project) => project.clone(),
19266 None => return None,
19267 };
19268 Some(self.perform_code_action_kind(
19269 project,
19270 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19271 window,
19272 cx,
19273 ))
19274 }
19275
19276 fn perform_code_action_kind(
19277 &mut self,
19278 project: Entity<Project>,
19279 kind: CodeActionKind,
19280 window: &mut Window,
19281 cx: &mut Context<Self>,
19282 ) -> Task<Result<()>> {
19283 let buffer = self.buffer.clone();
19284 let buffers = buffer.read(cx).all_buffers();
19285 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19286 let apply_action = project.update(cx, |project, cx| {
19287 project.apply_code_action_kind(buffers, kind, true, cx)
19288 });
19289 cx.spawn_in(window, async move |_, cx| {
19290 let transaction = futures::select_biased! {
19291 () = timeout => {
19292 log::warn!("timed out waiting for executing code action");
19293 None
19294 }
19295 transaction = apply_action.log_err().fuse() => transaction,
19296 };
19297 buffer.update(cx, |buffer, cx| {
19298 // check if we need this
19299 if let Some(transaction) = transaction
19300 && !buffer.is_singleton()
19301 {
19302 buffer.push_transaction(&transaction.0, cx);
19303 }
19304 cx.notify();
19305 });
19306 Ok(())
19307 })
19308 }
19309
19310 pub fn restart_language_server(
19311 &mut self,
19312 _: &RestartLanguageServer,
19313 _: &mut Window,
19314 cx: &mut Context<Self>,
19315 ) {
19316 if let Some(project) = self.project.clone() {
19317 self.buffer.update(cx, |multi_buffer, cx| {
19318 project.update(cx, |project, cx| {
19319 project.restart_language_servers_for_buffers(
19320 multi_buffer.all_buffers().into_iter().collect(),
19321 HashSet::default(),
19322 cx,
19323 );
19324 });
19325 })
19326 }
19327 }
19328
19329 pub fn stop_language_server(
19330 &mut self,
19331 _: &StopLanguageServer,
19332 _: &mut Window,
19333 cx: &mut Context<Self>,
19334 ) {
19335 if let Some(project) = self.project.clone() {
19336 self.buffer.update(cx, |multi_buffer, cx| {
19337 project.update(cx, |project, cx| {
19338 project.stop_language_servers_for_buffers(
19339 multi_buffer.all_buffers().into_iter().collect(),
19340 HashSet::default(),
19341 cx,
19342 );
19343 });
19344 });
19345 }
19346 }
19347
19348 fn cancel_language_server_work(
19349 workspace: &mut Workspace,
19350 _: &actions::CancelLanguageServerWork,
19351 _: &mut Window,
19352 cx: &mut Context<Workspace>,
19353 ) {
19354 let project = workspace.project();
19355 let buffers = workspace
19356 .active_item(cx)
19357 .and_then(|item| item.act_as::<Editor>(cx))
19358 .map_or(HashSet::default(), |editor| {
19359 editor.read(cx).buffer.read(cx).all_buffers()
19360 });
19361 project.update(cx, |project, cx| {
19362 project.cancel_language_server_work_for_buffers(buffers, cx);
19363 });
19364 }
19365
19366 fn show_character_palette(
19367 &mut self,
19368 _: &ShowCharacterPalette,
19369 window: &mut Window,
19370 _: &mut Context<Self>,
19371 ) {
19372 window.show_character_palette();
19373 }
19374
19375 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19376 if !self.diagnostics_enabled() {
19377 return;
19378 }
19379
19380 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19381 let buffer = self.buffer.read(cx).snapshot(cx);
19382 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19383 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19384 let is_valid = buffer
19385 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19386 .any(|entry| {
19387 entry.diagnostic.is_primary
19388 && !entry.range.is_empty()
19389 && entry.range.start == primary_range_start
19390 && entry.diagnostic.message == active_diagnostics.active_message
19391 });
19392
19393 if !is_valid {
19394 self.dismiss_diagnostics(cx);
19395 }
19396 }
19397 }
19398
19399 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19400 match &self.active_diagnostics {
19401 ActiveDiagnostic::Group(group) => Some(group),
19402 _ => None,
19403 }
19404 }
19405
19406 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19407 if !self.diagnostics_enabled() {
19408 return;
19409 }
19410 self.dismiss_diagnostics(cx);
19411 self.active_diagnostics = ActiveDiagnostic::All;
19412 }
19413
19414 fn activate_diagnostics(
19415 &mut self,
19416 buffer_id: BufferId,
19417 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19418 window: &mut Window,
19419 cx: &mut Context<Self>,
19420 ) {
19421 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19422 return;
19423 }
19424 self.dismiss_diagnostics(cx);
19425 let snapshot = self.snapshot(window, cx);
19426 let buffer = self.buffer.read(cx).snapshot(cx);
19427 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19428 return;
19429 };
19430
19431 let diagnostic_group = buffer
19432 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19433 .collect::<Vec<_>>();
19434
19435 let language_registry = self
19436 .project()
19437 .map(|project| project.read(cx).languages().clone());
19438
19439 let blocks = renderer.render_group(
19440 diagnostic_group,
19441 buffer_id,
19442 snapshot,
19443 cx.weak_entity(),
19444 language_registry,
19445 cx,
19446 );
19447
19448 let blocks = self.display_map.update(cx, |display_map, cx| {
19449 display_map.insert_blocks(blocks, cx).into_iter().collect()
19450 });
19451 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19452 active_range: buffer.anchor_before(diagnostic.range.start)
19453 ..buffer.anchor_after(diagnostic.range.end),
19454 active_message: diagnostic.diagnostic.message.clone(),
19455 group_id: diagnostic.diagnostic.group_id,
19456 blocks,
19457 });
19458 cx.notify();
19459 }
19460
19461 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19462 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19463 return;
19464 };
19465
19466 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19467 if let ActiveDiagnostic::Group(group) = prev {
19468 self.display_map.update(cx, |display_map, cx| {
19469 display_map.remove_blocks(group.blocks, cx);
19470 });
19471 cx.notify();
19472 }
19473 }
19474
19475 /// Disable inline diagnostics rendering for this editor.
19476 pub fn disable_inline_diagnostics(&mut self) {
19477 self.inline_diagnostics_enabled = false;
19478 self.inline_diagnostics_update = Task::ready(());
19479 self.inline_diagnostics.clear();
19480 }
19481
19482 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19483 self.diagnostics_enabled = false;
19484 self.dismiss_diagnostics(cx);
19485 self.inline_diagnostics_update = Task::ready(());
19486 self.inline_diagnostics.clear();
19487 }
19488
19489 pub fn disable_word_completions(&mut self) {
19490 self.word_completions_enabled = false;
19491 }
19492
19493 pub fn diagnostics_enabled(&self) -> bool {
19494 self.diagnostics_enabled && self.mode.is_full()
19495 }
19496
19497 pub fn inline_diagnostics_enabled(&self) -> bool {
19498 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19499 }
19500
19501 pub fn show_inline_diagnostics(&self) -> bool {
19502 self.show_inline_diagnostics
19503 }
19504
19505 pub fn toggle_inline_diagnostics(
19506 &mut self,
19507 _: &ToggleInlineDiagnostics,
19508 window: &mut Window,
19509 cx: &mut Context<Editor>,
19510 ) {
19511 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19512 self.refresh_inline_diagnostics(false, window, cx);
19513 }
19514
19515 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19516 self.diagnostics_max_severity = severity;
19517 self.display_map.update(cx, |display_map, _| {
19518 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19519 });
19520 }
19521
19522 pub fn toggle_diagnostics(
19523 &mut self,
19524 _: &ToggleDiagnostics,
19525 window: &mut Window,
19526 cx: &mut Context<Editor>,
19527 ) {
19528 if !self.diagnostics_enabled() {
19529 return;
19530 }
19531
19532 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19533 EditorSettings::get_global(cx)
19534 .diagnostics_max_severity
19535 .filter(|severity| severity != &DiagnosticSeverity::Off)
19536 .unwrap_or(DiagnosticSeverity::Hint)
19537 } else {
19538 DiagnosticSeverity::Off
19539 };
19540 self.set_max_diagnostics_severity(new_severity, cx);
19541 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19542 self.active_diagnostics = ActiveDiagnostic::None;
19543 self.inline_diagnostics_update = Task::ready(());
19544 self.inline_diagnostics.clear();
19545 } else {
19546 self.refresh_inline_diagnostics(false, window, cx);
19547 }
19548
19549 cx.notify();
19550 }
19551
19552 pub fn toggle_minimap(
19553 &mut self,
19554 _: &ToggleMinimap,
19555 window: &mut Window,
19556 cx: &mut Context<Editor>,
19557 ) {
19558 if self.supports_minimap(cx) {
19559 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19560 }
19561 }
19562
19563 fn refresh_inline_diagnostics(
19564 &mut self,
19565 debounce: bool,
19566 window: &mut Window,
19567 cx: &mut Context<Self>,
19568 ) {
19569 let max_severity = ProjectSettings::get_global(cx)
19570 .diagnostics
19571 .inline
19572 .max_severity
19573 .unwrap_or(self.diagnostics_max_severity);
19574
19575 if !self.inline_diagnostics_enabled()
19576 || !self.diagnostics_enabled()
19577 || !self.show_inline_diagnostics
19578 || max_severity == DiagnosticSeverity::Off
19579 {
19580 self.inline_diagnostics_update = Task::ready(());
19581 self.inline_diagnostics.clear();
19582 return;
19583 }
19584
19585 let debounce_ms = ProjectSettings::get_global(cx)
19586 .diagnostics
19587 .inline
19588 .update_debounce_ms;
19589 let debounce = if debounce && debounce_ms > 0 {
19590 Some(Duration::from_millis(debounce_ms))
19591 } else {
19592 None
19593 };
19594 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19595 if let Some(debounce) = debounce {
19596 cx.background_executor().timer(debounce).await;
19597 }
19598 let Some(snapshot) = editor.upgrade().map(|editor| {
19599 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19600 }) else {
19601 return;
19602 };
19603
19604 let new_inline_diagnostics = cx
19605 .background_spawn(async move {
19606 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19607 for diagnostic_entry in
19608 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19609 {
19610 let message = diagnostic_entry
19611 .diagnostic
19612 .message
19613 .split_once('\n')
19614 .map(|(line, _)| line)
19615 .map(SharedString::new)
19616 .unwrap_or_else(|| {
19617 SharedString::new(&*diagnostic_entry.diagnostic.message)
19618 });
19619 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19620 let (Ok(i) | Err(i)) = inline_diagnostics
19621 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19622 inline_diagnostics.insert(
19623 i,
19624 (
19625 start_anchor,
19626 InlineDiagnostic {
19627 message,
19628 group_id: diagnostic_entry.diagnostic.group_id,
19629 start: diagnostic_entry.range.start.to_point(&snapshot),
19630 is_primary: diagnostic_entry.diagnostic.is_primary,
19631 severity: diagnostic_entry.diagnostic.severity,
19632 },
19633 ),
19634 );
19635 }
19636 inline_diagnostics
19637 })
19638 .await;
19639
19640 editor
19641 .update(cx, |editor, cx| {
19642 editor.inline_diagnostics = new_inline_diagnostics;
19643 cx.notify();
19644 })
19645 .ok();
19646 });
19647 }
19648
19649 fn pull_diagnostics(
19650 &mut self,
19651 buffer_id: BufferId,
19652 _window: &Window,
19653 cx: &mut Context<Self>,
19654 ) -> Option<()> {
19655 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19656 // skip any LSP updates for it.
19657
19658 if self.active_diagnostics == ActiveDiagnostic::All
19659 || !self.mode().is_full()
19660 || !self.diagnostics_enabled()
19661 {
19662 return None;
19663 }
19664 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19665 .diagnostics
19666 .lsp_pull_diagnostics;
19667 if !pull_diagnostics_settings.enabled {
19668 return None;
19669 }
19670 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19671 let project = self.project()?.downgrade();
19672 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19673
19674 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19675 cx.background_executor().timer(debounce).await;
19676 if let Ok(task) = project.update(cx, |project, cx| {
19677 project.lsp_store().update(cx, |lsp_store, cx| {
19678 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19679 })
19680 }) {
19681 task.await.log_err();
19682 }
19683 project
19684 .update(cx, |project, cx| {
19685 project.lsp_store().update(cx, |lsp_store, cx| {
19686 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19687 })
19688 })
19689 .log_err();
19690 });
19691
19692 Some(())
19693 }
19694
19695 pub fn set_selections_from_remote(
19696 &mut self,
19697 selections: Vec<Selection<Anchor>>,
19698 pending_selection: Option<Selection<Anchor>>,
19699 window: &mut Window,
19700 cx: &mut Context<Self>,
19701 ) {
19702 let old_cursor_position = self.selections.newest_anchor().head();
19703 self.selections
19704 .change_with(&self.display_snapshot(cx), |s| {
19705 s.select_anchors(selections);
19706 if let Some(pending_selection) = pending_selection {
19707 s.set_pending(pending_selection, SelectMode::Character);
19708 } else {
19709 s.clear_pending();
19710 }
19711 });
19712 self.selections_did_change(
19713 false,
19714 &old_cursor_position,
19715 SelectionEffects::default(),
19716 window,
19717 cx,
19718 );
19719 }
19720
19721 pub fn transact(
19722 &mut self,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19726 ) -> Option<TransactionId> {
19727 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19728 this.start_transaction_at(Instant::now(), window, cx);
19729 update(this, window, cx);
19730 this.end_transaction_at(Instant::now(), cx)
19731 })
19732 }
19733
19734 pub fn start_transaction_at(
19735 &mut self,
19736 now: Instant,
19737 window: &mut Window,
19738 cx: &mut Context<Self>,
19739 ) -> Option<TransactionId> {
19740 self.end_selection(window, cx);
19741 if let Some(tx_id) = self
19742 .buffer
19743 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19744 {
19745 self.selection_history
19746 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19747 cx.emit(EditorEvent::TransactionBegun {
19748 transaction_id: tx_id,
19749 });
19750 Some(tx_id)
19751 } else {
19752 None
19753 }
19754 }
19755
19756 pub fn end_transaction_at(
19757 &mut self,
19758 now: Instant,
19759 cx: &mut Context<Self>,
19760 ) -> Option<TransactionId> {
19761 if let Some(transaction_id) = self
19762 .buffer
19763 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19764 {
19765 if let Some((_, end_selections)) =
19766 self.selection_history.transaction_mut(transaction_id)
19767 {
19768 *end_selections = Some(self.selections.disjoint_anchors_arc());
19769 } else {
19770 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19771 }
19772
19773 cx.emit(EditorEvent::Edited { transaction_id });
19774 Some(transaction_id)
19775 } else {
19776 None
19777 }
19778 }
19779
19780 pub fn modify_transaction_selection_history(
19781 &mut self,
19782 transaction_id: TransactionId,
19783 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19784 ) -> bool {
19785 self.selection_history
19786 .transaction_mut(transaction_id)
19787 .map(modify)
19788 .is_some()
19789 }
19790
19791 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19792 if self.selection_mark_mode {
19793 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19794 s.move_with(&mut |_, sel| {
19795 sel.collapse_to(sel.head(), SelectionGoal::None);
19796 });
19797 })
19798 }
19799 self.selection_mark_mode = true;
19800 cx.notify();
19801 }
19802
19803 pub fn swap_selection_ends(
19804 &mut self,
19805 _: &actions::SwapSelectionEnds,
19806 window: &mut Window,
19807 cx: &mut Context<Self>,
19808 ) {
19809 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19810 s.move_with(&mut |_, sel| {
19811 if sel.start != sel.end {
19812 sel.reversed = !sel.reversed
19813 }
19814 });
19815 });
19816 self.request_autoscroll(Autoscroll::newest(), cx);
19817 cx.notify();
19818 }
19819
19820 pub fn toggle_focus(
19821 workspace: &mut Workspace,
19822 _: &actions::ToggleFocus,
19823 window: &mut Window,
19824 cx: &mut Context<Workspace>,
19825 ) {
19826 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19827 return;
19828 };
19829 workspace.activate_item(&item, true, true, window, cx);
19830 }
19831
19832 pub fn toggle_fold(
19833 &mut self,
19834 _: &actions::ToggleFold,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19839 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19840 let selection = self.selections.newest::<Point>(&display_map);
19841
19842 let range = if selection.is_empty() {
19843 let point = selection.head().to_display_point(&display_map);
19844 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19845 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19846 .to_point(&display_map);
19847 start..end
19848 } else {
19849 selection.range()
19850 };
19851 if display_map.folds_in_range(range).next().is_some() {
19852 self.unfold_lines(&Default::default(), window, cx)
19853 } else {
19854 self.fold(&Default::default(), window, cx)
19855 }
19856 } else {
19857 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19858 let buffer_ids: HashSet<_> = self
19859 .selections
19860 .disjoint_anchor_ranges()
19861 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19862 .collect();
19863
19864 let should_unfold = buffer_ids
19865 .iter()
19866 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19867
19868 for buffer_id in buffer_ids {
19869 if should_unfold {
19870 self.unfold_buffer(buffer_id, cx);
19871 } else {
19872 self.fold_buffer(buffer_id, cx);
19873 }
19874 }
19875 }
19876 }
19877
19878 pub fn toggle_fold_recursive(
19879 &mut self,
19880 _: &actions::ToggleFoldRecursive,
19881 window: &mut Window,
19882 cx: &mut Context<Self>,
19883 ) {
19884 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19885
19886 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19887 let range = if selection.is_empty() {
19888 let point = selection.head().to_display_point(&display_map);
19889 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19890 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19891 .to_point(&display_map);
19892 start..end
19893 } else {
19894 selection.range()
19895 };
19896 if display_map.folds_in_range(range).next().is_some() {
19897 self.unfold_recursive(&Default::default(), window, cx)
19898 } else {
19899 self.fold_recursive(&Default::default(), window, cx)
19900 }
19901 }
19902
19903 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19904 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19905 let mut to_fold = Vec::new();
19906 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19907 let selections = self.selections.all_adjusted(&display_map);
19908
19909 for selection in selections {
19910 let range = selection.range().sorted();
19911 let buffer_start_row = range.start.row;
19912
19913 if range.start.row != range.end.row {
19914 let mut found = false;
19915 let mut row = range.start.row;
19916 while row <= range.end.row {
19917 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19918 {
19919 found = true;
19920 row = crease.range().end.row + 1;
19921 to_fold.push(crease);
19922 } else {
19923 row += 1
19924 }
19925 }
19926 if found {
19927 continue;
19928 }
19929 }
19930
19931 for row in (0..=range.start.row).rev() {
19932 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19933 && crease.range().end.row >= buffer_start_row
19934 {
19935 to_fold.push(crease);
19936 if row <= range.start.row {
19937 break;
19938 }
19939 }
19940 }
19941 }
19942
19943 self.fold_creases(to_fold, true, window, cx);
19944 } else {
19945 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19946 let buffer_ids = self
19947 .selections
19948 .disjoint_anchor_ranges()
19949 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19950 .collect::<HashSet<_>>();
19951 for buffer_id in buffer_ids {
19952 self.fold_buffer(buffer_id, cx);
19953 }
19954 }
19955 }
19956
19957 pub fn toggle_fold_all(
19958 &mut self,
19959 _: &actions::ToggleFoldAll,
19960 window: &mut Window,
19961 cx: &mut Context<Self>,
19962 ) {
19963 let has_folds = if self.buffer.read(cx).is_singleton() {
19964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19965 let has_folds = display_map
19966 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19967 .next()
19968 .is_some();
19969 has_folds
19970 } else {
19971 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19972 let has_folds = buffer_ids
19973 .iter()
19974 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19975 has_folds
19976 };
19977
19978 if has_folds {
19979 self.unfold_all(&actions::UnfoldAll, window, cx);
19980 } else {
19981 self.fold_all(&actions::FoldAll, window, cx);
19982 }
19983 }
19984
19985 fn fold_at_level(
19986 &mut self,
19987 fold_at: &FoldAtLevel,
19988 window: &mut Window,
19989 cx: &mut Context<Self>,
19990 ) {
19991 if !self.buffer.read(cx).is_singleton() {
19992 return;
19993 }
19994
19995 let fold_at_level = fold_at.0;
19996 let snapshot = self.buffer.read(cx).snapshot(cx);
19997 let mut to_fold = Vec::new();
19998 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19999
20000 let row_ranges_to_keep: Vec<Range<u32>> = self
20001 .selections
20002 .all::<Point>(&self.display_snapshot(cx))
20003 .into_iter()
20004 .map(|sel| sel.start.row..sel.end.row)
20005 .collect();
20006
20007 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20008 while start_row < end_row {
20009 match self
20010 .snapshot(window, cx)
20011 .crease_for_buffer_row(MultiBufferRow(start_row))
20012 {
20013 Some(crease) => {
20014 let nested_start_row = crease.range().start.row + 1;
20015 let nested_end_row = crease.range().end.row;
20016
20017 if current_level < fold_at_level {
20018 stack.push((nested_start_row, nested_end_row, current_level + 1));
20019 } else if current_level == fold_at_level {
20020 // Fold iff there is no selection completely contained within the fold region
20021 if !row_ranges_to_keep.iter().any(|selection| {
20022 selection.end >= nested_start_row
20023 && selection.start <= nested_end_row
20024 }) {
20025 to_fold.push(crease);
20026 }
20027 }
20028
20029 start_row = nested_end_row + 1;
20030 }
20031 None => start_row += 1,
20032 }
20033 }
20034 }
20035
20036 self.fold_creases(to_fold, true, window, cx);
20037 }
20038
20039 pub fn fold_at_level_1(
20040 &mut self,
20041 _: &actions::FoldAtLevel1,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20046 }
20047
20048 pub fn fold_at_level_2(
20049 &mut self,
20050 _: &actions::FoldAtLevel2,
20051 window: &mut Window,
20052 cx: &mut Context<Self>,
20053 ) {
20054 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20055 }
20056
20057 pub fn fold_at_level_3(
20058 &mut self,
20059 _: &actions::FoldAtLevel3,
20060 window: &mut Window,
20061 cx: &mut Context<Self>,
20062 ) {
20063 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20064 }
20065
20066 pub fn fold_at_level_4(
20067 &mut self,
20068 _: &actions::FoldAtLevel4,
20069 window: &mut Window,
20070 cx: &mut Context<Self>,
20071 ) {
20072 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20073 }
20074
20075 pub fn fold_at_level_5(
20076 &mut self,
20077 _: &actions::FoldAtLevel5,
20078 window: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20082 }
20083
20084 pub fn fold_at_level_6(
20085 &mut self,
20086 _: &actions::FoldAtLevel6,
20087 window: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20091 }
20092
20093 pub fn fold_at_level_7(
20094 &mut self,
20095 _: &actions::FoldAtLevel7,
20096 window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20100 }
20101
20102 pub fn fold_at_level_8(
20103 &mut self,
20104 _: &actions::FoldAtLevel8,
20105 window: &mut Window,
20106 cx: &mut Context<Self>,
20107 ) {
20108 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20109 }
20110
20111 pub fn fold_at_level_9(
20112 &mut self,
20113 _: &actions::FoldAtLevel9,
20114 window: &mut Window,
20115 cx: &mut Context<Self>,
20116 ) {
20117 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20118 }
20119
20120 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20121 if self.buffer.read(cx).is_singleton() {
20122 let mut fold_ranges = Vec::new();
20123 let snapshot = self.buffer.read(cx).snapshot(cx);
20124
20125 for row in 0..snapshot.max_row().0 {
20126 if let Some(foldable_range) = self
20127 .snapshot(window, cx)
20128 .crease_for_buffer_row(MultiBufferRow(row))
20129 {
20130 fold_ranges.push(foldable_range);
20131 }
20132 }
20133
20134 self.fold_creases(fold_ranges, true, window, cx);
20135 } else {
20136 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20137 editor
20138 .update_in(cx, |editor, _, cx| {
20139 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20140 editor.fold_buffer(buffer_id, cx);
20141 }
20142 })
20143 .ok();
20144 });
20145 }
20146 }
20147
20148 pub fn fold_function_bodies(
20149 &mut self,
20150 _: &actions::FoldFunctionBodies,
20151 window: &mut Window,
20152 cx: &mut Context<Self>,
20153 ) {
20154 let snapshot = self.buffer.read(cx).snapshot(cx);
20155
20156 let ranges = snapshot
20157 .text_object_ranges(
20158 MultiBufferOffset(0)..snapshot.len(),
20159 TreeSitterOptions::default(),
20160 )
20161 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20162 .collect::<Vec<_>>();
20163
20164 let creases = ranges
20165 .into_iter()
20166 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20167 .collect();
20168
20169 self.fold_creases(creases, true, window, cx);
20170 }
20171
20172 pub fn fold_recursive(
20173 &mut self,
20174 _: &actions::FoldRecursive,
20175 window: &mut Window,
20176 cx: &mut Context<Self>,
20177 ) {
20178 let mut to_fold = Vec::new();
20179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20180 let selections = self.selections.all_adjusted(&display_map);
20181
20182 for selection in selections {
20183 let range = selection.range().sorted();
20184 let buffer_start_row = range.start.row;
20185
20186 if range.start.row != range.end.row {
20187 let mut found = false;
20188 for row in range.start.row..=range.end.row {
20189 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20190 found = true;
20191 to_fold.push(crease);
20192 }
20193 }
20194 if found {
20195 continue;
20196 }
20197 }
20198
20199 for row in (0..=range.start.row).rev() {
20200 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20201 if crease.range().end.row >= buffer_start_row {
20202 to_fold.push(crease);
20203 } else {
20204 break;
20205 }
20206 }
20207 }
20208 }
20209
20210 self.fold_creases(to_fold, true, window, cx);
20211 }
20212
20213 pub fn fold_at(
20214 &mut self,
20215 buffer_row: MultiBufferRow,
20216 window: &mut Window,
20217 cx: &mut Context<Self>,
20218 ) {
20219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20220
20221 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20222 let autoscroll = self
20223 .selections
20224 .all::<Point>(&display_map)
20225 .iter()
20226 .any(|selection| crease.range().overlaps(&selection.range()));
20227
20228 self.fold_creases(vec![crease], autoscroll, window, cx);
20229 }
20230 }
20231
20232 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20233 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20234 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20235 let buffer = display_map.buffer_snapshot();
20236 let selections = self.selections.all::<Point>(&display_map);
20237 let ranges = selections
20238 .iter()
20239 .map(|s| {
20240 let range = s.display_range(&display_map).sorted();
20241 let mut start = range.start.to_point(&display_map);
20242 let mut end = range.end.to_point(&display_map);
20243 start.column = 0;
20244 end.column = buffer.line_len(MultiBufferRow(end.row));
20245 start..end
20246 })
20247 .collect::<Vec<_>>();
20248
20249 self.unfold_ranges(&ranges, true, true, cx);
20250 } else {
20251 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20252 let buffer_ids = self
20253 .selections
20254 .disjoint_anchor_ranges()
20255 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20256 .collect::<HashSet<_>>();
20257 for buffer_id in buffer_ids {
20258 self.unfold_buffer(buffer_id, cx);
20259 }
20260 }
20261 }
20262
20263 pub fn unfold_recursive(
20264 &mut self,
20265 _: &UnfoldRecursive,
20266 _window: &mut Window,
20267 cx: &mut Context<Self>,
20268 ) {
20269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20270 let selections = self.selections.all::<Point>(&display_map);
20271 let ranges = selections
20272 .iter()
20273 .map(|s| {
20274 let mut range = s.display_range(&display_map).sorted();
20275 *range.start.column_mut() = 0;
20276 *range.end.column_mut() = display_map.line_len(range.end.row());
20277 let start = range.start.to_point(&display_map);
20278 let end = range.end.to_point(&display_map);
20279 start..end
20280 })
20281 .collect::<Vec<_>>();
20282
20283 self.unfold_ranges(&ranges, true, true, cx);
20284 }
20285
20286 pub fn unfold_at(
20287 &mut self,
20288 buffer_row: MultiBufferRow,
20289 _window: &mut Window,
20290 cx: &mut Context<Self>,
20291 ) {
20292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20293
20294 let intersection_range = Point::new(buffer_row.0, 0)
20295 ..Point::new(
20296 buffer_row.0,
20297 display_map.buffer_snapshot().line_len(buffer_row),
20298 );
20299
20300 let autoscroll = self
20301 .selections
20302 .all::<Point>(&display_map)
20303 .iter()
20304 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20305
20306 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20307 }
20308
20309 pub fn unfold_all(
20310 &mut self,
20311 _: &actions::UnfoldAll,
20312 _window: &mut Window,
20313 cx: &mut Context<Self>,
20314 ) {
20315 if self.buffer.read(cx).is_singleton() {
20316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20317 self.unfold_ranges(
20318 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20319 true,
20320 true,
20321 cx,
20322 );
20323 } else {
20324 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20325 editor
20326 .update(cx, |editor, cx| {
20327 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20328 editor.unfold_buffer(buffer_id, cx);
20329 }
20330 })
20331 .ok();
20332 });
20333 }
20334 }
20335
20336 pub fn fold_selected_ranges(
20337 &mut self,
20338 _: &FoldSelectedRanges,
20339 window: &mut Window,
20340 cx: &mut Context<Self>,
20341 ) {
20342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20343 let selections = self.selections.all_adjusted(&display_map);
20344 let ranges = selections
20345 .into_iter()
20346 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20347 .collect::<Vec<_>>();
20348 self.fold_creases(ranges, true, window, cx);
20349 }
20350
20351 pub fn fold_ranges<T: ToOffset + Clone>(
20352 &mut self,
20353 ranges: Vec<Range<T>>,
20354 auto_scroll: bool,
20355 window: &mut Window,
20356 cx: &mut Context<Self>,
20357 ) {
20358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20359 let ranges = ranges
20360 .into_iter()
20361 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20362 .collect::<Vec<_>>();
20363 self.fold_creases(ranges, auto_scroll, window, cx);
20364 }
20365
20366 pub fn fold_creases<T: ToOffset + Clone>(
20367 &mut self,
20368 creases: Vec<Crease<T>>,
20369 auto_scroll: bool,
20370 _window: &mut Window,
20371 cx: &mut Context<Self>,
20372 ) {
20373 if creases.is_empty() {
20374 return;
20375 }
20376
20377 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20378
20379 if auto_scroll {
20380 self.request_autoscroll(Autoscroll::fit(), cx);
20381 }
20382
20383 cx.notify();
20384
20385 self.scrollbar_marker_state.dirty = true;
20386 self.folds_did_change(cx);
20387 }
20388
20389 /// Removes any folds whose ranges intersect any of the given ranges.
20390 pub fn unfold_ranges<T: ToOffset + Clone>(
20391 &mut self,
20392 ranges: &[Range<T>],
20393 inclusive: bool,
20394 auto_scroll: bool,
20395 cx: &mut Context<Self>,
20396 ) {
20397 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20398 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20399 });
20400 self.folds_did_change(cx);
20401 }
20402
20403 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20404 self.fold_buffers([buffer_id], cx);
20405 }
20406
20407 pub fn fold_buffers(
20408 &mut self,
20409 buffer_ids: impl IntoIterator<Item = BufferId>,
20410 cx: &mut Context<Self>,
20411 ) {
20412 if self.buffer().read(cx).is_singleton() {
20413 return;
20414 }
20415
20416 let ids_to_fold: Vec<BufferId> = buffer_ids
20417 .into_iter()
20418 .filter(|id| !self.is_buffer_folded(*id, cx))
20419 .collect();
20420
20421 if ids_to_fold.is_empty() {
20422 return;
20423 }
20424
20425 let mut all_folded_excerpt_ids = Vec::new();
20426 for buffer_id in &ids_to_fold {
20427 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20428 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
20429 }
20430
20431 self.display_map.update(cx, |display_map, cx| {
20432 display_map.fold_buffers(ids_to_fold.clone(), cx)
20433 });
20434
20435 let snapshot = self.display_snapshot(cx);
20436 self.selections.change_with(&snapshot, |selections| {
20437 for buffer_id in ids_to_fold {
20438 selections.remove_selections_from_buffer(buffer_id);
20439 }
20440 });
20441
20442 cx.emit(EditorEvent::BufferFoldToggled {
20443 ids: all_folded_excerpt_ids,
20444 folded: true,
20445 });
20446 cx.notify();
20447 }
20448
20449 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20450 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20451 return;
20452 }
20453 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20454 self.display_map.update(cx, |display_map, cx| {
20455 display_map.unfold_buffers([buffer_id], cx);
20456 });
20457 cx.emit(EditorEvent::BufferFoldToggled {
20458 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
20459 folded: false,
20460 });
20461 cx.notify();
20462 }
20463
20464 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20465 self.display_map.read(cx).is_buffer_folded(buffer)
20466 }
20467
20468 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20469 if self.buffer().read(cx).is_singleton() {
20470 return false;
20471 }
20472 !self.folded_buffers(cx).is_empty()
20473 }
20474
20475 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20476 self.display_map.read(cx).folded_buffers()
20477 }
20478
20479 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20480 self.display_map.update(cx, |display_map, cx| {
20481 display_map.disable_header_for_buffer(buffer_id, cx);
20482 });
20483 cx.notify();
20484 }
20485
20486 /// Removes any folds with the given ranges.
20487 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20488 &mut self,
20489 ranges: &[Range<T>],
20490 type_id: TypeId,
20491 auto_scroll: bool,
20492 cx: &mut Context<Self>,
20493 ) {
20494 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20495 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20496 });
20497 self.folds_did_change(cx);
20498 }
20499
20500 fn remove_folds_with<T: ToOffset + Clone>(
20501 &mut self,
20502 ranges: &[Range<T>],
20503 auto_scroll: bool,
20504 cx: &mut Context<Self>,
20505 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20506 ) {
20507 if ranges.is_empty() {
20508 return;
20509 }
20510
20511 let mut buffers_affected = HashSet::default();
20512 let multi_buffer = self.buffer().read(cx);
20513 for range in ranges {
20514 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20515 buffers_affected.insert(buffer.read(cx).remote_id());
20516 };
20517 }
20518
20519 self.display_map.update(cx, update);
20520
20521 if auto_scroll {
20522 self.request_autoscroll(Autoscroll::fit(), cx);
20523 }
20524
20525 cx.notify();
20526 self.scrollbar_marker_state.dirty = true;
20527 self.active_indent_guides_state.dirty = true;
20528 }
20529
20530 pub fn update_renderer_widths(
20531 &mut self,
20532 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20533 cx: &mut Context<Self>,
20534 ) -> bool {
20535 self.display_map
20536 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20537 }
20538
20539 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20540 self.display_map.read(cx).fold_placeholder.clone()
20541 }
20542
20543 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20544 self.buffer.update(cx, |buffer, cx| {
20545 buffer.set_all_diff_hunks_expanded(cx);
20546 });
20547 }
20548
20549 pub fn expand_all_diff_hunks(
20550 &mut self,
20551 _: &ExpandAllDiffHunks,
20552 _window: &mut Window,
20553 cx: &mut Context<Self>,
20554 ) {
20555 self.buffer.update(cx, |buffer, cx| {
20556 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20557 });
20558 }
20559
20560 pub fn collapse_all_diff_hunks(
20561 &mut self,
20562 _: &CollapseAllDiffHunks,
20563 _window: &mut Window,
20564 cx: &mut Context<Self>,
20565 ) {
20566 self.buffer.update(cx, |buffer, cx| {
20567 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20568 });
20569 }
20570
20571 pub fn toggle_selected_diff_hunks(
20572 &mut self,
20573 _: &ToggleSelectedDiffHunks,
20574 _window: &mut Window,
20575 cx: &mut Context<Self>,
20576 ) {
20577 let ranges: Vec<_> = self
20578 .selections
20579 .disjoint_anchors()
20580 .iter()
20581 .map(|s| s.range())
20582 .collect();
20583 self.toggle_diff_hunks_in_ranges(ranges, cx);
20584 }
20585
20586 pub fn diff_hunks_in_ranges<'a>(
20587 &'a self,
20588 ranges: &'a [Range<Anchor>],
20589 buffer: &'a MultiBufferSnapshot,
20590 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20591 ranges.iter().flat_map(move |range| {
20592 let end_excerpt_id = range.end.excerpt_id;
20593 let range = range.to_point(buffer);
20594 let mut peek_end = range.end;
20595 if range.end.row < buffer.max_row().0 {
20596 peek_end = Point::new(range.end.row + 1, 0);
20597 }
20598 buffer
20599 .diff_hunks_in_range(range.start..peek_end)
20600 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20601 })
20602 }
20603
20604 pub fn has_stageable_diff_hunks_in_ranges(
20605 &self,
20606 ranges: &[Range<Anchor>],
20607 snapshot: &MultiBufferSnapshot,
20608 ) -> bool {
20609 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20610 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20611 }
20612
20613 pub fn toggle_staged_selected_diff_hunks(
20614 &mut self,
20615 _: &::git::ToggleStaged,
20616 _: &mut Window,
20617 cx: &mut Context<Self>,
20618 ) {
20619 let snapshot = self.buffer.read(cx).snapshot(cx);
20620 let ranges: Vec<_> = self
20621 .selections
20622 .disjoint_anchors()
20623 .iter()
20624 .map(|s| s.range())
20625 .collect();
20626 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20627 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20628 }
20629
20630 pub fn set_render_diff_hunk_controls(
20631 &mut self,
20632 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20633 cx: &mut Context<Self>,
20634 ) {
20635 self.render_diff_hunk_controls = render_diff_hunk_controls;
20636 cx.notify();
20637 }
20638
20639 pub fn stage_and_next(
20640 &mut self,
20641 _: &::git::StageAndNext,
20642 window: &mut Window,
20643 cx: &mut Context<Self>,
20644 ) {
20645 self.do_stage_or_unstage_and_next(true, window, cx);
20646 }
20647
20648 pub fn unstage_and_next(
20649 &mut self,
20650 _: &::git::UnstageAndNext,
20651 window: &mut Window,
20652 cx: &mut Context<Self>,
20653 ) {
20654 self.do_stage_or_unstage_and_next(false, window, cx);
20655 }
20656
20657 pub fn stage_or_unstage_diff_hunks(
20658 &mut self,
20659 stage: bool,
20660 ranges: Vec<Range<Anchor>>,
20661 cx: &mut Context<Self>,
20662 ) {
20663 if self.delegate_stage_and_restore {
20664 let snapshot = self.buffer.read(cx).snapshot(cx);
20665 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20666 if !hunks.is_empty() {
20667 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20668 }
20669 return;
20670 }
20671 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20672 cx.spawn(async move |this, cx| {
20673 task.await?;
20674 this.update(cx, |this, cx| {
20675 let snapshot = this.buffer.read(cx).snapshot(cx);
20676 let chunk_by = this
20677 .diff_hunks_in_ranges(&ranges, &snapshot)
20678 .chunk_by(|hunk| hunk.buffer_id);
20679 for (buffer_id, hunks) in &chunk_by {
20680 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20681 }
20682 })
20683 })
20684 .detach_and_log_err(cx);
20685 }
20686
20687 fn save_buffers_for_ranges_if_needed(
20688 &mut self,
20689 ranges: &[Range<Anchor>],
20690 cx: &mut Context<Editor>,
20691 ) -> Task<Result<()>> {
20692 let multibuffer = self.buffer.read(cx);
20693 let snapshot = multibuffer.read(cx);
20694 let buffer_ids: HashSet<_> = ranges
20695 .iter()
20696 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20697 .collect();
20698 drop(snapshot);
20699
20700 let mut buffers = HashSet::default();
20701 for buffer_id in buffer_ids {
20702 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20703 let buffer = buffer_entity.read(cx);
20704 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20705 {
20706 buffers.insert(buffer_entity);
20707 }
20708 }
20709 }
20710
20711 if let Some(project) = &self.project {
20712 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20713 } else {
20714 Task::ready(Ok(()))
20715 }
20716 }
20717
20718 fn do_stage_or_unstage_and_next(
20719 &mut self,
20720 stage: bool,
20721 window: &mut Window,
20722 cx: &mut Context<Self>,
20723 ) {
20724 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20725
20726 if ranges.iter().any(|range| range.start != range.end) {
20727 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20728 return;
20729 }
20730
20731 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20732 let snapshot = self.snapshot(window, cx);
20733 let position = self
20734 .selections
20735 .newest::<Point>(&snapshot.display_snapshot)
20736 .head();
20737 let mut row = snapshot
20738 .buffer_snapshot()
20739 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20740 .find(|hunk| hunk.row_range.start.0 > position.row)
20741 .map(|hunk| hunk.row_range.start);
20742
20743 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20744 // Outside of the project diff editor, wrap around to the beginning.
20745 if !all_diff_hunks_expanded {
20746 row = row.or_else(|| {
20747 snapshot
20748 .buffer_snapshot()
20749 .diff_hunks_in_range(Point::zero()..position)
20750 .find(|hunk| hunk.row_range.end.0 < position.row)
20751 .map(|hunk| hunk.row_range.start)
20752 });
20753 }
20754
20755 if let Some(row) = row {
20756 let destination = Point::new(row.0, 0);
20757 let autoscroll = Autoscroll::center();
20758
20759 self.unfold_ranges(&[destination..destination], false, false, cx);
20760 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20761 s.select_ranges([destination..destination]);
20762 });
20763 }
20764 }
20765
20766 pub(crate) fn do_stage_or_unstage(
20767 &self,
20768 stage: bool,
20769 buffer_id: BufferId,
20770 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20771 cx: &mut App,
20772 ) -> Option<()> {
20773 let project = self.project()?;
20774 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20775 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20776 let buffer_snapshot = buffer.read(cx).snapshot();
20777 let file_exists = buffer_snapshot
20778 .file()
20779 .is_some_and(|file| file.disk_state().exists());
20780 diff.update(cx, |diff, cx| {
20781 diff.stage_or_unstage_hunks(
20782 stage,
20783 &hunks
20784 .map(|hunk| buffer_diff::DiffHunk {
20785 buffer_range: hunk.buffer_range,
20786 // We don't need to pass in word diffs here because they're only used for rendering and
20787 // this function changes internal state
20788 base_word_diffs: Vec::default(),
20789 buffer_word_diffs: Vec::default(),
20790 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20791 ..hunk.diff_base_byte_range.end.0,
20792 secondary_status: hunk.status.secondary,
20793 range: Point::zero()..Point::zero(), // unused
20794 })
20795 .collect::<Vec<_>>(),
20796 &buffer_snapshot,
20797 file_exists,
20798 cx,
20799 )
20800 });
20801 None
20802 }
20803
20804 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20805 let ranges: Vec<_> = self
20806 .selections
20807 .disjoint_anchors()
20808 .iter()
20809 .map(|s| s.range())
20810 .collect();
20811 self.buffer
20812 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20813 }
20814
20815 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20816 self.buffer.update(cx, |buffer, cx| {
20817 let ranges = vec![Anchor::min()..Anchor::max()];
20818 if !buffer.all_diff_hunks_expanded()
20819 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20820 {
20821 buffer.collapse_diff_hunks(ranges, cx);
20822 true
20823 } else {
20824 false
20825 }
20826 })
20827 }
20828
20829 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20830 if self.buffer.read(cx).all_diff_hunks_expanded() {
20831 return true;
20832 }
20833 let ranges = vec![Anchor::min()..Anchor::max()];
20834 self.buffer
20835 .read(cx)
20836 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20837 }
20838
20839 fn toggle_diff_hunks_in_ranges(
20840 &mut self,
20841 ranges: Vec<Range<Anchor>>,
20842 cx: &mut Context<Editor>,
20843 ) {
20844 self.buffer.update(cx, |buffer, cx| {
20845 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20846 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20847 })
20848 }
20849
20850 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20851 self.buffer.update(cx, |buffer, cx| {
20852 buffer.toggle_single_diff_hunk(range, cx);
20853 })
20854 }
20855
20856 pub(crate) fn apply_all_diff_hunks(
20857 &mut self,
20858 _: &ApplyAllDiffHunks,
20859 window: &mut Window,
20860 cx: &mut Context<Self>,
20861 ) {
20862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20863
20864 let buffers = self.buffer.read(cx).all_buffers();
20865 for branch_buffer in buffers {
20866 branch_buffer.update(cx, |branch_buffer, cx| {
20867 branch_buffer.merge_into_base(Vec::new(), cx);
20868 });
20869 }
20870
20871 if let Some(project) = self.project.clone() {
20872 self.save(
20873 SaveOptions {
20874 format: true,
20875 autosave: false,
20876 },
20877 project,
20878 window,
20879 cx,
20880 )
20881 .detach_and_log_err(cx);
20882 }
20883 }
20884
20885 pub(crate) fn apply_selected_diff_hunks(
20886 &mut self,
20887 _: &ApplyDiffHunk,
20888 window: &mut Window,
20889 cx: &mut Context<Self>,
20890 ) {
20891 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20892 let snapshot = self.snapshot(window, cx);
20893 let hunks = snapshot.hunks_for_ranges(
20894 self.selections
20895 .all(&snapshot.display_snapshot)
20896 .into_iter()
20897 .map(|selection| selection.range()),
20898 );
20899 let mut ranges_by_buffer = HashMap::default();
20900 self.transact(window, cx, |editor, _window, cx| {
20901 for hunk in hunks {
20902 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20903 ranges_by_buffer
20904 .entry(buffer.clone())
20905 .or_insert_with(Vec::new)
20906 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20907 }
20908 }
20909
20910 for (buffer, ranges) in ranges_by_buffer {
20911 buffer.update(cx, |buffer, cx| {
20912 buffer.merge_into_base(ranges, cx);
20913 });
20914 }
20915 });
20916
20917 if let Some(project) = self.project.clone() {
20918 self.save(
20919 SaveOptions {
20920 format: true,
20921 autosave: false,
20922 },
20923 project,
20924 window,
20925 cx,
20926 )
20927 .detach_and_log_err(cx);
20928 }
20929 }
20930
20931 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20932 if hovered != self.gutter_hovered {
20933 self.gutter_hovered = hovered;
20934 cx.notify();
20935 }
20936 }
20937
20938 pub fn insert_blocks(
20939 &mut self,
20940 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20941 autoscroll: Option<Autoscroll>,
20942 cx: &mut Context<Self>,
20943 ) -> Vec<CustomBlockId> {
20944 let blocks = self
20945 .display_map
20946 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20947 if let Some(autoscroll) = autoscroll {
20948 self.request_autoscroll(autoscroll, cx);
20949 }
20950 cx.notify();
20951 blocks
20952 }
20953
20954 pub fn resize_blocks(
20955 &mut self,
20956 heights: HashMap<CustomBlockId, u32>,
20957 autoscroll: Option<Autoscroll>,
20958 cx: &mut Context<Self>,
20959 ) {
20960 self.display_map
20961 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20962 if let Some(autoscroll) = autoscroll {
20963 self.request_autoscroll(autoscroll, cx);
20964 }
20965 cx.notify();
20966 }
20967
20968 pub fn replace_blocks(
20969 &mut self,
20970 renderers: HashMap<CustomBlockId, RenderBlock>,
20971 autoscroll: Option<Autoscroll>,
20972 cx: &mut Context<Self>,
20973 ) {
20974 self.display_map
20975 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20976 if let Some(autoscroll) = autoscroll {
20977 self.request_autoscroll(autoscroll, cx);
20978 }
20979 cx.notify();
20980 }
20981
20982 pub fn remove_blocks(
20983 &mut self,
20984 block_ids: HashSet<CustomBlockId>,
20985 autoscroll: Option<Autoscroll>,
20986 cx: &mut Context<Self>,
20987 ) {
20988 self.display_map.update(cx, |display_map, cx| {
20989 display_map.remove_blocks(block_ids, cx)
20990 });
20991 if let Some(autoscroll) = autoscroll {
20992 self.request_autoscroll(autoscroll, cx);
20993 }
20994 cx.notify();
20995 }
20996
20997 pub fn row_for_block(
20998 &self,
20999 block_id: CustomBlockId,
21000 cx: &mut Context<Self>,
21001 ) -> Option<DisplayRow> {
21002 self.display_map
21003 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21004 }
21005
21006 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21007 self.focused_block = Some(focused_block);
21008 }
21009
21010 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21011 self.focused_block.take()
21012 }
21013
21014 pub fn insert_creases(
21015 &mut self,
21016 creases: impl IntoIterator<Item = Crease<Anchor>>,
21017 cx: &mut Context<Self>,
21018 ) -> Vec<CreaseId> {
21019 self.display_map
21020 .update(cx, |map, cx| map.insert_creases(creases, cx))
21021 }
21022
21023 pub fn remove_creases(
21024 &mut self,
21025 ids: impl IntoIterator<Item = CreaseId>,
21026 cx: &mut Context<Self>,
21027 ) -> Vec<(CreaseId, Range<Anchor>)> {
21028 self.display_map
21029 .update(cx, |map, cx| map.remove_creases(ids, cx))
21030 }
21031
21032 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21033 self.display_map
21034 .update(cx, |map, cx| map.snapshot(cx))
21035 .longest_row()
21036 }
21037
21038 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21039 self.display_map
21040 .update(cx, |map, cx| map.snapshot(cx))
21041 .max_point()
21042 }
21043
21044 pub fn text(&self, cx: &App) -> String {
21045 self.buffer.read(cx).read(cx).text()
21046 }
21047
21048 pub fn is_empty(&self, cx: &App) -> bool {
21049 self.buffer.read(cx).read(cx).is_empty()
21050 }
21051
21052 pub fn text_option(&self, cx: &App) -> Option<String> {
21053 let text = self.text(cx);
21054 let text = text.trim();
21055
21056 if text.is_empty() {
21057 return None;
21058 }
21059
21060 Some(text.to_string())
21061 }
21062
21063 pub fn set_text(
21064 &mut self,
21065 text: impl Into<Arc<str>>,
21066 window: &mut Window,
21067 cx: &mut Context<Self>,
21068 ) {
21069 self.transact(window, cx, |this, _, cx| {
21070 this.buffer
21071 .read(cx)
21072 .as_singleton()
21073 .expect("you can only call set_text on editors for singleton buffers")
21074 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21075 });
21076 }
21077
21078 pub fn display_text(&self, cx: &mut App) -> String {
21079 self.display_map
21080 .update(cx, |map, cx| map.snapshot(cx))
21081 .text()
21082 }
21083
21084 fn create_minimap(
21085 &self,
21086 minimap_settings: MinimapSettings,
21087 window: &mut Window,
21088 cx: &mut Context<Self>,
21089 ) -> Option<Entity<Self>> {
21090 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21091 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21092 }
21093
21094 fn initialize_new_minimap(
21095 &self,
21096 minimap_settings: MinimapSettings,
21097 window: &mut Window,
21098 cx: &mut Context<Self>,
21099 ) -> Entity<Self> {
21100 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21101 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21102
21103 let mut minimap = Editor::new_internal(
21104 EditorMode::Minimap {
21105 parent: cx.weak_entity(),
21106 },
21107 self.buffer.clone(),
21108 None,
21109 Some(self.display_map.clone()),
21110 window,
21111 cx,
21112 );
21113 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21114 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21115 minimap.scroll_manager.clone_state(
21116 &self.scroll_manager,
21117 &my_snapshot,
21118 &minimap_snapshot,
21119 cx,
21120 );
21121 minimap.set_text_style_refinement(TextStyleRefinement {
21122 font_size: Some(MINIMAP_FONT_SIZE),
21123 font_weight: Some(MINIMAP_FONT_WEIGHT),
21124 font_family: Some(MINIMAP_FONT_FAMILY),
21125 ..Default::default()
21126 });
21127 minimap.update_minimap_configuration(minimap_settings, cx);
21128 cx.new(|_| minimap)
21129 }
21130
21131 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21132 let current_line_highlight = minimap_settings
21133 .current_line_highlight
21134 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21135 self.set_current_line_highlight(Some(current_line_highlight));
21136 }
21137
21138 pub fn minimap(&self) -> Option<&Entity<Self>> {
21139 self.minimap
21140 .as_ref()
21141 .filter(|_| self.minimap_visibility.visible())
21142 }
21143
21144 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21145 let mut wrap_guides = smallvec![];
21146
21147 if self.show_wrap_guides == Some(false) {
21148 return wrap_guides;
21149 }
21150
21151 let settings = self.buffer.read(cx).language_settings(cx);
21152 if settings.show_wrap_guides {
21153 match self.soft_wrap_mode(cx) {
21154 SoftWrap::Column(soft_wrap) => {
21155 wrap_guides.push((soft_wrap as usize, true));
21156 }
21157 SoftWrap::Bounded(soft_wrap) => {
21158 wrap_guides.push((soft_wrap as usize, true));
21159 }
21160 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21161 }
21162 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21163 }
21164
21165 wrap_guides
21166 }
21167
21168 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21169 let settings = self.buffer.read(cx).language_settings(cx);
21170 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21171 match mode {
21172 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21173 SoftWrap::None
21174 }
21175 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21176 language_settings::SoftWrap::PreferredLineLength => {
21177 SoftWrap::Column(settings.preferred_line_length)
21178 }
21179 language_settings::SoftWrap::Bounded => {
21180 SoftWrap::Bounded(settings.preferred_line_length)
21181 }
21182 }
21183 }
21184
21185 pub fn set_soft_wrap_mode(
21186 &mut self,
21187 mode: language_settings::SoftWrap,
21188 cx: &mut Context<Self>,
21189 ) {
21190 self.soft_wrap_mode_override = Some(mode);
21191 cx.notify();
21192 }
21193
21194 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21195 self.hard_wrap = hard_wrap;
21196 cx.notify();
21197 }
21198
21199 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21200 self.text_style_refinement = Some(style);
21201 }
21202
21203 /// called by the Element so we know what style we were most recently rendered with.
21204 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21205 // We intentionally do not inform the display map about the minimap style
21206 // so that wrapping is not recalculated and stays consistent for the editor
21207 // and its linked minimap.
21208 if !self.mode.is_minimap() {
21209 let font = style.text.font();
21210 let font_size = style.text.font_size.to_pixels(window.rem_size());
21211 let display_map = self
21212 .placeholder_display_map
21213 .as_ref()
21214 .filter(|_| self.is_empty(cx))
21215 .unwrap_or(&self.display_map);
21216
21217 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21218 }
21219 self.style = Some(style);
21220 }
21221
21222 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21223 if self.style.is_none() {
21224 self.style = Some(self.create_style(cx));
21225 }
21226 self.style.as_ref().unwrap()
21227 }
21228
21229 // Called by the element. This method is not designed to be called outside of the editor
21230 // element's layout code because it does not notify when rewrapping is computed synchronously.
21231 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21232 if self.is_empty(cx) {
21233 self.placeholder_display_map
21234 .as_ref()
21235 .map_or(false, |display_map| {
21236 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21237 })
21238 } else {
21239 self.display_map
21240 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21241 }
21242 }
21243
21244 pub fn set_soft_wrap(&mut self) {
21245 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21246 }
21247
21248 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21249 if self.soft_wrap_mode_override.is_some() {
21250 self.soft_wrap_mode_override.take();
21251 } else {
21252 let soft_wrap = match self.soft_wrap_mode(cx) {
21253 SoftWrap::GitDiff => return,
21254 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21255 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21256 language_settings::SoftWrap::None
21257 }
21258 };
21259 self.soft_wrap_mode_override = Some(soft_wrap);
21260 }
21261 cx.notify();
21262 }
21263
21264 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21265 let Some(workspace) = self.workspace() else {
21266 return;
21267 };
21268 let fs = workspace.read(cx).app_state().fs.clone();
21269 let current_show = TabBarSettings::get_global(cx).show;
21270 update_settings_file(fs, cx, move |setting, _| {
21271 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21272 });
21273 }
21274
21275 pub fn toggle_indent_guides(
21276 &mut self,
21277 _: &ToggleIndentGuides,
21278 _: &mut Window,
21279 cx: &mut Context<Self>,
21280 ) {
21281 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21282 self.buffer
21283 .read(cx)
21284 .language_settings(cx)
21285 .indent_guides
21286 .enabled
21287 });
21288 self.show_indent_guides = Some(!currently_enabled);
21289 cx.notify();
21290 }
21291
21292 fn should_show_indent_guides(&self) -> Option<bool> {
21293 self.show_indent_guides
21294 }
21295
21296 pub fn disable_indent_guides_for_buffer(
21297 &mut self,
21298 buffer_id: BufferId,
21299 cx: &mut Context<Self>,
21300 ) {
21301 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21302 cx.notify();
21303 }
21304
21305 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21306 self.buffers_with_disabled_indent_guides
21307 .contains(&buffer_id)
21308 }
21309
21310 pub fn toggle_line_numbers(
21311 &mut self,
21312 _: &ToggleLineNumbers,
21313 _: &mut Window,
21314 cx: &mut Context<Self>,
21315 ) {
21316 let mut editor_settings = EditorSettings::get_global(cx).clone();
21317 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21318 EditorSettings::override_global(editor_settings, cx);
21319 }
21320
21321 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21322 if let Some(show_line_numbers) = self.show_line_numbers {
21323 return show_line_numbers;
21324 }
21325 EditorSettings::get_global(cx).gutter.line_numbers
21326 }
21327
21328 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21329 match (
21330 self.use_relative_line_numbers,
21331 EditorSettings::get_global(cx).relative_line_numbers,
21332 ) {
21333 (None, setting) => setting,
21334 (Some(false), _) => RelativeLineNumbers::Disabled,
21335 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21336 (Some(true), _) => RelativeLineNumbers::Enabled,
21337 }
21338 }
21339
21340 pub fn toggle_relative_line_numbers(
21341 &mut self,
21342 _: &ToggleRelativeLineNumbers,
21343 _: &mut Window,
21344 cx: &mut Context<Self>,
21345 ) {
21346 let is_relative = self.relative_line_numbers(cx);
21347 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21348 }
21349
21350 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21351 self.use_relative_line_numbers = is_relative;
21352 cx.notify();
21353 }
21354
21355 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21356 self.show_gutter = show_gutter;
21357 cx.notify();
21358 }
21359
21360 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21361 self.show_scrollbars = ScrollbarAxes {
21362 horizontal: show,
21363 vertical: show,
21364 };
21365 cx.notify();
21366 }
21367
21368 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21369 self.show_scrollbars.vertical = show;
21370 cx.notify();
21371 }
21372
21373 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21374 self.show_scrollbars.horizontal = show;
21375 cx.notify();
21376 }
21377
21378 pub fn set_minimap_visibility(
21379 &mut self,
21380 minimap_visibility: MinimapVisibility,
21381 window: &mut Window,
21382 cx: &mut Context<Self>,
21383 ) {
21384 if self.minimap_visibility != minimap_visibility {
21385 if minimap_visibility.visible() && self.minimap.is_none() {
21386 let minimap_settings = EditorSettings::get_global(cx).minimap;
21387 self.minimap =
21388 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21389 }
21390 self.minimap_visibility = minimap_visibility;
21391 cx.notify();
21392 }
21393 }
21394
21395 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21396 self.set_show_scrollbars(false, cx);
21397 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21398 }
21399
21400 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21401 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21402 }
21403
21404 /// Normally the text in full mode and auto height editors is padded on the
21405 /// left side by roughly half a character width for improved hit testing.
21406 ///
21407 /// Use this method to disable this for cases where this is not wanted (e.g.
21408 /// if you want to align the editor text with some other text above or below)
21409 /// or if you want to add this padding to single-line editors.
21410 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21411 self.offset_content = offset_content;
21412 cx.notify();
21413 }
21414
21415 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21416 self.show_line_numbers = Some(show_line_numbers);
21417 cx.notify();
21418 }
21419
21420 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21421 self.disable_expand_excerpt_buttons = true;
21422 cx.notify();
21423 }
21424
21425 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21426 self.number_deleted_lines = number;
21427 cx.notify();
21428 }
21429
21430 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21431 self.delegate_expand_excerpts = delegate;
21432 }
21433
21434 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21435 self.delegate_stage_and_restore = delegate;
21436 }
21437
21438 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21439 self.delegate_open_excerpts = delegate;
21440 }
21441
21442 pub fn set_on_local_selections_changed(
21443 &mut self,
21444 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21445 ) {
21446 self.on_local_selections_changed = callback;
21447 }
21448
21449 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21450 self.suppress_selection_callback = suppress;
21451 }
21452
21453 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21454 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21455 cx.notify();
21456 }
21457
21458 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21459 self.show_code_actions = Some(show_code_actions);
21460 cx.notify();
21461 }
21462
21463 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21464 self.show_runnables = Some(show_runnables);
21465 cx.notify();
21466 }
21467
21468 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21469 self.show_breakpoints = Some(show_breakpoints);
21470 cx.notify();
21471 }
21472
21473 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21474 self.show_diff_review_button = show;
21475 cx.notify();
21476 }
21477
21478 pub fn show_diff_review_button(&self) -> bool {
21479 self.show_diff_review_button
21480 }
21481
21482 pub fn render_diff_review_button(
21483 &self,
21484 display_row: DisplayRow,
21485 width: Pixels,
21486 cx: &mut Context<Self>,
21487 ) -> impl IntoElement {
21488 let text_color = cx.theme().colors().text;
21489 let icon_color = cx.theme().colors().icon_accent;
21490
21491 h_flex()
21492 .id("diff_review_button")
21493 .cursor_pointer()
21494 .w(width - px(1.))
21495 .h(relative(0.9))
21496 .justify_center()
21497 .rounded_sm()
21498 .border_1()
21499 .border_color(text_color.opacity(0.1))
21500 .bg(text_color.opacity(0.15))
21501 .hover(|s| {
21502 s.bg(icon_color.opacity(0.4))
21503 .border_color(icon_color.opacity(0.5))
21504 })
21505 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21506 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21507 .on_mouse_down(
21508 gpui::MouseButton::Left,
21509 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21510 editor.start_diff_review_drag(display_row, window, cx);
21511 }),
21512 )
21513 }
21514
21515 pub fn start_diff_review_drag(
21516 &mut self,
21517 display_row: DisplayRow,
21518 window: &mut Window,
21519 cx: &mut Context<Self>,
21520 ) {
21521 let snapshot = self.snapshot(window, cx);
21522 let point = snapshot
21523 .display_snapshot
21524 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21525 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21526 self.diff_review_drag_state = Some(DiffReviewDragState {
21527 start_anchor: anchor,
21528 current_anchor: anchor,
21529 });
21530 cx.notify();
21531 }
21532
21533 pub fn update_diff_review_drag(
21534 &mut self,
21535 display_row: DisplayRow,
21536 window: &mut Window,
21537 cx: &mut Context<Self>,
21538 ) {
21539 if self.diff_review_drag_state.is_none() {
21540 return;
21541 }
21542 let snapshot = self.snapshot(window, cx);
21543 let point = snapshot
21544 .display_snapshot
21545 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21546 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21547 if let Some(drag_state) = &mut self.diff_review_drag_state {
21548 drag_state.current_anchor = anchor;
21549 cx.notify();
21550 }
21551 }
21552
21553 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21554 if let Some(drag_state) = self.diff_review_drag_state.take() {
21555 let snapshot = self.snapshot(window, cx);
21556 let range = drag_state.row_range(&snapshot.display_snapshot);
21557 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21558 }
21559 cx.notify();
21560 }
21561
21562 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21563 self.diff_review_drag_state = None;
21564 cx.notify();
21565 }
21566
21567 /// Calculates the appropriate block height for the diff review overlay.
21568 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21569 /// and 2 lines per comment when expanded.
21570 fn calculate_overlay_height(
21571 &self,
21572 hunk_key: &DiffHunkKey,
21573 comments_expanded: bool,
21574 snapshot: &MultiBufferSnapshot,
21575 ) -> u32 {
21576 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21577 let base_height: u32 = 2; // Input row with avatar and buttons
21578
21579 if comment_count == 0 {
21580 base_height
21581 } else if comments_expanded {
21582 // Header (1 line) + 2 lines per comment
21583 base_height + 1 + (comment_count as u32 * 2)
21584 } else {
21585 // Just header when collapsed
21586 base_height + 1
21587 }
21588 }
21589
21590 pub fn show_diff_review_overlay(
21591 &mut self,
21592 display_range: Range<DisplayRow>,
21593 window: &mut Window,
21594 cx: &mut Context<Self>,
21595 ) {
21596 let Range { start, end } = display_range.sorted();
21597
21598 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21599 let editor_snapshot = self.snapshot(window, cx);
21600
21601 // Convert display rows to multibuffer points
21602 let start_point = editor_snapshot
21603 .display_snapshot
21604 .display_point_to_point(start.as_display_point(), Bias::Left);
21605 let end_point = editor_snapshot
21606 .display_snapshot
21607 .display_point_to_point(end.as_display_point(), Bias::Left);
21608 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21609
21610 // Create anchor range for the selected lines (start of first line to end of last line)
21611 let line_end = Point::new(
21612 end_point.row,
21613 buffer_snapshot.line_len(end_multi_buffer_row),
21614 );
21615 let anchor_range =
21616 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21617
21618 // Compute the hunk key for this display row
21619 let file_path = buffer_snapshot
21620 .file_at(start_point)
21621 .map(|file: &Arc<dyn language::File>| file.path().clone())
21622 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21623 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21624 let new_hunk_key = DiffHunkKey {
21625 file_path,
21626 hunk_start_anchor,
21627 };
21628
21629 // Check if we already have an overlay for this hunk
21630 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21631 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21632 }) {
21633 // Just focus the existing overlay's prompt editor
21634 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21635 window.focus(&focus_handle, cx);
21636 return;
21637 }
21638
21639 // Dismiss overlays that have no comments for their hunks
21640 self.dismiss_overlays_without_comments(cx);
21641
21642 // Get the current user's avatar URI from the project's user_store
21643 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21644 let user_store = project.read(cx).user_store();
21645 user_store
21646 .read(cx)
21647 .current_user()
21648 .map(|user| user.avatar_uri.clone())
21649 });
21650
21651 // Create anchor at the end of the last row so the block appears immediately below it
21652 // Use multibuffer coordinates for anchor creation
21653 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21654 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21655
21656 // Use the hunk key we already computed
21657 let hunk_key = new_hunk_key;
21658
21659 // Create the prompt editor for the review input
21660 let prompt_editor = cx.new(|cx| {
21661 let mut editor = Editor::single_line(window, cx);
21662 editor.set_placeholder_text("Add a review comment...", window, cx);
21663 editor
21664 });
21665
21666 // Register the Newline action on the prompt editor to submit the review
21667 let parent_editor = cx.entity().downgrade();
21668 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21669 prompt_editor.register_action({
21670 let parent_editor = parent_editor.clone();
21671 move |_: &crate::actions::Newline, window, cx| {
21672 if let Some(editor) = parent_editor.upgrade() {
21673 editor.update(cx, |editor, cx| {
21674 editor.submit_diff_review_comment(window, cx);
21675 });
21676 }
21677 }
21678 })
21679 });
21680
21681 // Calculate initial height based on existing comments for this hunk
21682 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21683
21684 // Create the overlay block
21685 let prompt_editor_for_render = prompt_editor.clone();
21686 let hunk_key_for_render = hunk_key.clone();
21687 let editor_handle = cx.entity().downgrade();
21688 let block = BlockProperties {
21689 style: BlockStyle::Sticky,
21690 placement: BlockPlacement::Below(anchor),
21691 height: Some(initial_height),
21692 render: Arc::new(move |cx| {
21693 Self::render_diff_review_overlay(
21694 &prompt_editor_for_render,
21695 &hunk_key_for_render,
21696 &editor_handle,
21697 cx,
21698 )
21699 }),
21700 priority: 0,
21701 };
21702
21703 let block_ids = self.insert_blocks([block], None, cx);
21704 let Some(block_id) = block_ids.into_iter().next() else {
21705 log::error!("Failed to insert diff review overlay block");
21706 return;
21707 };
21708
21709 self.diff_review_overlays.push(DiffReviewOverlay {
21710 anchor_range,
21711 block_id,
21712 prompt_editor: prompt_editor.clone(),
21713 hunk_key,
21714 comments_expanded: true,
21715 inline_edit_editors: HashMap::default(),
21716 inline_edit_subscriptions: HashMap::default(),
21717 user_avatar_uri,
21718 _subscription: subscription,
21719 });
21720
21721 // Focus the prompt editor
21722 let focus_handle = prompt_editor.focus_handle(cx);
21723 window.focus(&focus_handle, cx);
21724
21725 cx.notify();
21726 }
21727
21728 /// Dismisses all diff review overlays.
21729 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21730 if self.diff_review_overlays.is_empty() {
21731 return;
21732 }
21733 let block_ids: HashSet<_> = self
21734 .diff_review_overlays
21735 .drain(..)
21736 .map(|overlay| overlay.block_id)
21737 .collect();
21738 self.remove_blocks(block_ids, None, cx);
21739 cx.notify();
21740 }
21741
21742 /// Dismisses overlays that have no comments stored for their hunks.
21743 /// Keeps overlays that have at least one comment.
21744 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21745 let snapshot = self.buffer.read(cx).snapshot(cx);
21746
21747 // First, compute which overlays have comments (to avoid borrow issues with retain)
21748 let overlays_with_comments: Vec<bool> = self
21749 .diff_review_overlays
21750 .iter()
21751 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21752 .collect();
21753
21754 // Now collect block IDs to remove and retain overlays
21755 let mut block_ids_to_remove = HashSet::default();
21756 let mut index = 0;
21757 self.diff_review_overlays.retain(|overlay| {
21758 let has_comments = overlays_with_comments[index];
21759 index += 1;
21760 if !has_comments {
21761 block_ids_to_remove.insert(overlay.block_id);
21762 }
21763 has_comments
21764 });
21765
21766 if !block_ids_to_remove.is_empty() {
21767 self.remove_blocks(block_ids_to_remove, None, cx);
21768 cx.notify();
21769 }
21770 }
21771
21772 /// Refreshes the diff review overlay block to update its height and render function.
21773 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21774 fn refresh_diff_review_overlay_height(
21775 &mut self,
21776 hunk_key: &DiffHunkKey,
21777 _window: &mut Window,
21778 cx: &mut Context<Self>,
21779 ) {
21780 // Extract all needed data from overlay first to avoid borrow conflicts
21781 let snapshot = self.buffer.read(cx).snapshot(cx);
21782 let (comments_expanded, block_id, prompt_editor) = {
21783 let Some(overlay) = self
21784 .diff_review_overlays
21785 .iter()
21786 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21787 else {
21788 return;
21789 };
21790
21791 (
21792 overlay.comments_expanded,
21793 overlay.block_id,
21794 overlay.prompt_editor.clone(),
21795 )
21796 };
21797
21798 // Calculate new height
21799 let snapshot = self.buffer.read(cx).snapshot(cx);
21800 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21801
21802 // Update the block height using resize_blocks (avoids flicker)
21803 let mut heights = HashMap::default();
21804 heights.insert(block_id, new_height);
21805 self.resize_blocks(heights, None, cx);
21806
21807 // Update the render function using replace_blocks (avoids flicker)
21808 let hunk_key_for_render = hunk_key.clone();
21809 let editor_handle = cx.entity().downgrade();
21810 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
21811 Arc::new(move |cx| {
21812 Self::render_diff_review_overlay(
21813 &prompt_editor,
21814 &hunk_key_for_render,
21815 &editor_handle,
21816 cx,
21817 )
21818 });
21819
21820 let mut renderers = HashMap::default();
21821 renderers.insert(block_id, render);
21822 self.replace_blocks(renderers, None, cx);
21823 }
21824
21825 /// Action handler for SubmitDiffReviewComment.
21826 pub fn submit_diff_review_comment_action(
21827 &mut self,
21828 _: &SubmitDiffReviewComment,
21829 window: &mut Window,
21830 cx: &mut Context<Self>,
21831 ) {
21832 self.submit_diff_review_comment(window, cx);
21833 }
21834
21835 /// Stores the diff review comment locally.
21836 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
21837 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21838 // Find the overlay that currently has focus
21839 let overlay_index = self
21840 .diff_review_overlays
21841 .iter()
21842 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
21843 let Some(overlay_index) = overlay_index else {
21844 return;
21845 };
21846 let overlay = &self.diff_review_overlays[overlay_index];
21847
21848 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
21849 if comment_text.is_empty() {
21850 return;
21851 }
21852
21853 let anchor_range = overlay.anchor_range.clone();
21854 let hunk_key = overlay.hunk_key.clone();
21855
21856 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
21857
21858 // Clear the prompt editor but keep the overlay open
21859 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
21860 overlay.prompt_editor.update(cx, |editor, cx| {
21861 editor.clear(window, cx);
21862 });
21863 }
21864
21865 // Refresh the overlay to update the block height for the new comment
21866 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
21867
21868 cx.notify();
21869 }
21870
21871 /// Returns the prompt editor for the diff review overlay, if one is active.
21872 /// This is primarily used for testing.
21873 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
21874 self.diff_review_overlays
21875 .first()
21876 .map(|overlay| &overlay.prompt_editor)
21877 }
21878
21879 /// Returns the line range for the first diff review overlay, if one is active.
21880 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
21881 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
21882 let overlay = self.diff_review_overlays.first()?;
21883 let snapshot = self.buffer.read(cx).snapshot(cx);
21884 let start_point = overlay.anchor_range.start.to_point(&snapshot);
21885 let end_point = overlay.anchor_range.end.to_point(&snapshot);
21886 let start_row = snapshot
21887 .point_to_buffer_point(start_point)
21888 .map(|(_, p, _)| p.row)
21889 .unwrap_or(start_point.row);
21890 let end_row = snapshot
21891 .point_to_buffer_point(end_point)
21892 .map(|(_, p, _)| p.row)
21893 .unwrap_or(end_point.row);
21894 Some((start_row, end_row))
21895 }
21896
21897 /// Sets whether the comments section is expanded in the diff review overlay.
21898 /// This is primarily used for testing.
21899 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
21900 for overlay in &mut self.diff_review_overlays {
21901 overlay.comments_expanded = expanded;
21902 }
21903 cx.notify();
21904 }
21905
21906 /// Compares two DiffHunkKeys for equality by resolving their anchors.
21907 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
21908 a.file_path == b.file_path
21909 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
21910 }
21911
21912 /// Returns comments for a specific hunk, ordered by creation time.
21913 pub fn comments_for_hunk<'a>(
21914 &'a self,
21915 key: &DiffHunkKey,
21916 snapshot: &MultiBufferSnapshot,
21917 ) -> &'a [StoredReviewComment] {
21918 let key_point = key.hunk_start_anchor.to_point(snapshot);
21919 self.stored_review_comments
21920 .iter()
21921 .find(|(k, _)| {
21922 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21923 })
21924 .map(|(_, comments)| comments.as_slice())
21925 .unwrap_or(&[])
21926 }
21927
21928 /// Returns the total count of stored review comments across all hunks.
21929 pub fn total_review_comment_count(&self) -> usize {
21930 self.stored_review_comments
21931 .iter()
21932 .map(|(_, v)| v.len())
21933 .sum()
21934 }
21935
21936 /// Returns the count of comments for a specific hunk.
21937 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
21938 let key_point = key.hunk_start_anchor.to_point(snapshot);
21939 self.stored_review_comments
21940 .iter()
21941 .find(|(k, _)| {
21942 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
21943 })
21944 .map(|(_, v)| v.len())
21945 .unwrap_or(0)
21946 }
21947
21948 /// Adds a new review comment to a specific hunk.
21949 pub fn add_review_comment(
21950 &mut self,
21951 hunk_key: DiffHunkKey,
21952 comment: String,
21953 anchor_range: Range<Anchor>,
21954 cx: &mut Context<Self>,
21955 ) -> usize {
21956 let id = self.next_review_comment_id;
21957 self.next_review_comment_id += 1;
21958
21959 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
21960
21961 let snapshot = self.buffer.read(cx).snapshot(cx);
21962 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
21963
21964 // Find existing entry for this hunk or add a new one
21965 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
21966 k.file_path == hunk_key.file_path
21967 && k.hunk_start_anchor.to_point(&snapshot) == key_point
21968 }) {
21969 comments.push(stored_comment);
21970 } else {
21971 self.stored_review_comments
21972 .push((hunk_key, vec![stored_comment]));
21973 }
21974
21975 cx.emit(EditorEvent::ReviewCommentsChanged {
21976 total_count: self.total_review_comment_count(),
21977 });
21978 cx.notify();
21979 id
21980 }
21981
21982 /// Removes a review comment by ID from any hunk.
21983 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
21984 for (_, comments) in self.stored_review_comments.iter_mut() {
21985 if let Some(index) = comments.iter().position(|c| c.id == id) {
21986 comments.remove(index);
21987 cx.emit(EditorEvent::ReviewCommentsChanged {
21988 total_count: self.total_review_comment_count(),
21989 });
21990 cx.notify();
21991 return true;
21992 }
21993 }
21994 false
21995 }
21996
21997 /// Updates a review comment's text by ID.
21998 pub fn update_review_comment(
21999 &mut self,
22000 id: usize,
22001 new_comment: String,
22002 cx: &mut Context<Self>,
22003 ) -> bool {
22004 for (_, comments) in self.stored_review_comments.iter_mut() {
22005 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22006 comment.comment = new_comment;
22007 comment.is_editing = false;
22008 cx.emit(EditorEvent::ReviewCommentsChanged {
22009 total_count: self.total_review_comment_count(),
22010 });
22011 cx.notify();
22012 return true;
22013 }
22014 }
22015 false
22016 }
22017
22018 /// Sets a comment's editing state.
22019 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22020 for (_, comments) in self.stored_review_comments.iter_mut() {
22021 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22022 comment.is_editing = is_editing;
22023 cx.notify();
22024 return;
22025 }
22026 }
22027 }
22028
22029 /// Takes all stored comments from all hunks, clearing the storage.
22030 /// Returns a Vec of (hunk_key, comments) pairs.
22031 pub fn take_all_review_comments(
22032 &mut self,
22033 cx: &mut Context<Self>,
22034 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22035 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22036 self.dismiss_all_diff_review_overlays(cx);
22037 let comments = std::mem::take(&mut self.stored_review_comments);
22038 // Reset the ID counter since all comments have been taken
22039 self.next_review_comment_id = 0;
22040 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22041 cx.notify();
22042 comments
22043 }
22044
22045 /// Removes review comments whose anchors are no longer valid or whose
22046 /// associated diff hunks no longer exist.
22047 ///
22048 /// This should be called when the buffer changes to prevent orphaned comments
22049 /// from accumulating.
22050 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22051 let snapshot = self.buffer.read(cx).snapshot(cx);
22052 let original_count = self.total_review_comment_count();
22053
22054 // Remove comments with invalid hunk anchors
22055 self.stored_review_comments
22056 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22057
22058 // Also clean up individual comments with invalid anchor ranges
22059 for (_, comments) in &mut self.stored_review_comments {
22060 comments.retain(|comment| {
22061 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22062 });
22063 }
22064
22065 // Remove empty hunk entries
22066 self.stored_review_comments
22067 .retain(|(_, comments)| !comments.is_empty());
22068
22069 let new_count = self.total_review_comment_count();
22070 if new_count != original_count {
22071 cx.emit(EditorEvent::ReviewCommentsChanged {
22072 total_count: new_count,
22073 });
22074 cx.notify();
22075 }
22076 }
22077
22078 /// Toggles the expanded state of the comments section in the overlay.
22079 pub fn toggle_review_comments_expanded(
22080 &mut self,
22081 _: &ToggleReviewCommentsExpanded,
22082 window: &mut Window,
22083 cx: &mut Context<Self>,
22084 ) {
22085 // Find the overlay that currently has focus, or use the first one
22086 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22087 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22088 overlay.comments_expanded = !overlay.comments_expanded;
22089 Some(overlay.hunk_key.clone())
22090 } else {
22091 None
22092 }
22093 });
22094
22095 // If no focused overlay found, toggle the first one
22096 let hunk_key = overlay_info.or_else(|| {
22097 self.diff_review_overlays.first_mut().map(|overlay| {
22098 overlay.comments_expanded = !overlay.comments_expanded;
22099 overlay.hunk_key.clone()
22100 })
22101 });
22102
22103 if let Some(hunk_key) = hunk_key {
22104 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22105 cx.notify();
22106 }
22107 }
22108
22109 /// Handles the EditReviewComment action - sets a comment into editing mode.
22110 pub fn edit_review_comment(
22111 &mut self,
22112 action: &EditReviewComment,
22113 window: &mut Window,
22114 cx: &mut Context<Self>,
22115 ) {
22116 let comment_id = action.id;
22117
22118 // Set the comment to editing mode
22119 self.set_comment_editing(comment_id, true, cx);
22120
22121 // Find the overlay that contains this comment and create an inline editor if needed
22122 // First, find which hunk this comment belongs to
22123 let hunk_key = self
22124 .stored_review_comments
22125 .iter()
22126 .find_map(|(key, comments)| {
22127 if comments.iter().any(|c| c.id == comment_id) {
22128 Some(key.clone())
22129 } else {
22130 None
22131 }
22132 });
22133
22134 let snapshot = self.buffer.read(cx).snapshot(cx);
22135 if let Some(hunk_key) = hunk_key {
22136 if let Some(overlay) = self
22137 .diff_review_overlays
22138 .iter_mut()
22139 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22140 {
22141 if let std::collections::hash_map::Entry::Vacant(entry) =
22142 overlay.inline_edit_editors.entry(comment_id)
22143 {
22144 // Find the comment text
22145 let comment_text = self
22146 .stored_review_comments
22147 .iter()
22148 .flat_map(|(_, comments)| comments)
22149 .find(|c| c.id == comment_id)
22150 .map(|c| c.comment.clone())
22151 .unwrap_or_default();
22152
22153 // Create inline editor
22154 let parent_editor = cx.entity().downgrade();
22155 let inline_editor = cx.new(|cx| {
22156 let mut editor = Editor::single_line(window, cx);
22157 editor.set_text(&*comment_text, window, cx);
22158 // Select all text for easy replacement
22159 editor.select_all(&crate::actions::SelectAll, window, cx);
22160 editor
22161 });
22162
22163 // Register the Newline action to confirm the edit
22164 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22165 inline_editor.register_action({
22166 let parent_editor = parent_editor.clone();
22167 move |_: &crate::actions::Newline, window, cx| {
22168 if let Some(editor) = parent_editor.upgrade() {
22169 editor.update(cx, |editor, cx| {
22170 editor.confirm_edit_review_comment(comment_id, window, cx);
22171 });
22172 }
22173 }
22174 })
22175 });
22176
22177 // Store the subscription to keep the action handler alive
22178 overlay
22179 .inline_edit_subscriptions
22180 .insert(comment_id, subscription);
22181
22182 // Focus the inline editor
22183 let focus_handle = inline_editor.focus_handle(cx);
22184 window.focus(&focus_handle, cx);
22185
22186 entry.insert(inline_editor);
22187 }
22188 }
22189 }
22190
22191 cx.notify();
22192 }
22193
22194 /// Confirms an inline edit of a review comment.
22195 pub fn confirm_edit_review_comment(
22196 &mut self,
22197 comment_id: usize,
22198 _window: &mut Window,
22199 cx: &mut Context<Self>,
22200 ) {
22201 // Get the new text from the inline editor
22202 // Find the overlay containing this comment's inline editor
22203 let snapshot = self.buffer.read(cx).snapshot(cx);
22204 let hunk_key = self
22205 .stored_review_comments
22206 .iter()
22207 .find_map(|(key, comments)| {
22208 if comments.iter().any(|c| c.id == comment_id) {
22209 Some(key.clone())
22210 } else {
22211 None
22212 }
22213 });
22214
22215 let new_text = hunk_key
22216 .as_ref()
22217 .and_then(|hunk_key| {
22218 self.diff_review_overlays
22219 .iter()
22220 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22221 })
22222 .as_ref()
22223 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22224 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22225
22226 if let Some(new_text) = new_text {
22227 if !new_text.is_empty() {
22228 self.update_review_comment(comment_id, new_text, cx);
22229 }
22230 }
22231
22232 // Remove the inline editor and its subscription
22233 if let Some(hunk_key) = hunk_key {
22234 if let Some(overlay) = self
22235 .diff_review_overlays
22236 .iter_mut()
22237 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22238 {
22239 overlay.inline_edit_editors.remove(&comment_id);
22240 overlay.inline_edit_subscriptions.remove(&comment_id);
22241 }
22242 }
22243
22244 // Clear editing state
22245 self.set_comment_editing(comment_id, false, cx);
22246 }
22247
22248 /// Cancels an inline edit of a review comment.
22249 pub fn cancel_edit_review_comment(
22250 &mut self,
22251 comment_id: usize,
22252 _window: &mut Window,
22253 cx: &mut Context<Self>,
22254 ) {
22255 // Find which hunk this comment belongs to
22256 let hunk_key = self
22257 .stored_review_comments
22258 .iter()
22259 .find_map(|(key, comments)| {
22260 if comments.iter().any(|c| c.id == comment_id) {
22261 Some(key.clone())
22262 } else {
22263 None
22264 }
22265 });
22266
22267 // Remove the inline editor and its subscription
22268 if let Some(hunk_key) = hunk_key {
22269 let snapshot = self.buffer.read(cx).snapshot(cx);
22270 if let Some(overlay) = self
22271 .diff_review_overlays
22272 .iter_mut()
22273 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22274 {
22275 overlay.inline_edit_editors.remove(&comment_id);
22276 overlay.inline_edit_subscriptions.remove(&comment_id);
22277 }
22278 }
22279
22280 // Clear editing state
22281 self.set_comment_editing(comment_id, false, cx);
22282 }
22283
22284 /// Action handler for ConfirmEditReviewComment.
22285 pub fn confirm_edit_review_comment_action(
22286 &mut self,
22287 action: &ConfirmEditReviewComment,
22288 window: &mut Window,
22289 cx: &mut Context<Self>,
22290 ) {
22291 self.confirm_edit_review_comment(action.id, window, cx);
22292 }
22293
22294 /// Action handler for CancelEditReviewComment.
22295 pub fn cancel_edit_review_comment_action(
22296 &mut self,
22297 action: &CancelEditReviewComment,
22298 window: &mut Window,
22299 cx: &mut Context<Self>,
22300 ) {
22301 self.cancel_edit_review_comment(action.id, window, cx);
22302 }
22303
22304 /// Handles the DeleteReviewComment action - removes a comment.
22305 pub fn delete_review_comment(
22306 &mut self,
22307 action: &DeleteReviewComment,
22308 window: &mut Window,
22309 cx: &mut Context<Self>,
22310 ) {
22311 // Get the hunk key before removing the comment
22312 // Find the hunk key from the comment itself
22313 let comment_id = action.id;
22314 let hunk_key = self
22315 .stored_review_comments
22316 .iter()
22317 .find_map(|(key, comments)| {
22318 if comments.iter().any(|c| c.id == comment_id) {
22319 Some(key.clone())
22320 } else {
22321 None
22322 }
22323 });
22324
22325 // Also get it from the overlay for refresh purposes
22326 let overlay_hunk_key = self
22327 .diff_review_overlays
22328 .first()
22329 .map(|o| o.hunk_key.clone());
22330
22331 self.remove_review_comment(action.id, cx);
22332
22333 // Refresh the overlay height after removing a comment
22334 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22335 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22336 }
22337 }
22338
22339 fn render_diff_review_overlay(
22340 prompt_editor: &Entity<Editor>,
22341 hunk_key: &DiffHunkKey,
22342 editor_handle: &WeakEntity<Editor>,
22343 cx: &mut BlockContext,
22344 ) -> AnyElement {
22345 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22346 if ranges.is_empty() {
22347 return None;
22348 }
22349 let formatted: Vec<String> = ranges
22350 .iter()
22351 .map(|(start, end)| {
22352 let start_line = start + 1;
22353 let end_line = end + 1;
22354 if start_line == end_line {
22355 format!("Line {start_line}")
22356 } else {
22357 format!("Lines {start_line}-{end_line}")
22358 }
22359 })
22360 .collect();
22361 // Don't show label for single line in single excerpt
22362 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22363 return None;
22364 }
22365 Some(formatted.join(" ⋯ "))
22366 }
22367
22368 let theme = cx.theme();
22369 let colors = theme.colors();
22370
22371 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22372 editor_handle
22373 .upgrade()
22374 .map(|editor| {
22375 let editor = editor.read(cx);
22376 let snapshot = editor.buffer().read(cx).snapshot(cx);
22377 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22378 let (expanded, editors, avatar_uri, line_ranges) = editor
22379 .diff_review_overlays
22380 .iter()
22381 .find(|overlay| {
22382 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22383 })
22384 .map(|o| {
22385 let start_point = o.anchor_range.start.to_point(&snapshot);
22386 let end_point = o.anchor_range.end.to_point(&snapshot);
22387 // Get line ranges per excerpt to detect discontinuities
22388 let buffer_ranges =
22389 snapshot.range_to_buffer_ranges(start_point..end_point);
22390 let ranges: Vec<(u32, u32)> = buffer_ranges
22391 .iter()
22392 .map(|(buffer, range, _)| {
22393 let start = buffer.offset_to_point(range.start.0).row;
22394 let end = buffer.offset_to_point(range.end.0).row;
22395 (start, end)
22396 })
22397 .collect();
22398 (
22399 o.comments_expanded,
22400 o.inline_edit_editors.clone(),
22401 o.user_avatar_uri.clone(),
22402 if ranges.is_empty() {
22403 None
22404 } else {
22405 Some(ranges)
22406 },
22407 )
22408 })
22409 .unwrap_or((true, HashMap::default(), None, None));
22410 (comments, expanded, editors, avatar_uri, line_ranges)
22411 })
22412 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22413
22414 let comment_count = comments.len();
22415 let avatar_size = px(20.);
22416 let action_icon_size = IconSize::XSmall;
22417
22418 v_flex()
22419 .w_full()
22420 .bg(colors.editor_background)
22421 .border_b_1()
22422 .border_color(colors.border)
22423 .px_2()
22424 .pb_2()
22425 .gap_2()
22426 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22427 .when_some(line_ranges, |el, ranges| {
22428 let label = format_line_ranges(&ranges);
22429 if let Some(label) = label {
22430 el.child(
22431 h_flex()
22432 .w_full()
22433 .px_2()
22434 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22435 )
22436 } else {
22437 el
22438 }
22439 })
22440 // Top row: editable input with user's avatar
22441 .child(
22442 h_flex()
22443 .w_full()
22444 .items_center()
22445 .gap_2()
22446 .px_2()
22447 .py_1p5()
22448 .rounded_md()
22449 .bg(colors.surface_background)
22450 .child(
22451 div()
22452 .size(avatar_size)
22453 .flex_shrink_0()
22454 .rounded_full()
22455 .overflow_hidden()
22456 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22457 Avatar::new(avatar_uri.clone())
22458 .size(avatar_size)
22459 .into_any_element()
22460 } else {
22461 Icon::new(IconName::Person)
22462 .size(IconSize::Small)
22463 .color(ui::Color::Muted)
22464 .into_any_element()
22465 }),
22466 )
22467 .child(
22468 div()
22469 .flex_1()
22470 .border_1()
22471 .border_color(colors.border)
22472 .rounded_md()
22473 .bg(colors.editor_background)
22474 .px_2()
22475 .py_1()
22476 .child(prompt_editor.clone()),
22477 )
22478 .child(
22479 h_flex()
22480 .flex_shrink_0()
22481 .gap_1()
22482 .child(
22483 IconButton::new("diff-review-close", IconName::Close)
22484 .icon_color(ui::Color::Muted)
22485 .icon_size(action_icon_size)
22486 .tooltip(Tooltip::text("Close"))
22487 .on_click(|_, window, cx| {
22488 window
22489 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22490 }),
22491 )
22492 .child(
22493 IconButton::new("diff-review-add", IconName::Return)
22494 .icon_color(ui::Color::Muted)
22495 .icon_size(action_icon_size)
22496 .tooltip(Tooltip::text("Add comment"))
22497 .on_click(|_, window, cx| {
22498 window.dispatch_action(
22499 Box::new(crate::actions::SubmitDiffReviewComment),
22500 cx,
22501 );
22502 }),
22503 ),
22504 ),
22505 )
22506 // Expandable comments section (only shown when there are comments)
22507 .when(comment_count > 0, |el| {
22508 el.child(Self::render_comments_section(
22509 comments,
22510 comments_expanded,
22511 inline_editors,
22512 user_avatar_uri,
22513 avatar_size,
22514 action_icon_size,
22515 colors,
22516 ))
22517 })
22518 .into_any_element()
22519 }
22520
22521 fn render_comments_section(
22522 comments: Vec<StoredReviewComment>,
22523 expanded: bool,
22524 inline_editors: HashMap<usize, Entity<Editor>>,
22525 user_avatar_uri: Option<SharedUri>,
22526 avatar_size: Pixels,
22527 action_icon_size: IconSize,
22528 colors: &theme::ThemeColors,
22529 ) -> impl IntoElement {
22530 let comment_count = comments.len();
22531
22532 v_flex()
22533 .w_full()
22534 .gap_1()
22535 // Header with expand/collapse toggle
22536 .child(
22537 h_flex()
22538 .id("review-comments-header")
22539 .w_full()
22540 .items_center()
22541 .gap_1()
22542 .px_2()
22543 .py_1()
22544 .cursor_pointer()
22545 .rounded_md()
22546 .hover(|style| style.bg(colors.ghost_element_hover))
22547 .on_click(|_, window: &mut Window, cx| {
22548 window.dispatch_action(
22549 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22550 cx,
22551 );
22552 })
22553 .child(
22554 Icon::new(if expanded {
22555 IconName::ChevronDown
22556 } else {
22557 IconName::ChevronRight
22558 })
22559 .size(IconSize::Small)
22560 .color(ui::Color::Muted),
22561 )
22562 .child(
22563 Label::new(format!(
22564 "{} Comment{}",
22565 comment_count,
22566 if comment_count == 1 { "" } else { "s" }
22567 ))
22568 .size(LabelSize::Small)
22569 .color(Color::Muted),
22570 ),
22571 )
22572 // Comments list (when expanded)
22573 .when(expanded, |el| {
22574 el.children(comments.into_iter().map(|comment| {
22575 let inline_editor = inline_editors.get(&comment.id).cloned();
22576 Self::render_comment_row(
22577 comment,
22578 inline_editor,
22579 user_avatar_uri.clone(),
22580 avatar_size,
22581 action_icon_size,
22582 colors,
22583 )
22584 }))
22585 })
22586 }
22587
22588 fn render_comment_row(
22589 comment: StoredReviewComment,
22590 inline_editor: Option<Entity<Editor>>,
22591 user_avatar_uri: Option<SharedUri>,
22592 avatar_size: Pixels,
22593 action_icon_size: IconSize,
22594 colors: &theme::ThemeColors,
22595 ) -> impl IntoElement {
22596 let comment_id = comment.id;
22597 let is_editing = inline_editor.is_some();
22598
22599 h_flex()
22600 .w_full()
22601 .items_center()
22602 .gap_2()
22603 .px_2()
22604 .py_1p5()
22605 .rounded_md()
22606 .bg(colors.surface_background)
22607 .child(
22608 div()
22609 .size(avatar_size)
22610 .flex_shrink_0()
22611 .rounded_full()
22612 .overflow_hidden()
22613 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22614 Avatar::new(avatar_uri.clone())
22615 .size(avatar_size)
22616 .into_any_element()
22617 } else {
22618 Icon::new(IconName::Person)
22619 .size(IconSize::Small)
22620 .color(ui::Color::Muted)
22621 .into_any_element()
22622 }),
22623 )
22624 .child(if let Some(editor) = inline_editor {
22625 // Inline edit mode: show an editable text field
22626 div()
22627 .flex_1()
22628 .border_1()
22629 .border_color(colors.border)
22630 .rounded_md()
22631 .bg(colors.editor_background)
22632 .px_2()
22633 .py_1()
22634 .child(editor)
22635 .into_any_element()
22636 } else {
22637 // Display mode: show the comment text
22638 div()
22639 .flex_1()
22640 .text_sm()
22641 .text_color(colors.text)
22642 .child(comment.comment)
22643 .into_any_element()
22644 })
22645 .child(if is_editing {
22646 // Editing mode: show close and confirm buttons
22647 h_flex()
22648 .gap_1()
22649 .child(
22650 IconButton::new(
22651 format!("diff-review-cancel-edit-{comment_id}"),
22652 IconName::Close,
22653 )
22654 .icon_color(ui::Color::Muted)
22655 .icon_size(action_icon_size)
22656 .tooltip(Tooltip::text("Cancel"))
22657 .on_click(move |_, window, cx| {
22658 window.dispatch_action(
22659 Box::new(crate::actions::CancelEditReviewComment {
22660 id: comment_id,
22661 }),
22662 cx,
22663 );
22664 }),
22665 )
22666 .child(
22667 IconButton::new(
22668 format!("diff-review-confirm-edit-{comment_id}"),
22669 IconName::Return,
22670 )
22671 .icon_color(ui::Color::Muted)
22672 .icon_size(action_icon_size)
22673 .tooltip(Tooltip::text("Confirm"))
22674 .on_click(move |_, window, cx| {
22675 window.dispatch_action(
22676 Box::new(crate::actions::ConfirmEditReviewComment {
22677 id: comment_id,
22678 }),
22679 cx,
22680 );
22681 }),
22682 )
22683 .into_any_element()
22684 } else {
22685 // Display mode: no action buttons for now (edit/delete not yet implemented)
22686 gpui::Empty.into_any_element()
22687 })
22688 }
22689
22690 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22691 if self.display_map.read(cx).masked != masked {
22692 self.display_map.update(cx, |map, _| map.masked = masked);
22693 }
22694 cx.notify()
22695 }
22696
22697 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22698 self.show_wrap_guides = Some(show_wrap_guides);
22699 cx.notify();
22700 }
22701
22702 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22703 self.show_indent_guides = Some(show_indent_guides);
22704 cx.notify();
22705 }
22706
22707 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22708 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22709 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22710 && let Some(dir) = file.abs_path(cx).parent()
22711 {
22712 return Some(dir.to_owned());
22713 }
22714 }
22715
22716 None
22717 }
22718
22719 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22720 self.active_excerpt(cx)?
22721 .1
22722 .read(cx)
22723 .file()
22724 .and_then(|f| f.as_local())
22725 }
22726
22727 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22728 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22729 let buffer = buffer.read(cx);
22730 if let Some(project_path) = buffer.project_path(cx) {
22731 let project = self.project()?.read(cx);
22732 project.absolute_path(&project_path, cx)
22733 } else {
22734 buffer
22735 .file()
22736 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22737 }
22738 })
22739 }
22740
22741 pub fn reveal_in_finder(
22742 &mut self,
22743 _: &RevealInFileManager,
22744 _window: &mut Window,
22745 cx: &mut Context<Self>,
22746 ) {
22747 if let Some(path) = self.target_file_abs_path(cx) {
22748 if let Some(project) = self.project() {
22749 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22750 } else {
22751 cx.reveal_path(&path);
22752 }
22753 }
22754 }
22755
22756 pub fn copy_path(
22757 &mut self,
22758 _: &zed_actions::workspace::CopyPath,
22759 _window: &mut Window,
22760 cx: &mut Context<Self>,
22761 ) {
22762 if let Some(path) = self.target_file_abs_path(cx)
22763 && let Some(path) = path.to_str()
22764 {
22765 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22766 } else {
22767 cx.propagate();
22768 }
22769 }
22770
22771 pub fn copy_relative_path(
22772 &mut self,
22773 _: &zed_actions::workspace::CopyRelativePath,
22774 _window: &mut Window,
22775 cx: &mut Context<Self>,
22776 ) {
22777 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22778 let project = self.project()?.read(cx);
22779 let path = buffer.read(cx).file()?.path();
22780 let path = path.display(project.path_style(cx));
22781 Some(path)
22782 }) {
22783 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22784 } else {
22785 cx.propagate();
22786 }
22787 }
22788
22789 /// Returns the project path for the editor's buffer, if any buffer is
22790 /// opened in the editor.
22791 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22792 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22793 buffer.read(cx).project_path(cx)
22794 } else {
22795 None
22796 }
22797 }
22798
22799 // Returns true if the editor handled a go-to-line request
22800 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22801 maybe!({
22802 let breakpoint_store = self.breakpoint_store.as_ref()?;
22803
22804 let (active_stack_frame, debug_line_pane_id) = {
22805 let store = breakpoint_store.read(cx);
22806 let active_stack_frame = store.active_position().cloned();
22807 let debug_line_pane_id = store.active_debug_line_pane_id();
22808 (active_stack_frame, debug_line_pane_id)
22809 };
22810
22811 let Some(active_stack_frame) = active_stack_frame else {
22812 self.clear_row_highlights::<ActiveDebugLine>();
22813 return None;
22814 };
22815
22816 if let Some(debug_line_pane_id) = debug_line_pane_id {
22817 if let Some(workspace) = self
22818 .workspace
22819 .as_ref()
22820 .and_then(|(workspace, _)| workspace.upgrade())
22821 {
22822 let editor_pane_id = workspace
22823 .read(cx)
22824 .pane_for_item_id(cx.entity_id())
22825 .map(|pane| pane.entity_id());
22826
22827 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
22828 self.clear_row_highlights::<ActiveDebugLine>();
22829 return None;
22830 }
22831 }
22832 }
22833
22834 let position = active_stack_frame.position;
22835 let buffer_id = position.buffer_id?;
22836 let snapshot = self
22837 .project
22838 .as_ref()?
22839 .read(cx)
22840 .buffer_for_id(buffer_id, cx)?
22841 .read(cx)
22842 .snapshot();
22843
22844 let mut handled = false;
22845 for (id, ExcerptRange { context, .. }) in
22846 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
22847 {
22848 if context.start.cmp(&position, &snapshot).is_ge()
22849 || context.end.cmp(&position, &snapshot).is_lt()
22850 {
22851 continue;
22852 }
22853 let snapshot = self.buffer.read(cx).snapshot(cx);
22854 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
22855
22856 handled = true;
22857 self.clear_row_highlights::<ActiveDebugLine>();
22858
22859 self.go_to_line::<ActiveDebugLine>(
22860 multibuffer_anchor,
22861 Some(cx.theme().colors().editor_debugger_active_line_background),
22862 window,
22863 cx,
22864 );
22865
22866 cx.notify();
22867 }
22868
22869 handled.then_some(())
22870 })
22871 .is_some()
22872 }
22873
22874 pub fn copy_file_name_without_extension(
22875 &mut self,
22876 _: &CopyFileNameWithoutExtension,
22877 _: &mut Window,
22878 cx: &mut Context<Self>,
22879 ) {
22880 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22881 let file = buffer.read(cx).file()?;
22882 file.path().file_stem()
22883 }) {
22884 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
22885 }
22886 }
22887
22888 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
22889 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22890 let file = buffer.read(cx).file()?;
22891 Some(file.file_name(cx))
22892 }) {
22893 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
22894 }
22895 }
22896
22897 pub fn toggle_git_blame(
22898 &mut self,
22899 _: &::git::Blame,
22900 window: &mut Window,
22901 cx: &mut Context<Self>,
22902 ) {
22903 self.show_git_blame_gutter = !self.show_git_blame_gutter;
22904
22905 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
22906 self.start_git_blame(true, window, cx);
22907 }
22908
22909 cx.notify();
22910 }
22911
22912 pub fn toggle_git_blame_inline(
22913 &mut self,
22914 _: &ToggleGitBlameInline,
22915 window: &mut Window,
22916 cx: &mut Context<Self>,
22917 ) {
22918 self.toggle_git_blame_inline_internal(true, window, cx);
22919 cx.notify();
22920 }
22921
22922 pub fn open_git_blame_commit(
22923 &mut self,
22924 _: &OpenGitBlameCommit,
22925 window: &mut Window,
22926 cx: &mut Context<Self>,
22927 ) {
22928 self.open_git_blame_commit_internal(window, cx);
22929 }
22930
22931 fn open_git_blame_commit_internal(
22932 &mut self,
22933 window: &mut Window,
22934 cx: &mut Context<Self>,
22935 ) -> Option<()> {
22936 let blame = self.blame.as_ref()?;
22937 let snapshot = self.snapshot(window, cx);
22938 let cursor = self
22939 .selections
22940 .newest::<Point>(&snapshot.display_snapshot)
22941 .head();
22942 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
22943 let (_, blame_entry) = blame
22944 .update(cx, |blame, cx| {
22945 blame
22946 .blame_for_rows(
22947 &[RowInfo {
22948 buffer_id: Some(buffer.remote_id()),
22949 buffer_row: Some(point.row),
22950 ..Default::default()
22951 }],
22952 cx,
22953 )
22954 .next()
22955 })
22956 .flatten()?;
22957 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22958 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
22959 let workspace = self.workspace()?.downgrade();
22960 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
22961 None
22962 }
22963
22964 pub fn git_blame_inline_enabled(&self) -> bool {
22965 self.git_blame_inline_enabled
22966 }
22967
22968 pub fn toggle_selection_menu(
22969 &mut self,
22970 _: &ToggleSelectionMenu,
22971 _: &mut Window,
22972 cx: &mut Context<Self>,
22973 ) {
22974 self.show_selection_menu = self
22975 .show_selection_menu
22976 .map(|show_selections_menu| !show_selections_menu)
22977 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
22978
22979 cx.notify();
22980 }
22981
22982 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
22983 self.show_selection_menu
22984 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
22985 }
22986
22987 fn start_git_blame(
22988 &mut self,
22989 user_triggered: bool,
22990 window: &mut Window,
22991 cx: &mut Context<Self>,
22992 ) {
22993 if let Some(project) = self.project() {
22994 if let Some(buffer) = self.buffer().read(cx).as_singleton()
22995 && buffer.read(cx).file().is_none()
22996 {
22997 return;
22998 }
22999
23000 let focused = self.focus_handle(cx).contains_focused(window, cx);
23001
23002 let project = project.clone();
23003 let blame = cx
23004 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23005 self.blame_subscription =
23006 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23007 self.blame = Some(blame);
23008 }
23009 }
23010
23011 fn toggle_git_blame_inline_internal(
23012 &mut self,
23013 user_triggered: bool,
23014 window: &mut Window,
23015 cx: &mut Context<Self>,
23016 ) {
23017 if self.git_blame_inline_enabled {
23018 self.git_blame_inline_enabled = false;
23019 self.show_git_blame_inline = false;
23020 self.show_git_blame_inline_delay_task.take();
23021 } else {
23022 self.git_blame_inline_enabled = true;
23023 self.start_git_blame_inline(user_triggered, window, cx);
23024 }
23025
23026 cx.notify();
23027 }
23028
23029 fn start_git_blame_inline(
23030 &mut self,
23031 user_triggered: bool,
23032 window: &mut Window,
23033 cx: &mut Context<Self>,
23034 ) {
23035 self.start_git_blame(user_triggered, window, cx);
23036
23037 if ProjectSettings::get_global(cx)
23038 .git
23039 .inline_blame_delay()
23040 .is_some()
23041 {
23042 self.start_inline_blame_timer(window, cx);
23043 } else {
23044 self.show_git_blame_inline = true
23045 }
23046 }
23047
23048 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23049 self.blame.as_ref()
23050 }
23051
23052 pub fn show_git_blame_gutter(&self) -> bool {
23053 self.show_git_blame_gutter
23054 }
23055
23056 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23057 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23058 }
23059
23060 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23061 self.show_git_blame_inline
23062 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23063 && !self.newest_selection_head_on_empty_line(cx)
23064 && self.has_blame_entries(cx)
23065 }
23066
23067 fn has_blame_entries(&self, cx: &App) -> bool {
23068 self.blame()
23069 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23070 }
23071
23072 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23073 let cursor_anchor = self.selections.newest_anchor().head();
23074
23075 let snapshot = self.buffer.read(cx).snapshot(cx);
23076 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23077
23078 snapshot.line_len(buffer_row) == 0
23079 }
23080
23081 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23082 let buffer_and_selection = maybe!({
23083 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23084 let selection_range = selection.range();
23085
23086 let multi_buffer = self.buffer().read(cx);
23087 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23088 let buffer_ranges = multi_buffer_snapshot
23089 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23090
23091 let (buffer, range, _) = if selection.reversed {
23092 buffer_ranges.first()
23093 } else {
23094 buffer_ranges.last()
23095 }?;
23096
23097 let buffer_range = range.to_point(buffer);
23098
23099 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23100 return Some((
23101 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23102 buffer_range.start.row..buffer_range.end.row,
23103 ));
23104 };
23105
23106 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23107 let start =
23108 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23109 let end =
23110 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23111
23112 Some((
23113 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23114 start.row..end.row,
23115 ))
23116 });
23117
23118 let Some((buffer, selection)) = buffer_and_selection else {
23119 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23120 };
23121
23122 let Some(project) = self.project() else {
23123 return Task::ready(Err(anyhow!("editor does not have project")));
23124 };
23125
23126 project.update(cx, |project, cx| {
23127 project.get_permalink_to_line(&buffer, selection, cx)
23128 })
23129 }
23130
23131 pub fn copy_permalink_to_line(
23132 &mut self,
23133 _: &CopyPermalinkToLine,
23134 window: &mut Window,
23135 cx: &mut Context<Self>,
23136 ) {
23137 let permalink_task = self.get_permalink_to_line(cx);
23138 let workspace = self.workspace();
23139
23140 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23141 Ok(permalink) => {
23142 cx.update(|_, cx| {
23143 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23144 })
23145 .ok();
23146 }
23147 Err(err) => {
23148 let message = format!("Failed to copy permalink: {err}");
23149
23150 anyhow::Result::<()>::Err(err).log_err();
23151
23152 if let Some(workspace) = workspace {
23153 workspace
23154 .update_in(cx, |workspace, _, cx| {
23155 struct CopyPermalinkToLine;
23156
23157 workspace.show_toast(
23158 Toast::new(
23159 NotificationId::unique::<CopyPermalinkToLine>(),
23160 message,
23161 ),
23162 cx,
23163 )
23164 })
23165 .ok();
23166 }
23167 }
23168 })
23169 .detach();
23170 }
23171
23172 pub fn copy_file_location(
23173 &mut self,
23174 _: &CopyFileLocation,
23175 _: &mut Window,
23176 cx: &mut Context<Self>,
23177 ) {
23178 let selection = self
23179 .selections
23180 .newest::<Point>(&self.display_snapshot(cx))
23181 .start
23182 .row
23183 + 1;
23184 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23185 let project = self.project()?.read(cx);
23186 let file = buffer.read(cx).file()?;
23187 let path = file.path().display(project.path_style(cx));
23188
23189 Some(format!("{path}:{selection}"))
23190 }) {
23191 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23192 }
23193 }
23194
23195 pub fn open_permalink_to_line(
23196 &mut self,
23197 _: &OpenPermalinkToLine,
23198 window: &mut Window,
23199 cx: &mut Context<Self>,
23200 ) {
23201 let permalink_task = self.get_permalink_to_line(cx);
23202 let workspace = self.workspace();
23203
23204 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23205 Ok(permalink) => {
23206 cx.update(|_, cx| {
23207 cx.open_url(permalink.as_ref());
23208 })
23209 .ok();
23210 }
23211 Err(err) => {
23212 let message = format!("Failed to open permalink: {err}");
23213
23214 anyhow::Result::<()>::Err(err).log_err();
23215
23216 if let Some(workspace) = workspace {
23217 workspace.update(cx, |workspace, cx| {
23218 struct OpenPermalinkToLine;
23219
23220 workspace.show_toast(
23221 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23222 cx,
23223 )
23224 });
23225 }
23226 }
23227 })
23228 .detach();
23229 }
23230
23231 pub fn insert_uuid_v4(
23232 &mut self,
23233 _: &InsertUuidV4,
23234 window: &mut Window,
23235 cx: &mut Context<Self>,
23236 ) {
23237 self.insert_uuid(UuidVersion::V4, window, cx);
23238 }
23239
23240 pub fn insert_uuid_v7(
23241 &mut self,
23242 _: &InsertUuidV7,
23243 window: &mut Window,
23244 cx: &mut Context<Self>,
23245 ) {
23246 self.insert_uuid(UuidVersion::V7, window, cx);
23247 }
23248
23249 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23250 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23251 self.transact(window, cx, |this, window, cx| {
23252 let edits = this
23253 .selections
23254 .all::<Point>(&this.display_snapshot(cx))
23255 .into_iter()
23256 .map(|selection| {
23257 let uuid = match version {
23258 UuidVersion::V4 => uuid::Uuid::new_v4(),
23259 UuidVersion::V7 => uuid::Uuid::now_v7(),
23260 };
23261
23262 (selection.range(), uuid.to_string())
23263 });
23264 this.edit(edits, cx);
23265 this.refresh_edit_prediction(true, false, window, cx);
23266 });
23267 }
23268
23269 pub fn open_selections_in_multibuffer(
23270 &mut self,
23271 _: &OpenSelectionsInMultibuffer,
23272 window: &mut Window,
23273 cx: &mut Context<Self>,
23274 ) {
23275 let multibuffer = self.buffer.read(cx);
23276
23277 let Some(buffer) = multibuffer.as_singleton() else {
23278 return;
23279 };
23280
23281 let Some(workspace) = self.workspace() else {
23282 return;
23283 };
23284
23285 let title = multibuffer.title(cx).to_string();
23286
23287 let locations = self
23288 .selections
23289 .all_anchors(&self.display_snapshot(cx))
23290 .iter()
23291 .map(|selection| {
23292 (
23293 buffer.clone(),
23294 (selection.start.text_anchor..selection.end.text_anchor)
23295 .to_point(buffer.read(cx)),
23296 )
23297 })
23298 .into_group_map();
23299
23300 cx.spawn_in(window, async move |_, cx| {
23301 workspace.update_in(cx, |workspace, window, cx| {
23302 Self::open_locations_in_multibuffer(
23303 workspace,
23304 locations,
23305 format!("Selections for '{title}'"),
23306 false,
23307 false,
23308 MultibufferSelectionMode::All,
23309 window,
23310 cx,
23311 );
23312 })
23313 })
23314 .detach();
23315 }
23316
23317 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23318 /// last highlight added will be used.
23319 ///
23320 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23321 pub fn highlight_rows<T: 'static>(
23322 &mut self,
23323 range: Range<Anchor>,
23324 color: Hsla,
23325 options: RowHighlightOptions,
23326 cx: &mut Context<Self>,
23327 ) {
23328 let snapshot = self.buffer().read(cx).snapshot(cx);
23329 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23330 let ix = row_highlights.binary_search_by(|highlight| {
23331 Ordering::Equal
23332 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23333 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23334 });
23335
23336 if let Err(mut ix) = ix {
23337 let index = post_inc(&mut self.highlight_order);
23338
23339 // If this range intersects with the preceding highlight, then merge it with
23340 // the preceding highlight. Otherwise insert a new highlight.
23341 let mut merged = false;
23342 if ix > 0 {
23343 let prev_highlight = &mut row_highlights[ix - 1];
23344 if prev_highlight
23345 .range
23346 .end
23347 .cmp(&range.start, &snapshot)
23348 .is_ge()
23349 {
23350 ix -= 1;
23351 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23352 prev_highlight.range.end = range.end;
23353 }
23354 merged = true;
23355 prev_highlight.index = index;
23356 prev_highlight.color = color;
23357 prev_highlight.options = options;
23358 }
23359 }
23360
23361 if !merged {
23362 row_highlights.insert(
23363 ix,
23364 RowHighlight {
23365 range,
23366 index,
23367 color,
23368 options,
23369 type_id: TypeId::of::<T>(),
23370 },
23371 );
23372 }
23373
23374 // If any of the following highlights intersect with this one, merge them.
23375 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23376 let highlight = &row_highlights[ix];
23377 if next_highlight
23378 .range
23379 .start
23380 .cmp(&highlight.range.end, &snapshot)
23381 .is_le()
23382 {
23383 if next_highlight
23384 .range
23385 .end
23386 .cmp(&highlight.range.end, &snapshot)
23387 .is_gt()
23388 {
23389 row_highlights[ix].range.end = next_highlight.range.end;
23390 }
23391 row_highlights.remove(ix + 1);
23392 } else {
23393 break;
23394 }
23395 }
23396 }
23397 }
23398
23399 /// Remove any highlighted row ranges of the given type that intersect the
23400 /// given ranges.
23401 pub fn remove_highlighted_rows<T: 'static>(
23402 &mut self,
23403 ranges_to_remove: Vec<Range<Anchor>>,
23404 cx: &mut Context<Self>,
23405 ) {
23406 let snapshot = self.buffer().read(cx).snapshot(cx);
23407 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23408 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23409 row_highlights.retain(|highlight| {
23410 while let Some(range_to_remove) = ranges_to_remove.peek() {
23411 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23412 Ordering::Less | Ordering::Equal => {
23413 ranges_to_remove.next();
23414 }
23415 Ordering::Greater => {
23416 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23417 Ordering::Less | Ordering::Equal => {
23418 return false;
23419 }
23420 Ordering::Greater => break,
23421 }
23422 }
23423 }
23424 }
23425
23426 true
23427 })
23428 }
23429
23430 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23431 pub fn clear_row_highlights<T: 'static>(&mut self) {
23432 self.highlighted_rows.remove(&TypeId::of::<T>());
23433 }
23434
23435 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23436 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23437 self.highlighted_rows
23438 .get(&TypeId::of::<T>())
23439 .map_or(&[] as &[_], |vec| vec.as_slice())
23440 .iter()
23441 .map(|highlight| (highlight.range.clone(), highlight.color))
23442 }
23443
23444 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23445 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23446 /// Allows to ignore certain kinds of highlights.
23447 pub fn highlighted_display_rows(
23448 &self,
23449 window: &mut Window,
23450 cx: &mut App,
23451 ) -> BTreeMap<DisplayRow, LineHighlight> {
23452 let snapshot = self.snapshot(window, cx);
23453 let mut used_highlight_orders = HashMap::default();
23454 self.highlighted_rows
23455 .iter()
23456 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23457 .fold(
23458 BTreeMap::<DisplayRow, LineHighlight>::new(),
23459 |mut unique_rows, highlight| {
23460 let start = highlight.range.start.to_display_point(&snapshot);
23461 let end = highlight.range.end.to_display_point(&snapshot);
23462 let start_row = start.row().0;
23463 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23464 {
23465 end.row().0.saturating_sub(1)
23466 } else {
23467 end.row().0
23468 };
23469 for row in start_row..=end_row {
23470 let used_index =
23471 used_highlight_orders.entry(row).or_insert(highlight.index);
23472 if highlight.index >= *used_index {
23473 *used_index = highlight.index;
23474 unique_rows.insert(
23475 DisplayRow(row),
23476 LineHighlight {
23477 include_gutter: highlight.options.include_gutter,
23478 border: None,
23479 background: highlight.color.into(),
23480 type_id: Some(highlight.type_id),
23481 },
23482 );
23483 }
23484 }
23485 unique_rows
23486 },
23487 )
23488 }
23489
23490 pub fn highlighted_display_row_for_autoscroll(
23491 &self,
23492 snapshot: &DisplaySnapshot,
23493 ) -> Option<DisplayRow> {
23494 self.highlighted_rows
23495 .values()
23496 .flat_map(|highlighted_rows| highlighted_rows.iter())
23497 .filter_map(|highlight| {
23498 if highlight.options.autoscroll {
23499 Some(highlight.range.start.to_display_point(snapshot).row())
23500 } else {
23501 None
23502 }
23503 })
23504 .min()
23505 }
23506
23507 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23508 self.highlight_background(
23509 HighlightKey::SearchWithinRange,
23510 ranges,
23511 |_, colors| colors.colors().editor_document_highlight_read_background,
23512 cx,
23513 )
23514 }
23515
23516 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23517 self.breadcrumb_header = Some(new_header);
23518 }
23519
23520 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23521 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23522 }
23523
23524 pub fn highlight_background(
23525 &mut self,
23526 key: HighlightKey,
23527 ranges: &[Range<Anchor>],
23528 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23529 cx: &mut Context<Self>,
23530 ) {
23531 self.background_highlights
23532 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23533 self.scrollbar_marker_state.dirty = true;
23534 cx.notify();
23535 }
23536
23537 pub fn clear_background_highlights(
23538 &mut self,
23539 key: HighlightKey,
23540 cx: &mut Context<Self>,
23541 ) -> Option<BackgroundHighlight> {
23542 let text_highlights = self.background_highlights.remove(&key)?;
23543 if !text_highlights.1.is_empty() {
23544 self.scrollbar_marker_state.dirty = true;
23545 cx.notify();
23546 }
23547 Some(text_highlights)
23548 }
23549
23550 pub fn highlight_gutter<T: 'static>(
23551 &mut self,
23552 ranges: impl Into<Vec<Range<Anchor>>>,
23553 color_fetcher: fn(&App) -> Hsla,
23554 cx: &mut Context<Self>,
23555 ) {
23556 self.gutter_highlights
23557 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23558 cx.notify();
23559 }
23560
23561 pub fn clear_gutter_highlights<T: 'static>(
23562 &mut self,
23563 cx: &mut Context<Self>,
23564 ) -> Option<GutterHighlight> {
23565 cx.notify();
23566 self.gutter_highlights.remove(&TypeId::of::<T>())
23567 }
23568
23569 pub fn insert_gutter_highlight<T: 'static>(
23570 &mut self,
23571 range: Range<Anchor>,
23572 color_fetcher: fn(&App) -> Hsla,
23573 cx: &mut Context<Self>,
23574 ) {
23575 let snapshot = self.buffer().read(cx).snapshot(cx);
23576 let mut highlights = self
23577 .gutter_highlights
23578 .remove(&TypeId::of::<T>())
23579 .map(|(_, highlights)| highlights)
23580 .unwrap_or_default();
23581 let ix = highlights.binary_search_by(|highlight| {
23582 Ordering::Equal
23583 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23584 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23585 });
23586 if let Err(ix) = ix {
23587 highlights.insert(ix, range);
23588 }
23589 self.gutter_highlights
23590 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23591 }
23592
23593 pub fn remove_gutter_highlights<T: 'static>(
23594 &mut self,
23595 ranges_to_remove: Vec<Range<Anchor>>,
23596 cx: &mut Context<Self>,
23597 ) {
23598 let snapshot = self.buffer().read(cx).snapshot(cx);
23599 let Some((color_fetcher, mut gutter_highlights)) =
23600 self.gutter_highlights.remove(&TypeId::of::<T>())
23601 else {
23602 return;
23603 };
23604 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23605 gutter_highlights.retain(|highlight| {
23606 while let Some(range_to_remove) = ranges_to_remove.peek() {
23607 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23608 Ordering::Less | Ordering::Equal => {
23609 ranges_to_remove.next();
23610 }
23611 Ordering::Greater => {
23612 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23613 Ordering::Less | Ordering::Equal => {
23614 return false;
23615 }
23616 Ordering::Greater => break,
23617 }
23618 }
23619 }
23620 }
23621
23622 true
23623 });
23624 self.gutter_highlights
23625 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23626 }
23627
23628 #[cfg(any(test, feature = "test-support"))]
23629 pub fn all_text_highlights(
23630 &self,
23631 window: &mut Window,
23632 cx: &mut Context<Self>,
23633 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23634 let snapshot = self.snapshot(window, cx);
23635 self.display_map.update(cx, |display_map, _| {
23636 display_map
23637 .all_text_highlights()
23638 .map(|(_, highlight)| {
23639 let (style, ranges) = highlight.as_ref();
23640 (
23641 *style,
23642 ranges
23643 .iter()
23644 .map(|range| range.clone().to_display_points(&snapshot))
23645 .collect(),
23646 )
23647 })
23648 .collect()
23649 })
23650 }
23651
23652 #[cfg(any(test, feature = "test-support"))]
23653 pub fn all_text_background_highlights(
23654 &self,
23655 window: &mut Window,
23656 cx: &mut Context<Self>,
23657 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23658 let snapshot = self.snapshot(window, cx);
23659 let buffer = &snapshot.buffer_snapshot();
23660 let start = buffer.anchor_before(MultiBufferOffset(0));
23661 let end = buffer.anchor_after(buffer.len());
23662 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23663 }
23664
23665 #[cfg(any(test, feature = "test-support"))]
23666 pub fn sorted_background_highlights_in_range(
23667 &self,
23668 search_range: Range<Anchor>,
23669 display_snapshot: &DisplaySnapshot,
23670 theme: &Theme,
23671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23672 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23673 res.sort_by(|a, b| {
23674 a.0.start
23675 .cmp(&b.0.start)
23676 .then_with(|| a.0.end.cmp(&b.0.end))
23677 .then_with(|| a.1.cmp(&b.1))
23678 });
23679 res
23680 }
23681
23682 #[cfg(any(test, feature = "test-support"))]
23683 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23684 let snapshot = self.buffer().read(cx).snapshot(cx);
23685
23686 let highlights = self
23687 .background_highlights
23688 .get(&HighlightKey::BufferSearchHighlights);
23689
23690 if let Some((_color, ranges)) = highlights {
23691 ranges
23692 .iter()
23693 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23694 .collect_vec()
23695 } else {
23696 vec![]
23697 }
23698 }
23699
23700 fn document_highlights_for_position<'a>(
23701 &'a self,
23702 position: Anchor,
23703 buffer: &'a MultiBufferSnapshot,
23704 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23705 let read_highlights = self
23706 .background_highlights
23707 .get(&HighlightKey::DocumentHighlightRead)
23708 .map(|h| &h.1);
23709 let write_highlights = self
23710 .background_highlights
23711 .get(&HighlightKey::DocumentHighlightWrite)
23712 .map(|h| &h.1);
23713 let left_position = position.bias_left(buffer);
23714 let right_position = position.bias_right(buffer);
23715 read_highlights
23716 .into_iter()
23717 .chain(write_highlights)
23718 .flat_map(move |ranges| {
23719 let start_ix = match ranges.binary_search_by(|probe| {
23720 let cmp = probe.end.cmp(&left_position, buffer);
23721 if cmp.is_ge() {
23722 Ordering::Greater
23723 } else {
23724 Ordering::Less
23725 }
23726 }) {
23727 Ok(i) | Err(i) => i,
23728 };
23729
23730 ranges[start_ix..]
23731 .iter()
23732 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23733 })
23734 }
23735
23736 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23737 self.background_highlights
23738 .get(&key)
23739 .is_some_and(|(_, highlights)| !highlights.is_empty())
23740 }
23741
23742 /// Returns all background highlights for a given range.
23743 ///
23744 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23745 pub fn background_highlights_in_range(
23746 &self,
23747 search_range: Range<Anchor>,
23748 display_snapshot: &DisplaySnapshot,
23749 theme: &Theme,
23750 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23751 let mut results = Vec::new();
23752 for (color_fetcher, ranges) in self.background_highlights.values() {
23753 let start_ix = match ranges.binary_search_by(|probe| {
23754 let cmp = probe
23755 .end
23756 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23757 if cmp.is_gt() {
23758 Ordering::Greater
23759 } else {
23760 Ordering::Less
23761 }
23762 }) {
23763 Ok(i) | Err(i) => i,
23764 };
23765 for (index, range) in ranges[start_ix..].iter().enumerate() {
23766 if range
23767 .start
23768 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23769 .is_ge()
23770 {
23771 break;
23772 }
23773
23774 let color = color_fetcher(&(start_ix + index), theme);
23775 let start = range.start.to_display_point(display_snapshot);
23776 let end = range.end.to_display_point(display_snapshot);
23777 results.push((start..end, color))
23778 }
23779 }
23780 results
23781 }
23782
23783 pub fn gutter_highlights_in_range(
23784 &self,
23785 search_range: Range<Anchor>,
23786 display_snapshot: &DisplaySnapshot,
23787 cx: &App,
23788 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23789 let mut results = Vec::new();
23790 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23791 let color = color_fetcher(cx);
23792 let start_ix = match ranges.binary_search_by(|probe| {
23793 let cmp = probe
23794 .end
23795 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23796 if cmp.is_gt() {
23797 Ordering::Greater
23798 } else {
23799 Ordering::Less
23800 }
23801 }) {
23802 Ok(i) | Err(i) => i,
23803 };
23804 for range in &ranges[start_ix..] {
23805 if range
23806 .start
23807 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23808 .is_ge()
23809 {
23810 break;
23811 }
23812
23813 let start = range.start.to_display_point(display_snapshot);
23814 let end = range.end.to_display_point(display_snapshot);
23815 results.push((start..end, color))
23816 }
23817 }
23818 results
23819 }
23820
23821 /// Get the text ranges corresponding to the redaction query
23822 pub fn redacted_ranges(
23823 &self,
23824 search_range: Range<Anchor>,
23825 display_snapshot: &DisplaySnapshot,
23826 cx: &App,
23827 ) -> Vec<Range<DisplayPoint>> {
23828 display_snapshot
23829 .buffer_snapshot()
23830 .redacted_ranges(search_range, |file| {
23831 if let Some(file) = file {
23832 file.is_private()
23833 && EditorSettings::get(
23834 Some(SettingsLocation {
23835 worktree_id: file.worktree_id(cx),
23836 path: file.path().as_ref(),
23837 }),
23838 cx,
23839 )
23840 .redact_private_values
23841 } else {
23842 false
23843 }
23844 })
23845 .map(|range| {
23846 range.start.to_display_point(display_snapshot)
23847 ..range.end.to_display_point(display_snapshot)
23848 })
23849 .collect()
23850 }
23851
23852 pub fn highlight_text_key(
23853 &mut self,
23854 key: HighlightKey,
23855 ranges: Vec<Range<Anchor>>,
23856 style: HighlightStyle,
23857 merge: bool,
23858 cx: &mut Context<Self>,
23859 ) {
23860 self.display_map.update(cx, |map, cx| {
23861 map.highlight_text(key, ranges, style, merge, cx);
23862 });
23863 cx.notify();
23864 }
23865
23866 pub fn highlight_text(
23867 &mut self,
23868 key: HighlightKey,
23869 ranges: Vec<Range<Anchor>>,
23870 style: HighlightStyle,
23871 cx: &mut Context<Self>,
23872 ) {
23873 self.display_map.update(cx, |map, cx| {
23874 map.highlight_text(key, ranges, style, false, cx)
23875 });
23876 cx.notify();
23877 }
23878
23879 pub fn text_highlights<'a>(
23880 &'a self,
23881 key: HighlightKey,
23882 cx: &'a App,
23883 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
23884 self.display_map.read(cx).text_highlights(key)
23885 }
23886
23887 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
23888 let cleared = self
23889 .display_map
23890 .update(cx, |map, _| map.clear_highlights(key));
23891 if cleared {
23892 cx.notify();
23893 }
23894 }
23895
23896 pub fn clear_highlights_with(
23897 &mut self,
23898 f: &mut dyn FnMut(&HighlightKey) -> bool,
23899 cx: &mut Context<Self>,
23900 ) {
23901 let cleared = self
23902 .display_map
23903 .update(cx, |map, _| map.clear_highlights_with(f));
23904 if cleared {
23905 cx.notify();
23906 }
23907 }
23908
23909 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
23910 (self.read_only(cx) || self.blink_manager.read(cx).visible())
23911 && self.focus_handle.is_focused(window)
23912 }
23913
23914 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
23915 self.show_cursor_when_unfocused = is_enabled;
23916 cx.notify();
23917 }
23918
23919 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
23920 cx.notify();
23921 }
23922
23923 fn on_debug_session_event(
23924 &mut self,
23925 _session: Entity<Session>,
23926 event: &SessionEvent,
23927 cx: &mut Context<Self>,
23928 ) {
23929 if let SessionEvent::InvalidateInlineValue = event {
23930 self.refresh_inline_values(cx);
23931 }
23932 }
23933
23934 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
23935 let Some(project) = self.project.clone() else {
23936 return;
23937 };
23938
23939 if !self.inline_value_cache.enabled {
23940 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
23941 self.splice_inlays(&inlays, Vec::new(), cx);
23942 return;
23943 }
23944
23945 let current_execution_position = self
23946 .highlighted_rows
23947 .get(&TypeId::of::<ActiveDebugLine>())
23948 .and_then(|lines| lines.last().map(|line| line.range.end));
23949
23950 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
23951 let inline_values = editor
23952 .update(cx, |editor, cx| {
23953 let Some(current_execution_position) = current_execution_position else {
23954 return Some(Task::ready(Ok(Vec::new())));
23955 };
23956
23957 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
23958 let snapshot = buffer.snapshot(cx);
23959
23960 let excerpt = snapshot.excerpt_containing(
23961 current_execution_position..current_execution_position,
23962 )?;
23963
23964 editor.buffer.read(cx).buffer(excerpt.buffer_id())
23965 })?;
23966
23967 let range =
23968 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
23969
23970 project.inline_values(buffer, range, cx)
23971 })
23972 .ok()
23973 .flatten()?
23974 .await
23975 .context("refreshing debugger inlays")
23976 .log_err()?;
23977
23978 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
23979
23980 for (buffer_id, inline_value) in inline_values
23981 .into_iter()
23982 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
23983 {
23984 buffer_inline_values
23985 .entry(buffer_id)
23986 .or_default()
23987 .push(inline_value);
23988 }
23989
23990 editor
23991 .update(cx, |editor, cx| {
23992 let snapshot = editor.buffer.read(cx).snapshot(cx);
23993 let mut new_inlays = Vec::default();
23994
23995 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
23996 let buffer_id = buffer_snapshot.remote_id();
23997 buffer_inline_values
23998 .get(&buffer_id)
23999 .into_iter()
24000 .flatten()
24001 .for_each(|hint| {
24002 let inlay = Inlay::debugger(
24003 post_inc(&mut editor.next_inlay_id),
24004 Anchor::in_buffer(excerpt_id, hint.position),
24005 hint.text(),
24006 );
24007 if !inlay.text().chars().contains(&'\n') {
24008 new_inlays.push(inlay);
24009 }
24010 });
24011 }
24012
24013 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24014 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24015
24016 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24017 })
24018 .ok()?;
24019 Some(())
24020 });
24021 }
24022
24023 fn on_buffer_event(
24024 &mut self,
24025 multibuffer: &Entity<MultiBuffer>,
24026 event: &multi_buffer::Event,
24027 window: &mut Window,
24028 cx: &mut Context<Self>,
24029 ) {
24030 match event {
24031 multi_buffer::Event::Edited { edited_buffer } => {
24032 self.scrollbar_marker_state.dirty = true;
24033 self.active_indent_guides_state.dirty = true;
24034 self.refresh_active_diagnostics(cx);
24035 self.refresh_code_actions(window, cx);
24036 self.refresh_single_line_folds(window, cx);
24037 let snapshot = self.snapshot(window, cx);
24038 self.refresh_matching_bracket_highlights(&snapshot, cx);
24039 self.refresh_outline_symbols_at_cursor(cx);
24040 self.refresh_sticky_headers(&snapshot, cx);
24041 if self.has_active_edit_prediction() {
24042 self.update_visible_edit_prediction(window, cx);
24043 }
24044
24045 // Clean up orphaned review comments after edits
24046 self.cleanup_orphaned_review_comments(cx);
24047
24048 if let Some(buffer) = edited_buffer {
24049 if buffer.read(cx).file().is_none() {
24050 cx.emit(EditorEvent::TitleChanged);
24051 }
24052
24053 if self.project.is_some() {
24054 let buffer_id = buffer.read(cx).remote_id();
24055 self.register_buffer(buffer_id, cx);
24056 self.update_lsp_data(Some(buffer_id), window, cx);
24057 self.refresh_inlay_hints(
24058 InlayHintRefreshReason::BufferEdited(buffer_id),
24059 cx,
24060 );
24061 }
24062 }
24063
24064 cx.emit(EditorEvent::BufferEdited);
24065 cx.emit(SearchEvent::MatchesInvalidated);
24066
24067 let Some(project) = &self.project else { return };
24068 let (telemetry, is_via_ssh) = {
24069 let project = project.read(cx);
24070 let telemetry = project.client().telemetry().clone();
24071 let is_via_ssh = project.is_via_remote_server();
24072 (telemetry, is_via_ssh)
24073 };
24074 telemetry.log_edit_event("editor", is_via_ssh);
24075 }
24076 multi_buffer::Event::ExcerptsAdded {
24077 buffer,
24078 predecessor,
24079 excerpts,
24080 } => {
24081 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24082 let buffer_id = buffer.read(cx).remote_id();
24083 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24084 && let Some(project) = &self.project
24085 {
24086 update_uncommitted_diff_for_buffer(
24087 cx.entity(),
24088 project,
24089 [buffer.clone()],
24090 self.buffer.clone(),
24091 cx,
24092 )
24093 .detach();
24094 }
24095 self.semantic_token_state
24096 .invalidate_buffer(&buffer.read(cx).remote_id());
24097 self.update_lsp_data(Some(buffer_id), window, cx);
24098 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24099 self.colorize_brackets(false, cx);
24100 self.refresh_selected_text_highlights(true, window, cx);
24101 cx.emit(EditorEvent::ExcerptsAdded {
24102 buffer: buffer.clone(),
24103 predecessor: *predecessor,
24104 excerpts: excerpts.clone(),
24105 });
24106 }
24107 multi_buffer::Event::ExcerptsRemoved {
24108 ids,
24109 removed_buffer_ids,
24110 } => {
24111 if let Some(inlay_hints) = &mut self.inlay_hints {
24112 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24113 }
24114 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24115 for buffer_id in removed_buffer_ids {
24116 self.registered_buffers.remove(buffer_id);
24117 self.tasks
24118 .retain(|(task_buffer_id, _), _| task_buffer_id != buffer_id);
24119 self.semantic_token_state.invalidate_buffer(buffer_id);
24120 self.display_map.update(cx, |display_map, cx| {
24121 display_map.invalidate_semantic_highlights(*buffer_id);
24122 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24123 });
24124 }
24125 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24126 cx.emit(EditorEvent::ExcerptsRemoved {
24127 ids: ids.clone(),
24128 removed_buffer_ids: removed_buffer_ids.clone(),
24129 });
24130 }
24131 multi_buffer::Event::ExcerptsEdited {
24132 excerpt_ids,
24133 buffer_ids,
24134 } => {
24135 self.display_map.update(cx, |map, cx| {
24136 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24137 });
24138 cx.emit(EditorEvent::ExcerptsEdited {
24139 ids: excerpt_ids.clone(),
24140 });
24141 }
24142 multi_buffer::Event::ExcerptsExpanded { ids } => {
24143 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24144 self.refresh_document_highlights(cx);
24145 let snapshot = multibuffer.read(cx).snapshot(cx);
24146 for id in ids {
24147 self.fetched_tree_sitter_chunks.remove(id);
24148 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24149 self.semantic_token_state
24150 .invalidate_buffer(&buffer.remote_id());
24151 }
24152 }
24153 self.colorize_brackets(false, cx);
24154 self.update_lsp_data(None, window, cx);
24155 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24156 }
24157 multi_buffer::Event::Reparsed(buffer_id) => {
24158 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24159 self.refresh_selected_text_highlights(true, window, cx);
24160 self.colorize_brackets(true, cx);
24161 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24162
24163 cx.emit(EditorEvent::Reparsed(*buffer_id));
24164 }
24165 multi_buffer::Event::DiffHunksToggled => {
24166 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24167 }
24168 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24169 if !is_fresh_language {
24170 self.registered_buffers.remove(&buffer_id);
24171 }
24172 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24173 cx.emit(EditorEvent::Reparsed(*buffer_id));
24174 cx.notify();
24175 }
24176 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24177 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24178 multi_buffer::Event::FileHandleChanged
24179 | multi_buffer::Event::Reloaded
24180 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24181 multi_buffer::Event::DiagnosticsUpdated => {
24182 self.update_diagnostics_state(window, cx);
24183 }
24184 _ => {}
24185 };
24186 }
24187
24188 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24189 if !self.diagnostics_enabled() {
24190 return;
24191 }
24192 self.refresh_active_diagnostics(cx);
24193 self.refresh_inline_diagnostics(true, window, cx);
24194 self.scrollbar_marker_state.dirty = true;
24195 cx.notify();
24196 }
24197
24198 pub fn start_temporary_diff_override(&mut self) {
24199 self.load_diff_task.take();
24200 self.temporary_diff_override = true;
24201 }
24202
24203 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24204 self.temporary_diff_override = false;
24205 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24206 self.buffer.update(cx, |buffer, cx| {
24207 buffer.set_all_diff_hunks_collapsed(cx);
24208 });
24209
24210 if let Some(project) = self.project.clone() {
24211 self.load_diff_task = Some(
24212 update_uncommitted_diff_for_buffer(
24213 cx.entity(),
24214 &project,
24215 self.buffer.read(cx).all_buffers(),
24216 self.buffer.clone(),
24217 cx,
24218 )
24219 .shared(),
24220 );
24221 }
24222 }
24223
24224 fn on_display_map_changed(
24225 &mut self,
24226 _: Entity<DisplayMap>,
24227 _: &mut Window,
24228 cx: &mut Context<Self>,
24229 ) {
24230 cx.notify();
24231 }
24232
24233 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24234 if !self.mode.is_full() {
24235 return None;
24236 }
24237
24238 let theme_settings = theme::ThemeSettings::get_global(cx);
24239 let theme = cx.theme();
24240 let accent_colors = theme.accents().clone();
24241
24242 let accent_overrides = theme_settings
24243 .theme_overrides
24244 .get(theme.name.as_ref())
24245 .map(|theme_style| &theme_style.accents)
24246 .into_iter()
24247 .flatten()
24248 .chain(
24249 theme_settings
24250 .experimental_theme_overrides
24251 .as_ref()
24252 .map(|overrides| &overrides.accents)
24253 .into_iter()
24254 .flatten(),
24255 )
24256 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24257 .collect();
24258
24259 Some(AccentData {
24260 colors: accent_colors,
24261 overrides: accent_overrides,
24262 })
24263 }
24264
24265 fn fetch_applicable_language_settings(
24266 &self,
24267 cx: &App,
24268 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24269 if !self.mode.is_full() {
24270 return HashMap::default();
24271 }
24272
24273 self.buffer().read(cx).all_buffers().into_iter().fold(
24274 HashMap::default(),
24275 |mut acc, buffer| {
24276 let buffer = buffer.read(cx);
24277 let language = buffer.language().map(|language| language.name());
24278 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
24279 let file = buffer.file();
24280 v.insert(language_settings(language, file, cx).into_owned());
24281 }
24282 acc
24283 },
24284 )
24285 }
24286
24287 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24288 let new_language_settings = self.fetch_applicable_language_settings(cx);
24289 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24290 self.applicable_language_settings = new_language_settings;
24291
24292 let new_accents = self.fetch_accent_data(cx);
24293 let accents_changed = new_accents != self.accent_data;
24294 self.accent_data = new_accents;
24295
24296 if self.diagnostics_enabled() {
24297 let new_severity = EditorSettings::get_global(cx)
24298 .diagnostics_max_severity
24299 .unwrap_or(DiagnosticSeverity::Hint);
24300 self.set_max_diagnostics_severity(new_severity, cx);
24301 }
24302 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
24303 self.update_edit_prediction_settings(cx);
24304 self.refresh_edit_prediction(true, false, window, cx);
24305 self.refresh_inline_values(cx);
24306
24307 let old_cursor_shape = self.cursor_shape;
24308 let old_show_breadcrumbs = self.show_breadcrumbs;
24309
24310 {
24311 let editor_settings = EditorSettings::get_global(cx);
24312 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24313 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24314 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24315 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24316 }
24317
24318 if old_cursor_shape != self.cursor_shape {
24319 cx.emit(EditorEvent::CursorShapeChanged);
24320 }
24321
24322 if old_show_breadcrumbs != self.show_breadcrumbs {
24323 cx.emit(EditorEvent::BreadcrumbsChanged);
24324 }
24325
24326 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24327 let project_settings = ProjectSettings::get_global(cx);
24328 (
24329 project_settings.session.restore_unsaved_buffers,
24330 project_settings.diagnostics.inline.enabled,
24331 project_settings.git.inline_blame.enabled,
24332 )
24333 };
24334 self.buffer_serialization = self
24335 .should_serialize_buffer()
24336 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24337
24338 if self.mode.is_full() {
24339 if self.show_inline_diagnostics != show_inline_diagnostics {
24340 self.show_inline_diagnostics = show_inline_diagnostics;
24341 self.refresh_inline_diagnostics(false, window, cx);
24342 }
24343
24344 if self.git_blame_inline_enabled != inline_blame_enabled {
24345 self.toggle_git_blame_inline_internal(false, window, cx);
24346 }
24347
24348 let minimap_settings = EditorSettings::get_global(cx).minimap;
24349 if self.minimap_visibility != MinimapVisibility::Disabled {
24350 if self.minimap_visibility.settings_visibility()
24351 != minimap_settings.minimap_enabled()
24352 {
24353 self.set_minimap_visibility(
24354 MinimapVisibility::for_mode(self.mode(), cx),
24355 window,
24356 cx,
24357 );
24358 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24359 minimap_entity.update(cx, |minimap_editor, cx| {
24360 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24361 })
24362 }
24363 }
24364
24365 if language_settings_changed || accents_changed {
24366 self.colorize_brackets(true, cx);
24367 }
24368
24369 if language_settings_changed {
24370 self.clear_disabled_lsp_folding_ranges(window, cx);
24371 self.refresh_document_symbols(None, cx);
24372 }
24373
24374 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24375 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24376 }) {
24377 if !inlay_splice.is_empty() {
24378 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24379 }
24380 self.refresh_document_colors(None, window, cx);
24381 }
24382
24383 self.refresh_inlay_hints(
24384 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24385 self.selections.newest_anchor().head(),
24386 &self.buffer.read(cx).snapshot(cx),
24387 cx,
24388 )),
24389 cx,
24390 );
24391
24392 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24393 .global_lsp_settings
24394 .semantic_token_rules
24395 .clone();
24396 let semantic_token_rules_changed = self
24397 .semantic_token_state
24398 .update_rules(new_semantic_token_rules);
24399 if language_settings_changed || semantic_token_rules_changed {
24400 self.invalidate_semantic_tokens(None);
24401 self.refresh_semantic_tokens(None, None, cx);
24402 }
24403 }
24404
24405 cx.notify();
24406 }
24407
24408 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24409 if !self.mode.is_full() {
24410 return;
24411 }
24412
24413 let new_accents = self.fetch_accent_data(cx);
24414 if new_accents != self.accent_data {
24415 self.accent_data = new_accents;
24416 self.colorize_brackets(true, cx);
24417 }
24418
24419 self.invalidate_semantic_tokens(None);
24420 self.refresh_semantic_tokens(None, None, cx);
24421 }
24422
24423 pub fn set_searchable(&mut self, searchable: bool) {
24424 self.searchable = searchable;
24425 }
24426
24427 pub fn searchable(&self) -> bool {
24428 self.searchable
24429 }
24430
24431 pub fn open_excerpts_in_split(
24432 &mut self,
24433 _: &OpenExcerptsSplit,
24434 window: &mut Window,
24435 cx: &mut Context<Self>,
24436 ) {
24437 self.open_excerpts_common(None, true, window, cx)
24438 }
24439
24440 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24441 self.open_excerpts_common(None, false, window, cx)
24442 }
24443
24444 pub(crate) fn open_excerpts_common(
24445 &mut self,
24446 jump_data: Option<JumpData>,
24447 split: bool,
24448 window: &mut Window,
24449 cx: &mut Context<Self>,
24450 ) {
24451 if self.buffer.read(cx).is_singleton() {
24452 cx.propagate();
24453 return;
24454 }
24455
24456 let mut new_selections_by_buffer = HashMap::default();
24457 match &jump_data {
24458 Some(JumpData::MultiBufferPoint {
24459 excerpt_id,
24460 position,
24461 anchor,
24462 line_offset_from_top,
24463 }) => {
24464 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24465 if let Some(buffer) = multi_buffer_snapshot
24466 .buffer_id_for_excerpt(*excerpt_id)
24467 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24468 {
24469 let buffer_snapshot = buffer.read(cx).snapshot();
24470 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24471 language::ToPoint::to_point(anchor, &buffer_snapshot)
24472 } else {
24473 buffer_snapshot.clip_point(*position, Bias::Left)
24474 };
24475 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24476 new_selections_by_buffer.insert(
24477 buffer,
24478 (
24479 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24480 Some(*line_offset_from_top),
24481 ),
24482 );
24483 }
24484 }
24485 Some(JumpData::MultiBufferRow {
24486 row,
24487 line_offset_from_top,
24488 }) => {
24489 let point = MultiBufferPoint::new(row.0, 0);
24490 if let Some((buffer, buffer_point, _)) =
24491 self.buffer.read(cx).point_to_buffer_point(point, cx)
24492 {
24493 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24494 new_selections_by_buffer
24495 .entry(buffer)
24496 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24497 .0
24498 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24499 }
24500 }
24501 None => {
24502 let selections = self
24503 .selections
24504 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24505 let multi_buffer = self.buffer.read(cx);
24506 for selection in selections {
24507 for (snapshot, range, _, anchor) in multi_buffer
24508 .snapshot(cx)
24509 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24510 {
24511 if let Some(anchor) = anchor {
24512 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24513 else {
24514 continue;
24515 };
24516 let offset = text::ToOffset::to_offset(
24517 &anchor.text_anchor,
24518 &buffer_handle.read(cx).snapshot(),
24519 );
24520 let range = BufferOffset(offset)..BufferOffset(offset);
24521 new_selections_by_buffer
24522 .entry(buffer_handle)
24523 .or_insert((Vec::new(), None))
24524 .0
24525 .push(range)
24526 } else {
24527 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24528 else {
24529 continue;
24530 };
24531 new_selections_by_buffer
24532 .entry(buffer_handle)
24533 .or_insert((Vec::new(), None))
24534 .0
24535 .push(range)
24536 }
24537 }
24538 }
24539 }
24540 }
24541
24542 if self.delegate_open_excerpts {
24543 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24544 .into_iter()
24545 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24546 .collect();
24547 if !selections_by_buffer.is_empty() {
24548 cx.emit(EditorEvent::OpenExcerptsRequested {
24549 selections_by_buffer,
24550 split,
24551 });
24552 }
24553 return;
24554 }
24555
24556 let Some(workspace) = self.workspace() else {
24557 cx.propagate();
24558 return;
24559 };
24560
24561 new_selections_by_buffer
24562 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24563
24564 if new_selections_by_buffer.is_empty() {
24565 return;
24566 }
24567
24568 Self::open_buffers_in_workspace(
24569 workspace.downgrade(),
24570 new_selections_by_buffer,
24571 split,
24572 window,
24573 cx,
24574 );
24575 }
24576
24577 pub(crate) fn open_buffers_in_workspace(
24578 workspace: WeakEntity<Workspace>,
24579 new_selections_by_buffer: HashMap<
24580 Entity<language::Buffer>,
24581 (Vec<Range<BufferOffset>>, Option<u32>),
24582 >,
24583 split: bool,
24584 window: &mut Window,
24585 cx: &mut App,
24586 ) {
24587 // We defer the pane interaction because we ourselves are a workspace item
24588 // and activating a new item causes the pane to call a method on us reentrantly,
24589 // which panics if we're on the stack.
24590 window.defer(cx, move |window, cx| {
24591 workspace
24592 .update(cx, |workspace, cx| {
24593 let pane = if split {
24594 workspace.adjacent_pane(window, cx)
24595 } else {
24596 workspace.active_pane().clone()
24597 };
24598
24599 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24600 let buffer_read = buffer.read(cx);
24601 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24602 (true, project::File::from_dyn(Some(file)).is_some())
24603 } else {
24604 (false, false)
24605 };
24606
24607 // If project file is none workspace.open_project_item will fail to open the excerpt
24608 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24609 // so we check if there's a tab match in that case first
24610 let editor = (!has_file || !is_project_file)
24611 .then(|| {
24612 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24613 // so `workspace.open_project_item` will never find them, always opening a new editor.
24614 // Instead, we try to activate the existing editor in the pane first.
24615 let (editor, pane_item_index, pane_item_id) =
24616 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24617 let editor = item.downcast::<Editor>()?;
24618 let singleton_buffer =
24619 editor.read(cx).buffer().read(cx).as_singleton()?;
24620 if singleton_buffer == buffer {
24621 Some((editor, i, item.item_id()))
24622 } else {
24623 None
24624 }
24625 })?;
24626 pane.update(cx, |pane, cx| {
24627 pane.activate_item(pane_item_index, true, true, window, cx);
24628 if !PreviewTabsSettings::get_global(cx)
24629 .enable_preview_from_multibuffer
24630 {
24631 pane.unpreview_item_if_preview(pane_item_id);
24632 }
24633 });
24634 Some(editor)
24635 })
24636 .flatten()
24637 .unwrap_or_else(|| {
24638 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24639 .enable_keep_preview_on_code_navigation;
24640 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24641 .enable_preview_from_multibuffer;
24642 workspace.open_project_item::<Self>(
24643 pane.clone(),
24644 buffer,
24645 true,
24646 true,
24647 keep_old_preview,
24648 allow_new_preview,
24649 window,
24650 cx,
24651 )
24652 });
24653
24654 editor.update(cx, |editor, cx| {
24655 if has_file && !is_project_file {
24656 editor.set_read_only(true);
24657 }
24658 let autoscroll = match scroll_offset {
24659 Some(scroll_offset) => {
24660 Autoscroll::top_relative(scroll_offset as usize)
24661 }
24662 None => Autoscroll::newest(),
24663 };
24664 let nav_history = editor.nav_history.take();
24665 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24666 let Some((excerpt_id, _, buffer_snapshot)) =
24667 multibuffer_snapshot.as_singleton()
24668 else {
24669 return;
24670 };
24671 editor.change_selections(
24672 SelectionEffects::scroll(autoscroll),
24673 window,
24674 cx,
24675 |s| {
24676 s.select_ranges(ranges.into_iter().map(|range| {
24677 let range = buffer_snapshot.anchor_before(range.start)
24678 ..buffer_snapshot.anchor_after(range.end);
24679 multibuffer_snapshot
24680 .anchor_range_in_excerpt(excerpt_id, range)
24681 .unwrap()
24682 }));
24683 },
24684 );
24685 editor.nav_history = nav_history;
24686 });
24687 }
24688 })
24689 .ok();
24690 });
24691 }
24692
24693 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24694 let snapshot = self.buffer.read(cx).read(cx);
24695 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24696 Some(
24697 ranges
24698 .iter()
24699 .map(move |range| {
24700 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24701 })
24702 .collect(),
24703 )
24704 }
24705
24706 fn selection_replacement_ranges(
24707 &self,
24708 range: Range<MultiBufferOffsetUtf16>,
24709 cx: &mut App,
24710 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24711 let selections = self
24712 .selections
24713 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24714 let newest_selection = selections
24715 .iter()
24716 .max_by_key(|selection| selection.id)
24717 .unwrap();
24718 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24719 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24720 let snapshot = self.buffer.read(cx).read(cx);
24721 selections
24722 .into_iter()
24723 .map(|mut selection| {
24724 selection.start.0.0 =
24725 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24726 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24727 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24728 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24729 })
24730 .collect()
24731 }
24732
24733 fn report_editor_event(
24734 &self,
24735 reported_event: ReportEditorEvent,
24736 file_extension: Option<String>,
24737 cx: &App,
24738 ) {
24739 if cfg!(any(test, feature = "test-support")) {
24740 return;
24741 }
24742
24743 let Some(project) = &self.project else { return };
24744
24745 // If None, we are in a file without an extension
24746 let file = self
24747 .buffer
24748 .read(cx)
24749 .as_singleton()
24750 .and_then(|b| b.read(cx).file());
24751 let file_extension = file_extension.or(file
24752 .as_ref()
24753 .and_then(|file| Path::new(file.file_name(cx)).extension())
24754 .and_then(|e| e.to_str())
24755 .map(|a| a.to_string()));
24756
24757 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24758 .map(|vim_mode| vim_mode.0)
24759 .unwrap_or(false);
24760
24761 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24762 let copilot_enabled = edit_predictions_provider
24763 == language::language_settings::EditPredictionProvider::Copilot;
24764 let copilot_enabled_for_language = self
24765 .buffer
24766 .read(cx)
24767 .language_settings(cx)
24768 .show_edit_predictions;
24769
24770 let project = project.read(cx);
24771 let event_type = reported_event.event_type();
24772
24773 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24774 telemetry::event!(
24775 event_type,
24776 type = if auto_saved {"autosave"} else {"manual"},
24777 file_extension,
24778 vim_mode,
24779 copilot_enabled,
24780 copilot_enabled_for_language,
24781 edit_predictions_provider,
24782 is_via_ssh = project.is_via_remote_server(),
24783 );
24784 } else {
24785 telemetry::event!(
24786 event_type,
24787 file_extension,
24788 vim_mode,
24789 copilot_enabled,
24790 copilot_enabled_for_language,
24791 edit_predictions_provider,
24792 is_via_ssh = project.is_via_remote_server(),
24793 );
24794 };
24795 }
24796
24797 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24798 /// with each line being an array of {text, highlight} objects.
24799 fn copy_highlight_json(
24800 &mut self,
24801 _: &CopyHighlightJson,
24802 window: &mut Window,
24803 cx: &mut Context<Self>,
24804 ) {
24805 #[derive(Serialize)]
24806 struct Chunk<'a> {
24807 text: String,
24808 highlight: Option<&'a str>,
24809 }
24810
24811 let snapshot = self.buffer.read(cx).snapshot(cx);
24812 let range = self
24813 .selected_text_range(false, window, cx)
24814 .and_then(|selection| {
24815 if selection.range.is_empty() {
24816 None
24817 } else {
24818 Some(
24819 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24820 selection.range.start,
24821 )))
24822 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
24823 selection.range.end,
24824 ))),
24825 )
24826 }
24827 })
24828 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
24829
24830 let chunks = snapshot.chunks(range, true);
24831 let mut lines = Vec::new();
24832 let mut line: VecDeque<Chunk> = VecDeque::new();
24833
24834 let Some(style) = self.style.as_ref() else {
24835 return;
24836 };
24837
24838 for chunk in chunks {
24839 let highlight = chunk
24840 .syntax_highlight_id
24841 .and_then(|id| id.name(&style.syntax));
24842 let mut chunk_lines = chunk.text.split('\n').peekable();
24843 while let Some(text) = chunk_lines.next() {
24844 let mut merged_with_last_token = false;
24845 if let Some(last_token) = line.back_mut()
24846 && last_token.highlight == highlight
24847 {
24848 last_token.text.push_str(text);
24849 merged_with_last_token = true;
24850 }
24851
24852 if !merged_with_last_token {
24853 line.push_back(Chunk {
24854 text: text.into(),
24855 highlight,
24856 });
24857 }
24858
24859 if chunk_lines.peek().is_some() {
24860 if line.len() > 1 && line.front().unwrap().text.is_empty() {
24861 line.pop_front();
24862 }
24863 if line.len() > 1 && line.back().unwrap().text.is_empty() {
24864 line.pop_back();
24865 }
24866
24867 lines.push(mem::take(&mut line));
24868 }
24869 }
24870 }
24871
24872 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
24873 return;
24874 };
24875 cx.write_to_clipboard(ClipboardItem::new_string(lines));
24876 }
24877
24878 pub fn open_context_menu(
24879 &mut self,
24880 _: &OpenContextMenu,
24881 window: &mut Window,
24882 cx: &mut Context<Self>,
24883 ) {
24884 self.request_autoscroll(Autoscroll::newest(), cx);
24885 let position = self
24886 .selections
24887 .newest_display(&self.display_snapshot(cx))
24888 .start;
24889 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
24890 }
24891
24892 pub fn replay_insert_event(
24893 &mut self,
24894 text: &str,
24895 relative_utf16_range: Option<Range<isize>>,
24896 window: &mut Window,
24897 cx: &mut Context<Self>,
24898 ) {
24899 if !self.input_enabled {
24900 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24901 return;
24902 }
24903 if let Some(relative_utf16_range) = relative_utf16_range {
24904 let selections = self
24905 .selections
24906 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24907 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24908 let new_ranges = selections.into_iter().map(|range| {
24909 let start = MultiBufferOffsetUtf16(OffsetUtf16(
24910 range
24911 .head()
24912 .0
24913 .0
24914 .saturating_add_signed(relative_utf16_range.start),
24915 ));
24916 let end = MultiBufferOffsetUtf16(OffsetUtf16(
24917 range
24918 .head()
24919 .0
24920 .0
24921 .saturating_add_signed(relative_utf16_range.end),
24922 ));
24923 start..end
24924 });
24925 s.select_ranges(new_ranges);
24926 });
24927 }
24928
24929 self.handle_input(text, window, cx);
24930 }
24931
24932 pub fn is_focused(&self, window: &Window) -> bool {
24933 self.focus_handle.is_focused(window)
24934 }
24935
24936 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24937 cx.emit(EditorEvent::Focused);
24938
24939 if let Some(descendant) = self
24940 .last_focused_descendant
24941 .take()
24942 .and_then(|descendant| descendant.upgrade())
24943 {
24944 window.focus(&descendant, cx);
24945 } else {
24946 if let Some(blame) = self.blame.as_ref() {
24947 blame.update(cx, GitBlame::focus)
24948 }
24949
24950 self.blink_manager.update(cx, BlinkManager::enable);
24951 self.show_cursor_names(window, cx);
24952 self.buffer.update(cx, |buffer, cx| {
24953 buffer.finalize_last_transaction(cx);
24954 if self.leader_id.is_none() {
24955 buffer.set_active_selections(
24956 &self.selections.disjoint_anchors_arc(),
24957 self.selections.line_mode(),
24958 self.cursor_shape,
24959 cx,
24960 );
24961 }
24962 });
24963
24964 if let Some(position_map) = self.last_position_map.clone() {
24965 EditorElement::mouse_moved(
24966 self,
24967 &MouseMoveEvent {
24968 position: window.mouse_position(),
24969 pressed_button: None,
24970 modifiers: window.modifiers(),
24971 },
24972 &position_map,
24973 None,
24974 window,
24975 cx,
24976 );
24977 }
24978 }
24979 }
24980
24981 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24982 cx.emit(EditorEvent::FocusedIn)
24983 }
24984
24985 fn handle_focus_out(
24986 &mut self,
24987 event: FocusOutEvent,
24988 _window: &mut Window,
24989 cx: &mut Context<Self>,
24990 ) {
24991 if event.blurred != self.focus_handle {
24992 self.last_focused_descendant = Some(event.blurred);
24993 }
24994 self.selection_drag_state = SelectionDragState::None;
24995 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
24996 }
24997
24998 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24999 self.blink_manager.update(cx, BlinkManager::disable);
25000 self.buffer
25001 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25002
25003 if let Some(blame) = self.blame.as_ref() {
25004 blame.update(cx, GitBlame::blur)
25005 }
25006 if !self.hover_state.focused(window, cx) {
25007 hide_hover(self, cx);
25008 }
25009 if !self
25010 .context_menu
25011 .borrow()
25012 .as_ref()
25013 .is_some_and(|context_menu| context_menu.focused(window, cx))
25014 {
25015 self.hide_context_menu(window, cx);
25016 }
25017 self.take_active_edit_prediction(cx);
25018 cx.emit(EditorEvent::Blurred);
25019 cx.notify();
25020 }
25021
25022 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25023 let mut pending: String = window
25024 .pending_input_keystrokes()
25025 .into_iter()
25026 .flatten()
25027 .filter_map(|keystroke| keystroke.key_char.clone())
25028 .collect();
25029
25030 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25031 pending = "".to_string();
25032 }
25033
25034 let existing_pending = self
25035 .text_highlights(HighlightKey::PendingInput, cx)
25036 .map(|(_, ranges)| ranges.to_vec());
25037 if existing_pending.is_none() && pending.is_empty() {
25038 return;
25039 }
25040 let transaction =
25041 self.transact(window, cx, |this, window, cx| {
25042 let selections = this
25043 .selections
25044 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25045 let edits = selections
25046 .iter()
25047 .map(|selection| (selection.end..selection.end, pending.clone()));
25048 this.edit(edits, cx);
25049 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25050 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25051 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25052 }));
25053 });
25054 if let Some(existing_ranges) = existing_pending {
25055 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25056 this.edit(edits, cx);
25057 }
25058 });
25059
25060 let snapshot = self.snapshot(window, cx);
25061 let ranges = self
25062 .selections
25063 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25064 .into_iter()
25065 .map(|selection| {
25066 snapshot.buffer_snapshot().anchor_after(selection.end)
25067 ..snapshot
25068 .buffer_snapshot()
25069 .anchor_before(selection.end + pending.len())
25070 })
25071 .collect();
25072
25073 if pending.is_empty() {
25074 self.clear_highlights(HighlightKey::PendingInput, cx);
25075 } else {
25076 self.highlight_text(
25077 HighlightKey::PendingInput,
25078 ranges,
25079 HighlightStyle {
25080 underline: Some(UnderlineStyle {
25081 thickness: px(1.),
25082 color: None,
25083 wavy: false,
25084 }),
25085 ..Default::default()
25086 },
25087 cx,
25088 );
25089 }
25090
25091 self.ime_transaction = self.ime_transaction.or(transaction);
25092 if let Some(transaction) = self.ime_transaction {
25093 self.buffer.update(cx, |buffer, cx| {
25094 buffer.group_until_transaction(transaction, cx);
25095 });
25096 }
25097
25098 if self
25099 .text_highlights(HighlightKey::PendingInput, cx)
25100 .is_none()
25101 {
25102 self.ime_transaction.take();
25103 }
25104 }
25105
25106 pub fn register_action_renderer(
25107 &mut self,
25108 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25109 ) -> Subscription {
25110 let id = self.next_editor_action_id.post_inc();
25111 self.editor_actions
25112 .borrow_mut()
25113 .insert(id, Box::new(listener));
25114
25115 let editor_actions = self.editor_actions.clone();
25116 Subscription::new(move || {
25117 editor_actions.borrow_mut().remove(&id);
25118 })
25119 }
25120
25121 pub fn register_action<A: Action>(
25122 &mut self,
25123 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25124 ) -> Subscription {
25125 let id = self.next_editor_action_id.post_inc();
25126 let listener = Arc::new(listener);
25127 self.editor_actions.borrow_mut().insert(
25128 id,
25129 Box::new(move |_, window, _| {
25130 let listener = listener.clone();
25131 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25132 let action = action.downcast_ref().unwrap();
25133 if phase == DispatchPhase::Bubble {
25134 listener(action, window, cx)
25135 }
25136 })
25137 }),
25138 );
25139
25140 let editor_actions = self.editor_actions.clone();
25141 Subscription::new(move || {
25142 editor_actions.borrow_mut().remove(&id);
25143 })
25144 }
25145
25146 pub fn file_header_size(&self) -> u32 {
25147 FILE_HEADER_HEIGHT
25148 }
25149
25150 pub fn restore(
25151 &mut self,
25152 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25153 window: &mut Window,
25154 cx: &mut Context<Self>,
25155 ) {
25156 self.buffer().update(cx, |multi_buffer, cx| {
25157 for (buffer_id, changes) in revert_changes {
25158 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25159 buffer.update(cx, |buffer, cx| {
25160 buffer.edit(
25161 changes
25162 .into_iter()
25163 .map(|(range, text)| (range, text.to_string())),
25164 None,
25165 cx,
25166 );
25167 });
25168 }
25169 }
25170 });
25171 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25172 selections.refresh()
25173 });
25174 }
25175
25176 pub fn to_pixel_point(
25177 &mut self,
25178 source: Anchor,
25179 editor_snapshot: &EditorSnapshot,
25180 window: &mut Window,
25181 cx: &mut App,
25182 ) -> Option<gpui::Point<Pixels>> {
25183 let source_point = source.to_display_point(editor_snapshot);
25184 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25185 }
25186
25187 pub fn display_to_pixel_point(
25188 &mut self,
25189 source: DisplayPoint,
25190 editor_snapshot: &EditorSnapshot,
25191 window: &mut Window,
25192 cx: &mut App,
25193 ) -> Option<gpui::Point<Pixels>> {
25194 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25195 let text_layout_details = self.text_layout_details(window, cx);
25196 let scroll_top = text_layout_details
25197 .scroll_anchor
25198 .scroll_position(editor_snapshot)
25199 .y;
25200
25201 if source.row().as_f64() < scroll_top.floor() {
25202 return None;
25203 }
25204 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25205 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25206 Some(gpui::Point::new(source_x, source_y))
25207 }
25208
25209 pub fn has_visible_completions_menu(&self) -> bool {
25210 !self.edit_prediction_preview_is_active()
25211 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25212 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25213 })
25214 }
25215
25216 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25217 if self.mode.is_minimap() {
25218 return;
25219 }
25220 self.addons
25221 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25222 }
25223
25224 pub fn unregister_addon<T: Addon>(&mut self) {
25225 self.addons.remove(&std::any::TypeId::of::<T>());
25226 }
25227
25228 pub fn addon<T: Addon>(&self) -> Option<&T> {
25229 let type_id = std::any::TypeId::of::<T>();
25230 self.addons
25231 .get(&type_id)
25232 .and_then(|item| item.to_any().downcast_ref::<T>())
25233 }
25234
25235 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25236 let type_id = std::any::TypeId::of::<T>();
25237 self.addons
25238 .get_mut(&type_id)
25239 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25240 }
25241
25242 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25243 let text_layout_details = self.text_layout_details(window, cx);
25244 let style = &text_layout_details.editor_style;
25245 let font_id = window.text_system().resolve_font(&style.text.font());
25246 let font_size = style.text.font_size.to_pixels(window.rem_size());
25247 let line_height = style.text.line_height_in_pixels(window.rem_size());
25248 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25249 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25250
25251 CharacterDimensions {
25252 em_width,
25253 em_advance,
25254 line_height,
25255 }
25256 }
25257
25258 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25259 self.load_diff_task.clone()
25260 }
25261
25262 fn read_metadata_from_db(
25263 &mut self,
25264 item_id: u64,
25265 workspace_id: WorkspaceId,
25266 window: &mut Window,
25267 cx: &mut Context<Editor>,
25268 ) {
25269 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25270 && !self.mode.is_minimap()
25271 && WorkspaceSettings::get(None, cx).restore_on_startup
25272 != RestoreOnStartupBehavior::EmptyTab
25273 {
25274 let buffer_snapshot = OnceCell::new();
25275
25276 // Get file path for path-based fold lookup
25277 let file_path: Option<Arc<Path>> =
25278 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25279 project::File::from_dyn(buffer.read(cx).file())
25280 .map(|file| Arc::from(file.abs_path(cx)))
25281 });
25282
25283 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25284 let (folds, needs_migration) = if let Some(ref path) = file_path {
25285 if let Some(folds) = DB.get_file_folds(workspace_id, path).log_err()
25286 && !folds.is_empty()
25287 {
25288 (Some(folds), false)
25289 } else if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
25290 && !folds.is_empty()
25291 {
25292 // Found old editor_folds data, will migrate to file_folds
25293 (Some(folds), true)
25294 } else {
25295 (None, false)
25296 }
25297 } else {
25298 // No file path, try editor_folds as fallback
25299 let folds = DB.get_editor_folds(item_id, workspace_id).log_err();
25300 (folds.filter(|f| !f.is_empty()), false)
25301 };
25302
25303 if let Some(folds) = folds {
25304 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25305 let snapshot_len = snapshot.len().0;
25306
25307 // Helper: search for fingerprint in buffer, return offset if found
25308 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25309 // Ensure we start at a character boundary (defensive)
25310 let search_start = snapshot
25311 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25312 .0;
25313 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25314
25315 let mut byte_offset = search_start;
25316 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25317 if byte_offset > search_end {
25318 break;
25319 }
25320 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25321 return Some(byte_offset);
25322 }
25323 byte_offset += ch.len_utf8();
25324 }
25325 None
25326 };
25327
25328 // Track search position to handle duplicate fingerprints correctly.
25329 // Folds are stored in document order, so we advance after each match.
25330 let mut search_start = 0usize;
25331
25332 // Collect db_folds for migration (only folds with valid fingerprints)
25333 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25334
25335 let valid_folds: Vec<_> = folds
25336 .into_iter()
25337 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25338 // Skip folds without fingerprints (old data before migration)
25339 let sfp = start_fp?;
25340 let efp = end_fp?;
25341 let efp_len = efp.len();
25342
25343 // Fast path: check if fingerprints match at stored offsets
25344 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25345 let start_matches = stored_start < snapshot_len
25346 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25347 let efp_check_pos = stored_end.saturating_sub(efp_len);
25348 let end_matches = efp_check_pos >= stored_start
25349 && stored_end <= snapshot_len
25350 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25351
25352 let (new_start, new_end) = if start_matches && end_matches {
25353 // Offsets unchanged, use stored values
25354 (stored_start, stored_end)
25355 } else if sfp == efp {
25356 // Short fold: identical fingerprints can only match once per search
25357 // Use stored fold length to compute new_end
25358 let new_start = find_fingerprint(&sfp, search_start)?;
25359 let fold_len = stored_end - stored_start;
25360 let new_end = new_start + fold_len;
25361 (new_start, new_end)
25362 } else {
25363 // Slow path: search for fingerprints in buffer
25364 let new_start = find_fingerprint(&sfp, search_start)?;
25365 // Search for end_fp after start, then add efp_len to get actual fold end
25366 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25367 let new_end = efp_pos + efp_len;
25368 (new_start, new_end)
25369 };
25370
25371 // Advance search position for next fold
25372 search_start = new_end;
25373
25374 // Validate fold makes sense (end must be after start)
25375 if new_end <= new_start {
25376 return None;
25377 }
25378
25379 // Collect for migration if needed
25380 if needs_migration {
25381 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25382 }
25383
25384 Some(
25385 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25386 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25387 )
25388 })
25389 .collect();
25390
25391 if !valid_folds.is_empty() {
25392 self.fold_ranges(valid_folds, false, window, cx);
25393
25394 // Migrate from editor_folds to file_folds if we loaded from old table
25395 if needs_migration {
25396 if let Some(ref path) = file_path {
25397 let path = path.clone();
25398 cx.spawn(async move |_, _| {
25399 DB.save_file_folds(workspace_id, path, db_folds_for_migration)
25400 .await
25401 .log_err();
25402 })
25403 .detach();
25404 }
25405 }
25406 }
25407 }
25408
25409 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
25410 && !selections.is_empty()
25411 {
25412 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25413 // skip adding the initial selection to selection history
25414 self.selection_history.mode = SelectionHistoryMode::Skipping;
25415 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25416 s.select_ranges(selections.into_iter().map(|(start, end)| {
25417 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25418 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25419 }));
25420 });
25421 self.selection_history.mode = SelectionHistoryMode::Normal;
25422 };
25423 }
25424
25425 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25426 }
25427
25428 /// Load folds from the file_folds database table by file path.
25429 /// Used when manually opening a file that was previously closed.
25430 fn load_folds_from_db(
25431 &mut self,
25432 workspace_id: WorkspaceId,
25433 file_path: PathBuf,
25434 window: &mut Window,
25435 cx: &mut Context<Editor>,
25436 ) {
25437 if self.mode.is_minimap()
25438 || WorkspaceSettings::get(None, cx).restore_on_startup
25439 == RestoreOnStartupBehavior::EmptyTab
25440 {
25441 return;
25442 }
25443
25444 let Some(folds) = DB.get_file_folds(workspace_id, &file_path).log_err() else {
25445 return;
25446 };
25447 if folds.is_empty() {
25448 return;
25449 }
25450
25451 let snapshot = self.buffer.read(cx).snapshot(cx);
25452 let snapshot_len = snapshot.len().0;
25453
25454 // Helper: search for fingerprint in buffer, return offset if found
25455 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25456 let search_start = snapshot
25457 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25458 .0;
25459 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25460
25461 let mut byte_offset = search_start;
25462 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25463 if byte_offset > search_end {
25464 break;
25465 }
25466 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25467 return Some(byte_offset);
25468 }
25469 byte_offset += ch.len_utf8();
25470 }
25471 None
25472 };
25473
25474 let mut search_start = 0usize;
25475
25476 let valid_folds: Vec<_> = folds
25477 .into_iter()
25478 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25479 let sfp = start_fp?;
25480 let efp = end_fp?;
25481 let efp_len = efp.len();
25482
25483 let start_matches = stored_start < snapshot_len
25484 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25485 let efp_check_pos = stored_end.saturating_sub(efp_len);
25486 let end_matches = efp_check_pos >= stored_start
25487 && stored_end <= snapshot_len
25488 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25489
25490 let (new_start, new_end) = if start_matches && end_matches {
25491 (stored_start, stored_end)
25492 } else if sfp == efp {
25493 let new_start = find_fingerprint(&sfp, search_start)?;
25494 let fold_len = stored_end - stored_start;
25495 let new_end = new_start + fold_len;
25496 (new_start, new_end)
25497 } else {
25498 let new_start = find_fingerprint(&sfp, search_start)?;
25499 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25500 let new_end = efp_pos + efp_len;
25501 (new_start, new_end)
25502 };
25503
25504 search_start = new_end;
25505
25506 if new_end <= new_start {
25507 return None;
25508 }
25509
25510 Some(
25511 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25512 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25513 )
25514 })
25515 .collect();
25516
25517 if !valid_folds.is_empty() {
25518 self.fold_ranges(valid_folds, false, window, cx);
25519 }
25520 }
25521
25522 fn update_lsp_data(
25523 &mut self,
25524 for_buffer: Option<BufferId>,
25525 window: &mut Window,
25526 cx: &mut Context<'_, Self>,
25527 ) {
25528 if !self.enable_lsp_data {
25529 return;
25530 }
25531
25532 if let Some(buffer_id) = for_buffer {
25533 self.pull_diagnostics(buffer_id, window, cx);
25534 }
25535 self.refresh_semantic_tokens(for_buffer, None, cx);
25536 self.refresh_document_colors(for_buffer, window, cx);
25537 self.refresh_folding_ranges(for_buffer, window, cx);
25538 self.refresh_document_symbols(for_buffer, cx);
25539 }
25540
25541 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25542 if !self.mode().is_full() {
25543 return;
25544 }
25545 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25546 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25547 }
25548 }
25549
25550 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25551 if !self.mode().is_full() {
25552 return;
25553 }
25554
25555 if !self.registered_buffers.contains_key(&buffer_id)
25556 && let Some(project) = self.project.as_ref()
25557 {
25558 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25559 project.update(cx, |project, cx| {
25560 self.registered_buffers.insert(
25561 buffer_id,
25562 project.register_buffer_with_language_servers(&buffer, cx),
25563 );
25564 });
25565 } else {
25566 self.registered_buffers.remove(&buffer_id);
25567 }
25568 }
25569 }
25570
25571 fn create_style(&self, cx: &App) -> EditorStyle {
25572 let settings = ThemeSettings::get_global(cx);
25573
25574 let mut text_style = match self.mode {
25575 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25576 color: cx.theme().colors().editor_foreground,
25577 font_family: settings.ui_font.family.clone(),
25578 font_features: settings.ui_font.features.clone(),
25579 font_fallbacks: settings.ui_font.fallbacks.clone(),
25580 font_size: rems(0.875).into(),
25581 font_weight: settings.ui_font.weight,
25582 line_height: relative(settings.buffer_line_height.value()),
25583 ..Default::default()
25584 },
25585 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25586 color: cx.theme().colors().editor_foreground,
25587 font_family: settings.buffer_font.family.clone(),
25588 font_features: settings.buffer_font.features.clone(),
25589 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25590 font_size: settings.buffer_font_size(cx).into(),
25591 font_weight: settings.buffer_font.weight,
25592 line_height: relative(settings.buffer_line_height.value()),
25593 ..Default::default()
25594 },
25595 };
25596 if let Some(text_style_refinement) = &self.text_style_refinement {
25597 text_style.refine(text_style_refinement)
25598 }
25599
25600 let background = match self.mode {
25601 EditorMode::SingleLine => cx.theme().system().transparent,
25602 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25603 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25604 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25605 };
25606
25607 EditorStyle {
25608 background,
25609 border: cx.theme().colors().border,
25610 local_player: cx.theme().players().local(),
25611 text: text_style,
25612 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25613 syntax: cx.theme().syntax().clone(),
25614 status: cx.theme().status().clone(),
25615 inlay_hints_style: make_inlay_hints_style(cx),
25616 edit_prediction_styles: make_suggestion_styles(cx),
25617 unnecessary_code_fade: settings.unnecessary_code_fade,
25618 show_underlines: self.diagnostics_enabled(),
25619 }
25620 }
25621
25622 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<BreadcrumbText>> {
25623 let multibuffer = self.buffer().read(cx);
25624 let is_singleton = multibuffer.is_singleton();
25625 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25626 let buffer = multibuffer.buffer(*buffer_id)?;
25627
25628 let buffer = buffer.read(cx);
25629 let settings = ThemeSettings::get_global(cx);
25630 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25631 let mut breadcrumbs = if is_singleton {
25632 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25633 buffer
25634 .snapshot()
25635 .resolve_file_path(
25636 self.project
25637 .as_ref()
25638 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25639 .unwrap_or_default(),
25640 cx,
25641 )
25642 .unwrap_or_else(|| {
25643 if multibuffer.is_singleton() {
25644 multibuffer.title(cx).to_string()
25645 } else {
25646 "untitled".to_string()
25647 }
25648 })
25649 });
25650 vec![BreadcrumbText {
25651 text,
25652 highlights: None,
25653 font: Some(settings.buffer_font.clone()),
25654 }]
25655 } else {
25656 vec![]
25657 };
25658
25659 breadcrumbs.extend(symbols.iter().map(|symbol| BreadcrumbText {
25660 text: symbol.text.clone(),
25661 highlights: Some(symbol.highlight_ranges.clone()),
25662 font: Some(settings.buffer_font.clone()),
25663 }));
25664 Some(breadcrumbs)
25665 }
25666
25667 fn disable_lsp_data(&mut self) {
25668 self.enable_lsp_data = false;
25669 }
25670
25671 fn disable_runnables(&mut self) {
25672 self.enable_runnables = false;
25673 }
25674}
25675
25676fn edit_for_markdown_paste<'a>(
25677 buffer: &MultiBufferSnapshot,
25678 range: Range<MultiBufferOffset>,
25679 to_insert: &'a str,
25680 url: Option<url::Url>,
25681) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25682 if url.is_none() {
25683 return (range, Cow::Borrowed(to_insert));
25684 };
25685
25686 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25687
25688 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25689 Cow::Borrowed(to_insert)
25690 } else {
25691 Cow::Owned(format!("[{old_text}]({to_insert})"))
25692 };
25693 (range, new_text)
25694}
25695
25696fn process_completion_for_edit(
25697 completion: &Completion,
25698 intent: CompletionIntent,
25699 buffer: &Entity<Buffer>,
25700 cursor_position: &text::Anchor,
25701 cx: &mut Context<Editor>,
25702) -> CompletionEdit {
25703 let buffer = buffer.read(cx);
25704 let buffer_snapshot = buffer.snapshot();
25705 let (snippet, new_text) = if completion.is_snippet() {
25706 let mut snippet_source = completion.new_text.clone();
25707 // Workaround for typescript language server issues so that methods don't expand within
25708 // strings and functions with type expressions. The previous point is used because the query
25709 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25710 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25711 let previous_point = if previous_point.column > 0 {
25712 cursor_position.to_previous_offset(&buffer_snapshot)
25713 } else {
25714 cursor_position.to_offset(&buffer_snapshot)
25715 };
25716 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25717 && scope.prefers_label_for_snippet_in_completion()
25718 && let Some(label) = completion.label()
25719 && matches!(
25720 completion.kind(),
25721 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25722 )
25723 {
25724 snippet_source = label;
25725 }
25726 match Snippet::parse(&snippet_source).log_err() {
25727 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25728 None => (None, completion.new_text.clone()),
25729 }
25730 } else {
25731 (None, completion.new_text.clone())
25732 };
25733
25734 let mut range_to_replace = {
25735 let replace_range = &completion.replace_range;
25736 if let CompletionSource::Lsp {
25737 insert_range: Some(insert_range),
25738 ..
25739 } = &completion.source
25740 {
25741 debug_assert_eq!(
25742 insert_range.start, replace_range.start,
25743 "insert_range and replace_range should start at the same position"
25744 );
25745 debug_assert!(
25746 insert_range
25747 .start
25748 .cmp(cursor_position, &buffer_snapshot)
25749 .is_le(),
25750 "insert_range should start before or at cursor position"
25751 );
25752 debug_assert!(
25753 replace_range
25754 .start
25755 .cmp(cursor_position, &buffer_snapshot)
25756 .is_le(),
25757 "replace_range should start before or at cursor position"
25758 );
25759
25760 let should_replace = match intent {
25761 CompletionIntent::CompleteWithInsert => false,
25762 CompletionIntent::CompleteWithReplace => true,
25763 CompletionIntent::Complete | CompletionIntent::Compose => {
25764 let insert_mode =
25765 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
25766 .completions
25767 .lsp_insert_mode;
25768 match insert_mode {
25769 LspInsertMode::Insert => false,
25770 LspInsertMode::Replace => true,
25771 LspInsertMode::ReplaceSubsequence => {
25772 let mut text_to_replace = buffer.chars_for_range(
25773 buffer.anchor_before(replace_range.start)
25774 ..buffer.anchor_after(replace_range.end),
25775 );
25776 let mut current_needle = text_to_replace.next();
25777 for haystack_ch in completion.label.text.chars() {
25778 if let Some(needle_ch) = current_needle
25779 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25780 {
25781 current_needle = text_to_replace.next();
25782 }
25783 }
25784 current_needle.is_none()
25785 }
25786 LspInsertMode::ReplaceSuffix => {
25787 if replace_range
25788 .end
25789 .cmp(cursor_position, &buffer_snapshot)
25790 .is_gt()
25791 {
25792 let range_after_cursor = *cursor_position..replace_range.end;
25793 let text_after_cursor = buffer
25794 .text_for_range(
25795 buffer.anchor_before(range_after_cursor.start)
25796 ..buffer.anchor_after(range_after_cursor.end),
25797 )
25798 .collect::<String>()
25799 .to_ascii_lowercase();
25800 completion
25801 .label
25802 .text
25803 .to_ascii_lowercase()
25804 .ends_with(&text_after_cursor)
25805 } else {
25806 true
25807 }
25808 }
25809 }
25810 }
25811 };
25812
25813 if should_replace {
25814 replace_range.clone()
25815 } else {
25816 insert_range.clone()
25817 }
25818 } else {
25819 replace_range.clone()
25820 }
25821 };
25822
25823 if range_to_replace
25824 .end
25825 .cmp(cursor_position, &buffer_snapshot)
25826 .is_lt()
25827 {
25828 range_to_replace.end = *cursor_position;
25829 }
25830
25831 let replace_range = range_to_replace.to_offset(buffer);
25832 CompletionEdit {
25833 new_text,
25834 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
25835 snippet,
25836 }
25837}
25838
25839struct CompletionEdit {
25840 new_text: String,
25841 replace_range: Range<BufferOffset>,
25842 snippet: Option<Snippet>,
25843}
25844
25845fn comment_delimiter_for_newline(
25846 start_point: &Point,
25847 buffer: &MultiBufferSnapshot,
25848 language: &LanguageScope,
25849) -> Option<Arc<str>> {
25850 let delimiters = language.line_comment_prefixes();
25851 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
25852 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25853
25854 let num_of_whitespaces = snapshot
25855 .chars_for_range(range.clone())
25856 .take_while(|c| c.is_whitespace())
25857 .count();
25858 let comment_candidate = snapshot
25859 .chars_for_range(range.clone())
25860 .skip(num_of_whitespaces)
25861 .take(max_len_of_delimiter + 2)
25862 .collect::<String>();
25863 let (delimiter, trimmed_len, is_repl) = delimiters
25864 .iter()
25865 .filter_map(|delimiter| {
25866 let prefix = delimiter.trim_end();
25867 if comment_candidate.starts_with(prefix) {
25868 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
25869 {
25870 stripped_comment.starts_with(" %%")
25871 } else {
25872 false
25873 };
25874 Some((delimiter, prefix.len(), is_repl))
25875 } else {
25876 None
25877 }
25878 })
25879 .max_by_key(|(_, len, _)| *len)?;
25880
25881 if let Some(BlockCommentConfig {
25882 start: block_start, ..
25883 }) = language.block_comment()
25884 {
25885 let block_start_trimmed = block_start.trim_end();
25886 if block_start_trimmed.starts_with(delimiter.trim_end()) {
25887 let line_content = snapshot
25888 .chars_for_range(range.clone())
25889 .skip(num_of_whitespaces)
25890 .take(block_start_trimmed.len())
25891 .collect::<String>();
25892
25893 if line_content.starts_with(block_start_trimmed) {
25894 return None;
25895 }
25896 }
25897 }
25898
25899 let cursor_is_placed_after_comment_marker =
25900 num_of_whitespaces + trimmed_len <= start_point.column as usize;
25901 if cursor_is_placed_after_comment_marker {
25902 if !is_repl {
25903 return Some(delimiter.clone());
25904 }
25905
25906 let line_content_after_cursor: String = snapshot
25907 .chars_for_range(range)
25908 .skip(start_point.column as usize)
25909 .collect();
25910
25911 if line_content_after_cursor.trim().is_empty() {
25912 return None;
25913 } else {
25914 return Some(delimiter.clone());
25915 }
25916 } else {
25917 None
25918 }
25919}
25920
25921fn documentation_delimiter_for_newline(
25922 start_point: &Point,
25923 buffer: &MultiBufferSnapshot,
25924 language: &LanguageScope,
25925 newline_config: &mut NewlineConfig,
25926) -> Option<Arc<str>> {
25927 let BlockCommentConfig {
25928 start: start_tag,
25929 end: end_tag,
25930 prefix: delimiter,
25931 tab_size: len,
25932 } = language.documentation_comment()?;
25933 let is_within_block_comment = buffer
25934 .language_scope_at(*start_point)
25935 .is_some_and(|scope| scope.override_name() == Some("comment"));
25936 if !is_within_block_comment {
25937 return None;
25938 }
25939
25940 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
25941
25942 let num_of_whitespaces = snapshot
25943 .chars_for_range(range.clone())
25944 .take_while(|c| c.is_whitespace())
25945 .count();
25946
25947 // 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.
25948 let column = start_point.column;
25949 let cursor_is_after_start_tag = {
25950 let start_tag_len = start_tag.len();
25951 let start_tag_line = snapshot
25952 .chars_for_range(range.clone())
25953 .skip(num_of_whitespaces)
25954 .take(start_tag_len)
25955 .collect::<String>();
25956 if start_tag_line.starts_with(start_tag.as_ref()) {
25957 num_of_whitespaces + start_tag_len <= column as usize
25958 } else {
25959 false
25960 }
25961 };
25962
25963 let cursor_is_after_delimiter = {
25964 let delimiter_trim = delimiter.trim_end();
25965 let delimiter_line = snapshot
25966 .chars_for_range(range.clone())
25967 .skip(num_of_whitespaces)
25968 .take(delimiter_trim.len())
25969 .collect::<String>();
25970 if delimiter_line.starts_with(delimiter_trim) {
25971 num_of_whitespaces + delimiter_trim.len() <= column as usize
25972 } else {
25973 false
25974 }
25975 };
25976
25977 let mut needs_extra_line = false;
25978 let mut extra_line_additional_indent = IndentSize::spaces(0);
25979
25980 let cursor_is_before_end_tag_if_exists = {
25981 let mut char_position = 0u32;
25982 let mut end_tag_offset = None;
25983
25984 'outer: for chunk in snapshot.text_for_range(range) {
25985 if let Some(byte_pos) = chunk.find(&**end_tag) {
25986 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
25987 end_tag_offset = Some(char_position + chars_before_match);
25988 break 'outer;
25989 }
25990 char_position += chunk.chars().count() as u32;
25991 }
25992
25993 if let Some(end_tag_offset) = end_tag_offset {
25994 let cursor_is_before_end_tag = column <= end_tag_offset;
25995 if cursor_is_after_start_tag {
25996 if cursor_is_before_end_tag {
25997 needs_extra_line = true;
25998 }
25999 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26000 if cursor_is_at_start_of_end_tag {
26001 extra_line_additional_indent.len = *len;
26002 }
26003 }
26004 cursor_is_before_end_tag
26005 } else {
26006 true
26007 }
26008 };
26009
26010 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26011 && cursor_is_before_end_tag_if_exists
26012 {
26013 let additional_indent = if cursor_is_after_start_tag {
26014 IndentSize::spaces(*len)
26015 } else {
26016 IndentSize::spaces(0)
26017 };
26018
26019 *newline_config = NewlineConfig::Newline {
26020 additional_indent,
26021 extra_line_additional_indent: if needs_extra_line {
26022 Some(extra_line_additional_indent)
26023 } else {
26024 None
26025 },
26026 prevent_auto_indent: true,
26027 };
26028 Some(delimiter.clone())
26029 } else {
26030 None
26031 }
26032}
26033
26034const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26035
26036fn list_delimiter_for_newline(
26037 start_point: &Point,
26038 buffer: &MultiBufferSnapshot,
26039 language: &LanguageScope,
26040 newline_config: &mut NewlineConfig,
26041) -> Option<Arc<str>> {
26042 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26043
26044 let num_of_whitespaces = snapshot
26045 .chars_for_range(range.clone())
26046 .take_while(|c| c.is_whitespace())
26047 .count();
26048
26049 let task_list_entries: Vec<_> = language
26050 .task_list()
26051 .into_iter()
26052 .flat_map(|config| {
26053 config
26054 .prefixes
26055 .iter()
26056 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26057 })
26058 .collect();
26059 let unordered_list_entries: Vec<_> = language
26060 .unordered_list()
26061 .iter()
26062 .map(|marker| (marker.as_ref(), marker.as_ref()))
26063 .collect();
26064
26065 let all_entries: Vec<_> = task_list_entries
26066 .into_iter()
26067 .chain(unordered_list_entries)
26068 .collect();
26069
26070 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26071 let candidate: String = snapshot
26072 .chars_for_range(range.clone())
26073 .skip(num_of_whitespaces)
26074 .take(max_prefix_len)
26075 .collect();
26076
26077 if let Some((prefix, continuation)) = all_entries
26078 .iter()
26079 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26080 .max_by_key(|(prefix, _)| prefix.len())
26081 {
26082 let end_of_prefix = num_of_whitespaces + prefix.len();
26083 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26084 let has_content_after_marker = snapshot
26085 .chars_for_range(range)
26086 .skip(end_of_prefix)
26087 .any(|c| !c.is_whitespace());
26088
26089 if has_content_after_marker && cursor_is_after_prefix {
26090 return Some((*continuation).into());
26091 }
26092
26093 if start_point.column as usize == end_of_prefix {
26094 if num_of_whitespaces == 0 {
26095 *newline_config = NewlineConfig::ClearCurrentLine;
26096 } else {
26097 *newline_config = NewlineConfig::UnindentCurrentLine {
26098 continuation: (*continuation).into(),
26099 };
26100 }
26101 }
26102
26103 return None;
26104 }
26105 }
26106
26107 let candidate: String = snapshot
26108 .chars_for_range(range.clone())
26109 .skip(num_of_whitespaces)
26110 .take(ORDERED_LIST_MAX_MARKER_LEN)
26111 .collect();
26112
26113 for ordered_config in language.ordered_list() {
26114 let regex = match Regex::new(&ordered_config.pattern) {
26115 Ok(r) => r,
26116 Err(_) => continue,
26117 };
26118
26119 if let Some(captures) = regex.captures(&candidate) {
26120 let full_match = captures.get(0)?;
26121 let marker_len = full_match.len();
26122 let end_of_prefix = num_of_whitespaces + marker_len;
26123 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26124
26125 let has_content_after_marker = snapshot
26126 .chars_for_range(range)
26127 .skip(end_of_prefix)
26128 .any(|c| !c.is_whitespace());
26129
26130 if has_content_after_marker && cursor_is_after_prefix {
26131 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26132 let continuation = ordered_config
26133 .format
26134 .replace("{1}", &(number + 1).to_string());
26135 return Some(continuation.into());
26136 }
26137
26138 if start_point.column as usize == end_of_prefix {
26139 let continuation = ordered_config.format.replace("{1}", "1");
26140 if num_of_whitespaces == 0 {
26141 *newline_config = NewlineConfig::ClearCurrentLine;
26142 } else {
26143 *newline_config = NewlineConfig::UnindentCurrentLine {
26144 continuation: continuation.into(),
26145 };
26146 }
26147 }
26148
26149 return None;
26150 }
26151 }
26152
26153 None
26154}
26155
26156fn is_list_prefix_row(
26157 row: MultiBufferRow,
26158 buffer: &MultiBufferSnapshot,
26159 language: &LanguageScope,
26160) -> bool {
26161 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26162 return false;
26163 };
26164
26165 let num_of_whitespaces = snapshot
26166 .chars_for_range(range.clone())
26167 .take_while(|c| c.is_whitespace())
26168 .count();
26169
26170 let task_list_prefixes: Vec<_> = language
26171 .task_list()
26172 .into_iter()
26173 .flat_map(|config| {
26174 config
26175 .prefixes
26176 .iter()
26177 .map(|p| p.as_ref())
26178 .collect::<Vec<_>>()
26179 })
26180 .collect();
26181 let unordered_list_markers: Vec<_> = language
26182 .unordered_list()
26183 .iter()
26184 .map(|marker| marker.as_ref())
26185 .collect();
26186 let all_prefixes: Vec<_> = task_list_prefixes
26187 .into_iter()
26188 .chain(unordered_list_markers)
26189 .collect();
26190 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26191 let candidate: String = snapshot
26192 .chars_for_range(range.clone())
26193 .skip(num_of_whitespaces)
26194 .take(max_prefix_len)
26195 .collect();
26196 if all_prefixes
26197 .iter()
26198 .any(|prefix| candidate.starts_with(*prefix))
26199 {
26200 return true;
26201 }
26202 }
26203
26204 let ordered_list_candidate: String = snapshot
26205 .chars_for_range(range)
26206 .skip(num_of_whitespaces)
26207 .take(ORDERED_LIST_MAX_MARKER_LEN)
26208 .collect();
26209 for ordered_config in language.ordered_list() {
26210 let regex = match Regex::new(&ordered_config.pattern) {
26211 Ok(r) => r,
26212 Err(_) => continue,
26213 };
26214 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26215 return captures.get(0).is_some();
26216 }
26217 }
26218
26219 false
26220}
26221
26222#[derive(Debug)]
26223enum NewlineConfig {
26224 /// Insert newline with optional additional indent and optional extra blank line
26225 Newline {
26226 additional_indent: IndentSize,
26227 extra_line_additional_indent: Option<IndentSize>,
26228 prevent_auto_indent: bool,
26229 },
26230 /// Clear the current line
26231 ClearCurrentLine,
26232 /// Unindent the current line and add continuation
26233 UnindentCurrentLine { continuation: Arc<str> },
26234}
26235
26236impl NewlineConfig {
26237 fn has_extra_line(&self) -> bool {
26238 matches!(
26239 self,
26240 Self::Newline {
26241 extra_line_additional_indent: Some(_),
26242 ..
26243 }
26244 )
26245 }
26246
26247 fn insert_extra_newline_brackets(
26248 buffer: &MultiBufferSnapshot,
26249 range: Range<MultiBufferOffset>,
26250 language: &language::LanguageScope,
26251 ) -> bool {
26252 let leading_whitespace_len = buffer
26253 .reversed_chars_at(range.start)
26254 .take_while(|c| c.is_whitespace() && *c != '\n')
26255 .map(|c| c.len_utf8())
26256 .sum::<usize>();
26257 let trailing_whitespace_len = buffer
26258 .chars_at(range.end)
26259 .take_while(|c| c.is_whitespace() && *c != '\n')
26260 .map(|c| c.len_utf8())
26261 .sum::<usize>();
26262 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26263
26264 language.brackets().any(|(pair, enabled)| {
26265 let pair_start = pair.start.trim_end();
26266 let pair_end = pair.end.trim_start();
26267
26268 enabled
26269 && pair.newline
26270 && buffer.contains_str_at(range.end, pair_end)
26271 && buffer.contains_str_at(
26272 range.start.saturating_sub_usize(pair_start.len()),
26273 pair_start,
26274 )
26275 })
26276 }
26277
26278 fn insert_extra_newline_tree_sitter(
26279 buffer: &MultiBufferSnapshot,
26280 range: Range<MultiBufferOffset>,
26281 ) -> bool {
26282 let (buffer, range) = match buffer
26283 .range_to_buffer_ranges(range.start..=range.end)
26284 .as_slice()
26285 {
26286 [(buffer, range, _)] => (*buffer, range.clone()),
26287 _ => return false,
26288 };
26289 let pair = {
26290 let mut result: Option<BracketMatch<usize>> = None;
26291
26292 for pair in buffer
26293 .all_bracket_ranges(range.start.0..range.end.0)
26294 .filter(move |pair| {
26295 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26296 })
26297 {
26298 let len = pair.close_range.end - pair.open_range.start;
26299
26300 if let Some(existing) = &result {
26301 let existing_len = existing.close_range.end - existing.open_range.start;
26302 if len > existing_len {
26303 continue;
26304 }
26305 }
26306
26307 result = Some(pair);
26308 }
26309
26310 result
26311 };
26312 let Some(pair) = pair else {
26313 return false;
26314 };
26315 pair.newline_only
26316 && buffer
26317 .chars_for_range(pair.open_range.end..range.start.0)
26318 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26319 .all(|c| c.is_whitespace() && c != '\n')
26320 }
26321}
26322
26323fn update_uncommitted_diff_for_buffer(
26324 editor: Entity<Editor>,
26325 project: &Entity<Project>,
26326 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26327 buffer: Entity<MultiBuffer>,
26328 cx: &mut App,
26329) -> Task<()> {
26330 let mut tasks = Vec::new();
26331 project.update(cx, |project, cx| {
26332 for buffer in buffers {
26333 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26334 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26335 }
26336 }
26337 });
26338 cx.spawn(async move |cx| {
26339 let diffs = future::join_all(tasks).await;
26340 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26341 return;
26342 }
26343
26344 buffer.update(cx, |buffer, cx| {
26345 for diff in diffs.into_iter().flatten() {
26346 buffer.add_diff(diff, cx);
26347 }
26348 });
26349 })
26350}
26351
26352fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26353 let tab_size = tab_size.get() as usize;
26354 let mut width = offset;
26355
26356 for ch in text.chars() {
26357 width += if ch == '\t' {
26358 tab_size - (width % tab_size)
26359 } else {
26360 1
26361 };
26362 }
26363
26364 width - offset
26365}
26366
26367#[cfg(test)]
26368mod tests {
26369 use super::*;
26370
26371 #[test]
26372 fn test_string_size_with_expanded_tabs() {
26373 let nz = |val| NonZeroU32::new(val).unwrap();
26374 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26375 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26376 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26377 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26378 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26379 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26380 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26381 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26382 }
26383}
26384
26385/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26386struct WordBreakingTokenizer<'a> {
26387 input: &'a str,
26388}
26389
26390impl<'a> WordBreakingTokenizer<'a> {
26391 fn new(input: &'a str) -> Self {
26392 Self { input }
26393 }
26394}
26395
26396fn is_char_ideographic(ch: char) -> bool {
26397 use unicode_script::Script::*;
26398 use unicode_script::UnicodeScript;
26399 matches!(ch.script(), Han | Tangut | Yi)
26400}
26401
26402fn is_grapheme_ideographic(text: &str) -> bool {
26403 text.chars().any(is_char_ideographic)
26404}
26405
26406fn is_grapheme_whitespace(text: &str) -> bool {
26407 text.chars().any(|x| x.is_whitespace())
26408}
26409
26410fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26411 text.chars()
26412 .next()
26413 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26414}
26415
26416#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26417enum WordBreakToken<'a> {
26418 Word { token: &'a str, grapheme_len: usize },
26419 InlineWhitespace { token: &'a str, grapheme_len: usize },
26420 Newline,
26421}
26422
26423impl<'a> Iterator for WordBreakingTokenizer<'a> {
26424 /// Yields a span, the count of graphemes in the token, and whether it was
26425 /// whitespace. Note that it also breaks at word boundaries.
26426 type Item = WordBreakToken<'a>;
26427
26428 fn next(&mut self) -> Option<Self::Item> {
26429 use unicode_segmentation::UnicodeSegmentation;
26430 if self.input.is_empty() {
26431 return None;
26432 }
26433
26434 let mut iter = self.input.graphemes(true).peekable();
26435 let mut offset = 0;
26436 let mut grapheme_len = 0;
26437 if let Some(first_grapheme) = iter.next() {
26438 let is_newline = first_grapheme == "\n";
26439 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26440 offset += first_grapheme.len();
26441 grapheme_len += 1;
26442 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26443 if let Some(grapheme) = iter.peek().copied()
26444 && should_stay_with_preceding_ideograph(grapheme)
26445 {
26446 offset += grapheme.len();
26447 grapheme_len += 1;
26448 }
26449 } else {
26450 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26451 let mut next_word_bound = words.peek().copied();
26452 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26453 next_word_bound = words.next();
26454 }
26455 while let Some(grapheme) = iter.peek().copied() {
26456 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26457 break;
26458 };
26459 if is_grapheme_whitespace(grapheme) != is_whitespace
26460 || (grapheme == "\n") != is_newline
26461 {
26462 break;
26463 };
26464 offset += grapheme.len();
26465 grapheme_len += 1;
26466 iter.next();
26467 }
26468 }
26469 let token = &self.input[..offset];
26470 self.input = &self.input[offset..];
26471 if token == "\n" {
26472 Some(WordBreakToken::Newline)
26473 } else if is_whitespace {
26474 Some(WordBreakToken::InlineWhitespace {
26475 token,
26476 grapheme_len,
26477 })
26478 } else {
26479 Some(WordBreakToken::Word {
26480 token,
26481 grapheme_len,
26482 })
26483 }
26484 } else {
26485 None
26486 }
26487 }
26488}
26489
26490#[test]
26491fn test_word_breaking_tokenizer() {
26492 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26493 ("", &[]),
26494 (" ", &[whitespace(" ", 2)]),
26495 ("Ʒ", &[word("Ʒ", 1)]),
26496 ("Ǽ", &[word("Ǽ", 1)]),
26497 ("⋑", &[word("⋑", 1)]),
26498 ("⋑⋑", &[word("⋑⋑", 2)]),
26499 (
26500 "原理,进而",
26501 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26502 ),
26503 (
26504 "hello world",
26505 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26506 ),
26507 (
26508 "hello, world",
26509 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26510 ),
26511 (
26512 " hello world",
26513 &[
26514 whitespace(" ", 2),
26515 word("hello", 5),
26516 whitespace(" ", 1),
26517 word("world", 5),
26518 ],
26519 ),
26520 (
26521 "这是什么 \n 钢笔",
26522 &[
26523 word("这", 1),
26524 word("是", 1),
26525 word("什", 1),
26526 word("么", 1),
26527 whitespace(" ", 1),
26528 newline(),
26529 whitespace(" ", 1),
26530 word("钢", 1),
26531 word("笔", 1),
26532 ],
26533 ),
26534 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26535 ];
26536
26537 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26538 WordBreakToken::Word {
26539 token,
26540 grapheme_len,
26541 }
26542 }
26543
26544 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26545 WordBreakToken::InlineWhitespace {
26546 token,
26547 grapheme_len,
26548 }
26549 }
26550
26551 fn newline() -> WordBreakToken<'static> {
26552 WordBreakToken::Newline
26553 }
26554
26555 for (input, result) in tests {
26556 assert_eq!(
26557 WordBreakingTokenizer::new(input)
26558 .collect::<Vec<_>>()
26559 .as_slice(),
26560 *result,
26561 );
26562 }
26563}
26564
26565fn wrap_with_prefix(
26566 first_line_prefix: String,
26567 subsequent_lines_prefix: String,
26568 unwrapped_text: String,
26569 wrap_column: usize,
26570 tab_size: NonZeroU32,
26571 preserve_existing_whitespace: bool,
26572) -> String {
26573 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26574 let subsequent_lines_prefix_len =
26575 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26576 let mut wrapped_text = String::new();
26577 let mut current_line = first_line_prefix;
26578 let mut is_first_line = true;
26579
26580 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26581 let mut current_line_len = first_line_prefix_len;
26582 let mut in_whitespace = false;
26583 for token in tokenizer {
26584 let have_preceding_whitespace = in_whitespace;
26585 match token {
26586 WordBreakToken::Word {
26587 token,
26588 grapheme_len,
26589 } => {
26590 in_whitespace = false;
26591 let current_prefix_len = if is_first_line {
26592 first_line_prefix_len
26593 } else {
26594 subsequent_lines_prefix_len
26595 };
26596 if current_line_len + grapheme_len > wrap_column
26597 && current_line_len != current_prefix_len
26598 {
26599 wrapped_text.push_str(current_line.trim_end());
26600 wrapped_text.push('\n');
26601 is_first_line = false;
26602 current_line = subsequent_lines_prefix.clone();
26603 current_line_len = subsequent_lines_prefix_len;
26604 }
26605 current_line.push_str(token);
26606 current_line_len += grapheme_len;
26607 }
26608 WordBreakToken::InlineWhitespace {
26609 mut token,
26610 mut grapheme_len,
26611 } => {
26612 in_whitespace = true;
26613 if have_preceding_whitespace && !preserve_existing_whitespace {
26614 continue;
26615 }
26616 if !preserve_existing_whitespace {
26617 // Keep a single whitespace grapheme as-is
26618 if let Some(first) =
26619 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26620 {
26621 token = first;
26622 } else {
26623 token = " ";
26624 }
26625 grapheme_len = 1;
26626 }
26627 let current_prefix_len = if is_first_line {
26628 first_line_prefix_len
26629 } else {
26630 subsequent_lines_prefix_len
26631 };
26632 if current_line_len + grapheme_len > wrap_column {
26633 wrapped_text.push_str(current_line.trim_end());
26634 wrapped_text.push('\n');
26635 is_first_line = false;
26636 current_line = subsequent_lines_prefix.clone();
26637 current_line_len = subsequent_lines_prefix_len;
26638 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26639 current_line.push_str(token);
26640 current_line_len += grapheme_len;
26641 }
26642 }
26643 WordBreakToken::Newline => {
26644 in_whitespace = true;
26645 let current_prefix_len = if is_first_line {
26646 first_line_prefix_len
26647 } else {
26648 subsequent_lines_prefix_len
26649 };
26650 if preserve_existing_whitespace {
26651 wrapped_text.push_str(current_line.trim_end());
26652 wrapped_text.push('\n');
26653 is_first_line = false;
26654 current_line = subsequent_lines_prefix.clone();
26655 current_line_len = subsequent_lines_prefix_len;
26656 } else if have_preceding_whitespace {
26657 continue;
26658 } else if current_line_len + 1 > wrap_column
26659 && current_line_len != current_prefix_len
26660 {
26661 wrapped_text.push_str(current_line.trim_end());
26662 wrapped_text.push('\n');
26663 is_first_line = false;
26664 current_line = subsequent_lines_prefix.clone();
26665 current_line_len = subsequent_lines_prefix_len;
26666 } else if current_line_len != current_prefix_len {
26667 current_line.push(' ');
26668 current_line_len += 1;
26669 }
26670 }
26671 }
26672 }
26673
26674 if !current_line.is_empty() {
26675 wrapped_text.push_str(¤t_line);
26676 }
26677 wrapped_text
26678}
26679
26680#[test]
26681fn test_wrap_with_prefix() {
26682 assert_eq!(
26683 wrap_with_prefix(
26684 "# ".to_string(),
26685 "# ".to_string(),
26686 "abcdefg".to_string(),
26687 4,
26688 NonZeroU32::new(4).unwrap(),
26689 false,
26690 ),
26691 "# abcdefg"
26692 );
26693 assert_eq!(
26694 wrap_with_prefix(
26695 "".to_string(),
26696 "".to_string(),
26697 "\thello world".to_string(),
26698 8,
26699 NonZeroU32::new(4).unwrap(),
26700 false,
26701 ),
26702 "hello\nworld"
26703 );
26704 assert_eq!(
26705 wrap_with_prefix(
26706 "// ".to_string(),
26707 "// ".to_string(),
26708 "xx \nyy zz aa bb cc".to_string(),
26709 12,
26710 NonZeroU32::new(4).unwrap(),
26711 false,
26712 ),
26713 "// xx yy zz\n// aa bb cc"
26714 );
26715 assert_eq!(
26716 wrap_with_prefix(
26717 String::new(),
26718 String::new(),
26719 "这是什么 \n 钢笔".to_string(),
26720 3,
26721 NonZeroU32::new(4).unwrap(),
26722 false,
26723 ),
26724 "这是什\n么 钢\n笔"
26725 );
26726 assert_eq!(
26727 wrap_with_prefix(
26728 String::new(),
26729 String::new(),
26730 format!("foo{}bar", '\u{2009}'), // thin space
26731 80,
26732 NonZeroU32::new(4).unwrap(),
26733 false,
26734 ),
26735 format!("foo{}bar", '\u{2009}')
26736 );
26737}
26738
26739pub trait CollaborationHub {
26740 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26741 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26742 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26743}
26744
26745impl CollaborationHub for Entity<Project> {
26746 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26747 self.read(cx).collaborators()
26748 }
26749
26750 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26751 self.read(cx).user_store().read(cx).participant_indices()
26752 }
26753
26754 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26755 let this = self.read(cx);
26756 let user_ids = this.collaborators().values().map(|c| c.user_id);
26757 this.user_store().read(cx).participant_names(user_ids, cx)
26758 }
26759}
26760
26761pub trait SemanticsProvider {
26762 fn hover(
26763 &self,
26764 buffer: &Entity<Buffer>,
26765 position: text::Anchor,
26766 cx: &mut App,
26767 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26768
26769 fn inline_values(
26770 &self,
26771 buffer_handle: Entity<Buffer>,
26772 range: Range<text::Anchor>,
26773 cx: &mut App,
26774 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26775
26776 fn applicable_inlay_chunks(
26777 &self,
26778 buffer: &Entity<Buffer>,
26779 ranges: &[Range<text::Anchor>],
26780 cx: &mut App,
26781 ) -> Vec<Range<BufferRow>>;
26782
26783 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26784
26785 fn inlay_hints(
26786 &self,
26787 invalidate: InvalidationStrategy,
26788 buffer: Entity<Buffer>,
26789 ranges: Vec<Range<text::Anchor>>,
26790 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26791 cx: &mut App,
26792 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
26793
26794 fn semantic_tokens(
26795 &self,
26796 buffer: Entity<Buffer>,
26797 refresh: Option<RefreshForServer>,
26798 cx: &mut App,
26799 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>;
26800
26801 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26802
26803 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
26804
26805 fn document_highlights(
26806 &self,
26807 buffer: &Entity<Buffer>,
26808 position: text::Anchor,
26809 cx: &mut App,
26810 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
26811
26812 fn definitions(
26813 &self,
26814 buffer: &Entity<Buffer>,
26815 position: text::Anchor,
26816 kind: GotoDefinitionKind,
26817 cx: &mut App,
26818 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
26819
26820 fn range_for_rename(
26821 &self,
26822 buffer: &Entity<Buffer>,
26823 position: text::Anchor,
26824 cx: &mut App,
26825 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
26826
26827 fn perform_rename(
26828 &self,
26829 buffer: &Entity<Buffer>,
26830 position: text::Anchor,
26831 new_name: String,
26832 cx: &mut App,
26833 ) -> Option<Task<Result<ProjectTransaction>>>;
26834}
26835
26836pub trait CompletionProvider {
26837 fn completions(
26838 &self,
26839 excerpt_id: ExcerptId,
26840 buffer: &Entity<Buffer>,
26841 buffer_position: text::Anchor,
26842 trigger: CompletionContext,
26843 window: &mut Window,
26844 cx: &mut Context<Editor>,
26845 ) -> Task<Result<Vec<CompletionResponse>>>;
26846
26847 fn resolve_completions(
26848 &self,
26849 _buffer: Entity<Buffer>,
26850 _completion_indices: Vec<usize>,
26851 _completions: Rc<RefCell<Box<[Completion]>>>,
26852 _cx: &mut Context<Editor>,
26853 ) -> Task<Result<bool>> {
26854 Task::ready(Ok(false))
26855 }
26856
26857 fn apply_additional_edits_for_completion(
26858 &self,
26859 _buffer: Entity<Buffer>,
26860 _completions: Rc<RefCell<Box<[Completion]>>>,
26861 _completion_index: usize,
26862 _push_to_history: bool,
26863 _cx: &mut Context<Editor>,
26864 ) -> Task<Result<Option<language::Transaction>>> {
26865 Task::ready(Ok(None))
26866 }
26867
26868 fn is_completion_trigger(
26869 &self,
26870 buffer: &Entity<Buffer>,
26871 position: language::Anchor,
26872 text: &str,
26873 trigger_in_words: bool,
26874 cx: &mut Context<Editor>,
26875 ) -> bool;
26876
26877 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
26878
26879 fn sort_completions(&self) -> bool {
26880 true
26881 }
26882
26883 fn filter_completions(&self) -> bool {
26884 true
26885 }
26886
26887 fn show_snippets(&self) -> bool {
26888 false
26889 }
26890}
26891
26892pub trait CodeActionProvider {
26893 fn id(&self) -> Arc<str>;
26894
26895 fn code_actions(
26896 &self,
26897 buffer: &Entity<Buffer>,
26898 range: Range<text::Anchor>,
26899 window: &mut Window,
26900 cx: &mut App,
26901 ) -> Task<Result<Vec<CodeAction>>>;
26902
26903 fn apply_code_action(
26904 &self,
26905 buffer_handle: Entity<Buffer>,
26906 action: CodeAction,
26907 excerpt_id: ExcerptId,
26908 push_to_history: bool,
26909 window: &mut Window,
26910 cx: &mut App,
26911 ) -> Task<Result<ProjectTransaction>>;
26912}
26913
26914impl CodeActionProvider for Entity<Project> {
26915 fn id(&self) -> Arc<str> {
26916 "project".into()
26917 }
26918
26919 fn code_actions(
26920 &self,
26921 buffer: &Entity<Buffer>,
26922 range: Range<text::Anchor>,
26923 _window: &mut Window,
26924 cx: &mut App,
26925 ) -> Task<Result<Vec<CodeAction>>> {
26926 self.update(cx, |project, cx| {
26927 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
26928 let code_actions = project.code_actions(buffer, range, None, cx);
26929 cx.background_spawn(async move {
26930 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
26931 Ok(code_lens_actions
26932 .context("code lens fetch")?
26933 .into_iter()
26934 .flatten()
26935 .chain(
26936 code_actions
26937 .context("code action fetch")?
26938 .into_iter()
26939 .flatten(),
26940 )
26941 .collect())
26942 })
26943 })
26944 }
26945
26946 fn apply_code_action(
26947 &self,
26948 buffer_handle: Entity<Buffer>,
26949 action: CodeAction,
26950 _excerpt_id: ExcerptId,
26951 push_to_history: bool,
26952 _window: &mut Window,
26953 cx: &mut App,
26954 ) -> Task<Result<ProjectTransaction>> {
26955 self.update(cx, |project, cx| {
26956 project.apply_code_action(buffer_handle, action, push_to_history, cx)
26957 })
26958 }
26959}
26960
26961fn snippet_completions(
26962 project: &Project,
26963 buffer: &Entity<Buffer>,
26964 buffer_anchor: text::Anchor,
26965 classifier: CharClassifier,
26966 cx: &mut App,
26967) -> Task<Result<CompletionResponse>> {
26968 let languages = buffer.read(cx).languages_at(buffer_anchor);
26969 let snippet_store = project.snippets().read(cx);
26970
26971 let scopes: Vec<_> = languages
26972 .iter()
26973 .filter_map(|language| {
26974 let language_name = language.lsp_id();
26975 let snippets = snippet_store.snippets_for(Some(language_name), cx);
26976
26977 if snippets.is_empty() {
26978 None
26979 } else {
26980 Some((language.default_scope(), snippets))
26981 }
26982 })
26983 .collect();
26984
26985 if scopes.is_empty() {
26986 return Task::ready(Ok(CompletionResponse {
26987 completions: vec![],
26988 display_options: CompletionDisplayOptions::default(),
26989 is_incomplete: false,
26990 }));
26991 }
26992
26993 let snapshot = buffer.read(cx).text_snapshot();
26994 let executor = cx.background_executor().clone();
26995
26996 cx.background_spawn(async move {
26997 let is_word_char = |c| classifier.is_word(c);
26998
26999 let mut is_incomplete = false;
27000 let mut completions: Vec<Completion> = Vec::new();
27001
27002 const MAX_PREFIX_LEN: usize = 128;
27003 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27004 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27005 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27006
27007 let max_buffer_window: String = snapshot
27008 .text_for_range(window_start..buffer_offset)
27009 .collect();
27010
27011 if max_buffer_window.is_empty() {
27012 return Ok(CompletionResponse {
27013 completions: vec![],
27014 display_options: CompletionDisplayOptions::default(),
27015 is_incomplete: true,
27016 });
27017 }
27018
27019 for (_scope, snippets) in scopes.into_iter() {
27020 // Sort snippets by word count to match longer snippet prefixes first.
27021 let mut sorted_snippet_candidates = snippets
27022 .iter()
27023 .enumerate()
27024 .flat_map(|(snippet_ix, snippet)| {
27025 snippet
27026 .prefix
27027 .iter()
27028 .enumerate()
27029 .map(move |(prefix_ix, prefix)| {
27030 let word_count =
27031 snippet_candidate_suffixes(prefix, &is_word_char).count();
27032 ((snippet_ix, prefix_ix), prefix, word_count)
27033 })
27034 })
27035 .collect_vec();
27036 sorted_snippet_candidates
27037 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27038
27039 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27040
27041 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27042 .take(
27043 sorted_snippet_candidates
27044 .first()
27045 .map(|(_, _, word_count)| *word_count)
27046 .unwrap_or_default(),
27047 )
27048 .collect_vec();
27049
27050 const MAX_RESULTS: usize = 100;
27051 // Each match also remembers how many characters from the buffer it consumed
27052 let mut matches: Vec<(StringMatch, usize)> = vec![];
27053
27054 let mut snippet_list_cutoff_index = 0;
27055 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27056 let word_count = buffer_index + 1;
27057 // Increase `snippet_list_cutoff_index` until we have all of the
27058 // snippets with sufficiently many words.
27059 while sorted_snippet_candidates
27060 .get(snippet_list_cutoff_index)
27061 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27062 *snippet_word_count >= word_count
27063 })
27064 {
27065 snippet_list_cutoff_index += 1;
27066 }
27067
27068 // Take only the candidates with at least `word_count` many words
27069 let snippet_candidates_at_word_len =
27070 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27071
27072 let candidates = snippet_candidates_at_word_len
27073 .iter()
27074 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27075 .enumerate() // index in `sorted_snippet_candidates`
27076 // First char must match
27077 .filter(|(_ix, prefix)| {
27078 itertools::equal(
27079 prefix
27080 .chars()
27081 .next()
27082 .into_iter()
27083 .flat_map(|c| c.to_lowercase()),
27084 buffer_window
27085 .chars()
27086 .next()
27087 .into_iter()
27088 .flat_map(|c| c.to_lowercase()),
27089 )
27090 })
27091 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27092 .collect::<Vec<StringMatchCandidate>>();
27093
27094 matches.extend(
27095 fuzzy::match_strings(
27096 &candidates,
27097 &buffer_window,
27098 buffer_window.chars().any(|c| c.is_uppercase()),
27099 true,
27100 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27101 &Default::default(),
27102 executor.clone(),
27103 )
27104 .await
27105 .into_iter()
27106 .map(|string_match| (string_match, buffer_window.len())),
27107 );
27108
27109 if matches.len() >= MAX_RESULTS {
27110 break;
27111 }
27112 }
27113
27114 let to_lsp = |point: &text::Anchor| {
27115 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27116 point_to_lsp(end)
27117 };
27118 let lsp_end = to_lsp(&buffer_anchor);
27119
27120 if matches.len() >= MAX_RESULTS {
27121 is_incomplete = true;
27122 }
27123
27124 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27125 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27126 sorted_snippet_candidates[string_match.candidate_id];
27127 let snippet = &snippets[snippet_index];
27128 let start = buffer_offset - buffer_window_len;
27129 let start = snapshot.anchor_before(start);
27130 let range = start..buffer_anchor;
27131 let lsp_start = to_lsp(&start);
27132 let lsp_range = lsp::Range {
27133 start: lsp_start,
27134 end: lsp_end,
27135 };
27136 Completion {
27137 replace_range: range,
27138 new_text: snippet.body.clone(),
27139 source: CompletionSource::Lsp {
27140 insert_range: None,
27141 server_id: LanguageServerId(usize::MAX),
27142 resolved: true,
27143 lsp_completion: Box::new(lsp::CompletionItem {
27144 label: snippet.prefix.first().unwrap().clone(),
27145 kind: Some(CompletionItemKind::SNIPPET),
27146 label_details: snippet.description.as_ref().map(|description| {
27147 lsp::CompletionItemLabelDetails {
27148 detail: Some(description.clone()),
27149 description: None,
27150 }
27151 }),
27152 insert_text_format: Some(InsertTextFormat::SNIPPET),
27153 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27154 lsp::InsertReplaceEdit {
27155 new_text: snippet.body.clone(),
27156 insert: lsp_range,
27157 replace: lsp_range,
27158 },
27159 )),
27160 filter_text: Some(snippet.body.clone()),
27161 sort_text: Some(char::MAX.to_string()),
27162 ..lsp::CompletionItem::default()
27163 }),
27164 lsp_defaults: None,
27165 },
27166 label: CodeLabel {
27167 text: matching_prefix.clone(),
27168 runs: Vec::new(),
27169 filter_range: 0..matching_prefix.len(),
27170 },
27171 icon_path: None,
27172 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27173 single_line: snippet.name.clone().into(),
27174 plain_text: snippet
27175 .description
27176 .clone()
27177 .map(|description| description.into()),
27178 }),
27179 insert_text_mode: None,
27180 confirm: None,
27181 match_start: Some(start),
27182 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27183 }
27184 }));
27185 }
27186
27187 Ok(CompletionResponse {
27188 completions,
27189 display_options: CompletionDisplayOptions::default(),
27190 is_incomplete,
27191 })
27192 })
27193}
27194
27195impl CompletionProvider for Entity<Project> {
27196 fn completions(
27197 &self,
27198 _excerpt_id: ExcerptId,
27199 buffer: &Entity<Buffer>,
27200 buffer_position: text::Anchor,
27201 options: CompletionContext,
27202 _window: &mut Window,
27203 cx: &mut Context<Editor>,
27204 ) -> Task<Result<Vec<CompletionResponse>>> {
27205 self.update(cx, |project, cx| {
27206 let task = project.completions(buffer, buffer_position, options, cx);
27207 cx.background_spawn(task)
27208 })
27209 }
27210
27211 fn resolve_completions(
27212 &self,
27213 buffer: Entity<Buffer>,
27214 completion_indices: Vec<usize>,
27215 completions: Rc<RefCell<Box<[Completion]>>>,
27216 cx: &mut Context<Editor>,
27217 ) -> Task<Result<bool>> {
27218 self.update(cx, |project, cx| {
27219 project.lsp_store().update(cx, |lsp_store, cx| {
27220 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27221 })
27222 })
27223 }
27224
27225 fn apply_additional_edits_for_completion(
27226 &self,
27227 buffer: Entity<Buffer>,
27228 completions: Rc<RefCell<Box<[Completion]>>>,
27229 completion_index: usize,
27230 push_to_history: bool,
27231 cx: &mut Context<Editor>,
27232 ) -> Task<Result<Option<language::Transaction>>> {
27233 self.update(cx, |project, cx| {
27234 project.lsp_store().update(cx, |lsp_store, cx| {
27235 lsp_store.apply_additional_edits_for_completion(
27236 buffer,
27237 completions,
27238 completion_index,
27239 push_to_history,
27240 cx,
27241 )
27242 })
27243 })
27244 }
27245
27246 fn is_completion_trigger(
27247 &self,
27248 buffer: &Entity<Buffer>,
27249 position: language::Anchor,
27250 text: &str,
27251 trigger_in_words: bool,
27252 cx: &mut Context<Editor>,
27253 ) -> bool {
27254 let mut chars = text.chars();
27255 let char = if let Some(char) = chars.next() {
27256 char
27257 } else {
27258 return false;
27259 };
27260 if chars.next().is_some() {
27261 return false;
27262 }
27263
27264 let buffer = buffer.read(cx);
27265 let snapshot = buffer.snapshot();
27266 let classifier = snapshot
27267 .char_classifier_at(position)
27268 .scope_context(Some(CharScopeContext::Completion));
27269 if trigger_in_words && classifier.is_word(char) {
27270 return true;
27271 }
27272
27273 buffer.completion_triggers().contains(text)
27274 }
27275
27276 fn show_snippets(&self) -> bool {
27277 true
27278 }
27279}
27280
27281impl SemanticsProvider for Entity<Project> {
27282 fn hover(
27283 &self,
27284 buffer: &Entity<Buffer>,
27285 position: text::Anchor,
27286 cx: &mut App,
27287 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27288 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
27289 }
27290
27291 fn document_highlights(
27292 &self,
27293 buffer: &Entity<Buffer>,
27294 position: text::Anchor,
27295 cx: &mut App,
27296 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27297 Some(self.update(cx, |project, cx| {
27298 project.document_highlights(buffer, position, cx)
27299 }))
27300 }
27301
27302 fn definitions(
27303 &self,
27304 buffer: &Entity<Buffer>,
27305 position: text::Anchor,
27306 kind: GotoDefinitionKind,
27307 cx: &mut App,
27308 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27309 Some(self.update(cx, |project, cx| match kind {
27310 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27311 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27312 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27313 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27314 }))
27315 }
27316
27317 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27318 self.update(cx, |project, cx| {
27319 if project
27320 .active_debug_session(cx)
27321 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27322 {
27323 return true;
27324 }
27325
27326 buffer.update(cx, |buffer, cx| {
27327 project.any_language_server_supports_inlay_hints(buffer, cx)
27328 })
27329 })
27330 }
27331
27332 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27333 self.update(cx, |project, cx| {
27334 buffer.update(cx, |buffer, cx| {
27335 project.any_language_server_supports_semantic_tokens(buffer, cx)
27336 })
27337 })
27338 }
27339
27340 fn inline_values(
27341 &self,
27342 buffer_handle: Entity<Buffer>,
27343 range: Range<text::Anchor>,
27344 cx: &mut App,
27345 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27346 self.update(cx, |project, cx| {
27347 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27348
27349 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27350 })
27351 }
27352
27353 fn applicable_inlay_chunks(
27354 &self,
27355 buffer: &Entity<Buffer>,
27356 ranges: &[Range<text::Anchor>],
27357 cx: &mut App,
27358 ) -> Vec<Range<BufferRow>> {
27359 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27360 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27361 })
27362 }
27363
27364 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27365 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
27366 lsp_store.invalidate_inlay_hints(for_buffers)
27367 });
27368 }
27369
27370 fn inlay_hints(
27371 &self,
27372 invalidate: InvalidationStrategy,
27373 buffer: Entity<Buffer>,
27374 ranges: Vec<Range<text::Anchor>>,
27375 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27376 cx: &mut App,
27377 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27378 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27379 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27380 }))
27381 }
27382
27383 fn semantic_tokens(
27384 &self,
27385 buffer: Entity<Buffer>,
27386 refresh: Option<RefreshForServer>,
27387 cx: &mut App,
27388 ) -> Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>> {
27389 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
27390 lsp_store.semantic_tokens(buffer, refresh, cx)
27391 })
27392 }
27393
27394 fn range_for_rename(
27395 &self,
27396 buffer: &Entity<Buffer>,
27397 position: text::Anchor,
27398 cx: &mut App,
27399 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27400 Some(self.update(cx, |project, cx| {
27401 let buffer = buffer.clone();
27402 let task = project.prepare_rename(buffer.clone(), position, cx);
27403 cx.spawn(async move |_, cx| {
27404 Ok(match task.await? {
27405 PrepareRenameResponse::Success(range) => Some(range),
27406 PrepareRenameResponse::InvalidPosition => None,
27407 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27408 // Fallback on using TreeSitter info to determine identifier range
27409 buffer.read_with(cx, |buffer, _| {
27410 let snapshot = buffer.snapshot();
27411 let (range, kind) = snapshot.surrounding_word(position, None);
27412 if kind != Some(CharKind::Word) {
27413 return None;
27414 }
27415 Some(
27416 snapshot.anchor_before(range.start)
27417 ..snapshot.anchor_after(range.end),
27418 )
27419 })
27420 }
27421 })
27422 })
27423 }))
27424 }
27425
27426 fn perform_rename(
27427 &self,
27428 buffer: &Entity<Buffer>,
27429 position: text::Anchor,
27430 new_name: String,
27431 cx: &mut App,
27432 ) -> Option<Task<Result<ProjectTransaction>>> {
27433 Some(self.update(cx, |project, cx| {
27434 project.perform_rename(buffer.clone(), position, new_name, cx)
27435 }))
27436 }
27437}
27438
27439fn consume_contiguous_rows(
27440 contiguous_row_selections: &mut Vec<Selection<Point>>,
27441 selection: &Selection<Point>,
27442 display_map: &DisplaySnapshot,
27443 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27444) -> (MultiBufferRow, MultiBufferRow) {
27445 contiguous_row_selections.push(selection.clone());
27446 let start_row = starting_row(selection, display_map);
27447 let mut end_row = ending_row(selection, display_map);
27448
27449 while let Some(next_selection) = selections.peek() {
27450 if next_selection.start.row <= end_row.0 {
27451 end_row = ending_row(next_selection, display_map);
27452 contiguous_row_selections.push(selections.next().unwrap().clone());
27453 } else {
27454 break;
27455 }
27456 }
27457 (start_row, end_row)
27458}
27459
27460fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27461 if selection.start.column > 0 {
27462 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27463 } else {
27464 MultiBufferRow(selection.start.row)
27465 }
27466}
27467
27468fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27469 if next_selection.end.column > 0 || next_selection.is_empty() {
27470 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27471 } else {
27472 MultiBufferRow(next_selection.end.row)
27473 }
27474}
27475
27476impl EditorSnapshot {
27477 pub fn remote_selections_in_range<'a>(
27478 &'a self,
27479 range: &'a Range<Anchor>,
27480 collaboration_hub: &dyn CollaborationHub,
27481 cx: &'a App,
27482 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27483 let participant_names = collaboration_hub.user_names(cx);
27484 let participant_indices = collaboration_hub.user_participant_indices(cx);
27485 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27486 let collaborators_by_replica_id = collaborators_by_peer_id
27487 .values()
27488 .map(|collaborator| (collaborator.replica_id, collaborator))
27489 .collect::<HashMap<_, _>>();
27490 self.buffer_snapshot()
27491 .selections_in_range(range, false)
27492 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27493 if replica_id == ReplicaId::AGENT {
27494 Some(RemoteSelection {
27495 replica_id,
27496 selection,
27497 cursor_shape,
27498 line_mode,
27499 collaborator_id: CollaboratorId::Agent,
27500 user_name: Some("Agent".into()),
27501 color: cx.theme().players().agent(),
27502 })
27503 } else {
27504 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27505 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27506 let user_name = participant_names.get(&collaborator.user_id).cloned();
27507 Some(RemoteSelection {
27508 replica_id,
27509 selection,
27510 cursor_shape,
27511 line_mode,
27512 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27513 user_name,
27514 color: if let Some(index) = participant_index {
27515 cx.theme().players().color_for_participant(index.0)
27516 } else {
27517 cx.theme().players().absent()
27518 },
27519 })
27520 }
27521 })
27522 }
27523
27524 pub fn hunks_for_ranges(
27525 &self,
27526 ranges: impl IntoIterator<Item = Range<Point>>,
27527 ) -> Vec<MultiBufferDiffHunk> {
27528 let mut hunks = Vec::new();
27529 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27530 HashMap::default();
27531 for query_range in ranges {
27532 let query_rows =
27533 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27534 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27535 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27536 ) {
27537 // Include deleted hunks that are adjacent to the query range, because
27538 // otherwise they would be missed.
27539 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27540 if hunk.status().is_deleted() {
27541 intersects_range |= hunk.row_range.start == query_rows.end;
27542 intersects_range |= hunk.row_range.end == query_rows.start;
27543 }
27544 if intersects_range {
27545 if !processed_buffer_rows
27546 .entry(hunk.buffer_id)
27547 .or_default()
27548 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27549 {
27550 continue;
27551 }
27552 hunks.push(hunk);
27553 }
27554 }
27555 }
27556
27557 hunks
27558 }
27559
27560 fn display_diff_hunks_for_rows<'a>(
27561 &'a self,
27562 display_rows: Range<DisplayRow>,
27563 folded_buffers: &'a HashSet<BufferId>,
27564 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27565 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27566 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27567
27568 self.buffer_snapshot()
27569 .diff_hunks_in_range(buffer_start..buffer_end)
27570 .filter_map(|hunk| {
27571 if folded_buffers.contains(&hunk.buffer_id)
27572 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27573 {
27574 return None;
27575 }
27576
27577 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27578 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27579 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27580 let line_len = self.buffer_snapshot().line_len(last_row);
27581 Point::new(last_row.0, line_len)
27582 } else {
27583 Point::new(hunk.row_range.end.0, 0)
27584 };
27585
27586 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27587 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27588
27589 let display_hunk = if hunk_display_start.column() != 0 {
27590 DisplayDiffHunk::Folded {
27591 display_row: hunk_display_start.row(),
27592 }
27593 } else {
27594 let mut end_row = hunk_display_end.row();
27595 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27596 end_row.0 += 1;
27597 }
27598 let is_created_file = hunk.is_created_file();
27599
27600 DisplayDiffHunk::Unfolded {
27601 status: hunk.status(),
27602 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27603 ..hunk.diff_base_byte_range.end.0,
27604 word_diffs: hunk.word_diffs,
27605 display_row_range: hunk_display_start.row()..end_row,
27606 multi_buffer_range: Anchor::range_in_buffer(
27607 hunk.excerpt_id,
27608 hunk.buffer_range,
27609 ),
27610 is_created_file,
27611 }
27612 };
27613
27614 Some(display_hunk)
27615 })
27616 }
27617
27618 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27619 self.display_snapshot
27620 .buffer_snapshot()
27621 .language_at(position)
27622 }
27623
27624 pub fn is_focused(&self) -> bool {
27625 self.is_focused
27626 }
27627
27628 pub fn placeholder_text(&self) -> Option<String> {
27629 self.placeholder_display_snapshot
27630 .as_ref()
27631 .map(|display_map| display_map.text())
27632 }
27633
27634 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27635 self.scroll_anchor.scroll_position(&self.display_snapshot)
27636 }
27637
27638 pub fn gutter_dimensions(
27639 &self,
27640 font_id: FontId,
27641 font_size: Pixels,
27642 style: &EditorStyle,
27643 window: &mut Window,
27644 cx: &App,
27645 ) -> GutterDimensions {
27646 if self.show_gutter
27647 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27648 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27649 {
27650 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27651 matches!(
27652 ProjectSettings::get_global(cx).git.git_gutter,
27653 GitGutterSetting::TrackedFiles
27654 )
27655 });
27656 let gutter_settings = EditorSettings::get_global(cx).gutter;
27657 let show_line_numbers = self
27658 .show_line_numbers
27659 .unwrap_or(gutter_settings.line_numbers);
27660 let line_gutter_width = if show_line_numbers {
27661 // Avoid flicker-like gutter resizes when the line number gains another digit by
27662 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27663 let min_width_for_number_on_gutter =
27664 ch_advance * gutter_settings.min_line_number_digits as f32;
27665 self.max_line_number_width(style, window)
27666 .max(min_width_for_number_on_gutter)
27667 } else {
27668 0.0.into()
27669 };
27670
27671 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27672 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27673
27674 let git_blame_entries_width =
27675 self.git_blame_gutter_max_author_length
27676 .map(|max_author_length| {
27677 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27678 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27679
27680 /// The number of characters to dedicate to gaps and margins.
27681 const SPACING_WIDTH: usize = 4;
27682
27683 let max_char_count = max_author_length.min(renderer.max_author_length())
27684 + ::git::SHORT_SHA_LENGTH
27685 + MAX_RELATIVE_TIMESTAMP.len()
27686 + SPACING_WIDTH;
27687
27688 ch_advance * max_char_count
27689 });
27690
27691 let is_singleton = self.buffer_snapshot().is_singleton();
27692
27693 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27694 left_padding += if !is_singleton {
27695 ch_width * 4.0
27696 } else if show_runnables || show_breakpoints {
27697 ch_width * 3.0
27698 } else if show_git_gutter && show_line_numbers {
27699 ch_width * 2.0
27700 } else if show_git_gutter || show_line_numbers {
27701 ch_width
27702 } else {
27703 px(0.)
27704 };
27705
27706 let shows_folds = is_singleton && gutter_settings.folds;
27707
27708 let right_padding = if shows_folds && show_line_numbers {
27709 ch_width * 4.0
27710 } else if shows_folds || (!is_singleton && show_line_numbers) {
27711 ch_width * 3.0
27712 } else if show_line_numbers {
27713 ch_width
27714 } else {
27715 px(0.)
27716 };
27717
27718 GutterDimensions {
27719 left_padding,
27720 right_padding,
27721 width: line_gutter_width + left_padding + right_padding,
27722 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27723 git_blame_entries_width,
27724 }
27725 } else if self.offset_content {
27726 GutterDimensions::default_with_margin(font_id, font_size, cx)
27727 } else {
27728 GutterDimensions::default()
27729 }
27730 }
27731
27732 pub fn render_crease_toggle(
27733 &self,
27734 buffer_row: MultiBufferRow,
27735 row_contains_cursor: bool,
27736 editor: Entity<Editor>,
27737 window: &mut Window,
27738 cx: &mut App,
27739 ) -> Option<AnyElement> {
27740 let folded = self.is_line_folded(buffer_row);
27741 let mut is_foldable = false;
27742
27743 if let Some(crease) = self
27744 .crease_snapshot
27745 .query_row(buffer_row, self.buffer_snapshot())
27746 {
27747 is_foldable = true;
27748 match crease {
27749 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27750 if let Some(render_toggle) = render_toggle {
27751 let toggle_callback =
27752 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27753 if folded {
27754 editor.update(cx, |editor, cx| {
27755 editor.fold_at(buffer_row, window, cx)
27756 });
27757 } else {
27758 editor.update(cx, |editor, cx| {
27759 editor.unfold_at(buffer_row, window, cx)
27760 });
27761 }
27762 });
27763 return Some((render_toggle)(
27764 buffer_row,
27765 folded,
27766 toggle_callback,
27767 window,
27768 cx,
27769 ));
27770 }
27771 }
27772 }
27773 }
27774
27775 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
27776
27777 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
27778 Some(
27779 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
27780 .toggle_state(folded)
27781 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
27782 if folded {
27783 this.unfold_at(buffer_row, window, cx);
27784 } else {
27785 this.fold_at(buffer_row, window, cx);
27786 }
27787 }))
27788 .into_any_element(),
27789 )
27790 } else {
27791 None
27792 }
27793 }
27794
27795 pub fn render_crease_trailer(
27796 &self,
27797 buffer_row: MultiBufferRow,
27798 window: &mut Window,
27799 cx: &mut App,
27800 ) -> Option<AnyElement> {
27801 let folded = self.is_line_folded(buffer_row);
27802 if let Crease::Inline { render_trailer, .. } = self
27803 .crease_snapshot
27804 .query_row(buffer_row, self.buffer_snapshot())?
27805 {
27806 let render_trailer = render_trailer.as_ref()?;
27807 Some(render_trailer(buffer_row, folded, window, cx))
27808 } else {
27809 None
27810 }
27811 }
27812
27813 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
27814 let digit_count = self.widest_line_number().ilog10() + 1;
27815 column_pixels(style, digit_count as usize, window)
27816 }
27817
27818 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
27819 ///
27820 /// This is positive if `base` is before `line`.
27821 fn relative_line_delta(
27822 &self,
27823 current_selection_head: DisplayRow,
27824 first_visible_row: DisplayRow,
27825 consider_wrapped_lines: bool,
27826 ) -> i64 {
27827 let current_selection_head = current_selection_head.as_display_point().to_point(self);
27828 let first_visible_row = first_visible_row.as_display_point().to_point(self);
27829
27830 if consider_wrapped_lines {
27831 let wrap_snapshot = self.wrap_snapshot();
27832 let base_wrap_row = wrap_snapshot
27833 .make_wrap_point(current_selection_head, Bias::Left)
27834 .row();
27835 let wrap_row = wrap_snapshot
27836 .make_wrap_point(first_visible_row, Bias::Left)
27837 .row();
27838
27839 wrap_row.0 as i64 - base_wrap_row.0 as i64
27840 } else {
27841 let fold_snapshot = self.fold_snapshot();
27842 let base_fold_row = fold_snapshot
27843 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
27844 .row();
27845 let fold_row = fold_snapshot
27846 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
27847 .row();
27848
27849 fold_row as i64 - base_fold_row as i64
27850 }
27851 }
27852
27853 /// Returns the unsigned relative line number to display for each row in `rows`.
27854 ///
27855 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
27856 pub fn calculate_relative_line_numbers(
27857 &self,
27858 rows: &Range<DisplayRow>,
27859 current_selection_head: DisplayRow,
27860 count_wrapped_lines: bool,
27861 ) -> HashMap<DisplayRow, u32> {
27862 let initial_offset =
27863 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
27864
27865 self.row_infos(rows.start)
27866 .take(rows.len())
27867 .enumerate()
27868 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
27869 .filter(|(_row, row_info)| {
27870 row_info.buffer_row.is_some()
27871 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
27872 })
27873 .enumerate()
27874 .filter_map(|(i, (row, row_info))| {
27875 // We want to ensure here that the current line has absolute
27876 // numbering, even if we are in a soft-wrapped line. With the
27877 // exception that if we are in a deleted line, we should number this
27878 // relative with 0, as otherwise it would have no line number at all
27879 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
27880
27881 (relative_line_number != 0
27882 || row_info
27883 .diff_status
27884 .is_some_and(|status| status.is_deleted()))
27885 .then_some((row, relative_line_number))
27886 })
27887 .collect()
27888 }
27889}
27890
27891pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
27892 let font_size = style.text.font_size.to_pixels(window.rem_size());
27893 let layout = window.text_system().shape_line(
27894 SharedString::from(" ".repeat(column)),
27895 font_size,
27896 &[TextRun {
27897 len: column,
27898 font: style.text.font(),
27899 color: Hsla::default(),
27900 ..Default::default()
27901 }],
27902 None,
27903 );
27904
27905 layout.width
27906}
27907
27908impl Deref for EditorSnapshot {
27909 type Target = DisplaySnapshot;
27910
27911 fn deref(&self) -> &Self::Target {
27912 &self.display_snapshot
27913 }
27914}
27915
27916#[derive(Clone, Debug, PartialEq, Eq)]
27917pub enum EditorEvent {
27918 /// Emitted when the stored review comments change (added, removed, or updated).
27919 ReviewCommentsChanged {
27920 /// The new total count of review comments.
27921 total_count: usize,
27922 },
27923 InputIgnored {
27924 text: Arc<str>,
27925 },
27926 InputHandled {
27927 utf16_range_to_replace: Option<Range<isize>>,
27928 text: Arc<str>,
27929 },
27930 ExcerptsAdded {
27931 buffer: Entity<Buffer>,
27932 predecessor: ExcerptId,
27933 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
27934 },
27935 ExcerptsRemoved {
27936 ids: Vec<ExcerptId>,
27937 removed_buffer_ids: Vec<BufferId>,
27938 },
27939 BufferFoldToggled {
27940 ids: Vec<ExcerptId>,
27941 folded: bool,
27942 },
27943 ExcerptsEdited {
27944 ids: Vec<ExcerptId>,
27945 },
27946 ExcerptsExpanded {
27947 ids: Vec<ExcerptId>,
27948 },
27949 ExpandExcerptsRequested {
27950 excerpt_ids: Vec<ExcerptId>,
27951 lines: u32,
27952 direction: ExpandExcerptDirection,
27953 },
27954 StageOrUnstageRequested {
27955 stage: bool,
27956 hunks: Vec<MultiBufferDiffHunk>,
27957 },
27958 OpenExcerptsRequested {
27959 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
27960 split: bool,
27961 },
27962 RestoreRequested {
27963 hunks: Vec<MultiBufferDiffHunk>,
27964 },
27965 BufferEdited,
27966 Edited {
27967 transaction_id: clock::Lamport,
27968 },
27969 Reparsed(BufferId),
27970 Focused,
27971 FocusedIn,
27972 Blurred,
27973 DirtyChanged,
27974 Saved,
27975 TitleChanged,
27976 SelectionsChanged {
27977 local: bool,
27978 },
27979 ScrollPositionChanged {
27980 local: bool,
27981 autoscroll: bool,
27982 },
27983 TransactionUndone {
27984 transaction_id: clock::Lamport,
27985 },
27986 TransactionBegun {
27987 transaction_id: clock::Lamport,
27988 },
27989 CursorShapeChanged,
27990 BreadcrumbsChanged,
27991 OutlineSymbolsChanged,
27992 PushedToNavHistory {
27993 anchor: Anchor,
27994 is_deactivate: bool,
27995 },
27996}
27997
27998impl EventEmitter<EditorEvent> for Editor {}
27999
28000impl Focusable for Editor {
28001 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28002 self.focus_handle.clone()
28003 }
28004}
28005
28006impl Render for Editor {
28007 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28008 EditorElement::new(&cx.entity(), self.create_style(cx))
28009 }
28010}
28011
28012impl EntityInputHandler for Editor {
28013 fn text_for_range(
28014 &mut self,
28015 range_utf16: Range<usize>,
28016 adjusted_range: &mut Option<Range<usize>>,
28017 _: &mut Window,
28018 cx: &mut Context<Self>,
28019 ) -> Option<String> {
28020 let snapshot = self.buffer.read(cx).read(cx);
28021 let start = snapshot.clip_offset_utf16(
28022 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28023 Bias::Left,
28024 );
28025 let end = snapshot.clip_offset_utf16(
28026 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28027 Bias::Right,
28028 );
28029 if (start.0.0..end.0.0) != range_utf16 {
28030 adjusted_range.replace(start.0.0..end.0.0);
28031 }
28032 Some(snapshot.text_for_range(start..end).collect())
28033 }
28034
28035 fn selected_text_range(
28036 &mut self,
28037 ignore_disabled_input: bool,
28038 _: &mut Window,
28039 cx: &mut Context<Self>,
28040 ) -> Option<UTF16Selection> {
28041 // Prevent the IME menu from appearing when holding down an alphabetic key
28042 // while input is disabled.
28043 if !ignore_disabled_input && !self.input_enabled {
28044 return None;
28045 }
28046
28047 let selection = self
28048 .selections
28049 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28050 let range = selection.range();
28051
28052 Some(UTF16Selection {
28053 range: range.start.0.0..range.end.0.0,
28054 reversed: selection.reversed,
28055 })
28056 }
28057
28058 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28059 let snapshot = self.buffer.read(cx).read(cx);
28060 let range = self
28061 .text_highlights(HighlightKey::InputComposition, cx)?
28062 .1
28063 .first()?;
28064 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28065 }
28066
28067 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28068 self.clear_highlights(HighlightKey::InputComposition, cx);
28069 self.ime_transaction.take();
28070 }
28071
28072 fn replace_text_in_range(
28073 &mut self,
28074 range_utf16: Option<Range<usize>>,
28075 text: &str,
28076 window: &mut Window,
28077 cx: &mut Context<Self>,
28078 ) {
28079 if !self.input_enabled {
28080 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28081 return;
28082 }
28083
28084 self.transact(window, cx, |this, window, cx| {
28085 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28086 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28087 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28088 Some(this.selection_replacement_ranges(range_utf16, cx))
28089 } else {
28090 this.marked_text_ranges(cx)
28091 };
28092
28093 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28094 let newest_selection_id = this.selections.newest_anchor().id;
28095 this.selections
28096 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28097 .iter()
28098 .zip(ranges_to_replace.iter())
28099 .find_map(|(selection, range)| {
28100 if selection.id == newest_selection_id {
28101 Some(
28102 (range.start.0.0 as isize - selection.head().0.0 as isize)
28103 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28104 )
28105 } else {
28106 None
28107 }
28108 })
28109 });
28110
28111 cx.emit(EditorEvent::InputHandled {
28112 utf16_range_to_replace: range_to_replace,
28113 text: text.into(),
28114 });
28115
28116 if let Some(new_selected_ranges) = new_selected_ranges {
28117 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28118 selections.select_ranges(new_selected_ranges)
28119 });
28120 this.backspace(&Default::default(), window, cx);
28121 }
28122
28123 this.handle_input(text, window, cx);
28124 });
28125
28126 if let Some(transaction) = self.ime_transaction {
28127 self.buffer.update(cx, |buffer, cx| {
28128 buffer.group_until_transaction(transaction, cx);
28129 });
28130 }
28131
28132 self.unmark_text(window, cx);
28133 }
28134
28135 fn replace_and_mark_text_in_range(
28136 &mut self,
28137 range_utf16: Option<Range<usize>>,
28138 text: &str,
28139 new_selected_range_utf16: Option<Range<usize>>,
28140 window: &mut Window,
28141 cx: &mut Context<Self>,
28142 ) {
28143 if !self.input_enabled {
28144 return;
28145 }
28146
28147 let transaction = self.transact(window, cx, |this, window, cx| {
28148 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28149 let snapshot = this.buffer.read(cx).read(cx);
28150 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28151 for marked_range in &mut marked_ranges {
28152 marked_range.end = marked_range.start + relative_range_utf16.end;
28153 marked_range.start += relative_range_utf16.start;
28154 marked_range.start =
28155 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28156 marked_range.end =
28157 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28158 }
28159 }
28160 Some(marked_ranges)
28161 } else if let Some(range_utf16) = range_utf16 {
28162 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28163 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28164 Some(this.selection_replacement_ranges(range_utf16, cx))
28165 } else {
28166 None
28167 };
28168
28169 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28170 let newest_selection_id = this.selections.newest_anchor().id;
28171 this.selections
28172 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28173 .iter()
28174 .zip(ranges_to_replace.iter())
28175 .find_map(|(selection, range)| {
28176 if selection.id == newest_selection_id {
28177 Some(
28178 (range.start.0.0 as isize - selection.head().0.0 as isize)
28179 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28180 )
28181 } else {
28182 None
28183 }
28184 })
28185 });
28186
28187 cx.emit(EditorEvent::InputHandled {
28188 utf16_range_to_replace: range_to_replace,
28189 text: text.into(),
28190 });
28191
28192 if let Some(ranges) = ranges_to_replace {
28193 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28194 s.select_ranges(ranges)
28195 });
28196 }
28197
28198 let marked_ranges = {
28199 let snapshot = this.buffer.read(cx).read(cx);
28200 this.selections
28201 .disjoint_anchors_arc()
28202 .iter()
28203 .map(|selection| {
28204 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28205 })
28206 .collect::<Vec<_>>()
28207 };
28208
28209 if text.is_empty() {
28210 this.unmark_text(window, cx);
28211 } else {
28212 this.highlight_text(
28213 HighlightKey::InputComposition,
28214 marked_ranges.clone(),
28215 HighlightStyle {
28216 underline: Some(UnderlineStyle {
28217 thickness: px(1.),
28218 color: None,
28219 wavy: false,
28220 }),
28221 ..Default::default()
28222 },
28223 cx,
28224 );
28225 }
28226
28227 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28228 let use_autoclose = this.use_autoclose;
28229 let use_auto_surround = this.use_auto_surround;
28230 this.set_use_autoclose(false);
28231 this.set_use_auto_surround(false);
28232 this.handle_input(text, window, cx);
28233 this.set_use_autoclose(use_autoclose);
28234 this.set_use_auto_surround(use_auto_surround);
28235
28236 if let Some(new_selected_range) = new_selected_range_utf16 {
28237 let snapshot = this.buffer.read(cx).read(cx);
28238 let new_selected_ranges = marked_ranges
28239 .into_iter()
28240 .map(|marked_range| {
28241 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28242 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28243 insertion_start.0 + new_selected_range.start,
28244 ));
28245 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28246 insertion_start.0 + new_selected_range.end,
28247 ));
28248 snapshot.clip_offset_utf16(new_start, Bias::Left)
28249 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28250 })
28251 .collect::<Vec<_>>();
28252
28253 drop(snapshot);
28254 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28255 selections.select_ranges(new_selected_ranges)
28256 });
28257 }
28258 });
28259
28260 self.ime_transaction = self.ime_transaction.or(transaction);
28261 if let Some(transaction) = self.ime_transaction {
28262 self.buffer.update(cx, |buffer, cx| {
28263 buffer.group_until_transaction(transaction, cx);
28264 });
28265 }
28266
28267 if self
28268 .text_highlights(HighlightKey::InputComposition, cx)
28269 .is_none()
28270 {
28271 self.ime_transaction.take();
28272 }
28273 }
28274
28275 fn bounds_for_range(
28276 &mut self,
28277 range_utf16: Range<usize>,
28278 element_bounds: gpui::Bounds<Pixels>,
28279 window: &mut Window,
28280 cx: &mut Context<Self>,
28281 ) -> Option<gpui::Bounds<Pixels>> {
28282 let text_layout_details = self.text_layout_details(window, cx);
28283 let CharacterDimensions {
28284 em_width,
28285 em_advance,
28286 line_height,
28287 } = self.character_dimensions(window, cx);
28288
28289 let snapshot = self.snapshot(window, cx);
28290 let scroll_position = snapshot.scroll_position();
28291 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28292
28293 let start =
28294 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28295 let x = Pixels::from(
28296 ScrollOffset::from(
28297 snapshot.x_for_display_point(start, &text_layout_details)
28298 + self.gutter_dimensions.full_width(),
28299 ) - scroll_left,
28300 );
28301 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28302
28303 Some(Bounds {
28304 origin: element_bounds.origin + point(x, y),
28305 size: size(em_width, line_height),
28306 })
28307 }
28308
28309 fn character_index_for_point(
28310 &mut self,
28311 point: gpui::Point<Pixels>,
28312 _window: &mut Window,
28313 _cx: &mut Context<Self>,
28314 ) -> Option<usize> {
28315 let position_map = self.last_position_map.as_ref()?;
28316 if !position_map.text_hitbox.contains(&point) {
28317 return None;
28318 }
28319 let display_point = position_map.point_for_position(point).previous_valid;
28320 let anchor = position_map
28321 .snapshot
28322 .display_point_to_anchor(display_point, Bias::Left);
28323 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28324 Some(utf16_offset.0.0)
28325 }
28326
28327 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28328 self.input_enabled
28329 }
28330}
28331
28332trait SelectionExt {
28333 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28334 fn spanned_rows(
28335 &self,
28336 include_end_if_at_line_start: bool,
28337 map: &DisplaySnapshot,
28338 ) -> Range<MultiBufferRow>;
28339}
28340
28341impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28342 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28343 let start = self
28344 .start
28345 .to_point(map.buffer_snapshot())
28346 .to_display_point(map);
28347 let end = self
28348 .end
28349 .to_point(map.buffer_snapshot())
28350 .to_display_point(map);
28351 if self.reversed {
28352 end..start
28353 } else {
28354 start..end
28355 }
28356 }
28357
28358 fn spanned_rows(
28359 &self,
28360 include_end_if_at_line_start: bool,
28361 map: &DisplaySnapshot,
28362 ) -> Range<MultiBufferRow> {
28363 let start = self.start.to_point(map.buffer_snapshot());
28364 let mut end = self.end.to_point(map.buffer_snapshot());
28365 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28366 end.row -= 1;
28367 }
28368
28369 let buffer_start = map.prev_line_boundary(start).0;
28370 let buffer_end = map.next_line_boundary(end).0;
28371 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28372 }
28373}
28374
28375impl<T: InvalidationRegion> InvalidationStack<T> {
28376 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28377 where
28378 S: Clone + ToOffset,
28379 {
28380 while let Some(region) = self.last() {
28381 let all_selections_inside_invalidation_ranges =
28382 if selections.len() == region.ranges().len() {
28383 selections
28384 .iter()
28385 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28386 .all(|(selection, invalidation_range)| {
28387 let head = selection.head().to_offset(buffer);
28388 invalidation_range.start <= head && invalidation_range.end >= head
28389 })
28390 } else {
28391 false
28392 };
28393
28394 if all_selections_inside_invalidation_ranges {
28395 break;
28396 } else {
28397 self.pop();
28398 }
28399 }
28400 }
28401}
28402
28403#[derive(Clone)]
28404struct ErasedEditorImpl(Entity<Editor>);
28405
28406impl ui_input::ErasedEditor for ErasedEditorImpl {
28407 fn text(&self, cx: &App) -> String {
28408 self.0.read(cx).text(cx)
28409 }
28410
28411 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28412 self.0.update(cx, |this, cx| {
28413 this.set_text(text, window, cx);
28414 })
28415 }
28416
28417 fn clear(&self, window: &mut Window, cx: &mut App) {
28418 self.0.update(cx, |this, cx| this.clear(window, cx));
28419 }
28420
28421 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28422 self.0.update(cx, |this, cx| {
28423 this.set_placeholder_text(text, window, cx);
28424 });
28425 }
28426
28427 fn focus_handle(&self, cx: &App) -> FocusHandle {
28428 self.0.read(cx).focus_handle(cx)
28429 }
28430
28431 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28432 let settings = ThemeSettings::get_global(cx);
28433 let theme_color = cx.theme().colors();
28434
28435 let text_style = TextStyle {
28436 font_family: settings.ui_font.family.clone(),
28437 font_features: settings.ui_font.features.clone(),
28438 font_size: rems(0.875).into(),
28439 font_weight: settings.ui_font.weight,
28440 font_style: FontStyle::Normal,
28441 line_height: relative(1.2),
28442 color: theme_color.text,
28443 ..Default::default()
28444 };
28445 let editor_style = EditorStyle {
28446 background: theme_color.ghost_element_background,
28447 local_player: cx.theme().players().local(),
28448 syntax: cx.theme().syntax().clone(),
28449 text: text_style,
28450 ..Default::default()
28451 };
28452 EditorElement::new(&self.0, editor_style).into_any()
28453 }
28454
28455 fn as_any(&self) -> &dyn Any {
28456 &self.0
28457 }
28458
28459 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28460 self.0.update(cx, |editor, cx| {
28461 let editor_offset = editor.buffer().read(cx).len(cx);
28462 editor.change_selections(
28463 SelectionEffects::scroll(Autoscroll::Next),
28464 window,
28465 cx,
28466 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28467 );
28468 });
28469 }
28470
28471 fn subscribe(
28472 &self,
28473 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28474 window: &mut Window,
28475 cx: &mut App,
28476 ) -> Subscription {
28477 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28478 let event = match event {
28479 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28480 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28481 _ => return,
28482 };
28483 (callback)(event, window, cx);
28484 })
28485 }
28486
28487 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28488 self.0.update(cx, |editor, cx| {
28489 editor.set_masked(masked, cx);
28490 });
28491 }
28492}
28493impl<T> Default for InvalidationStack<T> {
28494 fn default() -> Self {
28495 Self(Default::default())
28496 }
28497}
28498
28499impl<T> Deref for InvalidationStack<T> {
28500 type Target = Vec<T>;
28501
28502 fn deref(&self) -> &Self::Target {
28503 &self.0
28504 }
28505}
28506
28507impl<T> DerefMut for InvalidationStack<T> {
28508 fn deref_mut(&mut self) -> &mut Self::Target {
28509 &mut self.0
28510 }
28511}
28512
28513impl InvalidationRegion for SnippetState {
28514 fn ranges(&self) -> &[Range<Anchor>] {
28515 &self.ranges[self.active_index]
28516 }
28517}
28518
28519fn edit_prediction_edit_text(
28520 current_snapshot: &BufferSnapshot,
28521 edits: &[(Range<Anchor>, impl AsRef<str>)],
28522 edit_preview: &EditPreview,
28523 include_deletions: bool,
28524 cx: &App,
28525) -> HighlightedText {
28526 let edits = edits
28527 .iter()
28528 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28529 .collect::<Vec<_>>();
28530
28531 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28532}
28533
28534fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28535 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
28536 // Just show the raw edit text with basic styling
28537 let mut text = String::new();
28538 let mut highlights = Vec::new();
28539
28540 let insertion_highlight_style = HighlightStyle {
28541 color: Some(cx.theme().colors().text),
28542 ..Default::default()
28543 };
28544
28545 for (_, edit_text) in edits {
28546 let start_offset = text.len();
28547 text.push_str(edit_text);
28548 let end_offset = text.len();
28549
28550 if start_offset < end_offset {
28551 highlights.push((start_offset..end_offset, insertion_highlight_style));
28552 }
28553 }
28554
28555 HighlightedText {
28556 text: text.into(),
28557 highlights,
28558 }
28559}
28560
28561pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28562 match severity {
28563 lsp::DiagnosticSeverity::ERROR => colors.error,
28564 lsp::DiagnosticSeverity::WARNING => colors.warning,
28565 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28566 lsp::DiagnosticSeverity::HINT => colors.info,
28567 _ => colors.ignored,
28568 }
28569}
28570
28571pub fn styled_runs_for_code_label<'a>(
28572 label: &'a CodeLabel,
28573 syntax_theme: &'a theme::SyntaxTheme,
28574 local_player: &'a theme::PlayerColor,
28575) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28576 let fade_out = HighlightStyle {
28577 fade_out: Some(0.35),
28578 ..Default::default()
28579 };
28580
28581 let mut prev_end = label.filter_range.end;
28582 label
28583 .runs
28584 .iter()
28585 .enumerate()
28586 .flat_map(move |(ix, (range, highlight_id))| {
28587 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28588 HighlightStyle {
28589 color: Some(local_player.cursor),
28590 ..Default::default()
28591 }
28592 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28593 HighlightStyle {
28594 background_color: Some(local_player.selection),
28595 ..Default::default()
28596 }
28597 } else if let Some(style) = highlight_id.style(syntax_theme) {
28598 style
28599 } else {
28600 return Default::default();
28601 };
28602 let muted_style = style.highlight(fade_out);
28603
28604 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28605 if range.start >= label.filter_range.end {
28606 if range.start > prev_end {
28607 runs.push((prev_end..range.start, fade_out));
28608 }
28609 runs.push((range.clone(), muted_style));
28610 } else if range.end <= label.filter_range.end {
28611 runs.push((range.clone(), style));
28612 } else {
28613 runs.push((range.start..label.filter_range.end, style));
28614 runs.push((label.filter_range.end..range.end, muted_style));
28615 }
28616 prev_end = cmp::max(prev_end, range.end);
28617
28618 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28619 runs.push((prev_end..label.text.len(), fade_out));
28620 }
28621
28622 runs
28623 })
28624}
28625
28626pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28627 let mut prev_index = 0;
28628 let mut prev_codepoint: Option<char> = None;
28629 text.char_indices()
28630 .chain([(text.len(), '\0')])
28631 .filter_map(move |(index, codepoint)| {
28632 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28633 let is_boundary = index == text.len()
28634 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28635 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28636 if is_boundary {
28637 let chunk = &text[prev_index..index];
28638 prev_index = index;
28639 Some(chunk)
28640 } else {
28641 None
28642 }
28643 })
28644}
28645
28646/// Given a string of text immediately before the cursor, iterates over possible
28647/// strings a snippet could match to. More precisely: returns an iterator over
28648/// suffixes of `text` created by splitting at word boundaries (before & after
28649/// every non-word character).
28650///
28651/// Shorter suffixes are returned first.
28652pub(crate) fn snippet_candidate_suffixes<'a>(
28653 text: &'a str,
28654 is_word_char: &'a dyn Fn(char) -> bool,
28655) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28656 let mut prev_index = text.len();
28657 let mut prev_codepoint = None;
28658 text.char_indices()
28659 .rev()
28660 .chain([(0, '\0')])
28661 .filter_map(move |(index, codepoint)| {
28662 let prev_index = std::mem::replace(&mut prev_index, index);
28663 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28664 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28665 None
28666 } else {
28667 let chunk = &text[prev_index..]; // go to end of string
28668 Some(chunk)
28669 }
28670 })
28671}
28672
28673pub trait RangeToAnchorExt: Sized {
28674 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28675
28676 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28677 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28678 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28679 }
28680}
28681
28682impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28683 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28684 let start_offset = self.start.to_offset(snapshot);
28685 let end_offset = self.end.to_offset(snapshot);
28686 if start_offset == end_offset {
28687 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28688 } else {
28689 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28690 }
28691 }
28692}
28693
28694pub trait RowExt {
28695 fn as_f64(&self) -> f64;
28696
28697 fn next_row(&self) -> Self;
28698
28699 fn previous_row(&self) -> Self;
28700
28701 fn minus(&self, other: Self) -> u32;
28702}
28703
28704impl RowExt for DisplayRow {
28705 fn as_f64(&self) -> f64 {
28706 self.0 as _
28707 }
28708
28709 fn next_row(&self) -> Self {
28710 Self(self.0 + 1)
28711 }
28712
28713 fn previous_row(&self) -> Self {
28714 Self(self.0.saturating_sub(1))
28715 }
28716
28717 fn minus(&self, other: Self) -> u32 {
28718 self.0 - other.0
28719 }
28720}
28721
28722impl RowExt for MultiBufferRow {
28723 fn as_f64(&self) -> f64 {
28724 self.0 as _
28725 }
28726
28727 fn next_row(&self) -> Self {
28728 Self(self.0 + 1)
28729 }
28730
28731 fn previous_row(&self) -> Self {
28732 Self(self.0.saturating_sub(1))
28733 }
28734
28735 fn minus(&self, other: Self) -> u32 {
28736 self.0 - other.0
28737 }
28738}
28739
28740trait RowRangeExt {
28741 type Row;
28742
28743 fn len(&self) -> usize;
28744
28745 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
28746}
28747
28748impl RowRangeExt for Range<MultiBufferRow> {
28749 type Row = MultiBufferRow;
28750
28751 fn len(&self) -> usize {
28752 (self.end.0 - self.start.0) as usize
28753 }
28754
28755 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
28756 (self.start.0..self.end.0).map(MultiBufferRow)
28757 }
28758}
28759
28760impl RowRangeExt for Range<DisplayRow> {
28761 type Row = DisplayRow;
28762
28763 fn len(&self) -> usize {
28764 (self.end.0 - self.start.0) as usize
28765 }
28766
28767 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
28768 (self.start.0..self.end.0).map(DisplayRow)
28769 }
28770}
28771
28772/// If select range has more than one line, we
28773/// just point the cursor to range.start.
28774fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
28775 if range.start.row == range.end.row {
28776 range
28777 } else {
28778 range.start..range.start
28779 }
28780}
28781pub struct KillRing(ClipboardItem);
28782impl Global for KillRing {}
28783
28784const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
28785
28786enum BreakpointPromptEditAction {
28787 Log,
28788 Condition,
28789 HitCondition,
28790}
28791
28792struct BreakpointPromptEditor {
28793 pub(crate) prompt: Entity<Editor>,
28794 editor: WeakEntity<Editor>,
28795 breakpoint_anchor: Anchor,
28796 breakpoint: Breakpoint,
28797 edit_action: BreakpointPromptEditAction,
28798 block_ids: HashSet<CustomBlockId>,
28799 editor_margins: Arc<Mutex<EditorMargins>>,
28800 _subscriptions: Vec<Subscription>,
28801}
28802
28803impl BreakpointPromptEditor {
28804 const MAX_LINES: u8 = 4;
28805
28806 fn new(
28807 editor: WeakEntity<Editor>,
28808 breakpoint_anchor: Anchor,
28809 breakpoint: Breakpoint,
28810 edit_action: BreakpointPromptEditAction,
28811 window: &mut Window,
28812 cx: &mut Context<Self>,
28813 ) -> Self {
28814 let base_text = match edit_action {
28815 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
28816 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
28817 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
28818 }
28819 .map(|msg| msg.to_string())
28820 .unwrap_or_default();
28821
28822 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
28823 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28824
28825 let prompt = cx.new(|cx| {
28826 let mut prompt = Editor::new(
28827 EditorMode::AutoHeight {
28828 min_lines: 1,
28829 max_lines: Some(Self::MAX_LINES as usize),
28830 },
28831 buffer,
28832 None,
28833 window,
28834 cx,
28835 );
28836 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
28837 prompt.set_show_cursor_when_unfocused(false, cx);
28838 prompt.set_placeholder_text(
28839 match edit_action {
28840 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
28841 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
28842 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
28843 },
28844 window,
28845 cx,
28846 );
28847
28848 prompt
28849 });
28850
28851 Self {
28852 prompt,
28853 editor,
28854 breakpoint_anchor,
28855 breakpoint,
28856 edit_action,
28857 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
28858 block_ids: Default::default(),
28859 _subscriptions: vec![],
28860 }
28861 }
28862
28863 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
28864 self.block_ids.extend(block_ids)
28865 }
28866
28867 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
28868 if let Some(editor) = self.editor.upgrade() {
28869 let message = self
28870 .prompt
28871 .read(cx)
28872 .buffer
28873 .read(cx)
28874 .as_singleton()
28875 .expect("A multi buffer in breakpoint prompt isn't possible")
28876 .read(cx)
28877 .as_rope()
28878 .to_string();
28879
28880 editor.update(cx, |editor, cx| {
28881 editor.edit_breakpoint_at_anchor(
28882 self.breakpoint_anchor,
28883 self.breakpoint.clone(),
28884 match self.edit_action {
28885 BreakpointPromptEditAction::Log => {
28886 BreakpointEditAction::EditLogMessage(message.into())
28887 }
28888 BreakpointPromptEditAction::Condition => {
28889 BreakpointEditAction::EditCondition(message.into())
28890 }
28891 BreakpointPromptEditAction::HitCondition => {
28892 BreakpointEditAction::EditHitCondition(message.into())
28893 }
28894 },
28895 cx,
28896 );
28897
28898 editor.remove_blocks(self.block_ids.clone(), None, cx);
28899 cx.focus_self(window);
28900 });
28901 }
28902 }
28903
28904 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
28905 self.editor
28906 .update(cx, |editor, cx| {
28907 editor.remove_blocks(self.block_ids.clone(), None, cx);
28908 window.focus(&editor.focus_handle, cx);
28909 })
28910 .log_err();
28911 }
28912
28913 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
28914 let settings = ThemeSettings::get_global(cx);
28915 let text_style = TextStyle {
28916 color: if self.prompt.read(cx).read_only(cx) {
28917 cx.theme().colors().text_disabled
28918 } else {
28919 cx.theme().colors().text
28920 },
28921 font_family: settings.buffer_font.family.clone(),
28922 font_fallbacks: settings.buffer_font.fallbacks.clone(),
28923 font_size: settings.buffer_font_size(cx).into(),
28924 font_weight: settings.buffer_font.weight,
28925 line_height: relative(settings.buffer_line_height.value()),
28926 ..Default::default()
28927 };
28928 EditorElement::new(
28929 &self.prompt,
28930 EditorStyle {
28931 background: cx.theme().colors().editor_background,
28932 local_player: cx.theme().players().local(),
28933 text: text_style,
28934 ..Default::default()
28935 },
28936 )
28937 }
28938}
28939
28940impl Render for BreakpointPromptEditor {
28941 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28942 let editor_margins = *self.editor_margins.lock();
28943 let gutter_dimensions = editor_margins.gutter;
28944 h_flex()
28945 .key_context("Editor")
28946 .bg(cx.theme().colors().editor_background)
28947 .border_y_1()
28948 .border_color(cx.theme().status().info_border)
28949 .size_full()
28950 .py(window.line_height() / 2.5)
28951 .on_action(cx.listener(Self::confirm))
28952 .on_action(cx.listener(Self::cancel))
28953 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
28954 .child(div().flex_1().child(self.render_prompt_editor(cx)))
28955 }
28956}
28957
28958impl Focusable for BreakpointPromptEditor {
28959 fn focus_handle(&self, cx: &App) -> FocusHandle {
28960 self.prompt.focus_handle(cx)
28961 }
28962}
28963
28964fn all_edits_insertions_or_deletions(
28965 edits: &Vec<(Range<Anchor>, Arc<str>)>,
28966 snapshot: &MultiBufferSnapshot,
28967) -> bool {
28968 let mut all_insertions = true;
28969 let mut all_deletions = true;
28970
28971 for (range, new_text) in edits.iter() {
28972 let range_is_empty = range.to_offset(snapshot).is_empty();
28973 let text_is_empty = new_text.is_empty();
28974
28975 if range_is_empty != text_is_empty {
28976 if range_is_empty {
28977 all_deletions = false;
28978 } else {
28979 all_insertions = false;
28980 }
28981 } else {
28982 return false;
28983 }
28984
28985 if !all_insertions && !all_deletions {
28986 return false;
28987 }
28988 }
28989 all_insertions || all_deletions
28990}
28991
28992struct MissingEditPredictionKeybindingTooltip;
28993
28994impl Render for MissingEditPredictionKeybindingTooltip {
28995 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28996 ui::tooltip_container(cx, |container, cx| {
28997 container
28998 .flex_shrink_0()
28999 .max_w_80()
29000 .min_h(rems_from_px(124.))
29001 .justify_between()
29002 .child(
29003 v_flex()
29004 .flex_1()
29005 .text_ui_sm(cx)
29006 .child(Label::new("Conflict with Accept Keybinding"))
29007 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29008 )
29009 .child(
29010 h_flex()
29011 .pb_1()
29012 .gap_1()
29013 .items_end()
29014 .w_full()
29015 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29016 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29017 }))
29018 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29019 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29020 })),
29021 )
29022 })
29023 }
29024}
29025
29026#[derive(Debug, Clone, Copy, PartialEq)]
29027pub struct LineHighlight {
29028 pub background: Background,
29029 pub border: Option<gpui::Hsla>,
29030 pub include_gutter: bool,
29031 pub type_id: Option<TypeId>,
29032}
29033
29034struct LineManipulationResult {
29035 pub new_text: String,
29036 pub line_count_before: usize,
29037 pub line_count_after: usize,
29038}
29039
29040fn render_diff_hunk_controls(
29041 row: u32,
29042 status: &DiffHunkStatus,
29043 hunk_range: Range<Anchor>,
29044 is_created_file: bool,
29045 line_height: Pixels,
29046 editor: &Entity<Editor>,
29047 _window: &mut Window,
29048 cx: &mut App,
29049) -> AnyElement {
29050 h_flex()
29051 .h(line_height)
29052 .mr_1()
29053 .gap_1()
29054 .px_0p5()
29055 .pb_1()
29056 .border_x_1()
29057 .border_b_1()
29058 .border_color(cx.theme().colors().border_variant)
29059 .rounded_b_lg()
29060 .bg(cx.theme().colors().editor_background)
29061 .gap_1()
29062 .block_mouse_except_scroll()
29063 .shadow_md()
29064 .child(if status.has_secondary_hunk() {
29065 Button::new(("stage", row as u64), "Stage")
29066 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29067 .tooltip({
29068 let focus_handle = editor.focus_handle(cx);
29069 move |_window, cx| {
29070 Tooltip::for_action_in(
29071 "Stage Hunk",
29072 &::git::ToggleStaged,
29073 &focus_handle,
29074 cx,
29075 )
29076 }
29077 })
29078 .on_click({
29079 let editor = editor.clone();
29080 move |_event, _window, cx| {
29081 editor.update(cx, |editor, cx| {
29082 editor.stage_or_unstage_diff_hunks(
29083 true,
29084 vec![hunk_range.start..hunk_range.start],
29085 cx,
29086 );
29087 });
29088 }
29089 })
29090 } else {
29091 Button::new(("unstage", row as u64), "Unstage")
29092 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29093 .tooltip({
29094 let focus_handle = editor.focus_handle(cx);
29095 move |_window, cx| {
29096 Tooltip::for_action_in(
29097 "Unstage Hunk",
29098 &::git::ToggleStaged,
29099 &focus_handle,
29100 cx,
29101 )
29102 }
29103 })
29104 .on_click({
29105 let editor = editor.clone();
29106 move |_event, _window, cx| {
29107 editor.update(cx, |editor, cx| {
29108 editor.stage_or_unstage_diff_hunks(
29109 false,
29110 vec![hunk_range.start..hunk_range.start],
29111 cx,
29112 );
29113 });
29114 }
29115 })
29116 })
29117 .child(
29118 Button::new(("restore", row as u64), "Restore")
29119 .tooltip({
29120 let focus_handle = editor.focus_handle(cx);
29121 move |_window, cx| {
29122 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29123 }
29124 })
29125 .on_click({
29126 let editor = editor.clone();
29127 move |_event, window, cx| {
29128 editor.update(cx, |editor, cx| {
29129 let snapshot = editor.snapshot(window, cx);
29130 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29131 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29132 });
29133 }
29134 })
29135 .disabled(is_created_file),
29136 )
29137 .when(
29138 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29139 |el| {
29140 el.child(
29141 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29142 .shape(IconButtonShape::Square)
29143 .icon_size(IconSize::Small)
29144 // .disabled(!has_multiple_hunks)
29145 .tooltip({
29146 let focus_handle = editor.focus_handle(cx);
29147 move |_window, cx| {
29148 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29149 }
29150 })
29151 .on_click({
29152 let editor = editor.clone();
29153 move |_event, window, cx| {
29154 editor.update(cx, |editor, cx| {
29155 let snapshot = editor.snapshot(window, cx);
29156 let position =
29157 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29158 editor.go_to_hunk_before_or_after_position(
29159 &snapshot,
29160 position,
29161 Direction::Next,
29162 window,
29163 cx,
29164 );
29165 editor.expand_selected_diff_hunks(cx);
29166 });
29167 }
29168 }),
29169 )
29170 .child(
29171 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29172 .shape(IconButtonShape::Square)
29173 .icon_size(IconSize::Small)
29174 // .disabled(!has_multiple_hunks)
29175 .tooltip({
29176 let focus_handle = editor.focus_handle(cx);
29177 move |_window, cx| {
29178 Tooltip::for_action_in(
29179 "Previous Hunk",
29180 &GoToPreviousHunk,
29181 &focus_handle,
29182 cx,
29183 )
29184 }
29185 })
29186 .on_click({
29187 let editor = editor.clone();
29188 move |_event, window, cx| {
29189 editor.update(cx, |editor, cx| {
29190 let snapshot = editor.snapshot(window, cx);
29191 let point =
29192 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29193 editor.go_to_hunk_before_or_after_position(
29194 &snapshot,
29195 point,
29196 Direction::Prev,
29197 window,
29198 cx,
29199 );
29200 editor.expand_selected_diff_hunks(cx);
29201 });
29202 }
29203 }),
29204 )
29205 },
29206 )
29207 .into_any_element()
29208}
29209
29210pub fn multibuffer_context_lines(cx: &App) -> u32 {
29211 EditorSettings::try_get(cx)
29212 .map(|settings| settings.excerpt_context_lines)
29213 .unwrap_or(2)
29214 .min(32)
29215}